「現場で役立つシステム設計の原則」 要約
書籍
第1章 小さくまとめてわかりやすくする
プログラムの変更が楽になる書き方
- わかりやすい名前を使う
- 長いメソッドは「段落」に分けて読みやすくする
- 目的ごとに変数を用意する
- メソッドとして独立させる
- 異なるクラスの重複したコードをなくす
- 狭い関心事に特化したクラスにする
- メソッドは短く、クラスは小さく
値オブジェクト
業務で扱うデータの種類ごとに専用のクラスやインターフェースを用意するやり方。
値オブジェクトは業務の用語そのもの。
クラス名と業務の用語が一致していると、プログラム上で変更が必要な箇所を直感的に特定できる。
値オブジェクトは「不変」にする
値を変更できるオブジェクトは、変更と参照のタイミングによって、思わぬ副作用の原因になる。
値オブジェクトを「不変」にする方法は次のとおり。
- インスタンス変数はコンストラクタでオブジェクトの生成時に設定する
- インスタンス変数を変更するメソッド(setter)を作らない
- 別の値が必要であれば、別のインスタンス(オブジェクト)を作る
【NG】
Money price = new Money(3000);
price.setValue(2000); // ✕ 値を書き換えている
price = new Money(1000); // ✕ 1つの変数に別の値を代入している
【OK】
Money basePrice = new Money(3000);
Money discounted = basePrice.minus(1000);
// minus() メソッドは別のMoneyオブジェクトを作成して返す
Money option = new Money(1000); // 新しくMoneyオブジェクトを作る
「型」を使ってコードをわかりやすく安全にする
例えば、金額計算のメソッドに渡す引数の「金額」と「数量」を、両方ともintで扱うと、渡す引数の順番を間違えても気付けない。
それぞれMoney型とQuantity型を使うことで意図もわかりやすく、引数の渡し間違えも防ぐことが出来る。
コレクションオブジェクト
コレクション型のデータとロジックを特別扱いにして、コレクションを1つだけ持つ専用のクラスを作るやり方。
「顧客の一覧」など業務の関心事を、そのままクラスで表現したもの。
【例】コレクションList<Customer>型の変数を1つだけもった「顧客一覧」の専用クラス
class Customers {
List<Customer> customers;
void add(Customer customer) {・・・}
void removeIfExist(Customer customer) {・・・}
int count() {・・・}
Customers importantCustomers() {・・・}
}
コレクションオブジェクトを安定させる
値オブジェクト同様にできるだけ「不変」スタイルで設計する。
コレクション操作を安定させる3つの方法
- コレクション操作のロジックをコレクションオブジェクトに移動する
- コレクション操作の結果も同じ型のコレクションオブジェクトとして返す
class Customers { List<Customer> customers; ・・・ Customers add(Customer customer) { List<Customer> result = new ArrayList<>(customers); result.add(customer); return new Customers(result); } }
- コレクションを「不変」にして外部に渡す
class Customers { List<Customer> customers; ・・・ Customers asList() { return Collections.unmodifiableList(customers); } }
第2章 場合分けのロジックを整理する
else句をなくす
else句を使わずに早期リターンする下記の書き方をガード節という。
Yen fee() {
if(isChild()) return childFee();
if(isSenior()) return seniorFee();
return adultFee();
}
第3章 業務ロジックをわかりやすく整理する
- 手続き型の設計:データクラスと、機能クラス(ロジッククラス)に分ける
- オブジェクト指向:データとロジックを1つにまとめる
メソッド
- メソッドはロジックの置き場
- メソッドには何らかの判断/加工/計算をさせることを考える
- インスタンス変数を返すだけのgetterメソッドは書かない
- メソッドは必ずインスタンス変数を使う(インスタンス変数を使わないメソッドは不適切なため、ロジックの置き場を再検討するべき)
- ロジックを、データを持つクラスに移動する
- 使う側のクラスにロジックを書き始めたら設計を見直す
- メソッドを短くして、ロジックの移動をやりやすくする
- クラスが肥大化したら小さく分ける
- パッケージを使ってクラスを整理する
ドメインオブジェクト
関連する業務データと業務ロジックを1つにまとめたオブジェクトをドメインオブジェクトという。
「ドメイン」とは、対象領域や問題領域という意味で、業務アプリケーションの場合、そのアプリケーションが対象とする業務活動全体のことをさす。
関連する業務データと業務ロジックをクラス単位で整理すると、ドメインオブジェクトを再利用できるため、あちこちのクラスに重複することがなくなる。
クラスの数が増えると全体の見通しが悪くなるため、さまざまなドメインオブジェクトを、関心事の単位にグルーピングし、パッケージに分ける。
また、単にグルーピングするだけでなく、参照関係を含めて整理することで、うまく整理できる。
ドメインモデル
業務アプリケーションの対象領域(ドメイン)をオブジェクトのモデルとして整理したものをドメインモデルと呼び、業務で扱うデータと関連する業務ロジックを集めて整理したもの。
三層+ドメインモデル
業務ロジックを記述するのはドメインモデルのみ。
業務的な判断/加工/計算のロジックは、すべてドメインオブジェクトに任せる。
名前 | 役割 |
---|---|
プレゼンテーション層 | UIなど外部との入出力を受け持つ |
アプリケーション層 | 業務機能のマクロな手順の記載 |
データソース層 | データベースとの入出力を受け持つ |
ドメインモデル | 業務データと関連する業務ロジックを表現したドメインオブジェクトの集合 |
第4章 ドメインモデルの考え方で設計する
ドメインモデルを開発するために
分析:人間のやりたいことを正しく理解する
- 要求の聞き取り
- 不明点を確かめるための会話
- 図や表を使っての整理
- 理解した結果を記録するための文書の作成
設計:人間のやりたいことを動くソフトウェアとして実現する方法を考える
- パッケージ構成と名前
- クラス構成と名前
- メソッド構成と名前
ドメインモデルの作り方
-
部分を作りながら全体を組み立てていく
個々の部品を作り始め、それを組み合わせながら、段階的に全体を作っていく。 -
全体と部分を行ったり来たりしながら作っていく
パッケージ図、業務フロー図を利用して全体を俯瞰する。 -
重要な部分から作っていく
重要な部分がわかりにくい場合、あまり重要でなさそうな部分を一旦除外しながら、重要な部分の候補を見つけていく。 -
独立した部品を組み合わせて機能を実現する
ドメインオブジェクトを、部品倉庫であるドメインモデルから取り出して組み合わせる。
組み合わせるのはアプリケーション層のクラスの役割。
ドメインオブジェクトの見つけ方
業務の関心事を3つに分類する。
- ヒト:業務活動の当事者。意思があり、判断し、行動する主体。
- モノ:ヒトが業務を遂行するときの関心の対象。権利や義務のような概念的に認識している対象もモノ。
- コト:ヒトの意思決定や行動の結果。
コトに注目すると全体の関係を整理しやすい。
また、次の関係も明らかになる。
- コトはヒトとモノとの関係として出現する(だれの何についての行動か)
- コトは時間軸に沿って明確な前後関係を持つ
ドメインオブジェクトの基本パターン
4種類のドメインオブジェクトを組み合わせて、4つの関心事のパターンに業務ロジックを分類して整理していくと、業務ロジックがドメインモデルに集まるようになる。
【ドメインオブジェクトの設計パターン】
- 値オブジェクト:数値、日付、文字列をラッピングしてロジックを整理する
- コレクションオブジェクト:配列やコレクションをラッピングしてロジックを整理する
- 区分オブジェクト:区分の定義と区分ごとのロジックを整理する
- 列挙型の集合操作:状態遷移ルールなどを列挙型の集合として整理する
【業務の関心事のパターン】
- 口座(Account)パターン:現在の値を表現し、妥当性を管理する
- 期日(DueDate)パターン:約束の期日と判断を表現する
- 方針(Policy)パターン:様々なルールが複合する、複雑な業務ロジックを表現する
- 状態(State)パターン:状態と、状態遷移のできる/できないを表現する
ドメインオブジェクトの設計を改善する
改善するポイントは3つ
- クラス名やメソッド名の変更
- ロジックの移動
- 取りまとめ役のクラスの導入
業務理解をするために
- マニュアルや利用者ガイドを読んでみる
- 一般的な知識を書籍などで勉強する
- 使っているデータに何があるか画面やファイルを調べる
- 経験者と会話する
第5章 アプリケーション機能を組み立てる
サービスクラス
アプリケーション層のクラスをサービスクラスと呼ぶ。
サービスクラスの設計では下記を徹底する。
- 業務ロジックは、サービスクラスに書かずにドメインオブジェクトに任せる
(サービスクラスで判断/加工/計算しない) - 画面の複雑さをそのままサービスクラスに持ち込まない
- データベースの入出力の都合からサービスクラスを独立させる
プレゼンテーション層(使う側)との約束事
- nullを渡さない/nullを返さない
- 状態に依存する場合、使う側が事前に確認する
- 約束を守ったうえでさらに以上が起きた場合、例外で通知する
第6章 データベース設計とドメインオブジェクト
悪いデータベース設計
- どこにどのようなデータが入っているか推測しにくい
- データが入っていないカラムが多い
- データが重複していて、どのデータが正しいのかわからない
- 1つのカラムがさまざまな目的に利用されている
- テーブル間の関係がはっきりしない
用途がわかりにくいカラム
- カラム名が省略形
- NULL値が入ったカラム
- ほかのカラムの内容に依存して値の意味が変わるカラム
- カラムから取得した文字列を、プログラムで分解する必要がある
- 意味が読み取れないコード(マジックナンバー等)がついている
いろいろな用途に使う巨大なテーブル
- 似たようなカラムが多く、その使い分け方がわからない
- NULL値が多い
テーブルの関係がわからない
- 外部キー制約がない
- キーとなるカラムの名前に一貫性がない
良いデータベース設計
- 名前を省略しない
- 適切なデータ型を使う
- 制約をきちんと使う
制約を必ず使う
この3つの制約のないカラムやテーブルは、設計として問題があることを疑う。
- NOT NULL制約
- 一意性制約
- 外部キー制約
NOT NULL制約
下記の理由からカラムにはNULLを含まないのが基本。
- NULLを含んだデータ演算の結果はすべてNULLになる
- 検索条件の対象とするカラムにNULL値があると、意図と違う結果になる可能性がある
そのため、カラムはすべてNOT NULL制約にする。
どうしてもNULL値が必要なカラムを見つけたら、別のテーブルに分けることを検討する。
カラムをNOT NULL制約にするために、「unknown」や「9999」を入れるといった「NULL逃れ」をしてはいけない。
一意性制約
すべてのカラムとその組み合わせが、一意性制約の候補であると考えることが大切。
外部キー制約
NOT NULL制約と一意性制約で正規化したテーブル間の関係を明確にすることが重要。
コトに注目するデータベース設計
ヒトやモノとの関係を正確に記録するための3つの工夫
記録のタイミングが異なるデータはテーブルを分ける
記録のタイミングが異なるデータを1つのテーブルで記録しようとすると、NULL可能なカラムが必要になってしまう。
記録の変更を禁止する
過去の記録なので、コトの記録テーブルのデータを変更してはいけない。(UPDATE分は使わない)
過去の記録を修正したい場合、まず過去の記録の「取り消し」を記録する。
そして、修正する事実を別の記録として追加する。
修正後には次の3つの記録が残る。
- 元データ
- 取り消しデータ
- 新データ
この方法は、すべてINSERT文だけの操作で実現できる。
カラムの追加はテーブルを追加する
データベース設計の変更でよくあるのが、今まで記録していなかったデータを記録できるように拡張すること。
その場合、追加するカラムには過去データが存在しないため、NULLを許容するか、「NULL逃れ」をすることになる。
そうならないための対処方法はテーブルを追加すること。
- 元のテーブルはそのまま利用する
- 追加するデータ項目をカラムに持つテーブルを新しく作る
- 追加したテーブルから元のテーブルに外部キー制約を宣言する
オブジェクトの設計とテーブルの設計
コトを記録するテーブルとコトを表現するドメインオブジェクトがほぼ1対1に対応することがある。
しかし、両者は似ているだけであって、同じものではない。
特性 | オブジェクト | テーブル |
---|---|---|
目的 | データとロジック特にロジックの整理 | データの整理 |
関心事 | 導入や加工のロジック、データを使った判断ロジック | 導入や加工の元になるデータ |
アプローチ | 部分から全体 | 全体から部分 |
設計変更のリズム | 頻繁 | ゆるやか |
第7章 画面とドメインオブジェクトの設計を連動させる
関心事を分けて整理する
画面アプリケーションのコードが複雑で変更がやっかいになる原因
- 画面そのものが複雑
- 画面の表示ロジックと業務ロジックが分離できていない
複雑さを改善し、わかりやすく変更が楽で安全にできる方針
- さまざまな表示項目やボタンを詰め込んだ何でもできる汎用画面ではなく、用途ごとのシンプルな画面に分ける
- 画面周りのロジックから業務ロジックを分離する
タスクベースのユーザーインターフェース
1つの注文登録画面ですべてを入力するのではなく、次のような複数の画面を用意する。
- 顧客の氏名の登録
- 注文内容の登録
- 決済方法の登録
- 配送手段の登録
- 連絡先の登録
- 注文の確定
このように、用途を特定した小さな単位に分けた画面を提供することをタスクベースのユーザーインターフェースと呼ぶ。
ドメインオブジェクトに書くべきロジック
ビューに書くべきことと、ドメインオブジェクトに書くべきことを整理する考え方は次の3つ
- 論理的な情報構造はドメインオブジェクトで表現する
- 場合ごとの表示の違いをドメインオブジェクトで出し分ける
- HTMLのclass属性をドメインオブジェクトから出力する
論理的な情報構造をドメインオブジェクトで表現する
ビューの記述は基本的に2つに分かれる
- 物理的なビュー:画面表示する技術方式に依存したビュー表現
HTMLのタグや改行コードなど - 論理的なビュー:技術方式に依存しない概念的な構造
「複数の段落」という「構造」だけを表現する
Javaで記述すると
String[] description;
場合ごとの表示の違いをドメインオブジェクトで出し分ける
画面表示でif文を使っている場合は、その条件判断をドメインオブジェクトに移動できないかを検討する。
次のようにドメインオブジェクトで実装することで、ビュー側にif文の条件判断が不要になる。
class Items {
List<Item> items;
String found() {
if(items.count() == 0 ) return "見つかりませんでした" ;
return String.format( "%s 件見つかりました ",items.count());
}
}
HTMLのclass属性をドメインオブジェクトから出力する
条件によって視覚表現を変える例として、「新着」は赤字+ボールドで強調表示する、という例を考える。
次のようにドメインオブジェクトが状態を表す情報を返し、それをclass属性で利用する方法で、画面の表示ロジックからif文をなくすことができる。
String readStatus() {
if( isUnread() ) return "unread";
return "read";
}
//HTML 記述での使い方
<p class="${mail.readStatus()}">
画面とソフトウェアを関係づける
項目の並び順とドメインオブジェクトのフィールドの並び順
画面の項目の並び順と、対応するドメインオブジェクトのフィールドの並び順は一致させる。
画面項目のグルーピング
画面デザインの基本原則
原則 | 説明 |
---|---|
近接 | 関係ある情報は近づける、関係ない情報は離す |
整列 | 同じ意味のものは同じラインに揃える(左端、上端など)、意味が異なれば異なるラインに揃える(インデントなど) |
対比 | 意味の重みの違いを文字の大きさや色の違いで区別する |
反復 | 同じ意味は同じパターンで視覚化する |
利用者向けの情報もソフトウェアと整合させる
整合させるべき情報
- プレスリリース
- リリースノート
- 利用者ガイド
重要な新しい機能を追加する場合の重要ポイント
- プレスリリースに記載したセールスポイント
- リリースノートでの新機能の概要説明
- 利用者ガイドへの新機能の説明の追加
- ドメインオブジェクトの追加
第8章 アプリケーション間の連携
WebAPI
データを登録するPOSTとPUT
登録はできるだけPOSTを使うべき。
そのほうがアプリケーションの独立性が高くなり、修正や拡張の影響を小さくできる。
メソッド | 対象の指定方法 | 説明 |
---|---|---|
POST | books | 書籍データを登録し、識別番号を発行してもらう |
PUT | books/1234 | 識別番号1234の書籍データを登録する |
エラー時の約束事
アプリケーション間で連携する場合、うまくいかなかったときの決め事も重要。
「500 Internal Server Error」は、エラー内容を詳細にすべきではない。
内部のエラー画の詳細は、セキュリティ的に保護すべき内容が含まれるリスクがある。
使いにくいWeb API
- データを取得するために指定するパラメータが多い
- 取得したデータの項目数が多い
- 登録時に指定しなければいけないデータ項目が多い
上記のような「大は小を兼ねるAPI」は利用する側の負担が大きい。
必要のないパラメータまで理解しなければいけず、受け取ったデータ項目から必要なデータ項目だけを取り出す処理が必要。
良いWeb API
組み立てやすく変更しやすい、適度な大きさに分割したもの。
APIの粒度 | 実現できる機能の多様性 | 組み立ての複雑さ |
---|---|---|
小さい | 幅広い | 複雑 |
大きい | 限定的 | 単純 |
発展性に富んだAPI開発方法
まずは、単純な用途をかんたんに実現できるAPIを開発するところから始める。
そして、組み立てながら修正や拡張を行っていく。
登録と参照は別のAPIにする
指定席を予約するAPIを例に考える。
機能を実現するAPI設計として2つの選択肢があるが、後者にすべき。
- POSTのレスポンスとして、予約内容の詳細を返す
- POSTのレスポンスは予約番号だけを返し、予約内容はその予約番号で別途GETする
登録と参照を分けることで、予約の結果の参照方法や応答内容に変更があっても、予約登録のAPIには影響しない。
目的 | API | 説明 |
---|---|---|
予約の登録 | POST reservations | レスポンスとして予約番号2345を返す |
予約の確認 | GET reservations/2345 | 予約番号2345の内容を返す |
リソースの単位を分ける
- GET member/1234/name
- GET member/1234/gender
- GET member/1234/dateOfBirth
- GET member/1234/contactMethods
- GET member/1234/address
古いWeb APIの廃止手順
- 新しいAPIを追加しても、互換性のため古いAPIも提供する
- 古いAPIは残すが、「303 See Other」を返すように変更する(新しいAPIの情報を返す)
- 古いAPIのレスポンスとして「404 Not Found」を返すように変更する
- API自体を削除する
ドメインオブジェクトとWeb API
ドメインモデルで設計した場合、Web APIの役割はドメインオブジェクトと、JSONなどのテキスト表現との変換。
データ構造の不一致
ドメインオブジェクトはロジックの整理を軸にクラス分けする。
一方、APIで使うデータ形式は、データだけが関心事。
データだけに注目した場合、ロジックの整理を重視したドメインオブジェクトの階層構造を、そのままデータ構造として表現することは、あまり意味を持たない。
関心事の不一致
ドメインオブジェクトの持つすべての情報が、APIを利用する側で必要だとは限らない。
また、ドメインオブジェクトが期待するデータ項目がすべてPOSTされるとは限らない。
ズレが大きい場合、変換用の中間オブジェクトを用意するべき。
導入結果か生データか
元データのままやり取りするのか、加工や計算の結果をやりとりするのかの判断が難しい。
マスタ項目のコードと名称
選択肢は基本3つ。
- コードのみ
コードから名称を取得できるAPIを別途用意。マスタ情報をAPIを使って共有することが必要な場合に選択。ただし、密結合になるため、避けたい設計。 - 名称のみ
名称の重複の可能性があり、厳密さに難がある。ただし、名称のみで十分であれば、わかりやすくて良い選択肢。 - コードと名称の両方
名称の重複を考慮する場合の選択肢。コードから名前を取得するAPIは不要。
計算ロジックの置き場
明細データの合計や誕生日から年齢の計算といった導出可能なデータをどうするか、という問題。
基本、合計計算のロジックをどちらのアプリケーションが管理すべきかで判断する。
業務ルールとは呼べないような単純な合計計算の場合、基本は提供側で管理。
日付データの形式
基本的には、日付と時刻は別の項目として扱うべき。
日付だけであれば、タイムゾーンに関係ないデータとして扱える。
時刻は「+09:00」ではなく、「14:30:15」のように記述する。
その時刻が現地時間か標準時間かの解釈はAPIの約束事として決めておく。
人間にとって日常で使っている表現形式に合わせておくほうが、間違いが少なく、変更にも強い設計になる。
複雑な連携
連携先が複数になったときのWeb API設計について考える。
共通部分と個別対応部分を明確にする
接続相手が複数の場合も、API利用側の目的や都合を理解するところから始める。
APIを次のような3つに分けて検討する。
- コアとなる基本API
どの利用者にも共通するAPI。関心事を小さな単位に分け、登録と参照を別にしたAPIのセットとして提供。 - 拡張API
利用者がより使いやすいようにコアの基本APIを組み合わせた複合API。どの利用者にも共通に使えるものだけを用意。 - 個別対応API
特定の利用者のニーズを満たすAPIの集合。
個別対応のAPIが増え続けたときの対処方法。
- APIを基本/拡張/個別対応にグルーピングする
- それぞれのグループ間でAPIを移動する
複数の利用に同じような個別対応APIを提供していることを見つけたら、共通利用のための複合APIや基本APIに移動する。
基本APIや拡張APIを特定の利用者だけが利用していることに気がついたら、個別対応APIに移動する。
マイクロサービス
1つのアプリケーションとして開発してきた内容を、複数の小さなアプリケーションに分割して、それを連携させるという考え方。
問題は、一度分割してしまうと、組み立て方の変更や、個々のマイクロサービスの修正や改善のコストが大きくなりがちなこと。
アプリケーションの対象業務の理解が不十分で、良い設計について見通しがない段階で、マイクロサービスに分けてしまうと失敗する可能性が高い。
マイクロサービスへの準備として、1つのアプリケーション内のオブジェクト指向の設計の改善が大切。
XML
属性情報をメタ情報も含めて扱いたい場合は、JSONだけでなくXMLも選択肢となる。
- JSON:プログラミング言語の基本データ型だけを使ったデータ交換を意図している
- XML:もっと複雑な情報を表現することを意図している
- 文章の構造を段階的な要素名(タグ名)の体系で表現する
- 要素ごとに、id、description、sequenceなどの属性情報を追加できる
- 文書名/作成者/文書の説明/有効期限など、文書のメタ情報を記述できる
- 要素名や要素の属性を使って、特定の要素を抜き出す操作がやりやすい
非同期メッセージ
連携するアプリケーションが増えてくると、アプリケーション間の接続関係が複雑になり、連携の改善や拡張が難しくなる。
このような問題を解決するためには、Web APIではなく、非同期メッセージングによる連携が有力な選択肢になる。
相手のアプリケーションの稼働状況から独立してメッセージを送ることができる
直接の通信先はメッセージング基盤のため、相手の状況とは無関係にメッセージを送り出せる。
そのため、大量のデータを高速に処理しやすくなる。
また、連携のテストも、メッセージング基盤とのメッセージのやりとりのテストだけで済む。
共通の中間加工がしやすい
メッセージング基盤に変換の仕組みを用意することで、個々のアプリケーションは変換を意識する必要がなくなる。
人間の仕事のやり方に合わせた処理を実現しやすい
人間の仕事のやり方をそのままシステムの処理形態に反映しやすい。
第9章 オブジェクト指向の開発プロセス
不要になるドキュメント
分析/設計/実装/テストのすべての工程を同じチームが一貫して担当する。
それによって、以下のドキュメントが不要になり、ソースコードがその代わりになる。
- 決定事項の記録(確認手段)
- 伝達手段
- 進捗の管理
重要になる活動
- 対面の質疑応答
- ラフスケッチ
- 質疑応答とその記録
更新すべきドキュメント
- 利用者向けのドキュメント
- 画面や帳票
- データベースのテーブル名/カラム名とコメント
全体を俯瞰するドキュメントを作成して共有する
- システム企画書やプロジェクト計画書のシステム概要説明
- プレスリリース
- リリースノート
- 利用者ガイドの導入部
- 営業ツールのキャッチフレーズ
技術方式のドキュメントもソースコードで表現する
- ビルドスクリプト
- テストスクリプト
- 環境構築スクリプト
- 配置スクリプト
非機能要件はテストコードで表現する
- 監視ツールの設定/実行のスクリプト
- 性能テストのコード
- 脆弱性診断
- 認証/認可のテスト
- 擬似的に障害を発生させ自動復旧することを確認するテストコード
分析と設計が一体となった開発のマネジメント
見積もりと契約
【従来】
分析や設計段階:準委任契約
詳細設計と実装:(完成に対して対価を支払う)請負契約が一般的
【オブジェクト指向】
従来の方式で契約を行うことは可能。
ただし、オブジェクト指向の変更容易性をより効率的に活かすには、後半も準委任契約にすべき。
仕様変更や開発の優先順位を変えたいとき、準委任のほうが柔軟に対応できる。
進捗の判断
どの程度分析や設計が進んでいるかはソースコードで判断する。
特にドメインモデルのパッケージや主要なクラスは、どこまで分析が進んでいるかの重要な指標になる。
品質保証
分析/設計/実装を同じ技術者が担当している場合、ソフトウェアの品質の判断を最も簡単で確実に行う方法は、その技術者と会話してみること。
その技術者が、業務の言葉で開発している内容を説明できれば、品質は高いと判断して問題ない。
要因と体制
プログラミングが一定レベルでできることを条件に、業務要件に関心を持つ人間を選び、業務要件をそのままソースコードで表現できる人材を確保し、育成する。
第10章 オブジェクト指向設計の学び方と考え方
既存のコードを改善しながらオブジェクト指向設計を学ぶ
メソッドが長くてクラスが巨大なコードを、短いメソッドや小さいクラスにうまく分解してみる。
このとき、分解前と分解後でどのような違いが生まれるのかしっかり観察する。
リファクタリングのやり方
重複したコード
複数のクラスにコードが重複していたら、次のどちらかを考える。
- 抽出したメソッドをどちらかのクラスだけに置き、他のクラスからそのメソッドを呼び出す
- 抽出したメソッドを置くための新しいクラスを作成する
長いメソッド
一番深いインデントの内容をメソッドに抽出する。
さらに次のインデントをメソッドに抽出する。
巨大なクラス
巨大なクラスの3つのパターンとその改善方法
-
インスタンス変数が多い
- (メソッドが長い場合)いくつかの短いメソッドに分解する
- 一つひとつのメソッドがどのインスタンス変数を使っているか調べる
- 使っているインスタンス変数ごとにメソッドをグループに分ける
- グループごとに別のクラスを作成し、そこにそのグループのインスタンス変数とメソッドを移動する
-
メソッドが巨大なデータクラスを受け取る
データを持つクラスにロジックを移動することを考える。
ロジックを移動しやすくするために、前述の「長いメソッド」で行ったメソッドの抽出を徹底。
移動後は「インスタンス変数が多い」パターンと同様。 -
メソッドがたくさんの引数を受け取る
すべての引数を持つ、引数の入れ物クラスを作る。
その後は「巨大なデータクラスを受け取る」パターンと同様。
組み立てやすい部品に改善する
- 名前を変更する
抽出したメソッドやクラスを他のクラスから利用するときに、名前に違和感があれば変更する。 - クラスにロジックを追加する
抽出したクラスが持つデータを使うロジックが他のクラスに書かれているのを見つけたら、抽出したクラスに追加していく。
そうすることで、抽出したクラスはさまざまな判断/加工/計算をしてくれる部品に成長する。 - 小さなクラスを束ねるクラスを追加する
たくさんの小さなクラスを組み合わせて使う際に、同じような組み合わせパターンを何度も利用していることに気がついたら、その一連のクラスをインスタンス変数に持つ、束ね役のクラスを作る。
その結果、使う側のクラスのコードがシンプルになる。
ちょっと過激なコーディング規則
- 1つのメソッドにつきインデントは1段落までにすること
- else句を使用しないこと
- すべてのプリミティブ型と文字列型をラップすること
- 1行につきドットは1つまでにすること
- 名前を省略しないこと
- すべてのエンティティを小さくすること
- 1つのクラスにつきインスタンス変数は2つまでにすること
- ファーストクラスコレクションを使用すること
- getter、setter、プロパティを使用しないこと
Discussion