こんにちは。AndroidアプリエンジニアのOishi、Yamada、Murakamiです。
先日開催されたDroidKaigi 2024のLINEヤフー企業ブースでは、「Code Bug Fix Challenge」を実施しました。
「Code Bug Fix Challenge」とは、問題コードに含まれるバグを見つけ、そのバグを修正してもらったり、将来のバグを防ぐためにはどんなテストコードを書けばいいのかを来場者に考えてもらう企画です。
ブースに来ていただいた方には意見を書いた付箋を貼ってもらい、その意見をもとにLINEヤフーの開発者と交流しました。
本記事では、今回出題した「Code Bug Fix Challenge」の4問目の解説をします。
The Enigma of Initialization
出題コード
4問目では、以下のコードが出題されました。
出題意図
オブジェクトの生成やライフサイクルに関して起こりがちなミスを議題にして出題しました。
コメント
ブースでいただいたコメント
この問題でいただいたコメントを以下にいくつか抜粋します。
出題コードに含まれるバグについて
FooFeatureManager
にActivityContext
を渡しているのでメモリーリークするisReadyMutableFlow
がnullの可能性があるON_STARTED
以降を想定していた場合にクラッシュするcollect
を呼んですぐにイベントが来るとStateFlow
のinit
より前にhandleEvent()
が実行されてしまうinit
前にhandleEvent()
を呼ぶ
どんなテストコードを書けばいいのか
handleNewIntent()
を呼んでhandleEvent()
の処理が呼ばれるかのテストを書く- DIする
ボードの写真
解説
出題したコード上のバグについて説明をします。
FooFeatureManager のインスタンスが Activity と ViewModel で共有されている
対象箇所(コード)
解説
ViewModel
はActivity
よりも生存期間が長く、recreateされても生き残るので、その場合はFooFeatureManager
のインスタンスがリークしてしまう問題が発生します。また、Activity
で使われるロジックとViewModel
で使われるロジックを同じFooFeatureManager
というクラスに書くのは設計的にあまりよい方法とは言えません。"~Manager"というクラス名自体を避けるよう設計をすることが推奨されます。
解決策
解決策としてFooViewModel
のfactory
をFooViewModel
自身のcompanion object
に持たせるようにすることで、誤ってActivity
と同じライフサイクルのインスタンスをViewModel
に渡してしまうことを防ぎ、同様の問題が発生しなくなるようにできます。
FooViewModelのinit内のFlowのcollectにて、Flowが即座に値をemitしたときにコンストラクタ中にhandleEvent()が呼ばれてNullPointerExceptionが発生する
対象箇所(コード)
解説
FooViewModel
のinit
内でFlow
のcollect
を行っていますが、このFlow
が即座に値をemit
したときはコンストラクタ中にhandleEvent()
が呼ばれることになります。その場合、FooViewModel
のプロパティisReadyMutableFlow
は未初期化なのでisReadyMutableFlow.value = true
のところでNullPointerException
が発生してしまいます。
解決策
解決策としてFooViewModel
のコンストラクタでコルーチンをlaunch
するのをやめ、別途start()
関数を作ってそれをfactory
から呼び出すようにすると同様の問題が発生しなくなります。
想定解答
修正コード
混入させた不具合を修正したコードです。
おわりに
「Code Bug Fix Challenge」の4問目について解説しました。
この記事では、オブジェクトの生成やライフサイクルに関してのよくあるミスとその解決策を紹介しました。
DroidKaigi 2024 Code Bug Fix Challengeのその他の問題の解説をまだ読んでない方はぜひそちらも確認してみてください。
全4回にわたり、問題解説をお届けしました。バグ視点の気づきは得られましたでしょうか?
いずれまたこのような企画を開催したいですが、その際は現地でお会いできることを楽しみにしております。