Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kotlin Fest 2024 - Pocket Code Battle 問題解説

Kotlin Fest 2024 - Pocket Code Battle 問題解説

Kotlin Fest 2024のLINEヤフーブースにておこなった企画「Pocket Code Battle」の問題の解説をアフターイベント 後夜祭.kt 2024 でおこなった際の資料です。

More Decks by LINEヤフーTech (LY Corporation Tech)

Other Decks in Technology

Transcript

  1. © LY Corporation Pocket Code Battle - 問題解説 LINEヤフー株式会社 /

    メッセンジャープロダクトアプリ開発2チーム Tamaki Hidetsugu
  2. © LY Corporation Tamaki Hidetsugu LINE App / Android Engineer

    2 • 2018年⼊社 • LINEのAndroid Appの開発を担当 • 背景エフェクト、QRコードでの端末移⾏機能 • 内部的なリファクタリング • etc • Kotlin Fest Core Member
  3. © LY Corporation • 同じ動作をする数種類の実装⽅法 • どちらのコードがより好ましいのか議論をする • 全11問 •

    Code Review Challengeのように、明確な答えはないことも • あなたならどちらのコードを実装・採⽤するかを問う内容 5 Pocket Code Battle
  4. © LY Corporation • 左はリスト全体をソートして先頭2つを取る、右はリストの全要素をなめて最⼤の2つを取る • 左はO(N log N)、右はO(N) •

    データ量が膨⼤な場合を除いて、左が好ましい • 右は計算量は少ないが仕様変更やメンテナンス性の⾯で劣る 7 #1 Haste makes waste 計算量 vs コードのシンプルさ
  5. © LY Corporation 9 #3 All that glitters is not

    immutable • varを使うのはアンチパターンと⾔われがちだが、使った⽅ がわかりやすいこともある varを使う vs 使わない
  6. © LY Corporation 10 #4 Good things come in small

    packages • get(): NoSuchElementExceptionやIOExceptionを 投げる関数 • A: 例外をそのまま投げる • ⾔語機能として検査例外がないので受け⼿側 へのtry-catchの制約がかからない • B: Result型にwrap • 例外が不要になるが、何のエラーが発⽣した のかを知るにはコード上で表現しにくい • C: 失敗時にはnull • 何のエラーが発⽣したのかを伝えるには向か ない 例外 vs Result vs sealed class
  7. © LY Corporation 11 #4 Good things come in small

    packages • D: 独⾃のResult型を作る • どのエラーで失敗したかを安全に検知できる • sealed classがユースケースごとに増えたり、 コードが⻑くなる • どれを採⽤すればいいかの判断にはいくつかの軸 • 例外ハンドリングが必須か • 失敗の区別をつける必要があるか • 例外のハンドリングが必要な場合 • A以外 • 例外の種類を知りたい場合 • D>A=B>C 例外 vs Result vs sealed class
  8. © LY Corporation 12 #4 Good things come in small

    packages • LINEヤフー Tech Blog 内 『コード品質向上のテクニック』シリーズ でも解説しています https://techblog.lycorp.co.jp/ja/20231109a 例外 vs Result vs sealed class
  9. © LY Corporation 13 #5 Time will tell • callFooの呼び出しにリトライのロジックを⼊れる場合を考える

    • Flow.retry()を使⽤する⽅法と、whileでリトライロジックを⾃分で実装する場合を考える • 第1引数: 最⼤リトライ回数, 第2引数にリトライする条件 • 単純なリトライだけの場合、左側の⽅が簡潔に記載できる Flow.retry()を使う上での注意点
  10. © LY Corporation 14 #5 Time will tell • リトライまでの時間追加する場合

    • リトライ条件のラムダにてdelayをおこなう • 何回⽬のリトライかに応じた制御をおこなう場合、retryWhenを使⽤する必要がある • Whileを使っている⽅では、delayを追加しただけ Flow.retry()"を使う上での注意点
  11. © LY Corporation 15 #5 Time will tell • もしcallFooが例外を投げず、Result型を返す関数だった場合

    • retryを使う場合、Resultのアンラップと再ラップの⼯程が挟まる • retryはExceptionが投げられた場合しかリトライできない Flow.retry()"を使う上での注意点
  12. © LY Corporation 16 #5 Time will tell • もしcallFooが例外を投げず、Result型を返す関数だった場合

    • whileの場合、むしろtry-catchが無くなりシンプルになる Flow.retry()"を使う上での注意点
  13. © LY Corporation 17 #5 Time will tell • ユースケースに応じて、便利なフレームワーククラスを使う場合と、愚直に書く場合を使い分ける

    • 便利にかけなくなってきたときはそのフレームワークが想定したユースケースからはずれてき たとき → 別の書き⽅ができないか⼀度振り返ってみる Flow.retry()"を使う上での注意点
  14. © LY Corporation 18 #6 A structure is worth a

    thousand variables? • 左は画⾯の状態をsealed classで表し、それぞれで使えるプ ロパティを厳格に定義している • 同じ名前のプロパティでも、アクセスするのにダウン キャストが必要になる • 右のコードでは、UI上あり得ない状態を作ることもできる がシンプル • UIを表⽰するだけであれば厳格なモデリングはしなく てもいいかもしれない • Stateを提供する側できちんとあり得ない組み合わせが 出ないように担保する(テストなど) モデリング
  15. © LY Corporation 19 #7 Peeling back the elements of

    the model • 実⾏中と終了の状態をどのように表現するべきかどうか • 左: 実⾏中・正常終了・エラーの3状態を同等に扱っている • 右: 実⾏中と終了で⼤きくカテゴリを分け、終了の中に成功orエラーの2状態をもつ データモデルの構造化
  16. © LY Corporation 20 #7 Peeling back the elements of

    the model • これを使う側のコードに応じてどちらの構造を使うべきか考える • 全ての状態で共通するロジックが多い/全てで違う : 左 • 実⾏中のみ特殊な動作・UI : 右 • それ以外の組み合わせも、ユースケースごとで採⽤しても良い データモデルの構造化
  17. © LY Corporation 21 #8 Look at the type before

    you jump • 左: early returnがあることで、関数の概要を理解しやすい • early returnしていない側でスマートキャストが効かない • 新しいclassがFooApiResultに追加されたときに気づけない Either のような型で early return を使うか、網羅性を保証するか
  18. © LY Corporation 22 #8 Look at the type before

    you jump • 右: whenによりコンパイル時に網羅性が保証される • ネストが深くなる • 使⽤している構⽂が増えるので、関数の概要を理解しにくくなる • サポート関数に各caseを切り出すのも⼿ Either のような型で early return を使うか、網羅性を保証するか
  19. © LY Corporation 23 #9 A place for every function,

    and every function in its place 左: サポート関数に処理を切り出す サポート関数⾃体ごとで独⽴ Þ 補助的な関数が参照するスコープ が限定される 右: ローカル関数に処理を切り出す 1つの関数として閉じている Þ 補助的な関数を参照するスコープ が限定される ローカル関数を使うべきかどうか
  20. © LY Corporation 24 #9 A place for every function,

    and every function in its place 左: サポート関数に処理を切り出す サポート関数⾃体ごとで独⽴ Þ 補助的な関数が参照するスコープ が限定される 右: ローカル関数に処理を切り出す 1つの関数として閉じている Þ 補助的な関数を参照するスコープ が限定される ローカル関数を使うべきかどうか
  21. © LY Corporation 25 #10 Responsibility for nothing • 左:

    必須の依存関係(Repository)がnullになる場合にインスタンスを作成させない • 右: 必須の依存関係(Repository)がnullになる場合でもインスタンスを作成できるようにする • ただし、中のロジックは動かない場合がある 必須の依存関係がnullになる可能性がある場合の扱い
  22. © LY Corporation 26 #11 In Search of Lost Event

    • 「画⾯を開く」のような「1度 Subscriberで処理されたらイベント⾃体を無かったことにする」 ロジックを考える • 左: StateFlowとconsumeする関数を⽤意し、collectされたらconsumeするようにする • 右: SharedFlowを使い、Subscriberが値を取り出すと同時にSharedFlowから値が消えるように する one-shotなアクションの通知をFlowで実装する場合、StateFlowとSharedFlowのどちらがいいのか
  23. © LY Corporation 27 #11 In Search of Lost Event

    • 左: 明⽰的にconsumeをしないと消えない • 2箇所でcollectしているときに、⽚⽅が値を受け取ったらconsumeするといったことができる • コードが冗⻑になりがち • ある時点でのvalueを取得することができる one-shotなアクションの通知をFlowで実装する場合、StateFlowとSharedFlowのどちらがいいのか
  24. © LY Corporation 28 #11 In Search of Lost Event

    • 右: Subscriberさえいればconsumeに相当することを⾏ってくれる • Lifecycleにそって正しくcollectしないと、処理されるべきイベントが消える可能性がある • onPauseでcollectをキャンセルせず、UIに反映されずイベントが消える • collectをキャンセルしたことで、内側の処理も中途半端になる可能性もある one-shotなアクションの通知をFlowで実装する場合、StateFlowとSharedFlowのどちらがいいのか