読書メモ: A Philosophy of Software Design

・最近よくタイトルを聞くようになったので読んでみたかった
・日本語訳は出ないらしいので英語で読む(英語で技術書読むの初めて)
・DeepLで一括翻訳して読もうとも思ったけど、翻訳された日本語が違和感バリバリでなんか間違った理解したらやだなと思ったので普通にKindleで読む
・?)で始まる行は本に書いてあった事ではなく、自分の疑問点やメモ書き

Chapter 1. Introduction
ソフトウェアデザインは複雑性との戦い
簡潔に言えば以下の2点でこれを解消する
- コードをシンプルに明示的にする
- 必要のない複雑性を隠す(モジュラーデザイン)

Chapter2. The Nature of Complexity
複雑性の定義
理解することや修正することを困難にするソフトウェアの構造に関するもの
複雑性によってもたらされるもの
- 要変更箇所の増加
単純な設計の変更がアプリケーションの至る所に影響を与えることになる
ex) 背景色の変更が必要な場合、それらを宣言しているすべての場所を直さないといけなくなる→一箇所にまとめるべき
?) ここだけ見るとDRY的な話? - 認知的負荷の増加
コードを理解するのが難しいとそれを使おうとしたり理解しようとしたプログラマーが多くの時間と労力を費やす必要がある
ex) 単一責任でなく多くのメソッドを抱えたクラス、インターフェースやコメントから読み取れる情報が少なく、実装を見るしかない関数など - 未知の未知数(unknown unknowns)
?) 日本語にどう訳すべきかわからん
インターフェースやコメント、ドキュメントから読み取れない情報。
適切なコードを修正する場合に知っていないといけない情報が存在するが、それを知る手段がソフトウェアのコード全体を全て読む以外に存在しない状態。
これが3つの中で最悪。
これらによって、複雑性が高いソフトウェアを安全に修正したり、機能追加するのは困難になっていく。
さらに時間が経つにつれ複雑性はより増していく傾向にある。

Chapter3 Working Code Isn't Enough
戦術的プログラミング
何か動くものをとりあえず作ることを指すらしい。
将来のことは考えず、現在のタスクをできるだけ早く終わらせることに特化する。
結果的にいい設計を考えるような時間は確保されず、複雑性の増加に繋がる。
戦略的プログラミング
動くコードだけでは不十分であり、システムが長期間運用されることを前提に良い設計を目指すことを指す。
良い設計を目指す分、戦術的プログラミングより時間はかかるものの複雑性の増加による時間のロスに比べれば大したことはない(長いスパンで見れば戦術的プログラミングよりも早くタスクを終わらせていくことができるようになる)
途中で路線変更するのはかなりの困難が伴うので、早めに戦略的プログラミングに移行するべき。
全員がこの路線に一気に切り替えるのはきついだろうから1日の10~20%くらいをまずはいい設計を考える時間に当てると良い

Chapter4 Modules Should Be Deep
インターフェース
このモジュールを使用するユーザーが知るべきものを記載したもの。
→対象から不必要な詳細を取り除いたシンプルな見た目(=抽象化)
深いモジュール
良いモジュールとはシンプルなインターフェースなのにパワフルな機能を提供してくれるもの(=深いモジュール)
1~数行程度のインターフェースの方が複雑な見た目をしているようなモジュールはよくない(=浅いモジュール)
?) これが本書の肝だと感じる。オブジェクト指向から入った人間としては分割すれば分割するほど個々のモジュールがシンプルになる=理解しやすくなるという認識がどうしてもあるのだがそうでないらしい(このアプローチをClassitisと本書では呼んでいた)
浅いモジュールは個々はシンプルになるが、システム全体の複雑性を引き上げる

Chapter5 Information Hiding(and Leakage)
情報の隠蔽
このモジュールをユーザーが使用するために必要な情報以外はユーザーに見えないようにする。
ex) 内部でどんなアルゴリズムが使われているか、どんなデータ構造を採用しているかなど
これによって
- インターフェースがシンプルになる
- 内部実装がモジュールの外に依存しなくなる→モジュールの内部を変更したり、機能追加するのが用意になる
これによってソフトウェアの複雑性を減らすことができる
情報の漏洩
情報の隠蔽の対義語。
1つの情報がさまざまなモジュールで参照されている状態。
ex) ファイルの読み込み、修正、書き込みのそれぞれが別モジュールで実装されており、ファイルフォーマットの情報がそれらに偏在している
処理の時間的な順番に着目(まず読み込んで、次に修正、最後にファイル書込み)するとこれらを別モジュールにしたくなるが、それは間違い。
どんな知識がその処理で必要化に着目するとこれらの処理を一つのモジュールにまとめるべきだと気付ける。
?) 愚直にやるとすごいでかいモジュールができそうだけど、それでいいのか。現実的な線引きというか基準が欲しいところ
深いモジュールと情報の隠蔽
より多くの情報を隠蔽するようにするとインターフェースがシンプルになる傾向がある→深いモジュールへ繋がる

