【読書録】「現場で役立つシステム設計の原則」第三章:業務ロジックをわかりやすく整理する

エンジニアリング
スポンサーリンク

目次

第一章:小さくまとめてわかりやすくする
第二章:場合分けのロジックを整理する
第三章:業務ロジックをわかりやすく整理する ※本記事

はじめに

この記事は増田 亨さんの著書「現場で役立つシステム設計の原則」を読んでの感想と備忘録です
自分用のメモのため、書籍の内容を網羅したものではありません

書籍のタイトルの通り、まさに現場ですぐにでも役立つ内容でしたので是非読んでみてください
特に新卒エンジニアに早いうちに読んで欲しい先輩達のクソコードに毒される前に

現場で役立つシステム設計の原則 ~変更を楽で安全にするオブジェクト指向の実践技法 | 増田 亨 |本 | 通販 | Amazon
Amazonで増田 亨の現場で役立つシステム設計の原則 ~変更を楽で安全にするオブジェクト指向の実践技法。アマゾンならポイント還元本が多数。増田 亨作品ほか、お急ぎ便対象商品は当日お届けも可能。また現場で役立つシステム設計の原則 ~変更を楽で安全にするオブジェクト指向の実践技法もアマゾン配送商品なら通常配送無料。

第三章の感想

データとロジックをまとめるということを通して以下を理解した

  • 業務データと業務ロジックをまとめることで凝集度を高め、結合度を下げられる
  • 凝集度を高め、結合度を下げることで保守/再利用/読解がしやすくなる
  • 画面やデータベースのことは業務の関心事ではないので業務データ/業務ロジックに持ち込まない

おそらく「凝集度を高め、結合度を下げること」がオブジェクト指向に従った設計の目標の第一段階である

この本を読んでから「業務データを持つクラスに関連した業務ロジックを持たせる」という実装を試してみたところ、
確かに実際に業務ロジックを変更する際にコードの理解が容易になった
ただ、実際にやってみると「このロジックの持ち主は誰なんだ…?」「データのまとめ方はこれで良いのか…?」と迷うことが多々あり、なかなか難しい

第三章の内容で基本的な方針はわかったような気がするけど、本当に効果を実感するのは第四章、第五章の内容を理解して良いドメインモデルを作り、適切に使う必要がありそう

キーワード

  • 手続き形/オブジェクト指向
  • データクラス/ロジッククラス
  • 三層アーキテクチャ
  • 凝集度
  • パッケージ
  • ドメインオブジェクト
  • ドメインモデル

第三章:業務ロジックをわかりやすく整理する

データとロジックを別のクラスに分けることがわかりにくさを生む

データクラスを使うと業務ロジックの見通しが悪くなる

従来の手続き形の設計だと

  • データクラス(データを格納するクラス)
  • ロジッククラス(ロジックを記述するクラス)

を分けることが基本になるが、こうするとデータを使うロジックをどこにでも書けるため

  • 同じ業務ロジックがあちこちに重複して書かれる
  • どこに業務ロジックがあるのかわかりづらくなる

ので、プログラムを修正する際の影響や変更対象の把握が難しくなる

業務アプリケーションでは業務ロジックを整理するために三層アーキテクチャを使うことが一般的

関心事
プレゼンテーション層画面や外部接続インタフェース
アプリケーション層業務ロジック、業務ルール
データソース層データベース入出力

データクラスを使うとアプリケーション層の構造が他の層に影響されてしまい、
どこに何があるのか見通しが悪くなる

プレゼンテーション層に影響される例:
「注文画面」の入力内容を「注文データクラス」に格納し、アプリケーション層の「注文登録クラス」で登録するという設計
こうすると、例えば「注文登録クラス」「注文更新クラス」それぞれに同じ金額計算ロジックを実装する必要が出てくる、と言ったように複数の機能クラスに同じロジックが重複してしまう
そうなると、業務ルールが変わった際にどの機能クラスが変更対象なのか追いきれなくなってしまう

データソース層に影響される例:
「注文テーブル」のCRUD操作と「出荷テーブル」のCRUD操作をそれぞれ別の機能クラスとして実装する設計
こうすると、例えば注文の合計金額を算出するロジックは注文レコードの登録時、出荷テーブルの登録時、または他のアプリケーション層のクラスなど、どこに実装されているのかわからなくなる

(感想)
今の業務で扱っているコードが「データソース層に影響される例」そのものだ!という感じ
setter/getterだけ定義されたDB操作のラッパーのようなクラス達、
そしてそれらを操作する超長いメソッドを持ったクラス達…
実際にプログラムを変更する時に変更箇所の特定がかなり難しくなっている

共通機能ライブラリが失敗する理由

