III. Processes
8章 スタイルガイドとルール
Googleのルールとガイダンスの開発を舵取りする原理とプロセス。
Google Style Guide: https://google.github.io/styleguide/
8.1 何故ルールを設けるのか
ルールを設置することのゴールは、「良い」行動を促し、「悪い」行動を思いとどまらせる。
コーディングの共通ルールがあれば、どう書くかより何を書くかに集中できる。
8.2 ルールを作る
スタイルガイドを作る上でのルール
- なんでもかんでもスタイルガイドに盛り込まない。goto禁止など自明なものは含めない。
- コードを書く人ではなく読む人に向けて最適化する
- ソースコードの一貫性を保つ。これは組織のスケールに役立つ
- 誤りが起きやすいあまり使われない構文の利用を避ける
- 必要ならスタイルガイドルールの例外を許す。パフォーマンス最適化のために一貫性やリーダビリティを犠牲にするなど
スタイルガイドのカテゴリ
- 危険を避けるためのルール。使ってはならない言語機能、正しく使うのが難しい機能の正しい適用方法など。
- ベストプラクティスを強制するためのルール
- 一貫性を保証するためのルール
8.3 ルールを変更する
時間の経過によってスタイルガイドを再評価する必要が出てくる。その際は、pros/consやトレードオフを議論し詳細な根拠を元に最終的に変更を決定する。
最終的な決定と承認は、スタイルガイドのオーナーによって行われる。C++は4人のオーナーがいる。
8.4 ガイダンス
推奨するベストプラクティス。
ルールが「絶対やらなければならないこと」であれば、ガイダンスは「やるべきこと」。
8.5 ルールを適用する
ルールを遵守しているかどうかの確認はツールによって自動化する。
自動化によって、ルールがどのように解釈され適用されるかのばらつきを最小化できる。
また、自動化はルールの強制をスケーラブルにもする。
9章 コードレビュー
Googleでは、すべてのコード変更がコミットされる前にレビューされており、園児唖然員がレビューの開始と変更のレビューを担当している
Googleでは、Critiqueと呼ばれるコードレビューツールを使っている。(詳細は19章)
この章ではツールではなく主にプロセスの話。
9.1 コードレビューのフロー
- authorは自分のワークスペースで行った変更をコードレビューツールにアップロード。ツールでは変更差分が表示される。
- authorは変更を1人以上のreviewerにメールでレビューを依頼する。
- reviewerは変更を見て差分上にコメントを残す。
- フィードバックに基づいてauthorは変更を修正し、reviewerに返信。必要に応じて3,4を繰り返す。
- reviewerが変更に満足したらaproveし、LGTMをする。デフォルトではLGTMが1つあればいいが、全reviewerにapproveを求めても良い。
- 全コメントを解決してそれがapproveされていれば、authorは変更をコードベースにコミットできる。
9.2 Googleでのコードレビューはどのように機能しているか
Approveの3つのポイント
- コードがauthorの期待通りに動作するか。
- コードベースのある特定のディレクトリにおいて適切な変更か。(Googleのコードベースは、特定の各ディレクトリに階層的なownerがいる木構造)
- コードが期待するフォーマットで書かれているか。
9.3 コードレビューの恩恵
コードレビュープロセスは当然コード変更に時間がかかるという問題がある。しかし、Gooogleではどんな小さな変更でもコードレビューを義務としている。
このコードレビュープロセス文化は、以下の恩恵をもたらす。
- コードの正しさをチェックできる
- コードが理解可能なものかを確認できる
- コードベース全体で一貫性を強制できる
9.4 コードレビューのベストプラクティス
レビュアーを尊重すべき。不十分な場合にのみ代案の指摘をする。
Googleでは24時間以内にレビューのフィードバックがされることが期待されている。できない場合はその旨を返信する。
レビューは小さい変更に留めるべき。200行程度。Googleのコード変更の約35%がたった一つのファイルの変更
レビュアーは最小限に留めるべき。Googleの大半のコードレビューは1名のレビュアーによってレビューされている。
9.5 コードレビューの類型
コードレビューの分類
- グリーンフィールドコードレビュー(完全に新しいコードに対するレビュー)
- 挙動の変更、改善、最適化
- バグ修正とロールバック
- リファクタリングと大規模変更
10章 ドキュメンテーション
Googleではドキュメントをコードとして扱うのが最も成功しているやり方。
10.1 何がドキュメンテーションとして適格か
独立したドキュメントだけでなく、コードのコメントも含まれる。
10.2 何故ドキュメンテーションが必要なのか
ドキュメンテーションは時間とともにスケールするし、組織においてもスケールする。
設計上の決定理由、実装の根拠、数年後自分のコードを見返したとき、などでドキュメンテーションは有用
10.3 ドキュメンテーションはコードのようなものである
ドキュメンテーションはコードと密結合していることが多いため、できるだけコードとして扱われるべき
Docs as Codeと呼ばれる: https://www.writethedocs.org/guide/docs-as-code/
10.4 対象読者を認識せよ
ドキュメンテーションを書く前に、読む対象の読者をはっきりさせる。その読者に応じたスキルレベルとドメイン知識に応じてドキュメントを書くべき。
デザインドキュメントであれば、意思決定者を説得させる内容を書くようになる。
チュートリアルであれば、コードベースに馴染みのない読者になるので、詳細まではっきりと書かなければならない。
主要な想定読者はSeekers(捜索者)とStumblers(遭遇者)
- Seekersは、自分の知りたいことが明確で、コードが要件を満たすかどうかを知りたい
- Stumblersは、自分が知りたいことがクリアになっておらず、コードの目的を説明する概要が知りたい。多くのGoogleドキュメントでは、冒頭にTL;DRが書かれている。
10.5 ドキュメンテーションの類型
ドキュメントのタイプによって書き方が異なる
ドキュメントのタイプ
- コードのコメントを含むリファレンスドキュメンテーション
- 作業着手前に承認をもらうための、提案する設計が書かれたデザインドキュメント
- 新しいチームメンバーが新しく構成を知るためのチュートリアル
- 実行時の手順に番号を明示的に振るのが重要。
- 各手順は作業者の具体的な作業を書く。サーバ側の処理は、各手順の副作用として記述する。
- リファレンスドキュメンテーションよりも深い説明や知見を要するコードのための、概念的ドキュメンテーション
- チームポータルページのような、ドキュメントのリンクが整理されたランディングページ
10.6 ドキュメンテーションのレビュー
ドキュメントレビューの種類
- 正確性のための技術的レビュー
- 明確性のための対象読者レビュー
- 一貫性のための作文法レビュー
10.7 ドキュメンテーション哲学
技術的な文章を書くためのベストプラクティス
Howの他に、Who, What, When Where, Whyを意識する。
完全性、正確性、明確性は、優れたドキュメンテーションの特徴的要素
古くなったドキュメントは、削除するか、古いものとして分類する。Googleではドキュメントのメタ情報としてfreshness dateをつけておく。数ヶ月変更がない場合はメールでリマインドを送る。
10.8 テクニカルライターが必要なのはどんなときか
Googleでは、昔は重要なプロジェクトはテクニカルライターにドキュメント作成を任せていた。プロジェクトメンバーのドキュメント作成や保守の負担を軽減するため。
しかし、これは誤りであった。対象読者がエンジニアリングチームならプロジェクトメンバーが書くことができる。対象読者がプロジェクトメンバー以外の人であれば、テクニカルライターにお願いする。
テクニカルライターのリソースは限られているので、そのような場合にのみ依頼したほうがスケールする。
11章 テスト概観
自動テストの導入にによって、バグを捕捉できる、変更が容易になり変化に合わせて高速に反復できる。
11.1 何故テストを書くのか
テストは後からの付け足しであってはならない。プロダクトの構成要素の一つ。
Googleでは、2005年頃にGoogleWebサーバに自動テストのポリシーを追加したことによって、1年以内にロールバックやバグフィックス等の緊急リリースが半減した。
自動テストによって、プロダクトのバグ発見についてプログラマーの能力だけに頼ることを避けることができる、
自動テストは手動デバッグにかけるコストを減らすことができる。
コードの正しさ、エッジケース、エラー条件のテストがコードレビューに含まれていれば、レビューを単純化できる。
テストがあると自信をもってリリースできる。
テストがスケールするには、自動化しなければならない。
11.2 テストスイートを設計する
テストには、規模(Size)と範囲(Scope)の2つの次元がある
- 規模は、テストケースの実行に必要なリソース。メモリ、プロセス、時間など。
- 範囲は、検証している特定のコードパス。
テストカバレッジは、テストされていないコードを知りために役立つが、システムがどれだけ適切にテストされているかまではわからない。
規模
小テスト
- 単一のプロセスで実行される。シングルスレッド内で実行されなければならない。
- ネットワーク、ディスクへのアクセスを許可されていない。
中テスト
- 単一のマシン上で実行される。スレッドが使える。
- ネットワーク、ディスクへのアクセスはlocalhostのみ許可。
大テスト
- 任意の好きな場所で実行される。
- ネットワーク、ディスクへのアクセスを許可。リモートのクラスタ内のシステムに対してテストを実行しても良い。
テスト環境の構築から解体までをテストに含むべき。外部環境に対する前提条件を持つべきではない。
テスト内での複雑なロジックは避ける。条件分岐やループはなるべき使わない。
範囲
狭い範囲のテスト(ユニットテスト)、中範囲のテスト(インテグレーションテスト)、大範囲のテスト(ファンクショナルテスト、エンドツーエンドテスト、システムテスト等)
Googleのガイドラインとして、これらテストの割合は、小テスト80%、中テスト15%、大テスト5%を目指している。
11.3 Google規模でのテスト
Googleのコードの大半がモノレポで管理されている。
テストが遅くなると、生産性が下がる。質の高いテストを保つため、テストを改善したエンジニアに対しても新機能リリースと同等となるようなインセンティブを与えるべき。
11.4 Googleでのテストの歴史
テスト文化を構築するためにGoogleが行っている試み
- オリエンテーション講習
- テスト認定プログラム
- トイレでのテスト(Testing on the Toilet/TotT)。社内のトイレの個室にテストに関するTipsを貼って、社員のテストに関する意識を高める。
- Introducing “Testing on the Toilet”: https://testing.googleblog.com/2007/01/introducing-testing-on-toilet.html
11.5 自動テストの限界
自動テストは必ずしもすべてのテストに適しているわけではない。
検索結果の品質チェックなどは、人間によるチェックが必要な場合がある。
12章 ユニットテスト
テストのメンテナンス性(Maintainability)とそれを達成するためのテクニックについて
12.1 保守性の重要さ
テストの保守性とは、「just work」するものであり、テストが失敗するまではそのテストについて考える必要がなく、テストの失敗時は明確にバグがあることが示される状態。
テスト保守性のために、
- 脆いテストを防ぐ
- 明確なテストを書く
12.2 脆いテストを防ぐ
脆いテストとは、コードの変更でバグがないにも関わらず無関係なテストが失敗してしまうこと。
理想のテストは、変化しないテスト。
テストを書いた後、システムをリファクタリングし、バグを修正し、新機能を追加する際に、そのテストに再度触れなければならないというのは何かが間違っているということである。
公開APIに対してテストをすれば、それが失敗した時ユーザのアクティビティもまた失敗することがわかる。
相互作用(インタラクション)ではなく状態(ステート)をテストする。つまり、どのようにその結果になったかの過程をそれぞれテストするのではなく、どういう結果になったかだけをチェックする。例えば、ユーザ追加のテストでHTTP PUTメソッドが呼ばれたことをチェックするのではなく、オブジェクトにユーザが追加されたかどうかをチェックする。
12.3 明確なテストを書く
テストが失敗した時、テスト対象に問題があるか、テスト自体に問題があるかを特定し、問題を究明する。
テストは完全かつ簡潔にする。つまり、読み手が必要なすべての情報が含まれていて、他の無関係な紛らわしい情報が含まれていない。
メソッドに対してではなく、各挙動に対してテストを書く。
テストの名称は、テスト対象の挙動を要約する。システムに対して行われる動作と、期待される結果が含まれていると良い。困ったら、shouldで始めるのが良い。
12.4 テストとコード共有:DRYではなくDAMP
テストにおいては、DRY(Don’t repeat yourself)ではなくDAMP(Descriptive And Meaningful Phrases)。
「説明的かつ意味がわかりやすい言い回し」。テストを単純かつ明確なものにするのであれば、多少の重複は許容される。
13章 テストダブル
テストダブルはモッキングとも呼ばれるが、それよりは少し抽象的な概念。
13.1 テストダブルの、ソフトウェア開発への影響
テストダブルは本物の実装と置き換えられるようになっているべき。
テストダブルは、適切に応用できればエンジニアリングのスピードを速めることができるが、不適切に利用すると脆く複雑なものになる。
どれだけ本物の実装の挙動に近いか。
13.2 Googleでのテストダブル
教訓として、モッキングフレームワークを使いすぎるのは危険である。バグを滅多に発見しない割には、保守に労力がかかる。
13.3 基本概念
Dependency Injectionは、テストダブルを利用できるようになり、コードをテスト可能にする。
モッキングフレームワークは、テストダブルを簡単に生成できるライブラリ。
13.4 テストダブル利用のためのテクニック
テストダブル利用のための3つのテクニック
- フェイキング
- 本物の実装同様に振る舞うlightweightなAPI実装
- メモリ内データベースなど
- スタビング
- 関数が返すべき値を指定
- Mockitoの
when(…).thenReturn(…)
- インタラクションテスト
- 関数がどのように呼び出されるかを、その関数を呼び出すことなく検証する方法
- Mockitoの
verify(…)
13.5 本物の実装
モッキングフレームワークを多用せず、なるべく現実的なテストを優先する。
例えば、値オブジェクトには本物の実装が利用されるべき。
本物の実装よりテストダブルを利用した方がいい場合:
- 本物の実装の実行時間が遅いとき
- 本物の実装のテストが非決定性であるとき。つまり、テスト対象が変化しないにも関わらずテストの結果が変化してしまうとき
- 本物の実装の依存関係が複雑で構築が大変なとき。例えば、テスト対象のオブジェクトの生成のために多くのオブジェクト生成が必要なとき
13.6 フェイキング
フェイクは本物の実装同様に振る舞うため、他のテストダブルのテクニックより好まれる。
フェイクの作成に最も重要な概念は、忠実性。フェイクの挙動を、本物の実装の挙動にどれだけ近づけられるか。
13.7 スタビング
挙動のハードコード。
スタビングを使いすぎると、テストが不明確になり、脆くなる。
スタビングが適切なのは、テスト対象をある状態に遷移させるために関数が特定の値を返さなければならない場合。
13.8 インタラクションテスト
インタラクションテストは必要な場合だけ実施する。
このテストが検証できるのは、あるかんせううが期待通りに呼ばれている点だけ。テスト対象が正常に動作しているかはわからない。
本物の実装やフェイクが使えない場合にはインタラクションテストの実施を検討。また、関数の呼び出し回数や順序によって望ましくない挙動を引き起こす可能性がある場合も実施を検討する。
14章 大規模テスト
大規模テストとはなにか、それをいつ実施するか、大規模テストのベストプラクティス
14.1 大規模テストとは何か
数時間から数日の間に実行されるテストもある。
大規模テストは密閉されていない(nonhermetic)かもしれない。密閉性は、テスト対象システムが他のコンポーネントから分離されている度合い。
大規模テストは忠実性(Fidelity)に対処する。忠実性は、テストがテスト対象の本物の挙動を反映している度合い。
大規模テストは、ユニットテストが扱えないものを扱う。
14.2 Googleの大規模テスト
可能な限り最小のテストにする。巨大なテスト1つより、いくつかの大テストに分かれていたほうが好ましい。
14.3 大テストの構造
大テストの共通の流れ
- テスト対象システムを取得する
- 前述したの密閉性と忠実性によって、テスト対象システムはいくつかに分類される
- 必要なテストデータをシードとして与える
- シードデータ、もしくはテストトラフィック
- テスト対象システムを用いて動作を実行する
- 挙動を検証する
- 検証方法: 手動、アサーション、ABテスト
14.4 大規模テストの類型
1つ以上のバイナリの機能テスト
- テスト対象システム: 単一マシン、クラウド
- データ: 手動作成
- 検証: アサーション
パフォーマンス、負荷、ストレステスト
- テスト対象システム: クラウド
- データ: 手動
- 検証: 差分(パフォーマンスメトリクス
デプロイ設定のテスト
- テスト対象システム: 単一マシン、クラウド
- データ: なし
- 検証: アサーション
探索的テスト(Explaratory Testing)。ユーザシナリオに対するシステムのふるまい。
- テスト対象システム: 本番環境、ステージング
- データ: 本番環境、テスト環境
- 検証: 手動
- Bug bashと呼ばれる手動テストアプローチ。https://en.wikipedia.org/wiki/Bug_bash
A/B差分リグレッションテスト。
- テスト対象システム: クラウド上の分離された2つの環境
- データ: 本番データ、またはサンプリングデータ
- 検証: A/B差分比較
- Googleにおける大規模テストの最も一般的な形式。
ユーザ受け入れテスト(UAT)。特定のユーザの挙動が意図通りであるかを担保するための、公開APIを通じた自動テスト。
- テスト対象システム: 単一マシン、クラウド
- データ: 手動作成
- 検証: アサーション
プローバーとカナリア分析。本番環境自体の健全性を担保する方法。
- テスト対象システム: 本番環境
- データ: 本番環境
- 検証: アサーション、A/B差分
- プローバーは、本番環境に対してコード化されたアサーションを実行する機能テスト。
- カナリア分析は、本番リリース中であることを対象にしている。
- あらゆる稼働中システム内で用いられるべき。
障害復旧、カオスエンジニアリング。予期しない変更や障害に対してどれだけ反応できるかのテスト。
- テスト対象システム: 本番環境
- データ: 本番環境とユーザの手動作成
- 検証: 手動、A/B差分
ユーザ評価
- テスト対象システム: 本番環境
- データ: 本番環境
- 検証: 手動またはA/B差分
14.5 大テストと開発者ワークフロー
打規模テストは開発者のワークフローに統合する。ビルドを別に分けるのがおすすめ。
大テストを作成するための、ライブラリ、ドキュメント、テストコード例を用意しておく。
テストの高速化のために、タイムアウトと遅延を減らす、不要なビルドを減らす。
信頼不能性を排除する。つまり、たまに成功したり失敗したりをなくす。そのためにテストの範囲を狭める。
テストを理解可能にする。つまり、何が失敗しているかを知るための明確なメッセージを出す。
大規模テストには、オーナーとなるエンジニアがいなければならない。オーナーがいないと、テストの変更と更新が難しくなり、失敗の原因究明に時間がかかり、最終的にそのテストは廃れる。
15章 廃止
deprecation。システムの廃止。
廃止を正しく計画実行すれば、リソースのコストが減少し、システム内に蓄積されてきた冗長性と複雑性が除去され、速度が改善される。
一方、やり方を間違えた場合には、放置した場合より大きなコストが掛かる可能性がある。
15.1 何故廃止するのか
コードは債務であり資産ではない。
古くなったシステムを稼働させ続けるか、もしくはシステムの停止に取り組むのか、のトレードオフ
15.2 何故廃止はこれほど難しいのか
Hyrumの法則によって予期しない使われ方をしている可能性があるので、システムを廃止することは難しい。
旧システムと新システムは機能面で全く同じにはならない。
旧システムへの管理者の愛着。
廃止のために予算をつけることは政治的な側面で難しい。
旧システムから新システムへの移行コストが極めて高い。コストは実際のコストよりも低く見積もられがち。
設計の際に、自分のプロダクトから移行することになったときの容易さはどの程度か、どうすれば部分的にインクリメンタルに置き換えることはできるか、を検討した方がいい。
15.3 廃止の類型
勧告的廃止
締切がなく低い優先度で行われる廃止。
新システムに移行するメリットが大きいとき。
チームが積極的に移行することは稀。旧システムの利用が多いことが、旧システムの新たな利用者を増やすことにもなる。
強制的廃止
旧システムの撤去の締切を伴う廃止。その日を過ぎるとシステムは動作しなくなる。
ユーザに十分に警告を行っていれば、それに従わないユーザの利用を止められる権限を持つことになる。
警告は、行動可能性と関連性を持っていなければならない。さもなければユーザは警告をすべて無視してしまう。関連性は、ユーザが警告された行動を行う際に警告が表示されること。
15.4 廃止プロセスを管理する
明示的なプロジェクトオーナーが必要。
廃止プロセスでも、新システムの構築と同様、完全な撤去のマイルストーンだけではなく、具体的でインクリメンタルなマイルストーンを持つべき。