アプリ開発に静的解析導入を進めている話
こんにちは、うつぼです。ナビタイムジャパンで「NAVITIME」アプリのAndroid版を担当しています。
今回は、Androidアプリの静的解析についてお話しします。
静的解析とは?
静的解析とは、ソースコードに対する解析を行うものです。
ソースコードの安全性やコーディングスタイルを解析することに用いることが多いほか、コードの行数や複雑度を計測するものもあります。
導入している静的解析
AndroidLint
AndroidLint(以下Lint)は、Androidが公式で用意してくれている静的解析ツールです。
開発中にIDE上にエラーを出してくれるのですが、コマンドラインで解析を実行すると、htmlで解析結果を出力してくれます。
Lintには強制終了やパフォーマンスの低下、こうした方がいいかもよ?といった軽いものまで、様々な指摘があります。
以下に例を挙げてみます。
minSdk内の特定APIレベルで使えないメソッドが使われている
RecyclerViewのAdapterに対するnotifyDataSetChangedは他のメソッド利用を検討したほうが良い
他のモジュールに同じ画像ファイルが存在している
非推奨のAPIを利用している
Lintは細かさや除外する指摘を設定することが出来ます。
指摘は「必ずしも悪く、修正が必須」とは言い切れないため、指摘が発生した際に調査や相談をして、修正するのか、除外するのかを判断するのがいいでしょう。
detekt
detektはKotlin向けの静的解析ツールです。
ベースはコマンドライン実行ですが、AndroidStudioのPluginが提供されているため、IDE上でも解析結果を表示してくれます。コマンドラインの場合はLintと同じく一覧をhtmlで出力できます。
detektにはこんな解析があります。
定義値ではなく、生の数値が使用されている(マジックナンバー)
折返し行数以上に長いコードがある
使っていない引数がある
また、行数や複雑度も出してくれるため、定量的なコードの品質を測るのにも使えると思います。
特に、cognitive complexity(認知的複雑度)を測れるところがポイントです。
よく使われるcyclomatic complexity(循環的複雑度)も測れるのですが、認知的複雑度が測れることで、より人間の理解しやすいか(可読性)に重きを置いた計測が可能になっています。
detektも指摘の有効無効や、無効ルール(data classでは無視する, interfaceでは無視する等)も設定できるので、チームで設定を相談して決定するのが良いでしょう。
2つのツールの使い分け
AndroidLintは、公式が提供していることもあり、Androidとして修正した方が良い指摘を出してくれます。
一方でdetektは、Kotlinで不具合になりうるコードはもちろん、コードスタイルや可読性の指摘も対応しています。
私のチームでは、AndroidLintをベースとして、detektではチームで設定した可読性やコードスタイルに合っているかチェックする目的でも利用しています。
コードスタイルに関しては、今までプルリクで指摘することも多くありましたが、detektを導入したことで、ルールを明確にして機械的にチェックすることができるようになりました。
運用に乗せる
ツールが有用なことは分かりましたが、これを「各自プルリク出す前にローカルで実行してね」と言ったのでは、面倒であり、忘れることもあります。忘れてしまうと他の人の指摘が自分のローカルでエラーになり、徐々に修正されなくなっていく…なんてことも予想できます。
そこで、CIの出番です。
弊社ではコード管理にBitbucket Cloudを利用しており、そこで利用できるCIとしてBitbucket Pipelinesがあります。
Bitbucket Pipelinesではプルリクエスト作成時やマージ時などに任意のコードを実行し、結果をSlackに通知したり、マージ出来ないようにすることが出来ます。
今までは、プルリクエスト作成時にユニットテストのみを実行していましたが、現在ではAndroidLintとdetektも実行し、エラーの場合はマージさせないようにしています。
実行結果はプルリクエストのpipelinesから確認でき、解析結果はArtifactsからDLして確認できます。
Bitbucket Cloud以外のCIでも、同じようなことができると思います。
他チームに広めている話
私のチームでこれらの仕組みを導入して、以下のような効果が見えてきました。
導入前では気にしていなかった観点での修正
実は危険なコードだったものや、MagicNumberなど書いているときは気づきにくく後で見るとコードが追いづらくなるものなど、Lint/detekt共にルール自体も参考になりました。
チームで決めたルールを守ることの心理的負担減
プルリクでコードスタイルなどを指摘する/されることが減ったため、時間コストもそうですが、心理的なコストも減りました。
指摘されていくうちに、ルールが身について来る
指摘されて修正されるうち、徐々に最初からチームで決めた良いコードを書けるようになってきました。
そこで「これはプロダクトの安全性を高める上でも、コードの安全性を高める上でも有用だ」と思い、他のチームにも広めることにしました。
広め始めた段階では、Androidアプリチームで静的解析を積極的に導入しているところは少なく、広め甲斐がある状態でした。
気持ちよく導入してもらう工夫
今までいくつかのプロダクトに導入をしてきましたが、進める際には寄り添うように注意しています。
元々安全性へのモチベーションが高かったり、開発リソースに余裕があるチームであれば、少し話すだけで導入が進むと思いますが、多くはありません。「導入メリットはこれで、この方法で導入できるようになっているので見てみてください」とドキュメントを渡すだけでは、思うように進まない可能性が高いです。
今回導入を進めるにあたって、チームの状況ごとに私がどこまで入るかを考えるようにしました。
ドキュメントを渡して、自分は聞かれたら答えるというスタンス
メリットの説明や導入までをモブで行い、その後はお任せする
導入後も定着するまでは見守る
CIとの連携は、導入していないとハードルが高いため、モブで行うことが多いです。
また、どのチームもプロダクトはある程度成熟しているため、静的解析という性質上入れたときには指摘が多数出てしまいます。そうなると「たくさん指摘が出すぎて減らすの大変…」となってしまうこともあるため、そこも「指摘をどう減らしていくか」を一緒に考えていくのが良いと考えています。
静的解析は実施しなくても動くものは出来てしまうもので、導入効果もすぐに目に見えるわけではないため、うまくいくまでサポートするのが良いと思います。
今後やっていきたいこと
引き続き社内への導入を進めていきたいと考えていますが、個人的に、より良くするためにやっていきたいと考えていることが2つあります。
解析結果をプルリクエストに自動で反映させる
現状解析結果は、自分たちが能動的に確認しにいく方法(プルリクから結果のhtmlファイルをDLするか、ローカルで実行してhtmlファイルを確認する)しかありません。
これでは少し面倒なので、AndroidStudioのように、エラーが出たら該当の場所にプルリクコメントを残してくれると、より受動的に指摘を修正することができると思いました。
Dangerというツールを使うと、プルリクにコメントすることもできるため、これをBitbucketCloudと連携させれば良さそうだなと妄想しています。
カスタムルールの作成
チーム内の独自ルールや、使っているライブラリならではの気を付けたほうがいいポイントなど、Lintやdetektの基本ルールでは解析できないものもあります。
Lint/detektどちらもカスタムルールを作成することができるので、作ることでドキュメントに書く以上に安全なコード運用ができると思い、作成していきたいと思っています。
しかし、Lintのカスタムルールは作ったことがあるのですが、取っ掛かり辛いところがありました。周りに導入していくには、自分のさらなる理解向上はもちろん、今回の静的解析導入のような寄り添った進め方が必要だと感じています。
まとめ
今回はAndroidアプリの静的解析についてお話しました。
導入していない方がいれば、参考になれば幸いです。
また、世の中にはもっと進んでいるチームもあるので、上を目指しつつコツコツと進めていければと考えています。