Software Engineering at Google(Chapter 9 ~ 12)

## この記事について

現在著書「Software Engineering at Google」を読んでおり、とても勉強になることが多々書かれているので、雑多なメモを残したので共有させていただきたい。
このメモを直接知識として取り入れることは難しいと思うが、本書への興味を抱いて実際に読んでいただければ幸いである。


https://www.oreilly.com/library/view/software-engineering-at/9781492082781/


## Disclaimer
- メモは途中の Chapter 9 あたりから取り始めたため、そこからの掲載となる。
- 原著を読んでおり、英語の理解が適切でなく認識に齟齬がある可能性がある。
- Chapter およびトピックによって、濃淡が多々生じている。
- メモには、英単語や日本語が混じっており、同じことを表すことばでも混合して使用している場合がある。


## メモ

### Chapter 9 Code Review

- レビュー依頼は小さい単位で
    - それによりレビューが早く**正確に**返ってくる
- レビュー依頼されたらなるべく早くレビューする
- コードレビューにはタイプがあり、それぞれReviewer, 目的が異なる
    - Greenfield
    - Behavioral changes improvement optimization
    - Bug fixes and rollback
    - Refactoring and Large-scale changes
- コードレビューにより以下のベネフィットがある:
    - correctness 担保
    - consistency 担保
    - comprehension 促進
    - 心理的/文化的 な利点
    - ナレッジの共有

### Chapter 10 Documentation

- ドキュメントもコードの一部、メンテナンスを要す。バージョン管理するべし。
- 1ファイルには1つのことの説明にフォーカスする。
    - wiki などでベアっと1ページに全て入れてしまうと、obsolete や broken link のオンパレードになる
- API ドキュメント記述中、うまく1つに記載をまとめられないなら API そのものに問題がある可能性を疑う(single responsibility になっていない)
- 誰に向けてのドキュメントかを明確にする。
    - e.g. API ドキュメントの場合、API 開発者向け/API ユーザー/ビジネス etc
- **clarity, completeness, accuracy はトレードオフ。一つを追求るとその他が犠牲になる**
- `canonical`(正統) なドキュメントを持つことが重要
    - これにより情報の duplication や obsolete なドキュメントを阻止できる
- How も重要だが、5W(what, where, when, who and why) も重要

### Chapter 11 Testing Overview

- テストの価値は、ソフトウェアを変更に耐えうるものにするという側面もある（変更時の間違い、エラーを検出する）
- テストの自動化が鍵
- テストをマニュアルステップではなくコードとして表現する理由
    - 機械のパワーの恩恵を得られる
    - 多種の環境への移植が容易
- ぶっ壊れたテストは即修復！それが生きたテストを書き、実行し続ける文化につながる
- テストコードの benefit から気になるもの抜粋:
    - デバッグが少なくて済む、なぜならバグが少なくなるから
    - 変更加える時に自信が持てる
    - 最良のドキュメンテーションの一つとなり得る。obsolete せず、最新の動くものの証明となる
    - デザイン再考のきっかけになり得る: もしある API テストが書きづらかったら、その API がやろうとしているジョブが多すぎることになる
