🦉

A Philosophy of Software Design, 2nd Editionメモ

2022/09/25に公開

書籍

メモ

ソフトウェア設計において「複雑性とどのように対峙していくか」について書かれている本。本書内に記述されていない内容 (自分が勝手に感じたこと、考えたこと)も含まれているので注意。

複雑性の問題にどのように対処するか

複雑性は

  • Dependencies (依存性)
  • Obscurity (不明瞭性)

の蓄積から生まれる。

そして複雑性が増すと、

  • Change amplification (変更の増大)
  • Cognitive load (認知負荷)
  • Unknown unknowns (未知の未知)

をもたらす。その結果、新しい機能を実装するたびに、より多くのコードの修正が必要になる。

こういった複雑性の問題を

  • 複雑性の排除
  • 複雑性の隠蔽 (もしあるシステムに複雑な部分があったとして、その部分に触れる必要がほとんどないのであればシステム全体の複雑性にはあまり影響しない)

といったアプローチで回避していく方法が紹介されている。

投資マインド

  • Tactical programming (戦術的プログラミング)
    • 新機能やバグ修正など、何かを動作させることに主眼が置かれる
    • tactical tornado (戦術的トルネード) = 他の人よりはるかに速くコードを書き出すが、完全に戦術的な方法で仕事をする多作なプログラマー
  • Strategic programming (戦略的プログラミング)
    • 不必要な複雑性を導入することは許されない。
    • システムの長期的な構造を第一に考え、不必要に複雑性が導入されないようシステムの設計を改善するために時間を投資する

アジャイル開発なども「まず機能を作り、ユーザに届けること」に主眼を置くがゆえにTactical programmingに陥りがち。Strategic programmingで常に中長期的な視点を持ち、設計の改善に適切に投資をすることが、結果的に「最短でユーザに価値を届け続けている状態」への近道だろう。

モジュールの設計

モジュール設計に関しては本書は若干主張強めなので注意。

参照:

最も良いモジュールは、そのインターフェースが実装よりもずっとシンプルなもの (Deep modules)。

  • シンプルなインターフェースはモジュールがシステムの残りの部分に与える複雑性を最小にする
  • あるモジュールがそのインターフェースを変更しない方法で変更された場合、他のモジュールはその変更の影響を受けない

抽象化とはある実体を簡略化して、重要でない細部を省略したものなので、重要ではない詳細を省略すればするほど良い。モジュールがもたらすメリットはその機能性であり、モジュールのコスト(システムの複雑性)はそのインターフェースである。

極端にこの考え方に全振りするというよりは以下のような感じで、バランス良く考え方を取り入れる感じが良いか。

  • 外部に公開する必要のない情報や実装詳細は隠蔽し、モジュール間を疎結合に保つ
    • 単一責任の法則に従う
      • 一つのモジュールで複数のことをやろうとしない
      • 同じようなことを担当するモジュールが重複しないようにする
    • 継承は親子クラスの依存関係が増え、複雑性や認知負荷が増すので、基本的にCompositionを使う
    • 各モジュールをテスタブルに保つ
  • 実装とインターフェースのバランスをうまく取りつつ、基本思想としてはモジュールをシンプルに保つ

情報隠蔽性が最も高く、依存関係が最も少なく、インターフェースが最も深い構造を選ぶことが重要である。

コメント、ドキュメント

特定の設計上の意思決定の根拠 (ADRやデザインドキュメントのようなもの)や、特定のメソッドを呼び出すことが理にかなっている条件など、コードだけではどうしても表現できない、重要な情報は多々ある。こういった情報が隠蔽、欠落した状態になっていると未来の開発チームの認知負荷が上がるため、コメントやドキュメントに残すことが重要である。

そしてコメントは関連するコードの近くに置く、重要な意思決定が記されたドキュメントは一元管理するなど、情報にアクセスするコスト、情報を更新するコストを下げられるようにメンテナンスしていく。重要な情報に常にアクセスし、認知負荷を容易に下げられる状態を維持すること。

筆者が行っているようにコメントやドキュメントを先に書き始めて抽象化や設計のイメージを沸かせてから詳細に実装するアプローチ (Comment-firstアプローチ)も有用そうだと感じた。ADRやデザインドキュメントなどは特にそのアプローチと相性が良いかもしれない。

正確な命名と一貫性の担保

変数やクラス、メソッドの名前から想像されるイメージと実体に乖離があると、バグが増えるリスクが上がり、認知負荷や複雑性の増加にもつながる。

良い名前は

  • Precision (正確性)
  • Consistency (一貫性)

を有している。

timecountなど、一般的すぎる命名は正確性に欠け、読み手の認知負荷が高くなるため、よりコンテキストに沿った正確な命名を心がけること。

そして名前の一貫性を担保するためには

  • 与えられた目的には常に共通の名前を使う
  • 与えられた目的以外には決して共通の名前を使わない
  • その名前を持つすべての変数が同じ動作をするほど目的が狭いものであることを確認する

ことが重要である。

一貫性はシステムの複雑さを軽減し、その動作をより明白にするための強力なツールなので、命名以外のスコープにおいても大切にしていきたい。以下のような取り組みによって、チームで一貫性を担保することが重要である。

  • Document
    • チームのコーディングガイドラインや開発VALUE等を用意して、メンバーに浸透させる
  • Enforce
    • 例えばコーディングスタイルなどは機械的に担保できるため、CIなどの仕組みでLintがパスしないとpull-requestがreadyにならないようにするような仕組みを導入する
  • When in Rome
    • すべての開発者が "When in Rome, do as the Romans do "という古い格言に従うべき
    • 新しいファイルで作業するときは、既存のコードがどのように構成されているかを見て回り、既存の慣習に従う
  • Don’t change existing conventions.
    • 既存の慣習を「改良」したい衝動に駆られないこと
    • 自分のアプローチが仮に正しいと思ったとして、本当に既存の慣習をすべて上書きしてメンバーに浸透させるコストを払ってまで価値がある取り組みかを疑うべき

Discussion