A Philosophy of Software Designを読み、複雑性について考えた


## はじめに
A Philosophy of Software Design（2nd Edition）を読んだ。

https://www.amazon.co.jp/Philosophy-Software-Design-2nd-English-ebook/dp/B09B8LFKQL

日本語訳されないらしい（噂）ので原書を読んだものの、平易な英語でかつ198ページと短く簡潔に書かれているため非常に読みやすかった。章ごとにコンセプトの概略→詳細→具体例→やりすぎの例→まとめの順に構成されており、わかりやすい。
ソフトウェア開発における複雑性の定義と、また複雑性を抑えるための設計手法についてシンプルなインターファイスに深いインプレメンテーションをもつモジュールを作成する重要性を主に議論している。以下、本の概略と感想メモ。

## 複雑性とはなにか

理解や修正、変更を妨げる要因となるもの全てと定義されている。

ソフトウェアを書く上での一番大きな阻害要因は、ソフトウェアエンジニアの作成しているシステムを理解する能力である。複雑なシステムを理解できない限り、良いシステムを作ることはできない。

システムが複雑であると、コードを修正・更新するためにソフトウェアエンジニアが知らなければならない必要な情報が増加していき、かつその情報の有無を認識することも困難になる。このことを以下の表で示すと、左下の認知負荷（cognitive load）と右下の無知の無知（unknown unknowns）が積み重ねで増加していくことになる。これでは更新に手間と時間がかかるのみならず、バグの発生確率も高くなる。

| 必要な情報について | 存在に気づいている | 存在に気づいていない |
|:-:|:-:|:-:|
| 内容を理解している  |　知識は十分にあり、理解できる |　気づいていないものの、知識はあるためバグとはなりにくい|
| 内容を理解していない  |  認知負荷（cognitive load）が高い状態。知識はないものの、情報が必要となることに気づいているため、これから調査して情報を得ることができる。 |無知の無知（unknown unknowns）。自分が知識を得ていないことに気づいてすらいないため、必要な情報を手に入れることができない。変更すべき箇所を変更できないなどの理由で、バグを招く可能性が高い。最悪。|

### どのようにシステム自体の複雑性を排除していくのか

複雑性は依存（dependency）と不明瞭さ（obvious）によって生じている。この２つは併存することも多く、依存性が不明瞭になると、ますます複雑になる。そのため、依存性と不明瞭さを消していく・最初から生まないようにすることが必要。よって、依存が少なく（ゼロにすることは不可能）、明瞭な、シンプルなモジュールを作らなくてはならない。

interfaceを狭くシンプルに、implementationを深く問題解決をやり切る形で作る。

## 特に気になった部分


### 5章 Information Hiding (and Leakage)
依存性を下げるために、情報を隠蔽することが重要である。情報を外側に漏洩してしまうと、そこに依存性が生まれる。デザインを行うときには、そのモジュールがタスクを実行するために知っておくべきことだけが含まれているか、またそれが外に漏れていないかを考える必要がある。

以前は手順を分割することばかりを考えていて、パススルーメソッドを書いたりしていた。やけに引数が多い（その引数は関数内で他の関数を呼び出すときにしか使われないなど）もあり、自分でも変更点が多くなりがちで微妙であるなとは思っていた。
必要な情報をどこに、どのように、いつ、持たせるのかをよく考えて判断するようにしたい。

### 6章 General-Purpose Modules are Deeper

Abstractなコードを書くべきであるというもの。
if文による特殊なケースの分岐や同じような機能を提供する別々のメソッドはコードを使う側にとってわかりにくくなるため、より抽象的なコードを書くのが望ましい。
テキスト編集クラスが例として挙げられており、delete(Cursor cursor)メソッドとbackspace(Cursor cursor)メソッドを別々に実装するよりも、delete(Position start, Position end)を実装する方が、インターフェイスが単純となり、認知負荷を減らすことができると述べられている。また、もしendとstartの位置が逆になっていても、エラーを投げずにメソッド内で位置を修正して削除を実行する方が、エラーというインターフェイスが減り、シンプルで使いやすくなる。

抽象的なモジュールを作るときは、YANGIの法則も踏まえつつ、何にでも使えるようなモジュールを最初から目指す必要はない。今必要な機能を備えつつ、インターフェイスだけを多用途に使えるようにしておけば十分である。これを著者は"somewhat" general-purposeなモジュールと呼んでいる。

私はコードを書くときかなり必要な機能だけを備えた具体的なモジュールを作りがちで、追加機能が必要になったときはそのモジュールを大幅に変更したり、別のモジュールを追加したりしていた。この"somewhat"という匙加減は自分が保守をもっと経験してよい塩梅を見つけていく必要があると思うものの、コードを書く前に一度よりシンプルなインターフェイスがないかや、他にどのような使われ方をする可能性があるのか、より抽象的なものにできないか検討する価値はあると思う。

なお、この6章は2nd Edition作成時に大幅に追加されたらしく、以下著者HPより読むことができる。内容が気になる人や英語の文章がどのくらいのレベルなのか見たい人は、[著者ページ](https://web.stanford.edu/~ouster/cgi-bin/aposd2ndEdExtract.pdf)から試しに読んでみると良いだろう。



### 10章 Define Errors Out of Existence
エラーハンドリングから多くのエラーが生じるという話。
90%以上のデータ集約型分散システムにおけるcatastrophic failureは不適切なエラーハンドリングから生じるという記述があった。本当かな？と思ったため、引用元となる論文を探して読んだ。
[元論文](https://www.usenix.org/conference/osdi14/technical-sessions/presentation/yuan)
[わかりやすいレビューブログ](https://muratbuffalo.blogspot.com/2015/03/paper-review-simple-testing-can-prevent.html)

論文によると、5つのオープンソース（Cassandra、HBase、Hadoop Distributed File System (HDFS)、Hadoop MapReduce、Redis）について、ユーザーから報告された198の障害を無作為にサンプリングして調査しており、その結果の一部として致命的なシステム障害のほとんどすべて（92％）は、ソフトウェアで明示的にシグナルされた致命的でないエラーの誤った処理の結果であると主張している。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2724475/70c025e4-f044-c11a-722c-358d2b0da022.png)

適切なエラー処理をエラーが起きる前から整えておくことがどれほど難しいかを示しているように思う。ただエラーが起きた場合における、致命的なエラーが発生する割合なので、そりゃ90％を超えるのもおかしくはなく、測り方の問題が大きいように感じた。

## 全体の感想
薄い本だったが、英語の本を読み切ったことは自信になった。
この本に書かれていること全てを実践していくことは難しい（し、全てを鵜呑みにする必要もない）が、設計する際に十分にモジュール化できているか？を考えるようになった。シンプルかつ、必要なことをそのモジュール内でしっかりやり切る（不用意にエラーを投げまくったり、調整をもとめたりしない）ようなコードを書くよう心がけたい。


## 参考
ブログ

https://engineers.ntt.com/entry/2022/05/23/083118

ポッドキャスト

fukabori.fmでt-wadaさんがこの本について話している回

https://open.spotify.com/episode/3hkhmGwnjl09WhlL1c0L2m?si=d277443ef59041f4

著者が出ているpodcast

https://www.se-radio.net/2022/07/episode-520-john-ousterhout-on-a-philosophy-of-software-design/


著者のGoogleでの講演

https://www.youtube.com/watch?v=bmSAYlu0NcY