[読書メモ] 良いコード/悪いコードで学ぶ設計入門

改訂新版 良いコード/悪いコードで学ぶ設計入門 ―保守しやすい 成長し続けるコードの書き方
改訂版のほうを購入。
背景としてはAIコード生成時代にも、やっぱり変更容易性の高い設計を人間がコントロールすることが重要だよねと実感したので、そのための書籍を一冊通読して改めて設計の世界に再入門すること。
なので全体的にAIにとって最適化の観点で読み進める。
関連: [読書メモ] コード×AI
また、本書は Java を用いたオブジェクト指向開発を具体例としているが、バックエンドは Ruby、フロントエンドは TypeScript (≠オブジェクト指向) を想定して適宜読み替える。

1章 悪しき構造の弊害を知覚する
変更容易性の重要さを知覚するには、変更容易性をないがしろにすると、どんな弊害が起こるのかを知ることが第一歩です。弊害とは、たとえば以下です。
・コードを読み解くのに時間がかかる
・バグを埋め込みやすくなる
・悪しき構造がさらに悪しき構造を誘発する
AI時代にとってはやっぱり変更容易性が重要性を増してきているように見える。もともとAIは特性上、まったく新しいコードを書くことには長けてる一方で、コンテキストを理解して既存コードを改修するのは苦手な側面もあるし、そこを設計で補うのが重要。
関連し合うロジックが離れた箇所に実装されていると、関連するものどうしの把握が困難になります。
すでに実装済みの機能があるのに、別の担当者が「この機能は未実装だ」と誤解し、同じようなロジックをいたるところに実装してしまう可能性が高まります。
本書はオブジェクト指向を用いたバックエンドをあげてるけど、フロントエンドにとってもコロケーションの優位性がAIによって高まってるようにも感じる。
重複コードが多く実装されている場合、仕様変更時にすべての重複コードを変更しなければなりません。しかし、重複コードをすべて把握していないと、修正漏れが生じ、バグとなってしまいます。
修正範囲の洗い出しはAIは割と得意な印象だけど、それでも重複している分、消耗するトークンは増加するから SSoT であるに越したことはなさそう。

2章 設計の初歩
基本中の基本の話。
- 命名ちゃんとする
- 変数使い回さない
- コード分割による関心の分離
- データとロジックの集約

3章 カプセル化の基礎
まず大事にしたいのは「クラスが単体で正常動作するように設計する」という考え方です。
オブジェクト指向に限らず、モジュールをどれだけモジュール単体で副作用や依存なく動かせるかが大事だと思う。テスタビリティのある設計が良い設計というのは違いない。
わざわざほかのクラスに初期化してもらったり、データをチェックしてもらったり、ほかのクラスのメソッドを呼び出さないと機能しないようなデータクラスは、安全に利用できないですし、単体では機能しません。このようなデータクラスを貧血ドメインモデルと呼びます。データやロジックに欠損や不整合がなく、正確な状況を維持できることを、ドメインモデルの完全性といいます。
DDD文脈で良く言われるドメインモデル貧血症ってこれの話か。データだけでそこにまつわるビジネスロジックがないクラス(データクラス) は多くの場合は不適切だよという話。
フロントエンドだとクラスはまず使わないから、データ構造とそれに対する関数をモジュールでまとめるとかに近い話かなー。
インスタンス変数の中身を変更するのではなく、変更値をもった Money クラスのインスタンスを、また新たに生成するのです。
クラスのインスタンス変数も可能な限りイミュータブルにしようという文脈の話。パフォーマンスセンシティブじゃない限りはこれ良いな。参照透過性のあるクラスに近づけられる。
この方式を用いると、コンストラクタにバリデーションを実装しておくことで、変更内容の妥当性も担保することができる。
クラス設計とは、完全性を保証する仕組みづくりと言っても過言ではありません。
完全性の保障を意識してカプセル化することが、変更に強くなる設計の基本です。
常に矛盾のない状態を維持できる構造を目指す。
大事なのはデータとデータを操作するロジックを一箇所にまとめ、完全性が保証された操作だけを外に公開するようカプセル化する考え方です。
全体的に、フールプルーフにクラスを設計しろって感じがする。イメージしやすい。

