Clean Architecture, Clean Life

仕事・個人での技術的なことつぶやきます

【読書感想文】「現場で役立つシステム設計の原則」

今回も読書感想文

なぜなら今私はアーキテクチャ勉強期に入っているから!!(激寒)

ということで、この本はクリーンアーキテクチャやその他アーキテクチャ関連の本の一番最初に読む入門本としてお勧めです

感想・まとめ

結論

ドメインモデルで設計すれば世界が平和になる

ドメインモデルとは

  • 業務で扱う最小単位でデータとロジックをひとまとめにして整理する技法

メリット

  • どこに何のロジックが書いてあるかソースを見ればわかる
  • 後から改修しやすい

従来の「手続き型」の問題点

改修が難しい

  • どこに何のロジックが書いてあるかわからない
    • ある修正をしようと思っても、どこまでが影響範囲かわからない
    • 使用している箇所全grepして調査
    • 結果、適当なところに書いて、を繰り返し悲惨な状態に
  • データとロジックが分離していて修正範囲が広い
  • 共通クラス、Utilクラスを作ってしまうから
    • 誰でも呼べる = そのクラスを使って何をやっているのか、影響範囲が大きくなる
    • 微妙にニーズが違うせいで、関係ない(使わない)分岐まで混ざってしまう
    • 重複コードが生まれる
    • 肥大化して結局どこに何が書いてあるかわからなくなる

手続き型

どうすればいいのか

  • データとロジックを一体化することで、業務ロジックを整理する
  • 三層+ドメインモデルの構成にする
  • すべての業務ロジックをドメインモデルに集める
  • 他の層は、判断、加工、計算はドメインモデルに任せる

具体例(重要ポイント)

値オブジェクトを使う

  • なにかの料金クラスがあるとして、そのインスタンス変数をInteger moneyとしない。
  • 専用の型(クラスやインターフェース)で、不適切な値は混入させない。
  • 値オブジェクトを用意すると、コードが安定する。

値オブジェクトは不変にする

  • 値が書き換わるときは、別オブジェクトをnewして返すこと
  • 内部で値を書き換えてしまうのは副作用が大きすぎる。
  • コードを読むときに、この変数はどこで値が変わっているか、を気にしないといけなくなる
    • 改修の手間(調査)が一気に面倒になる
  • 変数の値は変わらない。だからコードが安定する。

悪い例

Money price = new Money(3000);

price.setValue(2000);  // × 値は書き換えている
price = new Money(4000); // 1つの変数に別の値を代入している

良い例

Money basePrice = new Money(3000);
Money discount = basePrice.Minus(1000); // minusメソッドは、内部で引数を元に計算した値をnewして返す

Money option = new Money(2000); // 変数は上書きしない

クラスを使う側にロジックを一切書かせない

従業員 classがあった場合

Employee class {
    // 入社日時 (データ
   JoinDate joinDate;

    // 生年月日 (データ
   Birthday birthday;

    // 職種 (データ
   JobType jobType;

    // 年齢取得メソッド(ロジック
   Integer Age () {
        // 生年月日から年齢を返す
   }

    // エンジニア判定メソッド
    boolean isEngineer () {
        // jobTypeがエンジニアならtrueを返す
    }

    // クリエイター判定メソッド
    boolean isCreator () {
        // jobTypeがエンジニアorデザイナならtrueを返す
    }
}
  • getterは基本的に使わない
    • 利用するクラスはgetしたデータでなにをしようとしている?
    • データを使って、加工/判断/計算をする場合は、ドメインオブジェクトに必ず閉じ込める
    • そのデータを使うロジックは、ドメインオブジェクトを見ればわかるようにする

利用側のクラスが↓こんな感じで判断してはいけない

if (employee.getJobType() == エンジニア) {
    XX処理
}

メソッドは短く

  • 処理をメソッドとして独立させる
    • 段落が増えた際、一つの段落をメソッドとして抽出する
    • 変更の影響をメソッド内に閉じ込めやすくなる
    • 詳細をメソッドに記載するので呼び出し元のコードが綺麗になる
    • メソッドの名前からコードの意図を理解しやすくなる

クラスは小さく

  • 狭い関心事に特化したクラスにする
  • クラスがたくさんできてしまう場合は、パッケージに分けて管理する
  • 階層が増えてきたり、ある階層には1クラスしかない、とかもあると思うが、それでもよい。
    • 大事なのは、どこに何があるかをわかりやすくすること
    • その階層を見るだけで、どこに何が書かれているかわかるようにする。
  • パッケージを分け、それの使う、使われるの依存関係をはっきりすることで、ロジックの重複を避けることができる
    • 知らないでいいことを知りすぎたり、責任を誰が持つのかを明確にする

クラスをデータの入れ物として考えない

  • あるクラスを「データの入れ物」と考えてしまうと、そのクラスからデータをgetして、自分でロジックを書くのが当たり前になってしまう
  • データを持つクラスのメソッドを「ロジックの置き場所」と考えれば、そのクラスが、判断、加工、計算までやってくれる、便利な部品になる

メソッドの書かれている場所の妥当性を考える

  • インスタンス変数を使わないメソッドは、そのクラスのメソッドとして不適切
    • インスタンス変数でない引数だけを使うメソッドはだめ
    • どこに何が書いてあるかわからなくなる原因
    • つまりそれは、その処理で使っているクラスのメソッドであるべき(データの近くにロジックを置く)

