本の概要
「現場で役立つシステム設計の原則 〜変更を楽で安全にするオブジェクト指向の実践技法」
こちらの本を読んだので、印象的に残った箇所のメモや感想を残します。
システム設計の原則というタイトルですが、内容はオブジェクト指向をドメイン駆動設計(DDD)に乗っけて開発を進める為の具体的なコードの書き方や考え方が6割、外部システム連携として良いAPI設計のやり方が3割、ドメイン駆動設計でプロジェクトを運営していく為のマネジメントや発注元との契約の進め方が1割。という感じでした。
業務ロジックをわかりやすく整理する
図3-3 ドメインオブジェクトの参照関係を図で整理して、全体を俯瞰する。
時間軸に沿った業務の基本の流れを軸に業務ロジックを整理する
p87
「パッケージ」という言葉が使われているけど、「クラス」でも問題なさそうかなと思った。
ドメインモデルの参照関係を図にして業務ロジックの時系列を整理するのは良さそう。
ドメインモデルとデータモデル
ドメインモデルとデータモデルは何が違うのか
ドメインモデルは、業務ロジックの整理の手法です。業務データを断/加工/計算するための業務ロジックを、データとひとまとまりにして「クラス」という単位で整理するのがオブジェクト指向の考え方です。関心の中心は業務ロジックであり、データではありません。
一方、データモデルは、文字どおりデータが主役です。業務で発生するさまざまなデータを整理して、どうテーブルに記録するかを考えます。
このように、業務ロジックに注目し、それをクラスという単位で設計するドメインモデルと、データの整理を目的とするデータモデル(テーブル設計)は、本質的に違うものなのです。
P100
要件定義(基本設計) -> 詳細設計 -> 実装 の流れの中で、基本設計時にDB設計まで固めるよくあるウォーターフォールの開発モデルをデータモデルと呼んでいてなるほどなーと思いました。
ドメイン駆動設計と対になる言葉で呼ぶなら、さしずめデータ駆動設計でしょうか。
ドメインモデルの考え方で設計する
ドメインモデルの設計でありがちな失敗に、業務では実際には使っていない抽象的な言葉をクラス名として使ってしまうことがあります。
クラス名を抽象的にすればするほど、その名前は広い範囲の対象を包含して説明できます。抽象的で意味の広い名前をクラス名やパッケージ名にしたほうが、さまざまな要素をシンプルにすっきりと整理できたように錯覚しがちです。
しかし、そういう意味の広い抽象的な名前を使ったクラスは、具体的には何も説明していません。業務の現実の詳細を的確にとらえてはいないのです。たとえば、業務のさまざまな活動をどれも「取引」として説明することはできます。「販売」も「仕入」も「取引」の一種です。だからといって、「取引」 クラスに「販売」の業務ルールも、「仕入」の業務ルールも、どちらも記述するのは、プログラムを複雑にするだけです。
P98
よくない命名・実装でありがちですよね。ぜんぶ取引だから取引クラスを作ってそこにまとめちゃうやつ。
命名で言うとUserInfoやUserDataみたいなクラス名もよくないですよね。
InfoもDataも意味がない言葉になってるので、ただUserクラスで十分みたいな。
業務を学びながらドメインモデルを成長させていく
「開発の初期の段階では、開発者はドメインオブジェクトを設計するだけの業務知識を持っていません。用語の意味があいまいだったり、重要な用話を見落としています。用語と用語の関係を正しく把握できていません。
そのような段階でも、理解した範囲で実際にクラスを設計し、実装してみることが大切です。業務の用語とうまく対応しないクラスは、業務の分析や理解が足りないことを示します。用語の意味やほかの用語との関係を確認しながら、より適切なクラスの候補を探します。
従来のやり方だと、まず要件を理解するための分析を行い、要求仕様としてドキュメントにまとめます。そして分析ドキュメントの作成が一段落してから設計をはじめ、設計が固まったらコードを書き始めるというスタイルです。しかし、これは業務の関心事とプログラムの構造を一致させるためには良いアプローチではありません。
分析して得た知識や理解は、さまざまな形式で表現ができます。クラス図で表現できるし、文書でも表現できます。そして、分析結果はプログラミング言語でも表現できます。
P135
最初から実装を意識して要件分析をする。
そんなプロジェクトに関わっていきたいですよね。
アプリケーション機能を組み立てる
業務アプリケーションを段階的に作っていくときに、サービスクラスのメソッドに業務ロジックを直接書いてしまうことが、その時点では最もかりやすく手っ取り早いことはよくあります。
しかし、サービスクラスに業務ロジックを書き始めると、手続き型のプログラミングで起こりがちなコードの重複が始まります。そして、アプリケーション全体の見通しが悪くなり、変更がやっかいになっていきます。
そうならないために、段階的にコードを追加するときには、いつも設計の改善を考えます。業務ロジックの置き場所として、より適切な場所を深します。適切なドメインオブジェクトがなければ、ドメインオブジェクトの追加を考えます。
P154
サービスクラスに業務ロジックを直接書いてしまうこと、減らしていきたいですよね〜。
ドメインモデルを育てる
サービスクラスの実装を始めると、詳細な業務ルールが発見されたり、例外的なケースへの対応の要求が追加されることがよくあります。そのようなルールの発見や要求の追加のために、サービスクラスに業務ロジックを安易に追加してしまうと、ドメインモデルの成長が止まります。ドメインモデルの成長が止まると、三層+ドメインモデルで実現できる変更の容易性が劣化します。
サービスクラスに業務ロジックを書きたくなったら、それはドメインモデルの改良の機会として積極的に活用しましょう。サービスクラスの設計を単純に保つために、ドメインオブジェクトの追加や改良を続ける努力が、ドメインモデルを育て、アプリケーション全体で業務ロジックをわかりやすく整理する基本です。
P156
小並感ですが、継続的なコードの改善していきたいですよね。
契約による設計 と 防御的プログラミング
基本的な約束事には次のものがあります。
- nullを渡さない/null を返さない
- 状態に依存する場合、使う側が事前に確認する
- 約束を守ったうえでさらに異常が起きた場合、例外で通知する
こういう約束事を前提にすることで、防御的なコードがなくなり、コードがシンプルになります。つまり読みやすく変更が楽で安全なコードになります。
この前のT・Wadaさんの堅牢なコードに近い内容ですね。
どこまで予防線を張ってあげればいいか。
用途がわかりにくいカラム
- カラム名が省路形
- NULL値が入ったカラム
- ほかのカラムの内容に依存して値の意味が変わるカラム
- カラムから取得した文字列を、プログラムで分解する必要がある
- 意味が読み取れないコード(0,1,9,….などのマジックナンバー)が付いている
こういうカラムは意味がわかりにくく、カラムの参照やデータの挿入を行うプログラムも、複雑でわかりにくいものになります。
もっとひどい設計が「自由項目」や「予備項目」と呼ばれるカラムです。
これは任意の文字列を任意の用途で使う拡張用のカラムです。テーブルにカラムを追加しなくても、あとから新しいデータを扱うための準備です。
しかし、こういう拡張用のカラムは、そのカラムの意図があいまいになりやすく、使い方もばらばらになります。拡張用のカラムはプログラムを複
維にするだけの、拙いテーブル設計の典型です。
P175
DB設計のお話しですね。
ここで挙げられてる良くない設計ぜんぶ乗せなDBを扱ってるプロジェクトに入ったことがあります。
カラム数600あって、そのうちのほとんどが予備カラムだったりして笑いました。
DB設計を後から変更しない想定のプロジェクトだとあるあるなんですかね?
業務ロジックはオブジェクトで、事実の記録はテーブルで
- 基本はコトの記録のテーブル
- 導出の性能を考慮して、コトの記録のたびに状態を更新するテーブルも用意する
- 状態を更新するテーブルはコトの記録からいつでも再構築可能な二次的な導出データ
たとえば、口座に入金があったら入金テーブルにコトを記録する。そして、残高テーブルのその口座の残高も増やす。口座から出金があったら、出金テーブルにコトを記録する。そして残高テーブルのその口座の残高を減らす。
データベースの本質は事実の記録です。まず、コトの記録を密度することが基本です。状態テーブルは補助的な役割であり、コトの記録から派生させる二次的な情報です。
P186
自分は今までこの例で言うと残高を記録することが大事だと思っていた。もちろんログを残すという意味では入出金を記録するけど。
残高は入金と出金から導出できるもので、本当に記録すべきは入金・出金の”コト”という考え方は今までしたことがなかったので新鮮でした。
ヒト・モノ・コトの中でコトを記録するという考え方は、実際に使えるのかどうか仕事でも意識していきたいです。
画面項目のグルーピング
画面とドメインオブジェクトの対応がとりやすいのは、画面がタスクベースの場合です。画面がタスクベースではなく、さまざまな関心事が混在した「何でも画面」の場合は、画面のデザインが利用者の関心事を適切に表現しているとは限りません。画面デザインがごちゃごちゃしている場合は、ドメインオブジェクトの設計のほうから、画面をより論理的にデザインする改善点を提供すべきです。
P222
ぜんぶ乗せな何でも画面を前に作っていたことがあるから、このへんは裁量権があるならそういう風にやりたいよね〜ってなりながら読んでました。
その時は、発注元が先にどっかの会社に依頼して作った画面モックを渡されて、そのモックと同じ動作をアプリで実現しなきゃいけなくて大変でした。
今の案件では何でも画面的な機能を要求されてもパフォーマンス的に良くないですよとか意見が言えるのでありがたい。。。
ドメインモデルを中心にしたソフトウェア開発の進め方
- ドメインモデルに業務ロジックを集めて整理する活動
- 要求の分析とソフトウェアの設計は同じ人間/チームが担当する体制
- 従来の開発の進め方
従来のやり方では、分析活動は開発の初期の段階で集中的に行います。分析を段階的に詳細化しながら大量のドキュメントを作成します。このやり方の場合、開発のマネジメントの主たる関心事はドキュメントになります。ドキュメントの作成量が進捗の指標です。
品質保証は、ドキュメント記述の網羅性と形式的な整合性のチェックです。特に機能要件を詳細に定義する次のドキュメントの作成が開発活動の中心になります。
・機能一覧
・機能詳細説明
・画面一覧
・画面項目定義書
●オブジェクト指向らしい開発の進め方
三層+ドメインモデルで開発する場合、これらのドキュメントで記述する内容は、ドメインモデルの設計に対応します。分析と設計を一体で進めるオブジェクト指向の開発スタイルでは、このドキュメントを作成するための調査や分析作業は、ドメインモデルを設計し実装するチームが担当します。
同じチームが担当するので、大量にドキュメントを作ってから、それをプログラミング言語で書き換えていく作業はムダです。分析しながら理解した内容を、直接ソースコードとして記録し、確認していくほうが効率的です。そして、業務を理解している人間が直接プログラムを書いているのですから、要求の取り違えや抜け漏れが起きにくくなります。
つまり、分析と設計を同じ開発者が担当することで、大量のドキュメント作成が不要になり、開発のスピードも上がり、かつ、品質も向上します。
P274
大量にドキュメントを作ってから、それをプログラミング言語で書き換えていく作業はムダです
大量にドキュメントを作ってから、それをプログラミング言語で書き換えていく作業はムダです
大量にドキュメントを作ってから、それをプログラミング言語で書き換えていく作業はムダです
大量にドキュメントを作ってから、それをプログラミング言語で書き換えていく作業はムダです
こういう案件に当たると本当につらいですよね。
川べりで石を積み上げているような気持ちになります。
設計は少しずつ改良を続ける
リファクタリングは、設計の改善活動です。そして、これがオブジェクト指向設計の基本です。
手続き型のプログラミングでは、設計は、プログラミングの「前」の作業でした。プログラミングを始めたあとの設計変更は避けるべき手戻りでした。
オブジェクト指向では、事前に設計を固定するアプローチではありません。開発の過程で、より良い部品を見つけたり、既存の部品を使いやすく改良することがオブジェクト指向の設計です。
P300
さっきも出てきたような気がするけど、大事なことなので何度も出てくるんでしょうね。
オブジェクト思考・ドメイン駆動設計は一発目から完璧で修正不要なコードができるわけではなく、継続的にコードを改善していくという考え方が大事なようです。
古い習慣から抜け出すためのちょっと過激なコーディング規則
・ルール 1:1つのメソッドにつきインデントは 1 段階までにすること
・ルール2:else 句を使用しないこと
・ルール3:すべてのプリミティブ型と文字列型をラップすること
・ルール4:1行につきドットは1つまでにすること
・ルール5:名前を省しないこと
・ルール 6:すべてのエンティティを小さくすること
・ルール7:1つのクラスにつきインスタンス変数は2つまでにすること
・ルール 8:ファーストクラスコレクションを使用すること
・ルール9:getter、setter、プロパティを使用しないこと
●すべてのプリミティブ型と文字列をラップする
第1章で説明した「値オブジェクト」です。
数値や文字列を判断/加工/計算するロジックをデータを持つクラスに置くことで、コードの重複が減り、変更の影響範囲を1つにクラスに閉じ込めることができます。
プリミティブ型や文字列を引数として渡したり、メソッドの戻り値として使うと、ロジックがどこに書いてあるかわかりにくくなります。
ロジックと、そのロジックが使うプリミティブ型や文字列型のデータが、いつも同じクラスにまとまっていることが、オブジェクト指向設計の基本
です。
P301
ドメイン駆動設計や適切な責務でクラスを分割する方法を脳筋的に体に馴染ませるためのルールですね。なるべく実践していきたいですね。
データをgetterメソッド経由で取り出してアレコレするのではなく、ロジックをデータを持つクラスに置くというのは、似たロジックが色々なところに作られているのを防ぐのに重要だなと思いました。
コメント