感想

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提案を出すような箇所のこと。意外とこれは自分も結構やらざるを得ない、と考えるケースが多いので、課題的なところ
  • Javaだとコンストラクタ関数をprivateにして、目的別のstaticファクトリ関数のみでそのクラスをインスタンス化するようなテクニックが使える

  • CommonUtilなどの神クラス削減

  • 外部クラスによるセッターメソッドの削減

    • 例えば、D.foo(A, B, C)ABCのフィールドを変更するような設計はなるべく避ける
  • 多すぎる引数回避

  • プリミティブ型に執着するな

    • 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を考えるとき、「商品」という概念を神クラス化しがち

    • 「注文品」「発送品」「在庫品」など、目的別に継承可能
  • 形容詞が発生するようなものが幾つも出てくる場合、クラス化しなければいけない可能性がある

    • OriginMaxHPCorrectedMaxHPなど、仕様上複雑にルール化されてる概念
  • 驚きの最小法則を心がける

    • 使う側が名前から予測して利用したとき、期待しなさすぎる動きをなるべく提供しない
  • InfoとかData使わず、それ切っちゃって良い。ProductInfoならProductへ、など

  • 静的メトリクスツールの活用

  • 格言

    • 粗悪なコードは綺麗なコードを書くより常に遅い
    • クラス化のパフォーマンスコストを心配しすぎるな
    • 設計ルールを多数決で決めるとコード品質が下がる