Chapter 6 General-Purpose Modules are Deeper
モジュールの専用化
特定の状況でしか使用できない(=専用化)ようなモジュールはソフトウェアの複雑性を高める
→インターフェースが複雑になりがち
モジュールの一般化
特定の状況に対しても使い方次第で対応できるような一般化されたモジュールの方がソフトウェアの複雑性の削減へ繋がる。
一般化されたモジュールの方が内部実装を気にしなくて良くなるし、シンプルなインターフェースになる
?) 個人的には一般化するとむしろインターフェースが肥大化するような気がしていた。
→多くのオプションを用意する必要があるなど
→おそらくこのアプローチが間違っている。これはまず専用的に作って、そこに一般的にも使えるようにオプションを付与するような作り方だが、この本での主張はそれを逆にするというか、一般化して作ることで使い方次第で特定の状況に対応できるようにするみたいな主張
→こうなるのって一般化できる処理と特定の状況に依存する処理が同じモジュールで管理されていることが問題な気がする(単一責任の原則に反してる)
→ただこの本の主張と単一責任の原則って両立するのだろうか?

Chapter7 Different Layer, Different Abstraction
パススルーメソッド
隣接するレイヤーが同じようなインターフェースを持っていて一方のレイヤーがもう一方のレイヤーをただ呼び出しているだけで何の機能も提供していない状態。
レイヤー間の責任がうまく分割されていないとこうなる(→違うレイヤーが同じ抽象性を提供していることになるため)
?) なんかレイヤードアーキテクチャー使ってるとキモいと思いつつありがちだったなあ
浅いモジュールにつながり、ソフトウェアが複雑化する
インターフェースが重複してもそれによってパワフルな機能が提供されるなら問題ない。
問題なのはインターフェースが厚いのに大した機能が提供されないこと(=浅い)
ex) Dispatcherパターンとか
デコレーター
既存のモジュールを拡張して機能追加する用途で用いられるが、これは浅いモジュールにつながりやすい。
個々にはパワフルでない機能を追加するために割りにコードがボイラープレート化し、冗長になる。

Chapter8 Pull Complexity Downwards
開発者としては実装をよりシンプルにして、難しい問題は他に任せたくなる
ex) エラーハンドリングせずにただthrowする
ex) 設定パラメータ(オプションパラメータ)を多用する
→ ユーザーが最適化された設定を渡せるようにする意図だが、そもそも最適な設定はユーザーが簡単には作れない(=ドキュメントや実装をちゃんと読む必要がある)。最適な設定を作れるのは基本モジュールを実装している本人。オプションパラメータをどうしても作りたい場合はできるだけ最適な値をデフォルト値として用意しておくべき

Chapter 9 Better Together Or Better Apart?
機能を結合するか分割するか
この目的はシステム全体の複雑性を減少させること
細分化の問題
・複雑性はコンポーネントの数に依存する(多ければ多いほどトラッキングするのが難しくなるし、知るべきインターフェースも増える)
・依存関係にあるのに細分化されたコンポーネントはある文脈であるコンポーネントを見たときに、見るべき他のコンポーネントの存在に気づかないこともある(別ファイルに分けられてたりすると一層)
・細分化はコードの重複にも繋がる
結合の指針
・共通の情報を参照するなら結合すること検討する
ex) 同じファイルフォーマットを参照する
・それらを一緒に使うことが想定されるなら結合することを検討する
→ただしこれはそれらの関係性が双方向である時のみ成り立つ
ex) ディスクブロックキャッシュはハッシュテーブルと一緒に使うことが想定されるが、ハッシュテーブルは他の用途でも使う→この場合は双方向関係ではないので結合してはいけない
・概念的に上位のカテゴリーが存在する場合
ex) 文字列の検索と大文字小文字の変換→文字列操作のカテゴリー
・片方のコードだけでは理解できず、もう片方のコードも知る必要があるときは結合を検討する
・インターフェースを単純にできるなら結合を検討する
・重複を削除できるなら結合を検討する
→重複したコードが至る所にあるなら抽象化がうまく機能していない証拠
分割の指針
・完全に独立したコンポーネントなら分割することは理にかなっている
・一般的な目的と専用的な目的を合わせ持った機能は分割することを検討する
メソッドの分割や結合
・メソッドの行数で分割するべきではない。長いメソッドが常に問題なわけではない。
→そのブロックが完全に独立した意味のあるブロックなら長くても分割しないほうが読みやすい。
・メソッドの分割のしすぎは多くのインターフェースを生み、結果的に複雑性が増す。
・メソッド分割した結果、分割されたメソッド単体を見ても理解できない(=分割された他のメソッドも見ないと理解できない)のは危険信号

Chapter 10 Define Errors Out Of Existence

Chapter5 Information Hiding(and Leakage)
情報の隠蔽
深いモジュールを作るための手法の1つ。
このモジュールをユーザーが使用するために必要な情報以外はユーザーに見えないようにする。
ex) 内部でどんなアルゴリズムが使われているか、どんなデータ構造を採用しているかなど
これによって
- インターフェースがシンプルになる
- 内部実装がモジュールの外に依存しなくなる→モジュールの内部を変更したり、機能追加するのが用意になる
これによってソフトウェアの複雑性を減らすことができる