わかり易い名前を使う

  • 省略した名前を使わない
  • クラス名やメソッド名を業務の用語と一致させる
    • クラス名=業務の関心事(予約、注文、支払い、キャンセル等)、メソッド名=利用者がやってほしいこと
    • このように設計しておくと、変更の要求=対象クラス、変更のソフトウェアの範囲=業務の範囲となって変更箇所が特定しやすい
  • そのためには

    - 業務分析者とソフト設計者は同じ人・チームが望ましい

具体例(その他)

長いメソッドは段落を分けて見やすくする

  • ステップごとに空白を入れる

目的ごとに関数を分ける

  • 目的ごとに変数(= 説明用の変数)を用意する
    • 例えば、料金計算の際に同じ「price」を使わない
  • 破壊的代入(一つの変数を使い回すこと)を避ける

値の範囲を制限する

  • 郵便番号や電話番号など

配列・コレクション型を扱うロジックを専用クラスに閉じ込め、安定させる

  • コレクション型のデータとロジックを特別扱いにして、コレクションを一つだけ持つクラスを作る(コレクションオブジェクトorファーストクラスコレクション)
  • 値を「不変」にして外部へ渡すことで安定する

判断や処理のロジックをメソッドに独立させる

  • if文の中に処理やロジックを書かない

else句を使わない

  • 結果が確定した段階でreturnする(早期リターン)
  • 早期リターンを使う書き方(ガード節)をすると、条件分岐の構造だけに集中して考えることが可能

区分ごとにロジックを別クラスに分け、同じ型として扱う

  • インターフェース型を利用して区分ごとに異なるクラスのオブジェクトを同じ型として扱う(多態)
  • 使う側のクラスが、区分の詳細を知らないことが重要

区分ごとのクラスのインスタンスを生成する

  • Map型やJavaの列挙型(enum)を使う
    • 区分ごとの業務ロジックをわかりやすく列挙することが可能

全体的な進め方

  • 部分を作りながら全体を組み立てていく
    • 部分と全体を行ったり来たりしながら作っていく
      • パッケージ図や業務フロー図を参考に
    • 手続き型は全体をまず組み立ててから部分ごとに作っていくが、それではうまく行かない
    • 重要な部分から作っていく
    • 作ったものを組み合わせながら広げていく
  • 設計初期の段階から、完璧なドメインモデルは作れない
    • むしろ作りながら、育てていく(リファクタしまくる)

ドメインオブジェクトの見つけ方

  • 業務の関心事を分類する
    • 「モノ」「ヒト」「コト」の3つに分類することができる
    • 「コト」に注目することで全体の関係を整理しやすくなる
  • 業務内容を理解する
    • 専門用語で重要な言葉をキャッチする
    • 言葉のやり取りで専門知識を引き出していく
  • ドメインオブジェクトの基本設計パターン
    1. 値オブジェクト
      • 数値 、日付、文字列をラッピングしてロジックを整理する
    2. コレクションオブジェクト
      • 配列やコレクションをラッピングしてロジックを整理する
    3. 区分オブジェクト
      • 区分の定義と区分ごとのロジックを整理する
    4. 列挙型の集合操作
      • 状態遷移ルールなどの列挙型の集合として整理する
  • 業務の関心事パターン
    • 口座(Account)パターン
      • 現在の値を表現し、妥当性を管理する
    • 期日(DueDate)パターン
      • 約束の期日と判断を表現する
    • 方針(Policy)パターン
      • 様々なルールが複合する、複雑な業務ロジックを表現する
    • 状態(State)パターン
      • 状態と状態遷移の可否を表現する

ドメインオブジェクト以外の層について

アプリケーション層

  • 小さく分ける
    • まずは登録系と参照系で分ける
  • 利用する側とされる側でルールを明確にする
    • nullを渡さない。nullを返さない
    • 状態に依存する場合、使う側で事前に確認する
    • 約束を守った上で異常が起こった場合、例外で通知をする
  • データベースの操作と分離する

データベース層

  • 下記の2つを正しく記録することを意識する
    • 現実に起きたコトの記録
    • 将来起きるコトの記録(約束の記録)
  • 名前を省略しない
  • 適切なデータ型を使う
  • 制約をきちんと使う
    • NOT NULL制約
    • 一意性制約
    • 外部キー制約
  • 記録のタイミングが異なるデータはテーブルを分ける
  • 記録の変更を禁止する
    • UPDATE文は使わない
  • カラムの追加はテーブルを追加する
  • 非同期処理を使用する
    • 残高更新など
  • オブジェクトとテーブルは似てくる

プレゼンテーション層

  • 画面を関心事ごとに分ける(タスクベースのUI)
    • 氏名に登録、注文内容の登録、決済方法の登録など
    • 「何でも画面」を作る際でも、内部の設計はタスクベースにするべき
  • 画面とドメインオブジェクトを連動させる
    • 食い違いが見つかった場合、ドメインオブジェクトを見直す
  • 下記はドメインオブジェクトに書くべき
    • 論理的な情報構造
    • 場合ごとの表示の違い
    • HTMLのclass属性
  • 画面(視覚表現)とソフトウェア(論理構造)を関係付ける
    • 項目とフィールドの並び順を統一する
  • 画面デザインの4原則
    1. 近接
      • 関係ある情報は近づける、関係のない情報は離す
    2. 整列
      • 同じ意味のものは同じラインに揃える(左端、上端など)
      • 意味が異なれば異なるラインに揃える(インデントなど)
    3. 対比
      • 意味の重みの違いを文字の大きさや色の違いで区別する
    4. 反復
      • 同じ意味は同じパターンで視覚化する

ほんと色々噛み砕いてすごくわかりやすいおすすめの一冊でした