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 でおこなった際の資料です。

LY Corporation Tech

July 02, 2024
Tweet

More Decks by 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のどちらがいいのか