アプリの安定性に向けたたゆまない旅程
3~4か月前、コマーススタートアップのCTOである知り合いの後輩が悩み相談をしてきた。運用中のアプリで同じバグが何度も発生し、重要な機能がある日突然動作しないことが繰り返されるが、どうすれば根本的な改善ができるかという質問をした。ちょうど今年、私たちのチームの目立つ成果がアプリの安定性を高めたことがある。コードやインフラ、開発プロセスを着実に発展させようとする取り組みをした末、アプリの安定性は着実に改善されつつある。後輩のチームの現状をもう少し詳しく聞いた後、短期・長期的に実行できるいくつかの対策を提案した。
アプリの安定性というのは人ごと、チームごとに異なる解釈ができるため、測定する方法も多様だ。ユーザーの立場から見ると、crash-free session、crash-free userの比率などを追跡することができる。ユーザーに直接影響を与えるこのような数値は、発売後に発生することに関する情報だ。しかし、発売前から測定できる安定性の数値も非常に重要で、これら2つは深く関連している。一般に、機能開発、機能テスト、回帰テストを経て、エンドユーザーにコードがデプロイされる。回帰テスト中、またはデプロイ後に発見される回帰バグ(regression bug)は、アプリの安定性に非常に致命的である。直接コードを修正しなかったと思っていた部分で予期外のバグが発生したというのは、一次的にはコードの問題だが、コードレビュー、自動化テスト、QAを経た後に発見されたというのは、チームのプロセスにバグが抜ける穴があるという意味なので、各段階別に改善点を見つけて埋めなければならない。各段階別の改善方法と目的が異なるため、それに合わせて対策を導入し、長期的に維持しなければならない。
1. コード品質を上げてテストする
まず、バグの根源であるコードの品質を改善し、テストを書くことが最も確実で長期的に維持可能な解決策だ。ロバート・マーティンは「数学では何かが正しいことを証明するが、科学では何かが間違っていることを証明する。科学はテストカバレッジが高いので信頼できる。ソフトウェアは科学なので、テストで私たちのプログラムが間違っていることを証明することができる。故にカバレッジの低いソフトウェアは信頼できない」(原文)と述べた。自分が作ったソフトウェアが意図したとおりに動作することを確認する方法は、テストを作成することだ。アプリの開発環境において、テストとアーキテクチャは密接な関係がある。iOSアプリアーキテクチャのデフォルトと呼ばれるMVCはテストするのに適していない。この問題を解決するために、様々なアーキテクチャが生まれれた。MVCが現在の主なアーキテクチャなら、中長期的な計画を立てて別のアーキテクチャにリファクタリングを敢行しよう。また、良いアーキテクチャは開発者のミスを減らし、正しい方向にコーディングするのを容易にする。
テストは速度と統合の度合いを基準に種類が分かれるので、状況や事情に合わせて優先順位を置いて導入しよう。iOSのXCTestフレームワークは、単体テストとUIテストに対応している。単体テストはモジュール単位でも実行できるため、通常は実行速度が非常に速い。単体テストはさらにApplication TestとLibrary Testに分けられるので、技術的に適切なものを選択すれば良い。また、XCodeが単体テストの結果としてコードカバレッジを知らせるため、DerivedDataに生成されたxcresultファイルを読み取ることでモジュール別のカバレッジを簡単に収集することができる。一方、UIテストはアプリ全体をビルドした後、シミュレータで実行する必要があるため、通常はテスト実行時間が長くなるが、コードの一部ではなく、人がアプリを使用しているかのように機能をテストできるため、重要な機能が本当にうまく動作しているかを確実に検査できるというメリットがある。
コードレビューもコード品質を高める方法の一つだ。コードレビューをうまく行う方法は、Googleが公開した内部ガイドが多く役立つ。(翻訳のまとめ)
2. インフラを構築する
自動化インフラ
アプリを継続的に開発、デプロイ、運用するには、CI/CDと呼ばれる自動化インフラが必要だ。最も基本的なデプロイ自動化、テスト自動化ができる。コミットごとにデプロイすることも、毎日特定の時間にデプロイすることもでき、必要に応じて開発者が希望する時にデプロイを行うこともできる。Siriにさせることもできる。また、プルリクエストベースの開発プロセスを持つチームなら、プルリクエストごとにテストを自動的に実行させ、新しいコードがメインブランチに作業がマージされる前に既存の機能が壊れていないかを確認することができる。自動化は、開発に関する全てのプロセスの基盤となる。自動化されているだけに、開発者は繰り返し作業に時間と労力を無駄にせず、自動化されたテストは回帰バグを捉える一次的な防御線なので、インフラを必ず構築しよう。
リアルタイム品質モニタリング
ユーザーに新機能をデプロイした後、サービス運用中に問題が発生した場合、迅速かつ正確に対応するためには、モニタリング基盤が整っている必要がある。エンドユーザーが感じるサービス品質を表す数値をQuality of Experience Metrics(以下、QEM)という。Google Analyticsといったアクセス解析ツールとは一線を画す。UI/UXを改善するために使うのがアクセス解析であれば、QEMはサービスの品質をリアルタイムでモニタリングし、問題が発生した時に解決に役立つツールだ。代表的なQEM値としては、Xの所要時間、Yの成功率/失敗率などがある。例えば、アプリの読み込み時間を記録して、ユーザーが実際に経験するサービスの速度を数値化できる。その数値を元に行動をすることもでき、どれだけ改善されたのか確認することもできる。また、中核のサーバーAPIなど重要なタスクの成功/失敗を記録し、リアルタイムでモニタリングすれば、サービス運営中に発生する異常状況を早く把握することができる。
モニタリングする時は、導出された数値が現象を観察する目的に合致しているかに注意する必要がある。様々な変数を考慮して値を抽出して分析する必要がある。特定のネットワークリクエストが失敗する回数が増えたとしても、ユーザーが増えて失敗が多いのか、それともシステムに問題が生じて失敗が増加したのかによって異なる行動を取らなければならないからだ。また、性能関連の数値を分析する際も慎重に値を確認しなければならない。スマートフォンの性能、地域別のネットワーク品質によって、ユーザーは私たちの予想とは全く異なる経験をすることがある。従って、有意義な特徴によって母集団を分割するのが良いかもしれない。また、単純平均値だけでなく、P50、P90、P95値など、色々と統計的に有意義な数値を一緒に確認してこそ、現象をより総合的に把握し、それに合った行動をとることができる。
様々な現況板
QEMなどのリアルタイムモニタリング以外にも、組織にとって有用な現況板を構築することができます。現況板は本来の目的に加えて、個人的に達成感とモチベーションの面で肯定的な効果があった。モジュール別のコードカバレッジを表示する現況板にカバレッジが低すぎると赤や黄色、目標値を達成すると緑で表示をしたが、最初は赤だらけだった現況板が徐々に緑に染まっていった。テストを作成することは新規機能開発のように結果物を目で見ることができないが、このように現況板を構成するので、テストコード作成の業務もまるで結果物を目で見るようになるような効果が生じた。クライアント開発者としては、視覚的な結果のおかげで、もっと熱意を持つことができたようだ。
ただ、現況板は頻繁に入って見る用途にしてはならない。Grabの元CTOだったマーク・ポーターは、「現況板を見続けてはいけない。現況板をいつ確認するかを知らせるのは警報の役割だ」と述べた。現況板を見事に作ることにとどまらず、異常現象が生じたら自動的に通知を受けられるようにして、誤って重要な事件を見逃したり、対応が遅れないようにしよう。
3. 業務文化の改善
プロセスは組織ごとに様々なので、全ての組織に対して一概に適用できるわけではない。それで、今年私たちのチームが実践したことのうち、効果が良いと思う2つのケースをまとめた。
QAチームとの緊密な協業
私たちのチームは仕事の2割をコードリファクタリングや構造改善に投入するため、新機能ではなく既存にうまく動作していた機能もしばしば影響を受ける。上半期にはこのようなリファクタリング業務にQA担当者が割り当てられず、開発チーム内部でのみ行われた。すると、情報がうまく共有されず、QAチームは定期的に回帰テストを実施する時に、どこに注意しなければならないのか分からず、重大な回帰バグが数回発生した。その後、開発チームとQAチームは、技術課題の影響範囲と手動テストの必要性を一緒に評価し始め、QAチームが知らないうちに既存部分に影響を与えることをなくそうとした。最近はさらに自動化して、開発者が自分のMRに影響範囲を表記すると、回帰テストの開始時に修正された部分が自動的に収集され、QAチームが確認できるようにしている。
また、Grabアプリはフィーチャーフラグ(feature flag)技法を導入しており、機能を全て完成しなくてもマスターブランチにコードをマージすることができる。しかし現実ではフィーチャーフラグとして管理できない開発業務もある。このような時はやむを得ずフィーチャーブランチで開発しなければならないが、単一のアプリに120人以上が寄与しているため、マージが遅れるほどコード衝突の可能性が非常に高く、コード衝突を解決する途中に新しいバグが生じるリスクも大きくなる。そのため、この類の機能もQAチームとうまく調整し、開発が完了すれば、できるだけ早く手動テストを経て迅速にマージできるようにプロセスを整えている。
週間バグレビュー会議
私たちのチームは、毎週1時間、一緒にその週に発生したバグを振り返り、バグの原因が何で、今後はどう修正すれば同じ問題が発生することを根本的に遮断できるかを議論する。バグを引き起こした人を探したり、責任を問うことは起こらない。誰でも間違えることはあるが、同じミスが2回以上発生するのはチームの問題だという考えに基づいている。テストが不十分だったことがわかったら、テストを補完する。複数の開発者が同じようにミスをする場合は、文書を作成して知識を共有したり、アーキテクチャを修正する。新しく導入する技術やプロセスがあれば、誰かが追加調査して後続措置をする。経験上、このレビュー会議で活発に議論され、チームメンバー間の情報と知識交流によって、チームがみんなで成長する。バグが減り、レビュー会議が短く終わる時は、やりがいと達成感を感じる。
マルチ防御線と自動化優先主義
各種対策は互いを補完する。荒い粉は何度もふるいにかけてこそ細かい粉を得ることができるように、いずれかの方法1つだけで全てのバグと問題を捉えることはできないため、様々な種類の対策を多重に配置しなければならない。そのため、それぞれの方法が持つ長所と限界まできちんと把握して空き穴を埋めることが重要だ。例えば、いくらテストコードを作成しても完璧ではない。テストケースが不十分であるか、コードのロジック自体が誤って書かれている可能性がある。テストに失敗したことを見つけた開発者が任意でコードを修正して通過したかのように見せかける場合もある。そのため、自動化テストも他の方法で補完しなければならない。コードレビューで、複数の開発者に修正を確認させることで、間違いを見つけることができる。それだけでなく、単体テストでコードの一部だけを独立してテストしていると、実際の環境で正常に動作しなかったり、またはクラッシュが発生したりする。より統合的なUIテストでこれを補うことができる。その後もQAチームを経るなど、新しく作成したコードがデプロイされる前に問題を事前に発見できるように防御線を重ねて構築すると考えてアプローチする。
新しいプロセスや政策を導入する時は、自動化する方法を最優先に模索しなければならない。自動化されていないプロセスが増えるにつれて、チームには足かせになる。開発者が手動でしなければならなかった単純な繰り返しの業務がある日、自分も知らないうちに消えることを経験した人がいるだろう。そうすると最悪の場合、以前に発生したバグや問題がまた起こる。プロセスは自動化してこそ、長期的に持続可能であるという事実をいつも念頭に置く必要がある。業務プロセスであれアプリの機能であれ、新たに追加するのは簡単だが、何かを除去するのははるかに難しく、誰も進んでしないため、新しいプロセスはできるだけ自動化できる方法を探し、手動で行うしかない業務はできるだけ消極的に導入する。
結論として、
アプリの安定性を高め、着実に維持するためには、コード品質から始め開発プロセス、テスト、リリース、運用段階まで、あらゆる領域で改善をしなければならない。 チームメンバーが問題意識を共有することから始まる。 ただ、全てを一度に行うことはできないので、チームの状況と能力、優先順位に応じて短期・中期・長期に分けて計画を立て、着実に新しい試みをしながら学んでいこう。 アプリの安定性を高めれば、最終的にはユーザーの満足度が上がるだけでなく、開発者も自信が生じて幸せになる。
Tags: tests, app stability, regression