感想
2022年の技術書ベストセラーです。読みたかったんですが、ようやく読めました。自分自身設計は悩みどころではあって、いわゆるリーダブルコードやCode Completeのような古典書籍を踏襲しつつ、クソコードを回避するための事例集みたいなものが載ってる内容でした。個人的に新しい概念だったのはポリシーパターンとかかな。
基本的に私自身他の本で呼んできたOOP理論の総括的内容ではあったので、ほぼ著者の主張には完全に同意です。が、実際難しいよな~、ということは多くあるので、こういうのがまさに己のスキル的なところだなあと改めて感じました。
特にOCP的設計とNull対応って、既存コードのリファクタリングから考えるの現実問題相当難しいケース多いんですよね。自分も分かっちゃいるけどなあ、って手詰まりになってしまうなんてシチュエーションは幾つかあって、そういうのは課題だなと感じました。
とはいえ、色々な本のエッセンスをギュッと凝縮してくれてるので、とてもいい本だなあと思いました。色々と肝に銘じたいなあと思います。
以下は読書のときとってたメモです。かなり雑多ですが、自分用の振り返りも含めて参考に。
メモ
技術駆動命名を避ける
fooint
,fooflag
連番命名を避ける
foo01
分岐ネストは避ける
データクラスのフィールド変更の責務はデータクラスが担うべき
- 外部のなんとか
Manager
が持ちがち
- 外部のなんとか
低凝集を避ける
- 著者の最も一貫した主張
- 関連データやロジックがバラバラになっており、まとまってない状態
- 実装済みロジックがあるのに別の人が同じことをしてしまう重複コードの発生率が高い
未初期化状態が発生しうるインスタンス
- 未初期化状態が発生するものは生焼けオブジェクトと言い、避けるべき
不正値が入り込まないようにフィールドをバリデーションする
名前は省略しない
変数はイミュータブルとし、目的ごとに変数を用意する
複雑な式はメソッドに置き換え、名前を示す
関係するデータ同士はクラス化する
- 代表的なのが
ValueObject
であり、HP
とかMP
とか - この中で、
damege()
やrecover()
のようなメソッドを作る
- 代表的なのが
クラスに自己防衛責務を守らせる
コンストラクタ関数内で確実に正常値を設定するようにする
値オブジェクトの変更時には新しいインスタンスを投げるようにするのも1つの手
必要なメソッドのみを実装する(気を効かせた要らないメソッドを出来るだけ避ける)
クラス設計の良い方向性
- 不正状態を許さないコンストラクタ関数
- データの値オブジェクト化
- ガード節などによる単純化
- ファーストクラスコレクションによるコレクション操作のラップ
- スプラウトクラスによる既存ロジック変更の回避
- 既存クラスの変更が複雑で難しい場合、別クラスに分離して、既存クラスから呼び出すなどを検討する
引数変数は関数内部での扱いを不変にするよう心がける(Javaだと
final
つけられるやつ、あとはCとかだとconst
とかのお作法ね)その
static
メソッド必要?を問い直す- ふつーファクトリメソッドとかに使う
- あとインスタンスメソッドでも事実上
static
にできるものもあるが、避けるべき- これはC#だと最近のVisual Studioは
static
提案を出すような箇所のこと。意外とこれは自分も結構やらざるを得ない、と考えるケースが多いので、課題的なところ
- これはC#だと最近のVisual Studioは
Javaだとコンストラクタ関数を
private
にして、目的別のstatic
ファクトリ関数のみでそのクラスをインスタンス化するようなテクニックが使えるCommon
・Util
などの神クラス削減外部クラスによるセッターメソッドの削減
- 例えば、
D.foo(A, B, C)
でA
やB
やC
のフィールドを変更するような設計はなるべく避ける
- 例えば、
多すぎる引数回避
プリミティブ型に執着するな
calcPrice()
でint
返すのか?Price
とか返せば意味が分かりやすいよね、という話
デメテルの法則の回避
- **メソッドチェーンで掘り下げてアクセスしすぎない。そのような処理があるならば、アクセスされる側のクラスに新規のメソッドが必要ではないか?**ということを検討する
switch
は重複コードが各所で発生しやすい- 一箇所のクラスをまとめる
interface
で型分けしたり、Map
のようなものでキャッシュするテクニックもある
bool
引数の抑制。フラグ変数、分かりにくいし要る?ポリシーパターンを利用して、ルールをコレクションとして扱い合致するかを確認するテクニックがある
ポリシーパターンとは?
以下のような構造を取るもの
例えば、ポイントカードだったら「レギュラー会員」とか「ゴールド会員」みたいなものがあり、それらの条件を
GoldMemberPolicy
などのように概念化できる。その到達条件は、購入金額が1000円以上など色々あるが、それら1つ1つをIRule
を満たすクラスに継承し、ポリシーにはそのリストを与える、のようなことが考えられる「条件」という視点をコレクション化することにより、条件のネストを防ぐことができる
public interface IProduct { public int Price { get; set; } // ... } // ProductPolicyを満たす条件式を一つ一つOkメソッドに格納する public interface IProductRule { public bool Ok(IProduct product); } // 条件を満たすかを判定する public class FooProductPolicy { private List<IProductRule> _rules = new List<IProductRule>(); public void Add(IProductRule rule) => _rules.Add(rule); public bool All(IProduct product) => _rules.All(x => x.Ok(product)); }
型チェックによる内部処理の分岐ネストは避ける
コレクション処理で車輪の再発明は避ける
- それLINQで出来るじゃん、みたいなやつ
クラスの細分化の推進
- 値オブジェクト化を企図しても、
Price
クラスを作ったとして、calcDeliveryPrice
とかcalcTax
とかやりがち - 実はこういうものすら
Tax
クラスとかDeliveryPrice
とかに分けたほうがいいことがある
- 値オブジェクト化を企図しても、
デッドコード、マジックナンバー、グローバル変数回避
null
許容な状態を極力避ける。null
返したり引数で渡せるような仕様を避ける- 例えば
EMPTY
みたいな定数作ればえーやん、という内容
- 例えば
例外を握りつぶすな
- 例外を
except
したときに何もしないで、エラーじゃないことにする、みたいな事例 - 逆に正しい使い方は実装者側が分かってる場合に、その理由をちゃんとログとかで述べるように付加情報を添付したりすること
- 例外を
ECを考えるとき、「商品」という概念を神クラス化しがち
- 「注文品」「発送品」「在庫品」など、目的別に継承可能
形容詞が発生するようなものが幾つも出てくる場合、クラス化しなければいけない可能性がある
OriginMaxHP
、CorrectedMaxHP
など、仕様上複雑にルール化されてる概念
驚きの最小法則を心がける
- 使う側が名前から予測して利用したとき、期待しなさすぎる動きをなるべく提供しない
Info
とかData
使わず、それ切っちゃって良い。ProductInfo
ならProduct
へ、など静的メトリクスツールの活用
格言
- 粗悪なコードは綺麗なコードを書くより常に遅い
- クラス化のパフォーマンスコストを心配しすぎるな
- 設計ルールを多数決で決めるとコード品質が下がる