データクラスと機能クラスに分ける設計でも共通で使いたいロジックを集めて共通ライブラリクラスとして用意することでコードの重複を防ぐことはできる
(いわゆるUtilクラス、Commonクラス)

ただし、これは以下の2つのパターンでうまくいかない

1. 汎用化のために使いにくくなる
微妙に異なるニーズに対応するために引数がどんどん増える
使う側は自分に関係ない引数のことまで気にしないといけなくなり、
「自分で作った方が早いじゃん」になる

2.用途別の細分化のために使いにくくなる
微妙に異なるニーズに対応するためにメソッドがどんどん増える
使う側は自分の用途にあったメソッドを探したり、微妙に異なるメソッドの違いを理解しないといけなくなり
「自分で作った方が早いじゃん」になる

(感想)
共通機能ライブラリが微妙に異なるニーズに対応できずに使えない、は本当によくあるのでわかる
うまくいかないパターンとして、共通機能ライブラリのメソッドを使う側それぞれで戻り値を加工しはじめる、というのもあるあるだと思う
例: fullNameを返すメソッドしかないから、戻り値を加工してfirstNameを作っちゃえ

業務ロジックをわかりやすく整理する基本のアプローチ

  1. データとロジックを一体にして業務ロジックを整理する
  2. 三層のそれぞれの関心事と業務ロジックの分離を徹底する

データとロジックを一体にして業務ロジックを整理する

業務ロジックを重複させないためにはどう設計すればよいか

使う側のクラスのコードがシンプルになるように設計する
= オブジェクト指向らしいクラス設計をする

メソッドをロジックの置き場所にする

メソッドは判断/加工/計算のロジックを実行するものにする
ただ値を返すだけでロジックを含まないgetterメソッドは書くべきではない

// 悪い例。getterメソッド
class Person {
  private String firstName;
  private String lastName;

  String getFirstName() {
    return firstName;
  }

  String getLastName() {
    return lastName;
  }
}
// 良い例。加工ロジックを持つメソッドを定義する
class Person {
  private String firstName;
  private String lastName;

  String fullName() {
    return String.format("%s %s", firstName, lastName);
  }
}

(感想)
getterで返された値を各所で判断/加工/計算するのでロジックがどこにあるのかわからなくなる、
ということだと思う

業務ロジックをデータを持つクラスに移動する

getterメソッドを見つけたら、そのメソッドに何か判断/加工/計算をさせることを考える
例えば数値データをgetするメソッドの場合、getする側のクラスで何か計算をしているならその計算をgetterメソッドを持つクラスに移動させ、getする側のクラスは計算結果を受け取るようにする

データを持つクラスにロジックを移動させればコードの重複がなくなり、変更の対象箇所を限定できる

(感想)
この方法であればリファクタリングしやすそう

使う側のクラスに業務ロジックを書き始めたら設計を見直す

ロジックをどこに書くのが良いのかを適切に判断するのが「設計」
設計の初期段階では「とりあえず動く」を目指してデータだけを持つクラスを作ることもあるが、
「とりあえず動く」で終わらせずロジックの置き場所やクラス名/メソッド名の改善を続け、
より良い設計を見つけて行くのが、オブジェクト指向設計の基本

(感想)
より良い設計を目指してコードを作り直していく、というのは一見コストがかかるように見えるけど実際にやってみると短期的にも開発速度に影響しない(むしろ早くなる)というのが最近の気付き

メソッドを短く書くとロジックの移動がやりやすくなる

長いメソッドを短いメソッドに分けるとそのクラスに相応しくないコードのかたまりを発見しやすくなる

(感想)
メソッドを分割するということはコードの責任を分割するということだと思う

メソッドでは必ずインスタンス変数を使う

インスタンス変数を使わないメソッドはそのクラスのメソッドとしては不適切なので置き場所を再検討すべき
引数だけを使っているメソッドは引数を渡している側、もしくは更に引数を渡す側が使っているクラスへの移動を検討する

(感想)
データとロジックをまとめるということのメリットとして、「引数を無くせる」ということがありそう
引数を使うということはクラスを使う側が使いたいクラスのことを知らなければいけなくなり、
結合がより密になる

クラスが肥大化したら小さく分ける

インスタンス変数が多いクラスに業務ロジックを集めるとクラスが大きくなる
クラスが大きいとどこに何が書いてあるかわかりづらく、変更の影響も追いづらい

クラスが大きくなったら関連するデータとロジックを抜き出して新しいクラスを作ることを検討する

// 大きいCustomerクラス
class Customer {
  String firstName;
  String lastName;

  String postalCode;
  String city;
  String address;

  String fullName() {
    return String.format("%s %s", firstName, lastName);
  }

  String fullAddress() {
    return String.format("%s %s %s", postalCode, city, address);
  }
}


