Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save muraaano/f9ae2c07ee7f8434556367d0bec548c8 to your computer and use it in GitHub Desktop.

Select an option

Save muraaano/f9ae2c07ee7f8434556367d0bec548c8 to your computer and use it in GitHub Desktop.

Rubyオブジェクト指向設計改善チェックリスト

オブジェクト指向設計実践ガイド』の読書会で学んだ内容をもとに、設計の改善ポイントを判断するためのチェックリストです。

出典:オブジェクト指向設計実践ガイド~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方

このチェックリストは、書籍の文中に記載されている言葉をもとに、独自のチェックリストを作成しています。

このチェックリストの使い方

  • 新規実装時: 実装前にこのチェックリストを確認し、適切な設計を検討する
  • コードレビュー時: レビュイーとレビュワーの両方がチェックリストを参照する
  • リファクタリング時: 改善ポイントを見つけるためにチェックリストを使用する
  • 定期的な見直し: 四半期に一度程度、主要なクラスに対してチェックを実施する

基本原則

  • このコードは後から変更しやすいか?
    • 変更が簡単なコードの特徴を確認する
      • 見通しが良い:変更がもたらす影響が明白
      • 合理的:変更にかかるコストが利益にふさわしい
      • 利用性が高い:どこでも再利用できる
      • 模範的:他の人が品質を自然と保つコードになっている

単一責任

出典:『オブジェクト指向設計実践ガイド』第2章

  • このクラスを1文で説明するとき「または」が含まれるか?

    • クラスを「〇〇するくん」のように1文で説明してみる
    • 「または」が含まれる場合は、責任を分割する
    • そのクラスに知覚があるかのように問いただし、責任を明確にする
  • 今日何もしないことの将来的なコストはどれだけか?

    • 何もしないことによる将来的なコストが今と変わらない時は、決定を延期する
    • 将来の方が情報が増えている可能性が高く、より精度の高い判断ができる
  • このメソッドは複数の責任を持っていないか?

    • 長いメソッドは小さなメソッドに分割する
    • 本質的でない責任がメソッドにじわじわ入り込んでいくのを許さない
    • 「ちょっと細かいか?」と思うくらいがちょうど良い

依存関係の管理

出典:『オブジェクト指向設計実践ガイド』第3章

  • 自身より変更されやすいものに依存していないか?

    • 「自身より変更されないものに依存しなさい」の原則を適用する
    • 変更される可能性が低いものを依存の終着点にする
    • 具体的なクラスより抽象的な概念に依存する
  • 「『何かを知るオブジェクト』を知るオブジェクト」を知るオブジェクトがあるか?

    • 依存オブジェクトの注入を検討する(ただし認知負荷とのトレードオフを考慮)
    • 依存の方向を一方通行にする
    • 主要なオブジェクトへの依存の流れを明確にする
  • 引数が3つ以上ある、またはオプショナルな引数があるか?

    • キーワード引数を使用する
    • 引数が多すぎる場合はオブジェクトにまとめる

柔軟なインターフェース

出典:『オブジェクト指向設計実践ガイド』第4章

  • メソッドチェーンで複数のオブジェクトを連鎖的に呼び出していないか?(例:hoge.fuga.target.display_name

    • 委譲メソッドを作成する
    • ただし、委譲で強固な結合を隠しても、コードの結合を解いたことにはならない点に注意
    • パブリックインターフェースが欠けているオブジェクトがないか検討する
    • 利用側が本当に必要としている情報を直接提供するメソッドを作る
  • 本当に公開すべきメソッドが公開されているか?

    • オブジェクトではなく、オブジェクト間で交わされるメッセージに注意を向ける
    • 設計時に複雑な処理がある場合はシーケンス図を書いてみる
    • 内部実装の詳細はプライベートメソッドにする
  • このオブジェクトは他のオブジェクトについて多くのことを知りすぎていないか?

    • 単純なコンテキストを持つオブジェクトを目指す
    • オブジェクトが存在するからメッセージを送るのではなく、メッセージを送るためにオブジェクトは存在する

ダックタイピング

出典:『オブジェクト指向設計実践ガイド』第5章

  • クラス名に基づいて分岐するcase文があるか?

    • ダックタイピングを使って共通のインターフェースを定義する
    • delegated typeなどを活用して、型ごとに異なる振る舞いを実装する
  • kind_of?is_a?responds_to?を使ってクラスで分岐しているか?

    • ダックタイピングでインターフェースを統一する
    • ただし、バリデーションの条件分岐など、妥当な理由がある場合は例外

継承

出典:『オブジェクト指向設計実践ガイド』第6章

  • サブクラスがたった1つだけの抽象的なスーパークラスを作っていないか?

    • 将来増える可能性があっても、今必要でなければ継承構造を作らない
    • 複製したコードを毎日変更する必要があるなら、そのときに階層構造を作る
  • サブクラスがスーパークラスの内部実装を全部知った上でsuperを呼ぶ必要があるか?

    • フックメッセージパターンを検討する(ただし、処理が行ったり来たりするので慎重に)
    • NotImplementedErrorを使って、実装が必須のメソッドを明示する
  • スーパークラスに具象的な実装が残っていないか?

    • 抽象を昇格できるようにコードを構成する
    • 具象を降格するような構成は避ける

モジュールとコンポジション

出典:『オブジェクト指向設計実践ガイド』第7章、第8章

  • 継承の方がより良い解決法だとはっきり言い切れるか?

    • 言い切れないときは、コンポジションを使う
    • 継承は「is-a」関係、コンポジションは「has-a」関係
  • 継承の階層が3階層を超えているか?

    • 深さと幅をトータルで考えて設計する
    • 深くなりすぎる場合は、一部をコンポジションにする

テスト設計

出典:『オブジェクト指向設計実践ガイド』第9章

  • 同じことを複数の場所でテストしていないか?

    • どのテストも一度だけ、それも適切な場所で行う
    • Request specで依存先の詳細までテストしない
    • イベント型モデルがコールされたかだけ検証し、詳細はそのクラスのspecでテストする
  • プライベートメソッドが大量にあるか?(目安:5個以上)

    • 責任を大量に持ちすぎた設計の臭いと捉える
    • クラスを分割して責任を明確にする
    • 共通処理はモジュールに切り出す
  • テストのセットアップに苦痛が伴うか?

    • コードがコンテキストを要求しすぎている可能性がある
    • クラスを分けて、それに対応するテストを書く
    • 依存オブジェクトの注入(DI)を検討する
  • 1つのテストファイルが100行を超えているか?

    • クラス分割を検討する
    • Finderオブジェクトなどを使って責任を分離する
    • Request specはFinderやSearcherに切り分けて軽量化する

実践的なチェック項目

  • AIが何でもかんでもprivateメソッドにしていないか?

    • 「あらゆる箇所を単一責任にする」の原則に則っているか確認する
    • 過度に細分化されている場合は統合を検討する
  • このコードは他の場所でも使えるか?

    • 単一責任の原則を守り、コンテキストへの依存を減らす
    • 汎用的なインターフェースを設計する
  • この設計判断は今すぐ必要か?

    • 何もしないことによる将来的なコストが今と変わらない時は、決定を延期する
    • 将来の方が情報が増えている可能性が高い
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment