Closed14
「良いコード/悪いコードで学ぶ設計入門」を読む
- 良いコード/悪いコードで学ぶ設計入門 - 仙塲 大也 - 技術評論社
- 要約ではなく、自分自身が長期的に覚えておきたいこと(意識しないと忘れそうなこと・まだ自分に定着していないこと)をメモる。
- 自分は普段C#を書いているが、この本はJavaで書かれているので、記事もJavaベースで書く。
2章: 設計の初歩
- 変数を使いまわさない。再代入しない。(p.15)
- 変数名が何を表しているかを伝え、関係性を見えやすくする。
- データとロジックをまとめる(のちに詳しく)(p.17)
3章: クラス設計
- データクラスはNG → 高凝集を目指す。
- インスタンス変数を操作するロジックが全く別のクラスに実装されていると、関連しあう者同士の認知が困難になり、重複コードの発生、修正漏れなどが発生する。
- 生焼けオブジェクト(初期化しただけではnullエラーなどが起きてまだ使えない)を避ける(p.26)
- 「インスタンスを生成する時点で、インスタンス変数に正常値が確実に設定されている状態」を作るコンストラクターを用意
- デフォルトコンストラクターを使わない。完全コンストラクタ
まだしっくり来ていないことたち(C#で普段見かけないから)
- インスタンス変数の上書きを防ぐ(p.29)
- インスタンス変数はfinalに。
- インスタンス変数を上書きしたいときは、あたらしいインスタンスを作成して返すメソッドを作成する
- メソッドの引数も上書きできないようにfinalをつける
- 引数の型をint等のプリミティブな型ではなく、Moneyなどの独自の型にする(p.32)
- 引数の渡し間違いを防ぐ(intだと違うクラスの変数なども渡せてしまう)
第4章: 不変の活用 - 安定動作を構築する -
- インスタンス変数を不変にする、3章の続きの話(p.50)
- インスタンス変数は上書きできないようにする。
- インスタンス変数を上書きするときは、別の新しいインスタンスを作る。
- メソッドの引数も、プリミティブな型ではなく、自作クラスの型を用いる。
- p.50のコードを見るべし!!
- 可変にしてよいとき(p.53)
- パフォーマンスを気にするとき(値を変更するにはインスタンスを生成しなければならない。値の変更が膨大に発生するなどのとき)
5章: 低凝集 - バラバラになったモノたち -
staticメソッドは低凝集を起こしやすい
- インスタンス変数を扱えないから。
- staticキーワードがついていないだけで、インスタンス変数を全く使っていないメソッドも同じく。
- staticメソッドを使ってよいのは、凝集度に影響がない場合。ログの出力用メソッド、フォーマット変換用メソッドなど。
初期化ロジックの分散
- コンストラクターを公開することで、様々な場所でインスタンスの生成が行われてしまう。
- 例えばコンストラクターの引数が1つだったものが、2つに変更されたとき、分散されたコンストラクターを変更しに行く必要がある
- ファクトリーメソッド: コンストラクターをprivateにして、代わりに目的別のファクトリーメソッドを用意する。(p.64)
- p.64のコードを見るべし!!
プリミティブ型執着
- 引数のバリデーション(負ではだめ)などが、プリミティブ型に執着していると何度も書く必要がでてくる
- クラス化していれば、そのクラスに値にチェックを任せることができる。
メソッドチェイン(デメテルの法則違反)
- デメテルの法則: 利用するオブジェクトの内部を知るべきではない(知らない人に話しかけるな)
- 尋ねるな、命じろ(Tell, Don't Ask.)
- 外部からは、インスタンスに対してメソッドとして命じるだけ。命令された側が、そのメソッドの実行の内部で詳細な判断をする
- 例: 「防具を変えられる状態なら防具を変える」
- 防具を変えられるか、などは外からは尋ねず、「変えろ」と命じる。
- 命じられた防具クラスのメソッドの中で、そのインスタンスが防具を変えられるか、などを判断する。
6章: 条件分岐 - 迷宮化した分岐処理を解きほぐす技法 -
早期return
- ネストが解消され、ロジックの見通しが良くなる
- 条件ロジックと実行ロジックが分離される(ガード節)
- 条件ロジックの追加などに強い
switch文の重複
- 同じswitch文を何カ所も書いてしまう。変更の際に対応漏れが起きる。
- 一つにまとめる。そのまとめたクラスのみが、switchで検討される内容(例: enum)の責任を持つ
- switch文で切り替えなくても、Interfaceを継承することで、同じメソッド名で呼べるようになる。(Interfaceの型の変数にいれることで。変数の実体はInterfaceを実装した個別のクラスであり、そのクラスのメソッドが呼ばれる)
- Strategyパターンというらしい
ポリシーパターン
- 条件の部品化、部品化した条件の組み換え
- 詳しくはp.115以降のコードを見る。
interfaceを使いこなす
- 分岐を書きそうになったら、まずinterface設計!
- interfaceをせっかく使っているのに、その中でinstanceofなどを用いて型判定をしない。interfaceにメソッドを実装すること。
フラグ引数
-
Damage(true, damageAmount)
で、trueなら物理攻撃、falseなら特殊攻撃みたいな。 - フラグ引数によって挙動を変えるメソッドはNG
- メソッドを分離しよう。で呼び出し元で変える
- Interfaceを作って継承するStrategyパターンをつかうと分岐がなくなって良き。(p.125のコード見る)
7章: コレクション - ネストを解消する構造化技法 -
早期continue
- 早期returnと同じ。for文の中で、早期に条件を確認して、抜けたいときにcontinue。
- 早期breakも。
コレクションの低凝集を避ける
- コレクションを操作するクラスを作る
- コレクション型インスタンス変数
- コレクション型インスタンス変数を不正状態から防御し、正常に操作するメソッド
8章: 密結合 - 絡まって解きほぐせない構造 -
- 単一責任原則
密結合の例
- 自分以外のクラスのインスタンス変数についてのバリデーションを、自分のクラス内のメソッドの中で実行している
DRY原則の乱用
- 一見、重複しているように見えても、概念が違えば共通化しない
- 例: 商品の「通常割引(300円引き)」と「夏季限定割引(400円引き)」を別のクラスで作る。コードはほぼ共通し、定数のみ違う。一見DRYに違反しているように見える。
- しかし、例えば通常割引の仕様が、300円引きから5%引きに変更されたらどうか?(夏季限定はそのまま)。共通化していると、通常割引の変更が夏季限定割引にも影響を及ぼしてしまう。
継承は要注意
- 基底クラス側のロジックをしっていなければ書けないメソッドを継承クラスに書いてしまう
- 基底メソッドを完全に上書きしたオーバーライド
- 基底クラスに継承側のロジックを実装(基底クラスで、継承クラスの型とかで条件分岐をしてしまう、など)
9章: 設計の健全性をそこなう様々な悪魔たち
技術駆動パッケージング
- 設計パターンなど、構造的に似ているパターンでフォルダ家をすること。意味の関連付けが難しくなるので良くない。
- 目的駆動名前設計へ(10章)
10章: 名前設計 - あるべき構造を見破る名前 -
名前設計
- 可能な限り具体的で、意味範囲が狭い、目的に特化した名前を選ぶ
- 存在ベースではなく、目的ベースで名前を考える
- 商品(NG)→ 入庫品、予約品、注文品、配送品
- ユーザー(NG)→ アカウント、個人プロフィール、職務経歴
- 金額(NG) → 請求金額、消費税額、キャンペーン割引料金
名前設計の不備に気づくために
- コードに無いが、口頭で出てくる概念。形容詞が必要な概念
- 「このフラグが立っているときのUserは要注意会員」
- 「この行のpriceは新品価格で、次の行のpriceは中古価格」
驚き最小の原則
- メソッド名から想定される挙動とかけ離れたロジックを実装しない
11章: コメント - 保守と変更の正確性を高める書き方 -
退化コメント
- コードと比べてコメントはメンテナンスされにくい。実装と比べてコメントの情報が古くなり、正しくなくなったものを退化コメントと呼ぶ
- ロジックの挙動をなぞるだけのコメントは退化しやすい
書いてよいコメント
- コードの保守・仕様変更時に気を付けることが書かれている
- 例: 「今後仕様変更で状態異常により表情変化が追加された場合、本メソッドへロジックを追加すること」
12章: メソッド(関数) - 良きクラスには良きメソッドあり -
自身のクラスのインスタンス変数を扱う
- 他のクラスのインスタンス変数を変更するメソッドを書くと低凝集になる。
- その場合は、その他のクラスに、インスタンス変数を変更するメソッドを実装する。
尋ねるな、命じろ
- Getter/Setterは「よそのクラスを気にしたり弄ったりするメソッド構造」になりやすい。
- 呼び出す側では命じるだけのメソッドを呼ぶ。呼び出されるメソッドの中で、複雑な制御をする。
コマンド・クエリ分離
- 1つのメソッドの中で、コマンド(状態の変更)と、クエリ(状態の問い合わせ)を両方担当しない。
引数
- フラグ引数は使わない
- nullを渡さない(nullを前提としたロジックはエラー、nullチェックによる複雑化を招く)
戻り値
- 型を使って戻り値の意図を表明すること(intなどのプリミティブ型ではなく独自の型を使う)
13章: モデリング - クラス設計の土台 -
抽象化も目的駆動で
NG(目的が分からない)
- サバ・サンマ < 魚類
- ブタ < 哺乳類
- 魚類・哺乳類 < 動物
OK
- サバ・サンマ・ブタ・加工食品... < 栄養摂取手段
- 二足歩行・馬車・電車・飛行機 < 移動手段
14章: リファクタリング - 既存コードを成長に導く技 -
- 早期returnでネスト解消
- 意味ごとにロジックをまとめる(条件チェックはメソッドの前まとめる。値の代入はその後にまとめる)
- ベタ書きロジックを目的を表すメソッドに置き換える(ifの中とか。コメントアウトに頼らない)
テスト
- テストはまだ不勉強なので今後
このスクラップは2022/11/27にクローズされました