現場で役立つシステム設計の原則 〜変更を楽で安全にするオブジェクト指向の実践技法〜 CHAPTER 3 業務ロジックをわかりやすく整理する
増田 亨. 現場で役立つシステム設計の原則 〜変更を楽で安全にするオブジェクト指向の実践技法 (Japanese Edition). Kindle 版.
前回
データとロジックを別のクラスに分けることがわかりにくさを生む
業務アプリケーションのコードの見通しが悪くなる原因
手続き型の設計では、データ(クラス)とロジックを別々のクラスで実装する。
「データクラス」はその名の通りデータを格納するだけのクラス。「機能クラス」はデータを加工するメソッド・ロジックを持つクラス。
この設計だと、「三層アーキテクチャ(プレゼンテーション層、アプリケーション層、データソース層)」であったとしても、ロジックの所在や重複などで変更に弱い実装になってしまう。
データクラスを使うと同じロジックがあちこちに重複する
「データクラス」に対するロジックは「機能クラス」書くが、どこ(三層)に書いたロジック(=つまり機能クラス)からでも参照できてしまう。
すると、「機能クラス」間で同様のロジックが重複してしまう。
本来Javaからするとクラスはデータとロジックを1つにまとめる仕組みであるはず。「データクラス」はデータのみでロジックを持たないため、この原則から外れてしまう。
データクラスを使うと業務ロジックの見通しが悪くなる
そもそもアプリケーション層の構造が画面やDBの都合に引きづられる。これはあるあるだ。
そして、業務ロジックをアプリケーション層に集めたとしても、データクラスを使うと上記と相まってロジックの見通しが悪くなってしまう。
画面の都合に引きずられるパターンだと、アプリケーション層のクラスと画面を1対1とした場合ロジックが重複し、変更も大変になるとのこと。
データベースの都合に引きづられるパターンでも同様に、「機能クラス」が複数あるとどこで何の処理を行っているかがわかりにくくなってしまう。
共通機能ライブラリが失敗する理由
共通ライブラリクラスを作ればデータクラスと機能切らすに分ける設計でもコード重複を防ぐことができるが、業務ロジックの共通化(重複を防ぐ)はあまりできない。
業務ロジックは汎用化しにくいし、小さな共通関数を作るやり方もメソッド数が膨れ上がるので難しい。
また、共通ライブラリが必ず使われるとも限らない。
そのため、共通ライブラリのようなものはかなり難しそう。
業務ロジックをわかりやすく整理する基本のアプローチ
ではどうやったら変更が楽なわかりやすい業務ロジックを作るのかというと、
- データとロジックを一体化して整理
- 三層アーキテクチャそれぞれで関心事を分離する
COLUMN データクラスが広く使われているのはなぜか
データクラスがよく使われるようになった背景にCOBOLからの遷移がある。
手続型をJavaでやるとこうなるらしい。
確かに自分も Struts なんかを触っていたときなど、COBOLからの流れを汲んだ結果誕生した「データクラスと業務ロジックの分離」という広く使われている設計方法が、実はオブジェクト指向のアンチパターンだったみたいな話。衝撃だった。
データとロジックを一体にして業務ロジックを整理する
データ構造と処理手順という典型的な手続型とは反対に、データとロジックを一つにまとめたクラスをプログラミング単位として開発するのがオブジェクト指向。
こうすることでコードの重複をなくせてシンプルになるとのこと。
使う側のクラスのコードがシンプルになることが、クラスの設計で大切。
業務ロジックを重複させないためにはどう設計すればよいか
データとロジックを一緒のクラスに書いておけば、”そのデータを使う側のクラス”でロジックを果敢無くて良くなるため、ロジックの重複が無くせる。
こうして使う側のクラスのコードをシンプルにすることが、オブジェクト指向らしいクラス設計。
つまり、ドメインオブジェクトが育つということなのかな。
気をつける点については以下7点あり、一つづつ解説されていく。
- メソッドをロジックの置き場所にする
- 業務ロジックをデータを持つクラスに移動する
- 使う側のクラスに業務ロジックを書き始めたら設計を見直す
- メソッドを短く書くとロジックの移動がやりやすくなる
- メソッドは必ずインスタンス変数を使う
- クラスが肥大化したら小さく分ける
- パッケージを使ってクラスを整理する
メソッドをロジックの置き場所にする
データクラスは自身のデータを別のクラスに渡してしまうことが原因。
そしてメソッドにはインスタンス変数を返すだけのような役に立たない処理は不要で、役立つことを実行させる。
例では、氏名それぞれのインスタンス変数のデータを返却するメソッドは役に立たないとのことだが、用途として必要であれば定義しても良いのかな。
業務ロジックをデータを持つクラスに移動する
と思ったが、それはデータを使う側のクラスにデータを渡していることに他ならなかった。なるほど。
この時点でかなり考え方にパラダイムシフトが起きていることを実感する。
データのクラスにロジックを集めることでコードの重複をなくし、変更箇所をそのクラスに限定できて、データを利用する側のクラスはシンプルになる。
使う側のクラスに業務ロジックを書き始めたら設計を見直す
データを置く場所とロジックを書く場所を一緒にすることで、そのクラスが保守もしやすい便利な部品になる。
初期はデータとロジックを分けた段階でも良いが、とりあえず動くようになったとで設計改善、つまりデータとロジックをまとめる作業はしたほうが良いとのこと。
メソッドを短く書くとロジックの移動がやりやすくなる
メソッドは小さくわけて独立させることでコードの移動が容易となる。
メソッドは必ずインスタンス変数を使う
インスタンス変数を使わないメソッドを見つけた場合、そのロジックをデータを持つクラスへ移動させ、データとロジックを一緒にしてあげる。
基本的にインスタンス変数を使わないメソッドは悪い例となる。
クラスが肥大化したら小さく分ける
クラスにデータとロジックを集めるとクラスが肥大化してくることがあり、その対処法として関連性の強いデータとロジックを抜き出して新しいクラスを作る。
インスタンス変数とメソッドの関係に注目して独立させることで、関連性の強いデータとロジックだけが集まったクラスとなる。
これを「凝集度が高い」という。
凝集度が高いクラスは意図が明確であるばかりではなく、他のクラスに影響しにくくなるため疎結合になる。
パッケージを使ってクラスを整理する
クラスが増えた場合に整理するためにパッケージを使う。
関連性が強いクラスを集め、パッケージが増えたらサブパッケージも使って整理する。
スコープをパッケージに限定させた方が良い。
パッケージの設計も継続して地道に改善する必要がある。
三層の関心事と業務ロジックの分離を徹底する
業務ロジックを小さなオブジェクトに分けて記述する
「業務データ(単価、数量)」から何かを算出(合計金額など)するロジックを「業務ロジック」と呼ぶ。
そして、「業務データ」と「業務ロジック」を一つにまとめたオブジェクトを「ドメインオブジェクト」と呼ぶ。
単価と数量から合計金額を算出するような業務ロジックの最小単位を作り、ドメインオブジェクトを設計していく。
さらにドメインオブジェクトを食い回せてより大きな「業務の関心事」を作成する。
「注文(ドメインオブジェクト)」と言う大きなドメインオブジェクトを作るとした場合、商品や数量、単価などの小さなドメインオブジェクトが内包される形になる。
業務ロジックの全体を俯瞰して整理する
多種多様な業務データと関連する業務ロジックをドメインオブジェクトとして整理していくと、クラスの数が膨大になる。
そのためにパッケージを利用して整理する( > パッケージを使ってクラスを整理する )。
図3-3がとてもわかりやすかった。見た感じテーブル設計のようにも見える。
例えば下記の -> の方向でオブジェクトを知っている必要がある。
- 入金 -> 請求 -> 出荷 -> 注文
- 注文 -> 顧客
- 注文 -> 商品
注文は顧客と商品を知っていればよく、出荷については知る必要がない。
実際に出荷されたかを知るためには出荷を参照し、何が出荷されたのかは「出荷 -> 注文」で参照する。
また、顧客は注文に含まれる他のオブジェクト(商品)を知る必要はない。
こうした「知って良いか、知ってはいけないか」を全体を俯瞰して整理し、 「対象領域(ドメイン)」をオブジェクトのモデルとして設計 されたものを「ドメインモデル」と呼ぶ。
三層+ドメインモデルで関心事をわかりやすく分離する
三層アーキテクチャとドメインモデルの構造では、業務ロジックを記述するのはドメインモデルだけ。
ドメインモデルに業務ロジックが集約されていれば、三層の記述は簡潔になる。
まとめ
データとロジックの関係性と、それらをどのように実装していくかについて、システムが育っていく先に生じる不都合(保守のしずらさ)を見据えた理由からまとめられた回だった。
データとロジックについてはどこのプロジェクトでも普通に分けて書かれていたのだが、それがアンチパターンの一種になりうるというのは衝撃だった。
パッケージの設計についてもそうなのだが、やはり個人的にソフトウェア設計に対してずっと思っていた 「どのコードをどこに配置するのかがソフトウェア設計で考えること」なのかな ということが合っている。
それを綺麗にやろうとすると、メソッドを小分けにしたり、データとロジックを凝集させる、あるいは流動的に地道に改善させる必要もあるといったテクニックが必要になる。
そういったテクニックがわかる内容だったように感じる。