- テストの size/scope
    - size: resource such as memory, process, time etc
        - size はテスト実行の速さと determinism を求める
        - small test とは..: heavy constrains すなわち、シングルプロセス/スレッド で I/O や N/W もなし。つまり速い。
        - medium test: contained within [localhost](http://localhost) つまり single machine で実行され得る全てのテスト(N/W や I/O も OK)
    - scope
        - scope は、どの code path を通るか
            - あるクラスや関数内など狭い範囲は narrow, コンポーネント間だと medium etc
        - よく言われるテストの種類との対応:
            - narrow: unit test, medium: integration test, broad: e2e(system) test
                - narrow にいくほど fast になり、限定的なテスト
                - large にいくほど slow になり、包括的なテストとなる
            - テストは基本 narrow を心がける(e.g. 80%-15%-5% などのバランス(small-midium-large)
                - fast fail の精神と、review の観点 and more
                - 最終的にはチームや組織、プロダクトにあった test suite を模索する
    - ちなみに多くの場合は size=small だと scope=narrow になるが、そうでない場合も存在する。
        - e.g. 一見大きなスコープの feature テストをしようとしているが、通信先が mock 化されている場合(small & broad)
        - e.g. 単純な UI コンポーネントのテストだが、date picker を使用しており、それにはブラウザの起動が必要である(medium & narrow)

### Chapter 12 Unit Testing

- (再掲)size と scope の関係性で、Unit test(scope=narrow) は size=small になりがちだが、必ずしもそうではない。
- Unit test の二つの効果：バグの発見と生産性の向上
    - このうち生産性を向上させるのは以下の観点から:
        1. small test になる傾向があるので、テスト実行が早く deterministic なものとなる、そのため開発ワークフローと親和性が高い
        2. テスト対象となるコード(production code)開発と同時にテストの作成を進めやすいため、システムの広範囲な事象に気を取られにくい
        3. カバレッジを高い水準にでき、開発コードが何も壊していないことに自信を持てる
        4. テスト自体がシンプルなため、問題があった場合に対処しやすい
        5. ドキュメントとしての側面も強く、システムの該当部分をどのように使用するのか、どのような機能の仕方をするのかを示すものとなる
- (再掲)以上のベネフィットより、Google では、Unit tests の割合を80%とすることを推奨している。
- どんなときにテストを変更・修正すべきか？
    - 変更すべきでない
        - refactor 時
        - new feature 入れるとき
        - bug 修正時
    - 変更すべき
        - behavior が変わるとき
    - → これにより、テスト量が線形に増えていくのではなく、必要な時のみ足していくことがわかる
- テストの実行シチュエーション、how to invoke test
    - Public API 経由で行う。エンドユーザーが使うのと同じシチュエーションであることが大事。
        - 本来 Private である関数(API) = implementation details をテスト時に使用すると、変更に弱い、brittle なテストとなる。
- Test state or interaction
    - テストで何を verify するのかは、state であるべき、interaction の場合はより brittle になりがち。テストの結果にどのように到達したか(interaction)ではなく、結果がどのようになったか(state)、に着目すべき
- Complete and concise test… テストの clarity を担保する
    - completeness: テストするのに不足している情報がないか
    - conciseness: テストするのに不要な情報がないか
    - → つまり、過不足なくテストが記述されているかが重要
- Behavior test and method test
    - テスト対象とするのは全ての method である必要はなく、それらが単数あるいは複数合わせて表現される behavior が対象となるべきである。
    - behavior は `given` , `when`, `then` で構成される:
        - given: how the system is set up
        - when: action to be taken on the system
        - then: validates the result
- テストの命名について
    - behavior にちなんだ名前をつける
        - ↑で method を対象とするようなテストを書いていると、method にちなんだ名前になってよくない
            - e.g. `updateUserInfo()` のテストとして `testUpdateUserInfo()` みたいな
    - behavior のうち、アクションや期待する結果を入れるべきであり、環境情報などの付加情報を入れ込むのも良い。
    - テストライブラリ/フレームワークによっては、ネストした形でテストケースを作成することで、コードの構造的にも、fail 時のメッセージ的にもわかりやすくできるものがある。
    - テスト名に **and** が使われているときは、単一のテストで複数のことをチェックしようとしている兆候であり、テストを分けるべき。
- テストのロジックを入れ込まない
    - 極力ホワイトボックス化する。production コードでは適さないような記述方法も、テストでは最適とされることがある。
        - 文字列の比較時、アクションによって生成される文字列と、比較元となる文字列はともにリテラルとしてもつ。
        - ループ、条件分岐も使用しないのがベター
- Fail 時のメッセージについて
    - なぜ Fail したのかぱっと見でわかるメッセージを心がける
- **DRY**(Don’t repeat yourself) より **DAMP**(Descriptive And Meaningful Phrases) を重視
    - production コードでは、同じロジックやデータは通常一箇所にまとめ、参照時はそこのみを見ることでコードのメンテナンス性を高めるが、**テストコードにおいては、そのテストが何をしているのかをわかりやすくすることに重きが置かれる。そのためには、多少の duplication の発生も辞さない。**
    - そのテストにおいて知っておく必要がある情報がわかりやすく整理されているか
        - e.g. 明示的に各テストケースで必要な値をセットする（テストケース共通部分での値をそのまま使用しない）
            - そのために言語で使用できる Constructor やヘルパー関数などの使用を検討する

## 所感

Google と言う巨大な企業で実際に行われてきた数々の実践方法を知ることは非常に勉強になる。また、Google といえど最初から完璧にできていたわけではなく、紆余曲折を経て現在の形になった変遷を垣間見れて、自分の所属する組織の改善に活かしたいと言う思いと、謎の親近感が湧いた次第である。
また、彼らがソフトウェア開発において特にテストに重きを置いていることは、巨大なシステムが定常的にサービスを提供し続けるにはテストが如何に重要かを物語っていると感じた。