4章 不変の活用
概ね2,3章で触れられた話の深堀り。
- 変数は定数化しよう
- 関数の引数も定数化しよう
- なるべくインスタンス変数も不変にし、変更時は新しいインスタンスを作ろう
- 同じ値でも文脈が異なれば異なる変数に割り当てよう
- やむを得ず状態を変更する場合も、ミューテーター(setter?) で安全に局所的に行おう

5章 バラバラなデータとロジックをカプセル化する実践技法
初期化ロジックの分散を防ぐには、コンストラクタを private にして、代わりに目的別のファクトリメソッドを用意します。ファクトリメソッドとは、インスタンスの生成を専門に行うメソッドです。
インスタンス生成方法自体にロジックが含まれる場合の対処法の例。あんまりファクトリメソッドを使ったことはなかったけど、確かにユースケースごとに生成方法を隠蔽する用途としては良さそう。イニシャライザの変更に対しても影響範囲を抑えられるし。
デメテルの法則と呼ばれる法則があります。利用するオブジェクトの内部を知るべきではない、とするもので、「知らない人に話しかけるな」と要約されたりもします。アクセス連鎖で内部詳細を渡り歩くつくりは、まさにデメテルの法則に違反していると言えます。
if (party.members.get(memberId).equipments.canChange)
みたいにロジック内でアクセス連鎖が発生している場合はクラスの責務が過剰になっている可能性を疑える。
ソフトウェア設計には、尋ねるな、めいじろという有名な格言があります。
これには、インスタンス変数を private にして、外部からアクセスできなくします。インスタンス変数に対する制御は、メソッドとして外部から命じる形にします。そして命令された側が、詳細な判断や制御を担う作りにします。
インスタンス変数をみだりに公開することでアクセス連鎖が発生してしまうので、private にして参照するためのメソッドを提供することで、その先を隠蔽できるようにする。

6章 関心の分離という考え方 -分けて整理する-
実際のプロダクションコードは泥臭く、混乱していて、依存関係が複雑です。うまく別クラスに分離するには、インスタンス変数やメソッドがそれぞれ何と関連づいているのかを把握することが大事です。
例えばインスタンス変数AはメソッドAでのみ利用し、インスタンス変数BはメソッドBでのみ利用しているという状況の場合、AとBは異なる関心事であるため、クラスを分離できる余地がある。
(適切なクラス分割が出来ていることに関する) メリットの一つは、注意力の負荷が低減することです。
リスト6.10 は、いっぺんの多くのことに注意を払う必要がありました。一方改善後は各関心事にクラスがわかれているため、注意を払う要素が少なくなり、脳にやさしくなります。たとえばダメージ量に関して保守したい場合、Damageクラスだけを見ればよくなります。
変更容易性のために大事なとこ。テスタビリティで言っても、Damage クラスのテストではダメージに関してだけテストすれば良いから直感的になる。間違いなくAIとの相性も良い。

7章 関心が混ざったコードを分けて整理する実践技法
つまりソフトウェアにおける責任とは、「ある関心事について、不正に動作にならないよう、正常に動作するよう制御する責任」と考えることが出来ます。
ここで重要な役割を果たすのが、単一責任の原則です。「クラスが担う責任は、たったひとつに限定すべき」とする設計原則です。
責務がどうってよく言葉にするけど、そいつが扱う関心ごとのすべてに責任を持てるかってちゃんと考えて切らないといけないなぁと思った。
同じようなロジックが複数あるからといって、責任を考えず無理にひとまとめにすると関心が混在してしまいます。
すべての知識はシステム内において、単一、かつ明瞭な、そして信頼できる表現になっていなければならない。
同じようなロジック、似ているロジックであっても、目的が違うロジックは共通化してはいけないのです。
DRYに関するよくある誤解の話。コードを共通化するんじゃなくて関心を共通化して SSoT 化する。
あるサブクラスにとっては関係があっても、別のサブクラスにとっては無関係なメソッドが登場し始めると問題です。
下手に継承を使わず、丁寧にカプセル化し、関心を分離することが重要です。値オブジェクトやコンポジション構造で設計できないか検討しましょう。
よっぽどでないと親クラス側にコードを追加するのは慎重に。基本的には継承より移譲。
privateメソッドが多いクラスは、様々な関心を扱ってしまっています。異なる関心のロジックが private メソッドとして実装されているのです。
これは身にしみる。あるメソッドのための private メソッド、別のメソッドのためのprivate メソッドが乱立しがちなんだよなぁ。
巨大なクラスは、関心事ごとにクラスを分割しましょう。プログラミング言語により多少の違いはありますが、適切に関心を分離しカプセル化したクラスは、だいたい100行程度、多くても200行程度になります。それぐらいクラス一つ一つは小さなものになります。
そのために RuboCop みたいなリンターがチェックしてくれてるんだから素直に従おうね。

