#linedevday Paying back technical debt – LINE Androidクライアントの事例から -にいってきたまとめ!【LINE DEVELOPER DAY 2017】
今年もLINE Developer Day 2017に行ってきました!!
本記事はAndroidの技術的負債をどういったアプローチで返済したかを解説した「Paying back technical debt – LINE Androidクライアントの事例から」のまとめになります!
Paying back technical debt – a case study of line android
石川宗寿様
なぜ技術的負債を返す必要があったのか
- まだ成長中にあるから
- プロダクトの立ち上げ、成長中、メンテナンスとわけることができるが、立ち上げに関してはスピードに注力し、メンテナンスは安定性に注力することが多い
- 継続可能な開発はアップデートの速度などを維持する
- そのためには継続的リファクタリングが必要
事例1:画像メッセージのclick listener
- チャット内の画像をタップすると画像を表示するが、表示するまでに5段階のコードスタックになっていた
- imageviewがクリックされたら様々なリスナーが必要がよばれ、最後に拡大画像のactivityがよばれていた
- 直接拡大用のactivityを呼ぶようにしたところ、5段階だったコールスタックが2段階まで減らせた
事例2:メッセージのinput view
- input viewにはシンプルに見えて、テキストの送信、スタンプ、音声メッセージ、VoIP、添付ファイル、公式アカウントなど様々な機能がある
- 依存関係としてループなども多く、どこか1箇所を変更すると無限ループもよく発生してた
- mediatorパターンを適用して改善した
- 依存関係はスッキリした依存関係になった
- 1箇所変更してもその変更はmediatorに吸収されるので他のコンポーネントに影響を与えなくなった
リファクタリングについて
- リファクタリングは結果だけを見ると簡単でやればできるように見えるが、リファクタリングをするのは技術や感、経験や知識が必要なため難しい。
- また、開発を継続しつつリファクタリングをしないといけない
どう改善したか
- 技術的負債を返済しやすい環境として、コードの環境、CI、チーム環境を整えた
コードの環境改善
- reactiveX, databinder、kotlinなどの新しい言語やライブラリを導入していった
- 特にkotlinはjavaの無駄な記述を削減できるので貢献したが、闇雲にkotlin導入しても意味がない
- 例えばapplyメソッドを使うことでinflateするついでにオブジェクトを初期化できるが、applyをネストすると、どのスコープで定義されたものかわからなくなってしまう
- 必要に応じて2つの制限を加えるようにした
- best practiceに踏み込んだコーディング規約を作る
- ライブラリの仕様を制限したラッパーを作る
コーディング規約
- 長いラムダは避ける→読むまでに時間がかかるし、何をしたかったかを見失いがち
- ラムダをメソッドリファレンスで書き出して非常にスッキリしたコードにする
- 読みやすいコードをかく手助けになる
X: だめな例
1 2 3 4 5 6 7 8 9 |
groupMemberIdList,map { memberId -> val memberData = memberId?.toData() if (memberdata != null && memberData.isValid) memberData else INVALID_DATA }.filter { memberData -> val relation = memberData.getRelationWith(me) val isFriend = relation.isFriend val isBlocked = relation.isBlocked isFriend && !isBlocked }.forEach { ... } |
O: 良い例
1 2 3 |
groupMemberIdList,map(MemberId?::toDataOrInvalid) .filter(::isTalkable) .forEach(...) |
ライブラリの制限
- reactive X全てをreactiveで書いてしまうかもしれない
- reactiveXは非常に沢山の用途に使うことができてしまって悪用もしやすくなってしまっているので、用途に応じてライブラリのインターフェースを切り分けて悪用しにくくした
- reactive Xのラッパーとしてeventbusを切り出した
- 内部としては同じライブラリを使うことができる
CI
コードの変更を容易にする
PRのコメントボット
- github上でレビューを行っているが、それによりよりよいコードが書ける
- レビューに補足情報をつければレビューしやすくなる
- pushを行うと自動的に生成される
- PRに対応したissue trackerのリンクを張り、PRの必要性を追いやすくなる
- 作られたAPKをダウンロードしてインストールできるリンクができるので手元で確認しやすい
- PRのレビュアーの提案をして、だれにレビューを依頼すればいいかを提案しやすくなる
- やりかたはCIを動かして、GithubのコメントAPIを叩くだけでできます
auto branch merger
- release / trunkの2本立てブランチにしている
- releaseでバグをみつけたらリリースにbugfixをいれて、リリースのものをtrunkにマージする
- しかし、trunkでリファクタリングしたものがリリースのものとでコンフリクトしてしまう
- releaseがあるとリファクタリングできなくなるので、releaseからtrunkに定期的にマージすることで解決した
- 細かいコンフリクトを小さくすることができるようになった
- やり方は、trunkからgit fetchし、releaseにgit mergeする
- コンフリクト無ければそのままpushするが、コンフリクトがあればLINE notifyを送ってコンフリクトをお知らせする
- これをCI上で回す
ルールを記載して負債を増やしくにくくする
全てQAのテストを受ける
- issue ticketでQAのタスクは管理されている
- 全てのPRは対応するissueがあり、それを管理するbotを作った
- 対応するissueがチケットがなければPRをマージできないようにしている
- やり方は、PR comment BOTと同じ
- PRのときにissue numberがあるかどうかを見ている
コードの状態の可視化をして負債を意識させる
- 古いライブラリはその存在自体が負債になる。ライブラリ同士の依存関係を解決するのが難しかったり緊急アップデートを適用しにくくなる
- gradle-version-pluginをいれて、気づいた人がライブラリを更新できるようにしている
チーム環境
文化を変える
- boy scout ruleを徹底した
- boy scoutはキャンプ場に行ったときは出て行く前によりきれいにしてから帰ろうという概念
- コードのレビューやいじったときに、ついでに周辺のコードをきれいにしてから帰ろうといった意識でやっている
知識やスキルの共有
- 毎週チーム内テックトークをして、資料を1箇所にまとめてみやすくしていたりする
まとめ
- 機能開発をしながらリファクリングをする必要があり、環境を改善してきた
- code:新しいライブラリや制限をうまくつかう
- CI:変更しやすい環境を作る
- チーム:文化を変えて知識共有を行う
@mogmetの所感
かなり面白いセッションでした!
技術的負債を環境から変えていって少しずつ返済していくのは実に参考になりました。
ちなみに後から聞いた話によるとkotlin化もリファクタリングする時と同様についでに実施したりなどしているようです。