// 分割後Customerのクラス
class Customer {
  PersonName personName;
  Address address;
}

class PersonName {
  String firstName;
  String lastName;

  String fullName() {
    return String.format("%s %s", firstName, lastName);
  }
}

class Address {
  String postalCode;
  String city;
  String address;

  String fullAddress() {
    return String.format("%s %s %s", postalCode, city, address);
  }
}

関連性の強いデータとロジックだけを集めたクラスは凝集度が高いと言える

(感想)
凝集度が高いと保守/再利用/読解がしやすくなるらしい(経験的にも正しそう)
凝集度を高くすることがクラス設計の目標の第一段階なのではないだろうか

パッケージを使ってクラスを整理する

インスタンス変数とメソッドの関連に注目してクラスを抽出していくとクラスの数が増える
クラスの数が増えるとどこに何が書いてあるか見つけにくくなる
クラスの数が増えてきた時の整理の手段にパッケージがある

関連性の強いクラスは同じパッケージに集める
クラスやメソッドのスコープ(参照範囲)は可能な限りパッケージ内に閉じ込める
パッケージのクラス数が増えたらさらにサブパッケージに分ける

パッケージの設計も継続的に改善する
開発初期のパッケージは少ない情報を元に浅い理解で設計したものになるが、
開発が進むにつれて対象領域の知識が広がり適切な設計が見えてる

(感想)
クラスもパッケージも最初から最初からベストな設計が出来るわけではないので継続的に改善できるように作るのがよい
凝集度を高く作っていけば継続的な改善もやりやすそう

三層の感心事と業務ロジックの分離を徹底する

業務ロジックを小さなオブジェクトに分けて記述する

業務アプリケーションの中核は業務データを使った判断/加工/計算の業務ロジックである
業務データと業務ロジックが1つにまとまっていると修正が容易になる
関連した業務データと業務ロジックを1つにまとめたオブジェクトをドメインオブジェクトという

「ドメイン」とは対象領域、問題領域という意味
業務アプリケーションの場合、業務活動全体がドメインとなる

ドメインオブジェクトは業務データをインスタンス変数として持ち、業務ロジックをメソッドとして持つオブジェクトのこと

業務データとそれを使った業務ロジックを整理する時、
いきなり大きなデータやロジックを扱うと大変になる
なので、まずはできるだけ小さな単位に整理するのが良い
例:

  • 受注日と今日の日付から受注日の妥当性を判断するロジック
  • 単価と数量から合計金額を計算するロジック
  • 数値データの価格を千円単位の文字列表記に加工するロジック

(感想)
今まで開発初期に設計を行う際は「どのような画面が必要か?」「どのようなデータが必要か?」からスタートすることがほぼ全てだったけど、オブジェクト指向的には違うのかもしれない

業務ロジックの全体を俯瞰して整理する

クラスの数が多くなるとどのクラスにどの業務ロジックがあるのかわかりづらくなる
クラスが多くなった時はパッケージでまとめ、パッケージの参照関係を明らかにすることで理解が容易になる
このように業務アプリケーションの対象領域をオブジェクトのモデルとして整理したものをドメインモデルという

パッケージの参照関係は基本的には時系列順になる
例: 注文 <- 出荷 <- 請求 <- 入金

業務ロジックの全体的な関係を明らかにすることで、どこに何が書いてあるかをわかりやすくし、変更の容易性を高めることができる

(感想)
パッケージの関係が業務と一致していれば業務に変更があった時にコードの変更箇所を洗い出しやすく、
逆にパッケージの関係を見れば業務を理解できるようになるということだろうか

今まで「データをどのように扱うか」を元に設計をすることが多かったけど、
そうやって作ったプログラムは更新処理が散在したり、
似たようなデータでも使う側で細かく加工していたりでコードの変更をするのは大変だと思っていた

パッケージで分割して、お互いのパッケージが知るべきことを最小限にしていくとそういうことは減るのかもしれない

三層+ドメインモデルで関心事をわかりやすく分離する

ドメインモデル方式ではドメインモデルに集めたロジックを三層が使う形になる
三層側はドメインモデルの業務ロジックを利用するだけなのでシンプルになる

データクラス方式との違いは業務ロジックをどのクラスが持つかどうか

データクラス方式: 業務ロジックはアプリケーション層の複数の機能クラスが持つ
ドメインモデル方式: 業務ロジックはドメインモデルが持つ

(感想)
三層がドメインモデルに集めたロジックを使う、といっても実際に呼び出すのはほとんど(全て?)アプリケーション層になりそう
実際に業務の流れを実装するにアプリケーション層とドメインモデルどちらにコードを書くのか、というのはまだよくわかっていない

コメント

タイトルとURLをコピーしました