8章 条件分岐
早期return にはもうひとつ利点があります。それは条件ロジックと実行ロジックを分離できることです。
これはたしかに。
ソフトウェアシステムが選択肢を提供しなければならないとき、そのシステムの中の1つのモジュールだけがその選択肢のすべてを把握すべきである。
選択肢による分岐、各所に分散するのは単一責務の観点では崩壊してるから、ストラテジーパターンみたいにして多様性を与えるのが最適解だよねって話。やや理想論ではある。
フラグ引数付きのメソッドは、何が起こるか読み手に想像を難しくさせます。何が起こるのか理解するには、メソッド内部のロジックを見に行かなければなりません。可読性が低下し、開発生産性が低下します。
mode みたいなパラメータが出てきても要注意だ。

9章 コレクション

10章 設計の健全性を損なう悪魔たち
コードの読み手がデッドコードの周辺を読むたびに、どういう条件で実行されるかを読み手に考えさせてしまいます。
ある。まさかデッドコードなわけないからと思い込んで、特殊な参照のされ方してるのかなとか考えちゃう。
これまで到達不能だったのが、なんらかの仕様変更によりデッドコード周辺のロジックが変わり、到達可能になる場合もあります。
これは怖い。デッドコードはガンガン消しておかないとそういうリスクもあるのか。

11章 名前設計
筆者はクラスやメソッドに名付けることを、「命名」ではなく、「名前設計」と呼んでいます。ここでの設計は、「ある課題を解決するための仕組みや構造を考え、つくり上げること」という意味で使います。
プログラミングにおける名前の役割は、可読性を高めることだけではないと考えます。
確かに、「命名」だと生まれたものに対してあとから名前をつけることを指しそうだけど、プログラミングにおいては関心ごとの分離を行うための設計で、名前をもとに生まれてくるって感じがする。
存在駆動ではなく、目的駆動で名前を考える
存在に着目したクラス名は、用途が多重になりがちで、目的不明オブジェクトになります。ロジックが混在します。
「商品」のような物理的な存在を指す名前よりも、「予約品」「在庫品」「発想品」「注文品」のような、関心事(目的) に応じた論理的な存在を名前にする。
目的に特化した名前を選ぶと、その目的とは関係の弱いロジックを寄せ付けにくくします。目的に強く関係するロジックが集まりやすくなり、関心が分離されます。関係の弱いロジックが混入しそうならば、名前を見直しましょう。
他の人が既存クラスに手をいれる際も、名前を見てそこを拡張するかの判断基準に出来そう。
名前が重要であり、名前とロジックが対応することに価値があること、名前がプログラム構造を大きく左右することを、チームの共通理解にしましょう。
「可読性」なんてあいまいなものに閉じないんだね
アンカリング効果はソフトウェア開発でも発生します。既存のクラス名やメソッド名が基準となってしまい、開発者の判断を歪ませ、混乱させてしまうケースがとても多いのです。
これはあるなぁ。あとから参画したプロジェクトだと、既存コードに倣うのがどうしても正解になる。一貫性も大事だから、不正解というわけではないけど、割れ窓理論でもあるから意識しないと。
会話には登場するが、コード上には登場しない名前には注意が必要です。
会話に登場する重要な概念が、ソースコード上で名前もつけられず、雑多なロジックの中に埋没していることが本当に頻繁に見受けられます。
また、こうした「名無しのロジック」はソースコードのあちこちに、無秩序に書き殴られる傾向があります。
これ面白いな。会話の中で登場するということは、人間にとっては重要な概念であるはずなのに、コード上でその概念が表現されていないことで変更容易性を引き下げるの、よくありそう。
MVCにおける Controllerは、受け取ったリクエストパラメータをほかのクラスへ渡す責任にとどめるべきです。金額計算をしたり、予約可否を判断したりなどの具体的なロジックが実装されているなら、その Controller は多くの関心事を扱ってしまっています。
Rails だと良く言われるし、多くのプロダクトができてないことだよなぁ。
関心ごとの異なるメソッドの存在を防ぐには、可能な限り動詞1語で済むよう名前設計するのがコツです。同時に、動詞1語で済むようにクラス設計します。
これも良く言われるやつ。オブジェクトが主語でメソッドが動詞。これだけで全てを表現できるようにクラスを分割する。理想論ではあるけど大事にしたい。

