オブジェクト指向設計
概要
このパックは、オブジェクト指向スタイルで書かれたコードベースのための設計ルールであり、普遍的なアドバイスではありません。プロジェクト独自の記法規則がパックよりも優先されます。変更中のモジュールのイディオムに従ってください。このスキルはタスクの規模を判断し、適合するスキルを指定します。
回答する前に、このインデックスが指定するスキルをロードしてください。そのルールと例はそこにあり、ここにはありません。
タスクに必要な設計の規模
| タスク | 使用する | スキップする |
|---|---|---|
| 一度だけ使用して捨てられる回答 | なし | すべて |
| 既存のクラス内の1つのメソッド | その名前と契約 | 再モデリング |
| 既存のフロー内の機能 | そのフローが使用するイディオム | 新しい境界線 |
| 新しいクラスまたはモジュール | 設計ループ | なし |
| 新しいコンテキスト | 設計ループ、次にその境界線 | なし |
| ゼロからの新しいシステム | 設計ループ、コンテキスト、次に構造 | なし |
設計を始める前に
設計を始める前に、ゲートを宣言します。ソフトウェアが従わなければならない1つのルールと、それが受け入れる1つの例と拒否する1つの例です。例えば、「スロットには最大1つの予約を保持できる」というルールです。これは、別のスロットに2人目の患者を予約することを受け入れ、同じスロットに2人目の患者を予約することを拒否します。ルールを書き留められるなら、開始してください。書き留められないなら、欠けているものは設計ではなく、このパックはそれを提供できません。
設計ループ
- ソフトウェアがドメインの言葉で何をしなければならないかを述べ(ドメイン言語を話す)、各振る舞いが必要とするものを知っているオブジェクトを見つける(責任の割り当て)。
- 一貫性の境界を見つける。1つのトランザクションで一緒に真でなければならないものは何か(集約の設計)。
- 各ルールがどこに存在するかを決定する。エンティティ、値オブジェクト、またはドメインサービス(ドメインロジックの配置)。
- 各オブジェクトが応答するメッセージを設計し(状態と振る舞いのカプセル化)、不正な状態を構築不可能にする(不正な状態を不可能にする)。
- ドメインがデータベースやフレームワークについて何も知らないように依存関係を逆転させる(依存関係の逆転、システムの階層化)。
- ドメインの言葉でものに名前を付け、コードが言えないことだけをコメントする(クラスとメソッドの命名、コードが言えないことのコメント)。
- 結果を以下のレッドフラグと照合する(オブジェクト設計のレビュー)。
ルールの厳格さ
このパックのすべてのコアルールには3つのラベルのいずれかが付けられており、そのラベルはルールがどの程度厳格に意図されているかを示します。
- ルール. 破ることは欠陥です。フレームワークもそれを免除しません。
- デフォルト. 明示された条件が成立しない限り、これを行ってください。条件はそれ自体が文であり、決して従属節ではありません。
- 判断. 2つの答えが許容されます。どちらにバランスが傾くかをスキルが示します。
症状からスキルへ
症状はそれを治療するスキルを指し、この表はスキルが追加されるにつれて拡大します。
| 症状 | スキル |
|---|---|
| どのクラスがこれをすべきか? | 責任の割り当て |
| このクラスはやりすぎている | 責任の割り当て |
| 変更が多くのクラスに波及する | 結合の診断 |
| この2つの部分はどの程度密に結合しているか? | 結合の診断 |
| これはオブジェクトか、単なるデータか? | オブジェクトかデータかの判断 |
| 私たちのドメインモデルは貧血である | オブジェクトかデータかの判断 |
| 他のコードがこのオブジェクトのために決定する | 状態と振る舞いのカプセル化 |
| セッターがこのオブジェクトにルールを破らせる | 状態と振る舞いのカプセル化 |
| このオブジェクトは半分構築された状態で存在できる | 有効なオブジェクトの構築 |
| このコンストラクタは引数が多すぎる | 有効なオブジェクトの構築 |
| 新しいケースごとに動作中のコードを編集する必要がある | 変化するもののカプセル化 |
| これはどれだけの柔軟性を持つべきか? | 変化するもののカプセル化 |
| これは値かエンティティか? | 値とエンティティのモデリング |
| 生の文字列や数値がドメインルールを運んでいる | 値とエンティティのモデリング |
| これはそのクラスを拡張すべきか、保持すべきか? | 継承よりコンポジションの選択 |
| 基底クラスの変更がそのサブクラスを壊す | 継承よりコンポジションの選択 |
| サブクラスが親の約束を破る | 代入可能性の尊重 |
| コードがどのサブタイプを保持しているかをチェックする | 代入可能性の尊重 |
| 同じ型に対するスイッチが繰り返し現れる | 条件文をポリモーフィズムで置き換える |
| サブクラスかパターンマッチングか? | 条件文をポリモーフィズムで置き換える |
| コードがオブジェクトの連鎖を介して到達する | オブジェクトが知る範囲の制限 |
| このクラスは呼び出しを転送するだけである | オブジェクトが知る範囲の制限 |
| このクラスやメソッドは何と呼ばれるべきか? | クラスとメソッドの命名 |
| Manager、Helper、Utilのような名前 | クラスとメソッドの命名 |
| 呼び出し側が使用しないインターフェースメソッドに依存する | インターフェースの設計 |
| インターフェースか抽象基底クラスか? | インターフェースの設計 |
| ドメインコードがデータベースやフレームワークに依存する | 依存関係の逆転 |
| オブジェクトはどこで生成され、配線されるか? | 依存関係の逆転 |
| 呼び出し側が別のクラスのデータ保存形式やフォーマットを知っている | 実装の隠蔽 |
| クラスが内部エラーや手順を漏洩する | 実装の隠蔽 |
| フラグやオプションフィールドが不可能な組み合わせを許容する | 不正な状態を不可能にする |
| 型がドメインに決して存在しない値を許容する | 不正な状態を不可能にする |
| コードの言葉がスタッフの言葉と異なる | ドメイン言語を話す |
| 同じ単語が2つのチームで異なる意味を持つ | ドメイン言語を話す |
| どのオブジェクトが一緒に変更されなければならないか? | 集約の設計 |
| 保存が衝突する、または1つのロードで過剰に取り込む | 集約の設計 |
| どのステータス変更が許可されるか? | ライフサイクルのモデリング |
| キャンセルまたはクローズされたオブジェクトが再び変更される | ライフサイクルのモデリング |
| ドメインオブジェクトはどのように保存され、見つけられるべきか? | リポジトリの設計 |
| リポジトリに画面ごとのfindメソッドがある | リポジトリの設計 |
| ユースケースが同じビジネスチェックを繰り返す | ドメインロジックの配置 |
| ドメインサービス、エンティティメソッド、アプリケーションサービス? | ドメインロジックの配置 |
| 変更が保存された後、他の部分が反応しなければならない | ドメインイベントの発行 |
| ロールバックされた変更に対してメッセージが送信された | ドメインイベントの発行 |
| あるチームのモデルが終わり、別のチームのモデルが始まるのはどこか? | コンテキストの境界付け |
| どの部分がコアであり、どれを購入できるか? | コンテキストの境界付け |
| レイヤーはどちらの方向に依存すべきか? | システムの階層化 |
| 画面やジョブがフレームワークなしではテストできない | システムの階層化 |
| モジュールが循環依存する | モジュールの整理 |
| モジュラーモノリスか、個別のサービスか? | モジュールの整理 |
| APIの変更がそれを呼び出すアプリを壊す | システム境界の越境 |
| このAPIやメッセージはどのようにバージョン管理すべきか? | システム境界の越境 |
| マッピングツールがドメインクラスを歪める | 永続化の制御 |
| リストが行ごとに1つのクエリを実行する | 永続化の制御 |
| 画面はモデルとは異なる形のデータを必要とする | 読み取りと書き込みの分離 |
| 読み取りモデル、プロジェクション、イベントソーシング? | 読み取りと書き込みの分離 |
| この画面にはMVC、MVP、MVVMのどれか? | プレゼンテーションとドメインの分離 |
| 画面がドメインオブジェクトを直接編集する | プレゼンテーションとドメインの分離 |
| この問題にはどのデザインパターンが適合するか? | パターンの選択 |
| 問題の前にパターンが追加された | パターンの選択 |
| これらのパターンクラスは関数にできる | 言語が提供するパターン |
| Visitor、それとも一致する閉じた集合? | 言語が提供するパターン |
| メソッドが何も見つからないときにnullを返す | 障害動作の設計 |
| 拒否が呼び出し側が無視するコードとして返される | 障害動作の設計 |
| スレッドがオブジェクトを共有し、変更を失ったり混在させたりする | 共有状態の管理 |
| この状態にはロックか単一所有者か? | 共有状態の管理 |
| 別のシステムへの呼び出しがハングするか、途中で失敗する | 作業の信頼性確保 |
| リトライが作業を2回実行した | 作業の信頼性確保 |
| 記録からインシデントを説明できない | 記録内容の設計 |
| 何をログ、カウント、監査すべきか? | 記録内容の設計 |
| レビューで神クラスや溶岩流が見つかった | アンチパターンの認識 |
| どのアンチパターンを最初に修正すべきか? | アンチパターンの認識 |
| 動作は変わっていないのにテストが壊れる | オブジェクト指向コードのテスト |
| このコラボレーターにはMock、Stub、Fakeのどれか? | オブジェクト指向コードのテスト |
| テストが時間やランダム値に依存する | テスト容易性のための設計 |
| テストがアクセスできるようにするためだけにコードが公開される | テスト容易性のための設計 |
| このコードを安全に再構築するにはどうすればよいか? | オブジェクト設計へのリファクタリング |
| リファクタリングが大きくなりすぎて完了できなかった | オブジェクト設計へのリファクタリング |
| テストのないコードを変更しなければならない | レガシーコードの扱い |
| 古いシステムを少しずつ置き換える | レガシーコードの扱い |
| このレビューでは何をチェックすべきか? | オブジェクト設計のレビュー |
| どのレビュー指摘事項がマージをブロックするか? | オブジェクト設計のレビュー |
| 誰もコードがこの形になった理由を覚えていない | 設計決定の文書化 |
| 何を、どのレベルで描くべきか? | 設計決定の文書化 |
| コメントがコードを繰り返すか、矛盾する | コードが言えないことのコメント |
| このコードが奇妙に見える理由をどこで説明するか? | コードが言えないことのコメント |
| フレームワークフックやマーカーがビジネスルールを持つ | フレームワーク内での作業 |
| フレームワークにどこまで従うべきか? | フレームワーク内での作業 |
| 計算が同時に保存、送信、または入力を変更する | 関数型テクニックの適用 |
| どの関数型テクニックがオブジェクトコードに適合するか? | 関数型テクニックの適用 |
言語スキル
言語スキルは、1つの言語のコアルールを保持し、それを決して置き換えません。ベース言語スキルは、フレームワークのないコード用です。
oo-java(object-oriented-design-java由来): レコード、シールド型、equalsとhashCode、チェック例外、Optional、拡張に対して閉じたクラス。oo-kotlin(object-oriented-design-kotlin由来): データクラス、シールド型、null許容型、byによる委譲、拡張関数。oo-typescript(object-oriented-design-typescript由来): ブランド化された識別子、#private状態、抽象クラス、デコレータ、識別共用体、コンポジションルート。oo-javascript(object-oriented-design-javascript由来): プライベートフィールド (#)、クラスフィールド、instanceofとtypeof、動的インポート、モジュールパターン、コンポジションルート。oo-ruby(object-oriented-design-ruby由来): モジュール、ミックスイン、attr_reader/attr_accessor、ダックタイピング、nilの安全な処理。oo-python(object-oriented-design-python由来):@dataclass、@property、抽象基底クラス、型ヒント、__slots__、コンテキストマネージャ。oo-go(object-oriented-design-go由来): 構造体、インターフェース、埋め込み、レシーバメソッド、エラー型、型アサーション。oo-csharp(object-oriented-design-csharp由来): レコード、抽象クラスとインターフェース、非同期メソッド、record、readonly構造体、プライマリコンストラクタ。oo-swift(object-oriented-design-swift由来):structとclass、プロトコルとプロトコル拡張、列挙型と関連値、値型と参照型。