12章 コメント
ロジックの挙動をなぞるだけのコメントは理解にさほど貢献しない上、逆に偽情報が流れ込んで害をなす可能性があり、役立ちません。
ロジックを日本語に訳すだけでも役立つ場合はある、と思うけど、それはロジックの関心の分離ができてないせいで、正しく設計できていればコードを直接読むだけで同じ情報を得られるようになるはずか。

13章 メソッド(関数)
メソッドは、必ず自身のクラスのインスタンス変数を使うよう設計しましょう。例外も稀にありますが、これが原則です。
よそのクラスを気にしたりいじったりするメソッド構造は、不正な値が子入牛安く、保守や変更が難しい構造です。
引数で受け取った他のクラスにインスタンス変数を書き換えるようなことは無いようにという話。それはそう。
コマンド・クエリ分離(CQS) と呼ばれる考え方があります。メソッドはコマンド(=変更) またはクエリ (=問い合わせ) のどちらか一方だけを行うように設計する、コマンドとクエリを分離する考え方です。
当たり前のことのように思いつつ、うっかり用途的にはどちらもまとめて欲しいからってお気持ちで1メソッドでやっちゃうこと、あると思います。
引数が多くなりそうな場合、別クラスへの分割を検討しましょう。
これも当たり前のことなんだけどね!

14章 モデリング
システムは目的達成手段です。そしてモデルはシステムの構成要素です。つまり、モデルhあ目的達成手段の一部です。特定の目的達成のために最低限考慮が必要な要素を備えたものがモデルです。
目的達成のためのシステムの一部がモデルである以上、個々のモデルには細分化された目的があるはず。よってモデルは目的駆動での分離や命名が期待されるというお話。
就職活動では、履歴書や職務経歴書、推薦状を使い分けます。これらは就活者個人の性質を表現した媒体です。各媒体は目的ごとに表現方法や名前が異なります。統一的に「User」などという名前では表されていません。つまり、利用者を表現する手段は、目的に応じて名前や形態が違ってくるのです。
物質的には「User」がほとんどのシステムで登場するけど、目的に応じて異なる側面を見せるから、一括で「User」として扱うのでなく目的駆動でモデリングされるべきという話。
このように、情報システムでは、現実世界での物理的な存在と、情報システム上のモデルが1対1になるとは限らず、1対多の関係になるケースがあることが大きな特徴です。
物理的な存在をモデル化しちゃうのはオブジェクト指向の弊害もある気がする。あと物理とは違うけど、画面上に出現するリソースをそのままモデル化しちゃうとかもアンチパターンかも。
目的駆動で名前設計することが、適切に目的達成するモデルを設計することに繋がります。
命名こそモデリングのはじまり。
目的と責任は対になっていると言えます。つまり、単一責任の原則とは、単一目的の原則であると筆者は考えます。「クラスが果たす目的は、たったひとつに限定すべき」だと考えます。
「責務」って言葉をよく設計周りで使うけど、目的のほうがまぁわかりやすいよなぁ。

15章 リファクタリング
新たに分割したクラスやメソッドは、検索時にノイズが混じらないようにユニークな命名をしました。名前の候補が決まったら、その名前でソースコードを全検索し、重複がないことを確認して命名するのです。
リファクタリングの文脈からはちょっと外れたコラムだけど、これAI時代にもめちゃくちゃ大事だなと思ってる。

16章 設計の意義と設計への向き合い方
ソフトウェアにおける設計とは、なんらかのソフトウェア品質特性の向上を促進するための仕組みを作ることです。たとえば性能効率性はパフォーマンス性能を表す品質特性であり、性能効率性を上げるためにはパフォーマンス設計をします。
設計には品質を高めるという目的がある。この本が扱ってるのは全体的に「保守性」全般かな。
変更容易性を高めることは、ソフトウェアの成長性を高めることなのです。ソフトウェアの成長性を高めることが、本書の意義です。
同じこと言ってた。AI時代においてもここが非常に重要だとは思ってる。
レガシーコードは資産の蓄積、すなわち技術力の成長を妨げてしまう恐ろしい存在です。
メンバーの成長文脈で書かれてるけど、これもAIにとって重要な話で、AIは既存コードを模倣して書いてくれるからレガシーコードがそのままだとそのままのものが生まれてしまう。
クラス内部で取り扱う概念が4±1個におさまるように設計し、大きなクラスは小さなクラスに分割しましょう。
人間の限界であるマジカルナンバー4を考慮して分割しようという話。

17章 設計を妨げる開発の進め方との戦い
仕事が忙しいと、実装を早く終わらせたい気持ちが先走り、動くコードをとにかく早く実装しがちです。納期の厳しい受託開発では顕著です。そのためクラス図すらろくに描かないなど、設計品質が無視されがちです。
コードが劣悪でも、動いている画面を見ると、非エンジニアを含む現場は喜んでしまいます。口々に「もう実装できたんですが、さすがですね!」ともてはやします。褒められたプログラマーは嬉しいですし、早く書けることがある種正義のような雰囲気が醸成されていきます。しかし、それが罠なのです。
うわああああああああああああああ
(割れ窓理論について) ソフトウェア開発でも同じことが言えます。粗悪で、複雑で秩序のないコードが放置されていると、ソフトウェア全体が無秩序になってしまいます。「ほかのコードも雑な作りなんだから、自分のも多少は雑でいいだろう」という心のスキが生じます。
割れ窓、マジでAI時代に余計に厄介な存在になってきてるので潰さなきゃならない。ボーイスカウトガンガンしていこう。

18章 設計技術の理解の深め方
一番良くないのは、設計効果を対して意識せず、構造だけをまねて満足してしまうことです。問題解決にならないばかりか、逆に構造を複雑にしてしまって、問題が深刻化する場合すらあります。
設計本読んでやる気に満ち溢れた結果陥りがちな問題だと思う。HOWよりWHYを意識してから手を動かすのが大事。
リファクタリングの練習題材にするのは、普段仕事で使っているプロダクションコードです。プロダクションコードは泥臭い複雑さを抱えています。だからこそ、実践レベルで使える設計力を養えます。
サンプルアプリケーションだとか個人開発じゃあたどり着けない泥臭さがあるし、実践の中で成長できるのが一番よね。
「スキルがある」とは、そのスキルを何度でも再現できることを意味します。
良い言葉。脱線するけど、採用面接において経歴上1回は出来たことだからってそれに再現性があるかは書類からは判断できないから、面接で深ぼる必要があるな。
再現性を高めるにあたり重要なのは、設計ではなく弊害を中心に深堀りして説明することです。
弊害を深堀りするためにはコードだけじゃなくて組織やドメインまでちゃんと理解してないといけなそう。
動くコードを書いたら、設計し直してからコミット
開発時に設計スキルを高め、かつ品質の高いコードをコミットする方法です。
コードを書く前にある程度設計してもいいですが、まずは動くコードを早く書くことをおすすめします。時間をかけて慎重に設計しても、いざコードに落とし込んでみると、動作に不可欠な要素を見落としているケースが多々あるからです。
実装前にどこまで設計するかは諸説ありそうだけど、動かしてみて、動作を自分の目で見て初めて見落としに気づくことは多数あるからこれは大事だと思う。でもそこで一旦完成したものを時間をかけてブラッシュアップできるかは自分との戦い。特に、開発の速さを称賛されすぎる文化だと危険が危ない。
設計がうまくいくと楽しくなってくるものです。そのときこそ、成長のチャンスです。
うおおおおお
良い本でした。