はじめに
- こんなことはありませんか?
- どこかのコードを変更すると、別の場所でバグが発生した。
- 変更の影響がありそうな箇所を。あちこち探し回らなければならなくなった。
- コードを読んでいるだけで日が暮れてしまった。
- 簡単んだと思っていた仕様変更やバグ修正に何日も費やしてしまった。
- 開発力を貶め、ソフトウェアの成長を阻害する、設計や実装状の問題、つまり「悪魔」を近くし、退治できるようにするための技術書。
第1章 悪しき構造の弊害を知覚する。
- 導入
- 設計を蔑ろにすることで発生する弊害
- コードを読み解くのに時間がかかる
- バグを埋め込みやすくなる
- 悪しき構造がさらに悪しき構造を誘発する
- 設計を蔑ろにすることで発生する弊害
- 意味不明な命名
- 技術駆動命名や連番命名は意図が全く読み取れない、悪しき手法
- 理解を困難にする条件分岐のネスト
- 深すぎるネストは理解困難
- 様々な悪魔を招きやすいデータクラス
- データの保持だけを目的としたクラスをデータクラスという
- データクラスとそのデータを操作するクラスが別になっている場合がある
- そのようなバラバラの状態を低凝集であるという
- 低業集であると弊害がいろいろとある
- 重複コード
- 修正漏れ
- 可読性低下
- 未初期化状態・生焼けオブジェクト
- 不整地の混入
- 悪魔退治の基本
- 悪しき構造の弊害とオブジェクト指向設計をしることで弊害を退治することができる
第2章 設計の初歩
- 導入
- 変数やメソッドなどの小さな単位の設計について扱う
- 省略せずに意図が伝わる名前を設計する
- 変数を使いまわさない、目的ごとの変数を用意する
- 再代入は読み手の混乱を招くので避ける。
- ベタ書きせず、意味のあるまとまりでメソッド化
- 関係し合うデータとロジックをクラスにまとめる
第3章 クラス設計 -- 全てにつながるい設計の基盤 --
-
導入
- クラスベースのオブジェクト指向設計の基本について扱う
-
クラス単体で正常に動作するよう設計する
- まどろっこしい初期設定をせずとも初めから使える設計にする
- 正しく操作できるメソッドのみを外部に提供する
- 悪魔に負けない、頑強なクラスの構成要素
- 良いクラスの構成要素
- インスタンス変数
- インスタンス変数を不正状態から防御し、正常に操作するメソッド
- すべてのクラスに備わる事故防衛責務
- 単体で安全に利用できる自己防衛責務を全てのクラスが備えるという考えかたがソフトウェア品質を考える上で重要
-
成熟したクラスへ成長させる設計術
- コンストラクタで確実に正常値を設計する
- 初期化時以外でインスタンス変数に値を代入しないと使えないクラスは生焼けオブジェクト
- 生焼けオブジェクトを防ぐには、「クラスのインスタンスを生成する時点で、インスタンス変数に正常値が確実に設定されている状態」にする
- コンストラクタに不整地を渡せないように、バリデーションチェックも忘れずに
- 処理の対象外となる条件を先頭で定義する方法をガード節という
- 計算ロジックをデータ保持側に寄せる
- 不変で思わぬ動作を防ぐ
- 新スタンス変数の上書きは、理解を難しくする。
- インスタンス変数を不変(イミュータブル)にする
- 変更したい場合は新しいインスタンスを作成する
- インスタンス変数を変更したい場合は新しいインスタンスを生成するようにする
- add() return new Money(addedAmount) のようなメソッドを用意しておく
- メソッド引数やローカル変数にも final を付け不変にする
- void doSomething(final int amount) のように引数もイミュータブルにする
- 「値の渡し間違い」を型で防止する
- 数値だからといって、考えなしに全て int 型では、チケットの枚数と間違えてチケットの金額を渡してしまうなどのミスが発生する。
- 金額なら Money 型、枚数なら TicketCount 型などといった具合に型を使って値の渡し間違いを防ぐ
- 現実の営みにはないメソッドを追加しないこと
- 金額同士の乗算のような、現実にないメソッドはバグの原因になる
- コンストラクタで確実に正常値を設計する
-
悪魔退治の効果を検証する
- クラス設計とは、インスタンス変数を不正状態に陥らせないための仕組みづくりと言っても過言ではない
- 密接に関係し合うロジックが一箇所にぎゅっと集まっている構造を高凝集と呼ぶ
- データとそのデータを操作するロジックを一つのクラスにまとめ、必要なメソッドのみを外部へ公開することをカプセル化という
-
プログラム構造の問題解決に役立つ設計パターン
- プログラム構造を改善する設計手法を設計パターン(デザインパターン)という
- 設計パターンの例
- 完全コンストラク: 不正状態から防護する
- 値オブジェクト: 特定の値に関するロジックを高凝集にする
- ストラテジ: 条件分岐を削減し、ロジックを単純化する
- ポリシー: 条件分岐を単純化したり、カスタマイズできるようにする
- ファーストクラスコレクション: 値オブジェクトの亜種で、コレクションに関するロジックを高凝集にする
- スプラウトクラス: 既存のロジックを変更せずに安全に新機能を追加する
- 完全コンストラクタ
- 不正状態から防護するための設計パターン
- インスタンス変数を全て初期化するコンストラクタ
- コンストラクタ内でガード節を使って不正状態を防ぐ
- 値オブジェクト
- 値をクラスとして表現する設計パターン
- 値にもいろいろある。それぞれのロジックを高凝集にする効果がある
- 値オブジェクトと完全コンストラクタはオブジェクト指向設計の基本形を体現している構造の一つ
第4章 不変の活用 -- 安定動作を構築する --
- 導入
- 変更可能なことを可変(ミュータブル)、不可能なことを不変(イミュータブル)という
- 再代入
- 変数に再度値を代入することを再代入、または、破壊的代入という
- 再代入はそのタイミングを追うのが難しくなる
- 別の変数を用意するべき
- 不変にして再代入を防ぐ
- final を付与する
- 引数も不変にする
- 引数にも void doSomething(final int amount) のように fainal を付与する
- 可変がもたらす意図せぬ影響
- 可変インスタンスの使い回し
- 意図せずに変更してしまうことがある
- 関数による可変インスタンスの操作
- 副作用のデメリット
- 関数の主作用: 関数が引数を受け取り、値を返すこと
- 関数の副作用: 主作用以外に状態変更すること
- 副作用の例
- インスタンス変数の変更
- グローバル変数の変更
- 引数の変更
- ファイルの読み書きなどの I/O 操作
- 副作用が予期せぬタイミングで働いてしまう場合がある
- 関数の影響範囲を限定する
- 副作用を抑えるため、関数が影響を受ける・与える範囲を限定するのが確実
- 関数を次のように設計する
- データ、つまり状態を引数で受け取る
- 状態を変更しない
- 値は関数の戻り値として返す
- オブジェクト指向では、副作用のない関数を厳密に作り込むのではなく、クラスのスコープ内で影響を閉じ込めるスタイルが一般的
- 不変にして予期せぬ動作を防ぐ
- 可変インスタンスの使い回し
- 可変と不変の取り扱い方針
- 可変と不変をどのように扱っていけばいいのか?
- デフォルトは不変に
- 不変にすることによるメリット
- 変数の意味が変化しなくなるので、混乱が抑えられる
- 挙動が安定し、結果を予測しやすくなる
- コードの影響範囲が限定的になり、保守が用意になる
- 間違った使い方ができない構造をフールプルーフという
- 不変にすることによるメリット
- どんなとき可変にしてよいか
- インスタンスを生む処理がパフォーマンスの面で問題なら可変もありえる
- スコープが局所的な場合も可変がありえる
- 正しく状態変更するメソッドを設計する
- 可変にする場合は、正しく状態変更できるつくりにする
- 状態変更メソッドをミューテーターという
- コード外とのやりとりは局所化する
- どれだけ不変を意識しても、I/O 操作や DB 操作など、コード外の状態に依存する場合はある
- なので、あまり考えずに外の状態に依存するコードを書くと、挙動の予測が困難になる
- 影響を最小限にするための局所化の設計手法としてリポジトリパターンが人気
- リポジトリパターン: データベースの永続化処理をカプセル化する設計パターン
第 5 章 低凝集 -- バラバラになったモノたち --
-
導入
- 高凝集は変更に強い、望ましい設計
- 低凝集は壊れやすく変更が困難
-
static メソッドの誤用
- static メソッドはインスタンス変数を使えない
- static メソッドはインスタンス変数を扱えない時点でデータとデータを扱うロジックが乖離する
- インスタンス変数を使う構造につくり変える
- インスタンスメソッドのフリした static メソッドに注意
- static キーワードがついていないだけで、static メソッドと同じ問題をかけているインスタンスメソッドもよくある
- 試しに、メソッドに static を付与して、エラーが派生しなければそれは実質 static メソッドだ
- どうして static メソッドが使われてしまうのか
- 手続き型言語の名残り
- どういうとき static メソッドを使えばいいのか
- ファクトリメソッドとして static メソッドを用いるのがよい
- static メソッドはインスタンス変数を使えない
-
初期化ロジックの分散
- 本当にコンストラクタを公開してよいのか吟味する
- コンストラクタを公開すると、様々な用途であちこちで使われてしまう
- 用途を限定したいならば、コンストラクタを private にして、static な目的別のファクトリメソッドを用意する
- private コンストラクタ + ファクトリメソッドで目的別初期化
- 生成ロジックが増えすぎたらファクトリクラスを検討すること
- 複数の生成パターンがあり、使い分けが必要(通常ユーザー、プレミアムユーザーなど)
-
共通処理クラス(Common・Util)
- 同じような処理が多数描かれそうなとき、再利用できるよう共通処理を実装した共通クラスがつくられることがある。
- このとき、共通処理用のメソッドは static メソッドとして実装されがち
- Common.calcAmoutIncludingTax(amount) のようなもの
- これもまた低凝集である
-
さまざまなロジックが雑多に置かれがち
- なんの関連性もないその他のロジックの置き場になってしまう場合がある
-
オブジェクト指向設計の基本に立ち返ろう
- 共通処理クラスを安易に作らないようにしよう
-
横断的関心事
- 横断的関心事は共通処理クラスにまとめ上げていい
- 横断的関心事: 様々なユースケースに広く横断する事柄
- ログ出力
- エラー検出
- デバッグ
- 例外処理
- キャッシュ
- 同期処理
- 分散処理
-
結果を返すために引数を使わないこと
- 引数はあくまで入力値
- メソッドの中で引数として受け取ったものを再代入したり、出力に用いたりしない
- 低凝集の原因になる
-
多すぎる引数
- 引数が多すぎると低凝集になりがち
- 引数が多すぎると、引数の渡しミスが発生しやすい
-
プリミティブ型執着
- プリミティブ型を濫用したコードをプリミティブ型執着という
- 例えば、プリミティブ型を使用すると同様のバリデーションが重複してしまう price < 0 など
- 値とそれを操作するためのメソッドが存在する以上、セットにしておくのが適切
- 割引料金、定価、割引率なども、それぞれ一つずつクラス化する
-
意味のある単位ごとにクラス化する
- 引数が多すぎる事態に陥らないためには、概念的に意味のあるクラスをつくることが肝要
- 引数が多い場合は、データを引数として扱うのではなく、その値をインスタンス変数としてもつクラスにできないか検討するとよい
-
メソッドチェイン
- 長すぎるメソッドチェインに値を代入するようなケースも低凝集に陥りやすい例
- party.members[memberId].equipments.armor = newArmor; のようなコード
- デメテルの法則: 「最小知識の原則」とも呼ばれる、「オブジェクトは、直接の友人とだけ会話すべき」という考え方
- 尋ねるな、命じろ
-
Don't ask, tell(訪ねるな、命じろ): オブジェクトの内部状態を外から取得して判断するのではなく、オブジェクトに直接処理を命じるべき
-
悪い例(外部で計算)
// 外部で計算(尋ねて判断) const product = new Product(1000, 0.2); const price = product.getPrice(); const rate = product.getDiscountRate(); const finalPrice = price * (1 - rate); // 外部で計算ロジックを実装
- 良い例(オブジェクトに命じる)
// 商品に計算を命じるだけ const product = new Product(1000, 0.2); const finalPrice = product.calculateDiscountedPrice();
第 6 章 条件分岐 -- 迷宮かした分岐処理を解きほぐす技法 --
-
条件分岐のネストによる可読性低下
- 早期 return でネスト解消
- 早期 return: 条件を満たしていない場合に、直ちに return すること
- 早期 return に変更するには、元の条件を反転させる
- 早期 return はガード節の考え方が元になっている
- 見通しを悪くする else 句も早期 return で解決
- else 句も、見通しを悪くする要因の一つ
- 早期 return でネスト解消
-
switch 文の重複
-
即座に switch 文を書いてしまう
-
同じ条件式の switch 文が複数かかれていく
-
使用変更時の修正漏れ(case 文追加漏れ)
-
爆発的に増殖する switch 文の重複
- 大量に分岐していくものを switch 文で処理しようとするとミスが発生する
-
条件分岐を一箇所にまとめる
- switch 文の重複コードを解消するには、単一責任の原則の考え方が重要
-
よりスマートに switch 文重複を解消する interface
- interface は異なる型を同じ型として利用できるようにするもの
-
interfafce を switch 重複文に応用(ストラテジパターン)
- interface Shape に area()メソッドを定義して、Circle や Rectangle でもこれを継承することにより、それぞれの area()メソッドを定義することができる
- Map の中から、MagicType に対応する Magic を取得することで魔法を切り替える手法もいい考え
- ストラテジパターン: interface を用いて処理を一成に切り替える設計
- interface を implements した場合、変数やメソッドのオーバーライドを忘れることがなくなる
-
-
条件分岐の重複とネスト
- 条件分岐の重複とネストは、コードの可読性を低下させる
- interface は多重ネストの解消にも役立つ
- ポリシーパターンで条件を集約する
- ポリシーパターン: 「同じような処理だけど、状況によって少しずつ異なる振る舞いが必要な場合に、その振る舞いを別々のクラスとして実装し、実行時に切り替えられるようにするデザインパターン」
- 例えば:
- 会員ランクによって異なる割引率の計算
- ユーザーの状態によって異なる通知方法
- 支払い方法による異なる決済処理
- など、「同じような処理だけど、条件によって少し違う」というケースで使用します。
-
型チェックで分岐しないこと
- わざわざス interface を実装しているのに、if (hotelRates instanceof RegularRates) のように型チェックによる条件分岐をするのは悪手
- ストラテジパターンでは、型チェックせずに、共通項を利用できるのがメリット。それを損なっている
- リスコフの置換原則にも反している
- リスコフの置換原則: 親クラスが使われている場所では、そのサブクラス(子クラス)で置き換えても、プログラムが正しく動作すべき」という原則
-
interface の使いこなしが中級者への第一歩
- interface を使いこなせるかが、設計スキルの分水嶺といっても過言ではない
- 分岐を書きそうになったら、まず interface 設計!
-
フラグ引数
- メソッドの機能を切り替えるための boolean 値をフラグ引数という
- フラグ引数は可読性を低下させる
- boolean 型以外、int 型等であっても、同様のものは可読性を損なう
- メソッドを分離する
- フラグ引数付きメソッドは、内部に複数の機能を持ち、フラグで切り替える構造になっている
- 単機能になるように、メソッドを分離するとよい
- 分離したメソッドに相応しい命名をすることで可読性が向上する
- 切り替え機構をストラテジパターンで実現する
- 分離したメソッドを boolean 型で条件分岐して使用してしまっては意味がない
- ストラテジパターンで切り替える
第 7 章 コレクション -- ネストを解消する構造化技法 --
-
わざわざ自前でコレクション処理を実装してしまう
- List などのコレクション処理には、便利なメソッドが行順ライブラリとして収録されている
- for や if を使いそうになったら、まずは標準ライブラリから探してみるといい
-
ループ処理中の条件分岐ネスト
- for 文の中に if 文が何重にもネストされているようなものを改善したい
-
早期 continue で条件分岐のネストを解消する
- 早期 continue: 条件を満たしていない場合に、直ちに continue すること
- 早期 continue によりネスト解消される
-
早期 break もネスト解消に役立つ
- 早期 break: 条件を満たしている場合に、直ちに break すること
-
低凝集なコレクション処理
- コレクション処理も低凝集に陥りやすい
- コレクションを操作する処理は、アプリケーションの複数の部分で必要とされるため、ついつい個別に実装されてしまい、コードが分散してしまう傾向がある
- コレクション処理をカプセル化する
- ファーストクラスコレクション: コレクションに関するロジックをカプセル化する設計パターン
- ファーストクラスコレクションは以下の要素を備える
- コレクション型インスタンス
- コレクション型インスタンス変数を不正状態から防御し、正常に操作するメソッド
- 外部へ渡す場合はコレクションを変更できなくする
- ファーストクラスコレクションからメソッドを通じてインスタンス変数を外部に渡す際には、return members.unmodifiableList*(); としてイミュータブルなものを渡す
第 8 章 密結合 -- 絡まって解きほぐせない構造 --
- 導入
- 結合度: モジュール間の、依存の度合いを表す指標
- 密結合: あるクラスが、他の多くのクラスに依存している構造
- 密結合と責務
- 発生する様々なバグ
- ロジックの起き場所がちぐはぐ
- 責務が考慮されていないクラス設計
- 特定のクラスが責務以上の処理を引き受けてしまっている
- 他のクラスが行うべきバリデーションを引き受けてしまっている
- 同じ名称のメソッドが複数のクラスに存在する
- 継承関係のクラスにおいて、オーバーライドにより、本来するべきでないロジックの流用を行なっている。
- 責務が考慮されていないクラス設計
- 単一責任の原則
- ソフトウェアにおける責任: ある関心事について、不正な動作にならないよう正常に動作するよう制御する責任
- 単一責任の原則: クラスが担う責任は、たったひとつに限定すべきとする設計原則
- 単一責任の原則違反で生まれる悪魔
- 単一責任の原則の未履行が過保護なクラスと未熟なクラスを生み、さらに重複コードを生む。
- 責任が単一になるようクラスを設計する
- 例: 商品の定価クラスを親とし、それを通常割引価格とかき割引価格がそれぞれ別に継承する。
- 関心事それぞれが分離、独立している構造を疎結合という
- DRY の原則の誤用
- DRY の原則: 全ての知識はシステム内において、単一、かつ明確な、そして信頼できる表現になっていなければならない。
- 同じようなロジック、にているロジックであっても、概念が違えば DRY にすべきではない。
- DRY にしすぎると密結合になり、単一責任原則を遵守できなくなる。
- 密結合の各種事例と対処方法
- 継承に絡む密結合
- 本書のスタンス: 継承はよっぽど注意して扱わないと危険、継承は推奨しない。
- スーパークラス依存: サブクラスはスーパークラスの変更の影響をもろに受けるので、常に気にしなければならない。
- 継承より委譲
- 委譲とは、コンポジション構造にすること
- 委譲: スーパークラスを継承するのではなく、private なインスタンス変数として持ち、呼び出せるようにすること
- 関連知識が基底クラスと継承クラスに分散してしまう
- インスタンス変数ごとにクラス分割可能なロジック
- インスタンス変数とそれを用いたメソッドのセットが他のセットと依存関係にないのなら、クラスを分離するべき
- 影響スケッチ: インスタンス変数やメソッドがそれぞれ何に関係付けられているのか把握するための図
- 影響スケッチ描画ツール: Jig
- なんでも public で密結合
- public: すべてのクラスからアクセス可能
- protected: 同じクラス、または継承クラスからアクセス可能
- なし: 同じパッケージからのみアクセス可能
- private: 同じクラスからのみアクセス可能
- privare メソッドだらけ
- 高凝集の誤解からくる密結合
- 高凝集を意図して強く関係していそうなロジックを一箇所にまとめあげようとしたものの、結果として見つけつ行に陥っているケースは非常に多く見られる。誰もが極めて陥りやすい罠。
- 疎結合高凝集を意識しなければならない。
- 疎結合高凝集: モジュール(クラス)の中は密接に関連した機能をまとめ(高凝集)、モジュール間の依存関係は最小限に抑える(疎結合)設計原則
- スマート UI
- スマート UI: 表示関連のクラスの中に、表示遺体の責務のロジックが実装されている構造
- スマート UI は表示責務と表示以外の計算等の責務が密結合になっているせいで、変更が難しくなる。
- 責務毎にグラスを分割すべき。
- 巨大データクラス
- 巨大データクラス: 大量のインスタンス変数を保持するクラス
- 巨大データクラスの持つデータがグローバル変数の性質を帯びてしまうことがあるので注意。
- トランザクションスクリプトパターン
- トランザクションスクリプトパターン: メソッド内に一連の処理手順がダラダラと長く書き連ねられている構造
- 神クラス
- 神クラス: 1 クラス内に何千何万行ものロジックを持ち、あらゆる責務のロジックが乱雑に絡み合うようにかき殴られているようなクラス
- 大変扱いにくいものになってしまう
- 密結合クラスの対処法
- 巨大データクラスもトランザクションスクリプトパターンも神クラスも、密結合なクラスの対処方法はどれも同じ。
- オブジェクト指向設計と単一責任の原則に基づき、丁寧に設計すること。
- 継承に絡む密結合
第 9 章 設計の健全性をそこなうさまざまな悪魔たち
- デッドコード
- デッドコード: どんな条件であっても決して実行されないコード
- 到達不能コードともいう
- 発見次第すぐに削除する
- YAGUNI 原則
- You aren't going to need it. -- 必要ないでしょう。
- YAGUNI 原則: 実際に必要になっとときに実装せよ。先回りして必要のないロジックを実装しない。
- マジックナンバー
- マジックナンバー: ロジックないに直接書き込まれている意味不明な数値
- マジックナンバーにならないよう、定数として定義する。
- 文字列型執着
- 文字列型執着: String title = "タイトル, 255, 250, 240, 64"のようなもの
- 扱い方が複雑になるので避ける
- グローバル変数
- グローバル変数: どこからでもアクセス可能な変数。public static などで宣言される。
- 影響範囲を最小化するよう設計すること
- 呼び出し箇所が少なく、局所化されているほど、ロジックの理解が容易になる。
- 無関係なロジックからはアクセスできないように設計する
- null 問題
- null を使いがちだが、
- 本来 null とは、「参照が何も指していない状態」または「参照の欠如」を表す特別な値であり、「空」や「未設定」とは異なる概念。
- null を返さない、渡さない
- null チェックだらけにならないようにするためにも、そもそも null を取り扱わない設計にすることが大切
- null 安全
- null 安全: null が原因のエラーを発生させない仕組み
- null 非許容型: null を許容しない型
- 例外の握りつぶし
- 例外の握りつぶし: 例外が発生しても何も対処せず、エラーを無視してしまうこと。特に空の catch ブロックを作ることで、エラーの発生を隠蔽してしまう行為
- 原因分析困難に陥り開発者を疲弊させる
- 問題検出時にけたたましく叫ばせる
- 設計秩序を破壊するメタプログラミング
- メタプログラミング: プログラム実行時にそのプログラム構造自体を制御するプログラミング
- リフレクションによるクラス構造および値の変更
- リフレクションを使うと final で不変にした変数を可変にすることができてしまう
- 型の強みを活かせなくなるハードコード
- リフレクションを使うと、クラス名を Stringto として渡すことでクラスを生成することができる。
- しかし、クラス名を変更してしまえば、その処理は機能しなくなる。
- 名前によるクラスの生成は、IDE がクラスの生成として見つけることもできない。
- デメリットを理解しようとを限定すること
- メタプログラミングは黒魔術
- 技術駆動パッケージング
- 技術駆動パッケージング: 設計パターンなど、構造的ににているもの同志でフォルダ分け、パッケージ分けなどをすること
- ビジネス概念として強く関係試合もの同志が一緒になるようにフォルダ分けをしなければならない
- サンプルコードのコピペ
- サンプルコードには保守性や変更用意性まで考えて書かれていない
- 銀の弾丸
- 現実のプロジェクトで発生する問題は特定の手法だけで解決可能なほど単純でない
- 銀の弾丸や特攻薬のようなものは求めないようにする
第 10 章 名前設計 -- あるべき構造を見破る名前 --
- 導入
- 目的駆動名前設計: コードの名前(クラス名、メソッド名、変数名)を、その目的や意図が明確に伝わるように設計すること
- 悪魔を呼び寄せる名前
- 関心の分離
- 関心の分離: 関心事それぞれのクラスへの分割が必要
- 関心事にふさわしい命名
- 商品ではなく、予約品、注文品、在庫品、発送品 など、関心事にふさわしい命名をする
- クラス毎の影響範囲が低減し、開発効率が上がる
- 大雑把で意味が不明瞭な名前
- 開発初期に決めた名前は大雑把であることがおおい
- 目的不明オブジェクト: 名前が大雑把で目的がわからないオブジェクト。
- 関心の分離
- 名前を設計する -- 目的駆動名前設計
- プログラミングにおける名前の役割は、可読性を高めることだけではない
- 目的駆動名前設計のポイント
- 可能な限り具体的で、意味範囲が狭い、特化した名前を選ぶ
- 存在ベースではなく、目的ベースで名前を考える
- どんな関心事があるか分析する
- 声に出して話してみる
- 利用規約を読んでみる
- 違う名前に置き換えられないか検討する
- 疎結合高凝集になっているか点検する
- 可能な限り具体的で、意味範囲が狭い、目的に特化した名前を選ぶ
- 極めて意味範囲の狭い名前をクラスに付与し、ビジネス目的に特化することで得られる効果 - 名前とは無関係なロジックを排除しやすくなる - クラスが小さくなる - 関係するクラスの個数が小さくなる。結合度が低減する。 - 関係クラスの個数が少ないので、使用変更時に考慮を要する影響範囲が小さく済む。 - 目的に特化した名前なので、どこを変更すれば良いかすぐ探し出せる。 - 開発生産性が向上する。 - 存在ベースではなく、目的ベースで名前を考える。
- 目的ベースで考えた命名の例
- 住所: 発送元、発送先、勤務先、本籍地
- ユーザー名: アカウント名、表示名、本名、法人名
- 目的ベースで考えた命名の例
- どんなビジネス目的があるのか分析する
- 声に出して話してみる
- ラバーダッキング: 問題に直面したとき、そのことを誰かに話すことによって自ら原因に気づき、自己解決する手法
- 利用規約を読んでみる
- サービスの利用規約には厳密な言葉が使われている。
- 利用規約からクラス名のヒントが得られることがある。
- 違う名前に置き換えられないか検討する
- 「顧客」でも広い。「宿泊客」と「支払い者」に分けるなど。
- 疎結合高凝集になっているか点検する
- 低凝集や密結合になっているなら、もっと狭い意味の、特化した名前を探してみる。
- 設計時の注意すべきリスク
- 名前無頓着になるな
- チーム開発においては、命名が重要であり、名前とロジックが対応する前提であること、名前がプログラム構造を大きく左右することをチームで約束すること
- 使用変更時の「意味範囲の変化」に警戒
- 名前と中身は連動している。
- 一方の仕様が変わるなら、もう一方についても検討するべき
- 会話には登場するのにコード上に登場しない名前に注意
- 会話に登場する重要な概念が、ソースコード上で名前も付けられず、雑多なロジックの中に埋没していることが本当に頻繁に見受けらる。
- その概念をソースコード上から見つけるのが大変になってしまう
- 形容詞で区別が必要なときはクラス化のチャンス
- 違いの見分けが難しいコードを、口頭でひたすら形容詞をつけて同僚に説明する状況がある。
- 「元々の〜」「補正された〜」などの形容詞を用いて、説明する必要があるのなら、それはクラス化する必要がある
- 要注意感イン、新品価格、中古価格、シニア料金用、平日のシニア料金用 など
- 名前無頓着になるな
- 意図がわからない名前
- 命名で陥りがちなさまざまな悪しきケース
- 技術駆動命名
- 技術駆動命名: プログラミング用語やコンピューター用語由来の命名
- MemotyStateManager, changeIntValue01 など
- ロジック構造をなぞった名前
- isMemberHpMoreThanZeroAndIsMenberCanAct のようなもの
- 意図、目的がわかるよう命名しよう
- 驚き最小の原則
- 驚き最小の原則: 使う側が想像した通りに利用できるよう、ロジックと名前を対応付ける設計が大事
- 構造を大きく歪ませてしまう名前
- データクラスに陥る名前
- ~Data, ~Ingo のような命名はデータクラスを生んでしまう。避けよう。
- Data Transfer Object (DTO) : 「データを運ぶだけの単純なオブジェクト」は認められる。
- クラスが巨大化する名前
- ~Manager, ~Service, ~Controller のような命名は、クラスが巨大化する原因になる。
- より厳密な命名をする
- 状況によって意味や扱いが異なる名前
- アカウント: 金融業界では「銀行口座」、コンピューターセキュリティでは「ログイン権限」
- 状況により意味合いが異なる
- コンテキスト(状況)が違う場合についてはクラスを分けよう。
- Car でも配送局面と販売局面でコンテキストが全く違うのでクラスを分ける。
- 連番命名
- 連番命名: クラスやメソッドに番号をつけて命名すること
- 連番命名はトランザクションスクリプトパターンに容易に陥らせてしまう
- トランザクションスクリプトパターン: メソッド内に一連の処理手順がダラダラと長く書き連ねられている構造
- 名前的に居場所が不自然なメソッド
- 「動詞 + 目的後」のメソッド名に注意
- addItemToParty のような「動詞 + 目的後」の命名は不適切なクラスへの実装だからこそ起こりやすい。
- 可能な限り同志1語で済む名前にする
- 「動詞 + 目的後」のメソッドは、目的後の概念を表現するクラスをつくり、そこに動詞1語のメソッドとして実装する。
- addItemToParty のようなメソッドは、PartyItenms クラスに add()として実装する。
- 不適切な居場所の boolean メソッド
- boolean 型を返すメソッドは責務外のクラスに実装されがち
- クラス名 is 状態. にして適切な文章になれば OK
- Member is in confusion. → Menber クラスに isConfusion()メソッドを実装する
- 「動詞 + 目的後」のメソッド名に注意
- 名前の省略
- 名前の省略には注意が必要
- 意図がわからなくなる省略
- 長い名前が嫌なため、省略した名前が書かれてしまう → 意図がわからなくなる
- 基本的に名前は省略しないこと
- エディタの保管機能があるから大丈夫。省略せずに書こう。
- そのほか省略をどう判断するか
- 可能な限り省略せ雨zに意図を伝える命名が望ましい
第 11 章 コメント -- 保守と変更の正確性を高める書き方 --
- 導入
- 理解を促し、保守や変更の正確性を高めるコメントとは
- 退化コメント
- 退化コメント: 情報が古くなり実装を正しく説明しなくなったコメント
- コメントは書き手の意思の劣化コピーにすぎないことを理解すること
- ロジックの挙動をなぞるだけのコメントは退化しやすい
- ロジックの挙動をなぞるだけのコメントは理解にさほど貢献しない上に、逆に偽情報が紛れ込んで害をなす可能性があり、役立たない。
- コメントで命名をごまかす
- ダメなメソッドに対してコメントで補足説明が必要になったときには、メソッド名のブラッシュアップを考えよう。
- 意図や仕様変更時の注意点を読み手に伝えること
- コードは保守と使用変更時に読まれる
- コード保守の際、読み手が気にすること「このロジックはどういう意図で動いているのか」
- 仕様変国の際、読み手が気にすること「何に注意すれば安全に変更できるか」
- コメントのルール まとめ
- コメントのルールまとめ
ルール 理由 ロジック変更時、同時に必ずコメントも変更すること。 コメントを変更しないと、ロジックと乖離した「退化コメント」が生じ、読み手が混乱するため。 ロジックの内容をなぞるだけのコメントをしないこと。 あまり可読性に貢献しない上、コメントのメンテナンスが大変になるため。退化コメントも発生しやすい。 可読性の悪いロジックを補足説明するようなコメントをしないこと。代わりにロジックの可読性を高めること。 コメントのメンテナンスが大変になるため。退化コメントも発生しやすい。 ロジックの意図や仕様変更時の注意点をコメントすること。 保守や仕様変更時の助けになる。
- コメントのルールまとめ
- ドキュメントコメント
- 積極的に使おう!コードの保守に特に役立つ。
第 12 章 メソッド(関数) -- 良きクラスには良きメソッドあり --
- 必ず自身のクラスのインスタンス変数を使うこと
- 他のクラスのインスタンス変数を変更するメソッド構造にしてはだめ。低凝集に陥る。
- 不変をベースに予期せぬ動作を防ぐ関数にすること
- 可変なインスタンス変数などを変更するメソッドは意図せず別の箇所に影響を及ぼし、予測しない動作が生じる場合がある。
- 訪ねるな、命じろ
- 「よそのクラスを気にしたりいじったりするメソッド構造」は低凝集構造
- シンプルな setter/getter でインスタンス変数を返し、呼び出し側で複雑な処理をさせるのではなく
- 呼び出されるメソッド側で複雑な処理をしたうえで、結果を返すようにする。
- コマンド・クエリ分離
- int gainAndGetPoint() のような変更と取得を同時に行うメソッドは混乱をもたらしやすい
- コマンド・クエリ分離: メソッドをコマンド(変更)とクエリ(取得)に分ける
- 引数
1。引数は不変にすること- 引数には final を付与する
- 引数を変更したい場合は、不変なローカル変数を容易し、そのローカル変数に変更値を代入する
- フラグ引数は使わない
- ストラテジパターンなど別の機能切り替え手法を使う
- null を渡さない
- null を前提としたロジックを実装しない
- 出力引数は渡さない
- 入力値である引数を出力に用いない
- 引数は可能な限り少なく
- 戻り値
- 「型」を使って戻り値の意図を表明すること
- int add() と Price add() とでは戻り値の明瞭性が違う。後者がわかりやすい。
- null を返さない
- エラーは戻り値で返さない、例外をスローすること
- エラーを特殊な値で知らせるのではない
- エラーは例外をスローして知らせる
- 「型」を使って戻り値の意図を表明すること
第 13 章 モデリング -- クラス設計の土台 --
- モデル: 動作原理やしくみを簡単に理解・説明するために、物事の解く量や関係性を図式化したもの
- モデリング: モデルを作ること
- 邪悪な構造に陥りがちな User クラス
- 例えば、1つのデータクラスを2つの~Manager クラスで管理するようなケースはだめ
- モデリングの考え方とあるべき構造
- システムとは何か
- システム: 目的達成のための手段
- システム構造とモデリング
- モデルとは特定の目的達成のために最低限考慮が必要な要素を備えたもの
- ソフトウェア設計におけるモデリング
- モデルを目的別に分けて書くようにすると、クラスの分かれ目に気づくことができる
- システムとは何か
- よくないモデルの問題点と解決方法
- 複数の目的のために無理やり利用されているような構造はだめ
- 一貫性がないといえる
- User とシステムの関係
- よく考えると、User はシステムの外にいる
- 仮想世界を表現する情報システム
- 情報システムとは、現実世界の概念のみをコンピューターの世界へ東映した仮想現実である
- 目的別にモデリングする
- モデルは「特定の目的達成のために最低限考慮が必要なようをを備えたもの」
- 目的に応じて形態が変わってくる
- 現実世界での物理的な存在と、情報システム上のモデルが 1:1 になるとは限らず、1:多の関係になるケースがあることが大きな解く量
- User が個人アカウント、法人アカウント、プロフィール、会社概要、職務経歴 と分かれる
- そもそも User という名前自体の意味が広い
- モデルはモノではなく目的達成手段
- PersonalAccout は個人認証手段、Profile は特徴表現手段
- 目的駆動で名前設計することが、適切に目的達成するモデルを設計することにつながっている
- 単一責任とは単一目的
- 単一責任の原則とは、単一目的の原則である
- クラスが果たす目的は、たった一つに限定すべき
- 特定の目的に特化して設計することで、変更につよい高品質な構造になる
- システムはなんらかの目的を達成するためにつくられるのであり、責務よりも目的が先にくる
- モデルの見直し方
- モデルにいびつさ、不自然さ、一貫性のなさを感じた場合には以下を検討する
- そのモデルが達成しようとしている目的を全て洗い出す
- 目的それぞれ特化したモデリングをし直す
- 目的駆動名前設計にもとづき、モデルに命名する
- モデルに目的外の要素が入りこんでいる場合、さらに見直す
- モデルにいびつさ、不自然さ、一貫性のなさを感じた場合には以下を検討する
- モデルと実装は必ず相互にフィードバックする
- モデルをクラスやコードに精緻化していく過程で見落としに気づく場合がある。そのときは必ずモデルにフィードバックする
- モデルと実施のクラスやコードが乖離してはいけない
- 機能性を左右するモデリング
- 機能性: 顧客のニーズを満たす度合い
- 裏に隠れた真の目的を見破る
- 商品購入 ではなく 売買契約 のように、利用規約や法の側面から考えることも必要
- でないと、法的な有効性が発揮されず、機能性が発揮されない
- 機能性をイノベートする「深いモデル』
- モデルは何らかの達成手段として考えるものである。
- 栄養摂取手段、移動手段、情報拡散手段...
- 深いモデル: 本質的課題を解決し、機能性の革新に貢献するモデル
- 世の中の優れた仕組みは優れた変換能力を有している
第 14 章 リファクタリング -- 既存コードを成長に導く技 --
- リファクタリングの流れ
- リファクタリング: 外から見た挙動を変えずに、構造を精里すること
- ネストを解消し、見通しを良くする
- 早期 return を使うだけでネストが解消され、見やすくなる
- 意味のある単位にロジックをまとめる
- 条件チェックブロックと値への代入ブロックに分ける
- 条件を読みやすくする
- !customer.isEnabled()のような否定条件は読みにくいので、customer.isDisabled()で代替する
- ベタ書きロジックを目的を表すメソッドに置き換える
- if(customer.possessionPoint.amout < comic.currentPurchasePoint.amount) を if(customer.isSortOfPoint(comic)) に置き換える
- ユニットテストでリファクタリングのミスを防ぐ
- ユニットテスト: 小さな機能単位で動作検証するテストの総称
- リファクタリングにはユニットテストが必須!
- コードの課題を精里する
- ネストを解消し、見やすくする
- テストコードを用いたリファクタリングの流れ
- テストコードを用いたリファクタリング方法
- あるべき構造の雛形クラスをある程度つくる
- ひな型クラスに対してテストコードを書く
- テストを失敗させる
- テストを成功させるための最低限のコードを書く
- ひな型クラス内部でリファクタリング対象のコードを呼び出す
- テストが成功するよう、あるべき構造へロジックを少しずつリファクタしていく
- テストコードを用いたリファクタリング方法
- あやふやな仕様を理解するための分析方法
- 仕様がわかっているという前提なら、テストを書いた上でリファクタリングもできる。
- 実際の開発では、使用がわからない場合もある。
- 書籍「レガシーコード改善ガイド」にはそんな場合の手法が書かれている。
- 仕様分析方法1: 仕様化テスト
- 仕様化テスト: テストコードを書くことで、仕様を分析する手法
- とりあえず、仕様不明のメソッドに引数を入れてテストしてみる。出力が少しわかる。
- テストを追加していくことによって、仕様不明のメソッドの仕様を分析する。
- 実際には、仕様化テストだけで全ての仕様を分析するのは難しい。
- その他のコンテキストも含めて仕様を分析する。
- 仕様分析方法 2: 試行リファクタリング
- 実際のプロダクションコードは、もっと複雑怪奇
- 試行リファクタリング: ロジックの意味や濃い有象を分析するためにお試しでリファクタリングすること
- まずは対象のコードをリポジトリからチェックアウトする
- テストコードを書かずに、プロダクションコードをどんどんリファクタしていく
- コードが精里されて見通しがよくなることによって以下の利点が得られる
- 可読性があがり、ロジックの仕様理解が進む
- あるべき構造が見えてくる。どの範囲をメソッドやクラスに切り出せばよいかが見えてくる。つまり本番リファクタリングのゴールが見えてくる
- 無駄なコードが見えてくる
- どのようにテストコードを書けばよいかが見えてくる
- 分析用のロジック変更なので、マージせず、破棄する
- IDE のリファクタリング機能
- リネーム(名前の変更)
- IDE の右クリック → Refactor → Rename でリネーム&一斉置き換えできる
- メソッド抽出 - IDE の右クリック → Refactor → Extract Method でメソッド抽出できる
- リネーム(名前の変更)
- リファクタリングで注意すべきこと
- 機能追加とリファクタリングを同時にやらない
- 2つの帽子: 機能追加とリファクタリングを同時にやること
- バグの原因が明らかになるためにも
- スモールステップで実施する
- どうリファクタしたのか、違いが分かるスモールステップな単位でコミットする
- 例えば、メソッド名の変更とメソッドの移動をわける
- 数回コミットしたら、Pull Request を作成する
- 無駄な仕様は削除することも視野に
- RubyMine: Ruby 用のリファクタリング向け IDE
- 機能追加とリファクタリングを同時にやらない
第 15 章 設計の意義と設計への向き合い方
- 本書はなんの設計について書いたものなのか
- 設計とは、なんらかの品質特性向上を促進するためのもの
- 中でも本書は、保守性に関する設計についてのもの
- 保守性の副特性には修復性がある
- 修復性 = 変更容易性
- 本書は特に、変更容易性の向上を目的にした設計手法についてのものである
品質特性 説明 品質副特性 機能適合性 機能がニーズを満たす度合い 機能完全性、機能正確性、機能適切性 性能効率性 リソース効率や性能の度合い 時間効率性、資源効率性、容量満足性 互換性 ほかのシステムと情報の共有・交換できる度合い 共有性、相互運用性 使用性 利用者がシステムを満足に利用できる度合い 適切度認識性、習得性、運用操作性、ユーザーエラー防止性、ユーザーインターフェイス快美性、アクセシビリティ 信頼性 必要なときに機能実行できる度合い 成熟性、可用性、障害許容性、回復性 セキュリティ 不正利用から保護する度合い 機密性、インテグリティ、否認防止性、責任追跡性、真正性 保守性 システムを修正する有効性や効率性の度合い モジュール性、再利用性、解析性、修正性、試験性 移植性 ほかの実行環境に移植できる度合い 適応性、設置性、置換性
- 設計しないと開発生産性が低下する
- レガシーコード: 変更が困難で壊れやすいコード
- 技術的負債: レガシーコードが蓄積している状態
- 変更容易性を設計しないと開発生産性が低下する。以下要因。
- 要因 1: バグを埋め込みやすくなる
- 低凝集な構造によって使用変更時に修正漏れが起きやすくなり、バグになる。
- コードの理解が難しいために、実装ミスが起こりやすく、バグになる。
- 不正値が容易に混入する構造になりがちで、バグが起こりやすくなる。
- 要因 2: 可読性が低下する
- 可読性が低下し、理解するまでに時間がかかってしまう
- ロジックの見通しが悪く、読み解くのに時間がかかる
- 関係し合うロジックがあちこちに散在しているために、仕様変更に関連するロジックを全て探し回る手間が増える。
- 不正値の混入でバグが発生した場合に、どこから不整地が混入したのか追跡が困難になる
- 可読性が低下し、理解するまでに時間がかかってしまう
- 木こりのジレンマ
- 木こりのジレンマ: 効率を上げるためのリファクタリングに割くための余裕がなくなる現象
- 一生懸命仕事した感覚だけが残って生産性は悪いまま
- 開発生産性が悪いと、成果を出せない体質になってしまう。
- 一生懸命やるべきは、成果を出しやすい構造を設計すること。
- 国家規模の経済損失
- 複雑で混乱したロジックがあると、もっと混乱したロジックがつくり込まれやすくなる
- 指数関数的に負債が生まれていく
- ソフトウェアとエンジニアの成長性
- コードの変更容易性が高いほど、ソフトウェアの価値を素早く高められる。ソフトウェアが素早く成長する。
- ソフトウェアの成長性を高めることが、本書の意義
- 変更容易性が悪化すると、あなたのスキルの成長性まで悪化する
- エンジニアにとっての資産とは何か
- エンジニアにとっての本質的な資産、それは技術力にほかならない
- レガシーコードに人は引きづられやすい
- 既存のレガシーコードを担当する新人エンジニアはそれがレガシーコードだと気づかずに、レガシーコードを書くように育ってしまう。
- レガシーコードは高品質設計を妨げる
- 納期の都合などにより、レガシーコードの設計改善ば難しく、結局高品質な設計実装の経験を積めなくなる
- レガシーコードは開発工数を減少させる
- レガシーコードは、時間と開発工数を奪う
- 課題を解決する
- では、どう解決すべきか
- 課題が見えないとそもそも設計する意思が生まれない
- 近く容易な課題と近く困難な課題がある - フィリップクルーシュティン「ソフトウェア価値のマトリックス」
外部品質(顧客価値) 内部品質(技術価値) 機能的価値 アーキテクチャ価値 (新機能など) (設計品質など) ------------------- ------------------- ビジネス価値 技術的負債 (収益など) (バグ、複雑さなど) - それがどんな悪さをするのかを知らなければ、技術的負債の存在を知覚することすら困難
- ソースコードの読解スキルと技術的負債の知覚スキルは別
- 理想系を知って初めて課題を知覚できる
- 正しい理想をもっていれば、現実とのギャップを知覚し、課題がわかる
- 変更容易性を比較できないジレンマ
- 変更容易性は比較検証することができない
- コードの良し悪しを判断する指標
- 未来の開発生産性を計測する手段はない
- コードメトリクス or ソフトウェアメトリクス: 現状のソースこーぢおの良し悪しを表す指標。コードの複雑さや、稼働区政などの一連の品質指標
- 実行可能コードの行数
- 行数が多いと、多くのことをやりすぎている可能性がある
- 扱う変数や条件分岐の増大が読み手を混乱させる
- RuboCop: Ruby のコード解析ライブラリ
- メソッド: 10 行以内
- クラス: 100 行以内
- 循環的複雑度
- 循環的複雑度(サイクロマティック複雑度): コードの構造的な複雑さを示す指標
- 条件分岐、ループ処理、ネストなどにより増大する
循環的複雑度 複雑さの状態 バグ混入確率 10 以下 非常に良い構造 25% 30 以上 構造的なリスクあり 40% 50 以上 テスト不可能 70% 75 以上 いかなる変更も誤修正を生む 98% - 複雑度は、早期 return、ストラテジパターン、ファーストクラスコレクションパターンなどで低減可能
- 凝集度
- 凝集度: データとロジックの関係性の強さを表す指標
- 凝集度が高いほど、変更容易性が高い
- LCOM: メソッドの凝集度を表す指標。計測ツールもある
- 結合度
- 結合度: モジュール間の依存関係の強さを表す指標
- 結合度が高いほど、変更による影響を考慮しなければならない
- 結合度が高いほど、単一責任ではない可能性がある
- 依存をもっと減らせないか、クラスをもっと小さく分割できないかを検討する
- チャンク
- マジカルナンバー4: 人間の短期記憶は一度に 4+1 個の概念しか把握できないとする説
- チャンク: 記憶個数の単位
- Hello を知らない人にとって、これは5チャンク。Hello を知っている人にとっては1チャンク。
- この説からも、初めて見る変数やロジックは少ない方がいいことがわかる
- 単一責任原則を遵守すれば、マジカルナンバー4を超えることはないはず
- コード分析をサポートする各種ツール
- Code Climate Quality
- コードの品質を計測するツール
- 独自の計算式により技術的負債を算出し、負債の増減を時系列でグラフ化
- 負債の度合いをファイル単位で可視化。ソートも可能
- 複雑度やコード行数など、メトリクス的に問題のある箇所を可視化
- ファイルごとの更新頻度を横軸、技術的負債を縦軸に取りグラフ化
- コードの品質を計測するツール
- Understand
- コード品質分析ツール
- コード行数や複雑度、凝集度、結合度、さまざまなメトリクスを計測可能
- グラフィカルビュー機能もある
- 使用変更時の影響範囲を把握できる
- Visual Studio
- IDE
- 全てのライセンス形態でコードメトリクス計測機能が利用可能
- 実行可能コード行数や複雑度、結合度をファイルごとに分析可能
- 保守容易性インデックスも算出可能
- シンタックスハイライトを品質可視化に利用する
- 以下のように設定すると、品質の悪い箇所は赤色系、品質の良い箇所は青色系に染まる。
- 数値: 赤 -- マジックナンバーの可能性
- 引数: オレンジ -- 多すぎる引数は低凝集の可能性
- ローカル変数: 黄色 -- ローカル変数が多いと、関心ごとの違う多くの処理をしている可能性
- メソッド: 黄緑 -- このままでも良いが改善の可能性があるかも?
- final インスタンス変数: 緑 -- 不変で頑強な構造
- クラス: 水色 -- 凝集度に貢献
- interface: 青 -- 条件分岐の低減に貢献
- 以下のように設定すると、品質の悪い箇所は赤色系、品質の良い箇所は青色系に染まる。
- Code Climate Quality
- 設計対象と費用対効果
- 予算や時間は有限なので、リファクタリングを無限に行うことはできない
- 仕様変更が生じるような箇所の変更容易性を高めるべきである
- パレートの法則(80:20 の法則)
- 売り上げの 80%は、全商品の 20%が生み出している
- ソフトウェアの処理時間の内 80%は、ソースコード全体の 20%の部分が占めている
- であるならば、この 20%の部分に注力して改善するべきである
- サービスの中心的領域、コアドメイン
- どのサービスにも、「これがウリだ!」と言えるような中心的な価値がある
- 中心的価値となるビジネス領域をコアドメインという
- コアドメインとは
- システム内で最大な価値を付加すべき場所
- 価値があり重要で、費用対効果が最大の箇所
- 競争優位性があり、差別化が測られ、ビジネス上優位に立つポイント
- 重点設計対象の選定にはビジネス知識が必要
- 重要な機能は高い頻度で仕様変更される傾向にある
- ドメイン駆動設計: コアドメインの価値を継続的に高め、サービスを長期的に成長させる設計手法
- ドメインエキスパート: サービスの事業領域に関して深い知識を持っている人
- ドメインエキスパートと協力し、何がコアドメインなのかを見定める必要がある
- アーキテクト: ビジネス戦略を実現するアーキテクチャを設計する役割を担うエンジニア
- アーキテクトの職務遂行にはビジネス理解が必須
- 時間を操る超能力者になろう
- 変更容易性設計は開発生産性、つまり未来の時間を操ることができる
- 非エンジニアはシステムの表面的な機能しか見えない。
- エンジニアは内部構造を可視化する目をもっている
- 設計のあるべき姿がわかるエンジニアはレガシーコードを可視化する目をもっている
- この目をもっているなら、それはつまり、時間を操る超能力者であるということ
第 16 章 設計を妨げる開発プロセスとの戦い
- 設計品質をおとしめてしまう開発プロセスの問題
- コミュニケーション
- コミュニケーションが希薄だと設計品質に問題が生じる
- コンウェイの法則
- コンウェイの法則: システムの構造が、それを設計する組織構造に似てくる
- あるべきシステム構造と組織構造に違いがあると、あるべきシステム構造をつくり上げるのが困難になる
- 逆コンウェイ作戦: 先にあるべきシステム構造を作成し、次に組織を編成する作戦
- 心理的安全性
- 心理的安全性: 自分が発言することを恥じたり、拒絶されるなど、不利益を被ることがないことをチームで共有されている心理状態
- 成功に導くチームを構築する上で重要
- 設計
- 「早く終わらせたい」心理が品質低下の罠
- 「早く終わらせたい」と思うと、動くコードをとにかく早く実装しがち
- 早く実装できると褒められる は罠
- 粗悪なコードは綺麗なコードを書くより常に遅い
- 書籍「Clean Architecture」による実験「テスト駆動開発を用いた実装と、用いない実装のどちらが早く完成するのか」
- テスト駆動開発の方が早い
- クラス設計と実装のフィードバックサイクルを回す
- 最低でもメモ程度のクラス図を書く
- クラス図作成 → チームでレビュー → 実装 → 見落としをクラス図にフィードバック → サイクルを回す
- 厳密に設計しすぎず、サイクルを回し続けるのがコツ
- たった一度の設計では、良き構造は見出せない
- 回し続けることが大切
- 「パフォーマンスが落ちるからクラスを追加しない」は正しい?
- パフォーマンス面でのボトルネックがわからない内からパフォーマンス改善に取り組むのは早すぎる最適化である
- 設計ルールを多数決で決めるとコード品捨は最低になる
- 多数決で決定すると、どうしてもレベルの低い方にあわせて基準をまとめることになってしまう
- 設計ルールづくりのポイント
- 設計スキルの高いメンバーが中心となってルールを作るようにする
- チームリーダーの権限で、ルールの遵守を推進する
- 設計ルールには、それぞれ、理由や意図を併記する
- 設計ルールは、パフォーマンスやフレームワークの制約など、様々な要件とトレードオフになる可能性がある
- 何がなんでもルールぜったい遵守ではなく、お年どころ、妥協点の模索が必要な状況がある
- 「早く終わらせたい」心理が品質低下の罠
- 実装
- 割れ窓理論とボーイスカウトの法則
- 割れ窓理論はコードでも言えることである
- 割れ窓理論: 割れた窓を放置しておくと、それを許容する雰囲気がいずれ凶悪犯罪を生む
- ボーイスカウトの法則: きた時より美しく
- この2つを遵守して、気がついた箇所を改善する癖をつける
- 既存コードを信用せず。冷静に正体を見破る
- レガシーコードがお手本になり、次のレガシーコードを生む。
- レガシーコードを撲滅するには、既存コードを一切信用しないくらいの心構えが必要
- 正体を見破る
- これは何を解決したコードなのか
- 達成したい目的はなんなのか
- アンカリング効果: 最初に提示された数値や情報が基準になることによって、その後の判断を歪めてしまう認知バイアス
- ジョシュアツリーの法則: 名前を知ってはじめて存在を知覚できるようになる。逆に名前を知らないと存在を知覚できない。
- コーディング規約を利用しよう
- コーディング規約: コードの品質担保を目的に、コーディングスタイルや命名規則などのルールを定めたもの
- プログラミング言語ごとに公開されている
- IDE にはチェック機能がついている
- ツールが個別に提供されている
- 命名規則
- Java の命名規則
要素 ルール 例 クラス アッパーキャメルケース Customer メソッド ロワーキャメルケース payMoney 定数 すべて大文字、区切りは_ MAX_NAME_LENGTH - 言語ごとに命名規則は異なる
- 同じ言語でも採用するコーディング規則によって、命名規則が異なる場合がある
- Java の命名規則
- 割れ窓理論とボーイスカウトの法則
- レビュー
- コードレビューを仕組み化しよう
- コードを設計視点でレビューしよう
- 設計的な妥当性に重点をおいてレビューすべき
- ロジックが機能要件を満足しているかどうか、欠陥の有無、コーディングスタイルのレビューよりも!
- 敬意と礼儀
- コードレビューにおいて、攻撃的なコメントは許されない
- 最重要は敬意と礼儀
- Google Chromium プロジェクトのレビュー方針「尊敬に満ちたコードレビュー」
- すべきこと
すべきこと 解説 能力と善意を想定する 開発者の十分な能力と善意を想定する。ミスは情報不足によるものと考える。 会って話し合う レビューで意見がまとまらなければ、直接話して意見を交換する。 理由を説明する 「間違っています」だけではなく、なぜ問題なのか、どうすれば良いのかを説明する。 理由を聞く 変更理由が不明な場合は、遠慮せずに聞く。記録を残し、良い実践を考える機会とする。 終わりを見つける 完璧を求めすぎず、適切なところでレビューを終了する。 適度な時間内に返信する レビューを放置せず、24 時間以内を目安に対応する。 ポジティブに述べる 欠点を探す姿勢ではなく、ポジティブな態度でレビューを行う。良い変更をした人には感謝の意を示す。 - いけないこと
いけないこと 解説 人を辱しない 相手は最善を尽くしていることを前提にし、「なぜ気づかなかったのか?」といった無意味なコメントは避ける。 極端な言葉やネガティブな表現を使わない 「ひどいアルゴリズム」などのネガティブな表現は避け、コードについて議論する。 ツールの使用を思いとどまらせない コードフォーマッターなどの自動化ツールの導入を評価し、使用の是非や好みを押し付けない。 自転車置き場の議論をしない 重要でないことについて、レビューで決着をつけようとしない。
- すべきこと
- 正しければ何を言っても良い というのは幼稚な考え
- 定期的に改善タスクを棚卸しすること
- ふいに発見された良くないコードを「後で直しておこう」と後回しにしないこと
- 必ずタスク管理ツールに積み上げる
- そして、定期的にタスクを棚卸しして、確実に対処するようにする
- タスク管理には GitHub Issues などを利用する
- チーム設計力を高める
- 設計に詳しいメンバーが誰もいないこともある
- 理解者すらもいないこともある
- チーム全体の設計力ための活動が必要
- 影響力を持つレベルにまで仲間を集める
- ランチェスターの法則 -- マーケットシェア理論 -- クープマン目標値: 影響力を無視できない存在レベルであり、シェア争いに本格参入する目標値 -- 10.9%
- 10%のエンジニアを仲間にして訴える
- 基本はスモールステップ
- スモールステップで設計の知識を共有する
- 実感が大事、手を動かしてみよう
- 百聞は一見にしかず
- 実践して、設計のありがたみを仲間と実感しよう
- フォローアップ勉強会を開いてみよう
- 仲間の輪を大きくするために、設計の勉強会を開催しよう
- 勉強会の流れ
- 本に書かれているノウハウを1、2個程度読み合わせる
- ノウハウを適用できそうな箇所を、プロダクションコードの中から探す
- ノウハウを使って、コードを改善する
- どう改善したのか、before, after が分かるように発表する
- 発表内容について質疑応答や議論をする
- 勉強会のバッドノウハウ
- 本の読み合わせだけ -- 実感が得られない
- これまでの実装を頭ごなしに否定する -- 良い気分がしない
- 無理に話を通そうとしない
- 大切なこと
- 相手の意見に共感すること、尊重すること
- スモールステップ
- 忍耐
- 変更容易性という品質特性がある。それを向上させる設計技術があることを認知させる程度に考えること
- リーダーやマネージャーに設計と費用対効果のは話をする
- 変更容易性が設計コストの視野に入っていないこともある
- 開発プロセスに設計を組み込む必要がある
- マネージャーには費用対効果を理由に説得する
- 設計責任者を立てる
- 設計責任者の推進事項
- 設計品質に関わるルールや開発プロセスの策定
- ルールの周知、教育
- リーダー・マネージャー層への共有
- 品質の可視化
- 設計品質の維持
- 設計責任者の推進事項
第 17 章 設計技術の理解の深め方
-
さらに奥深く設計を学んでいくための書籍や学習方法
-
さらにステップアップするための設計技術書紹介
-
現場で役立つシステム設計の原則 -- 変更を楽で安全にするオブジェクト指向の実践技法
- オブジェクト指向設計の実践的なテクニックを学べる
- 特に変更容易性に焦点を当てた日本の現場向けの解説
-
リーダブルコード -- より良いコードを書くためのシンプルで実践的なテクニック
- 可読性の高いコードを書くための具体的なテクニック集
- 実践的で分かりやすい例が豊富
-
リファクタリング -- 既存のコードを安全んに改善する(第2版)
- コードの改善手法のカタログ
- 具体的なリファクタリング手順とパターンを学べる
-
Clean Code -- アジャイルソフトウェア達人の技
- 良いコードの原則と実践
- コードの品質向上のための具体的なガイドライン
-
レガシーコード改善ガイド
- テストがない古いコードの改善手法
- 実践的なリファクタリング戦略
-
レガシーソフトウェア改善ガイド
- システム全体レベルでの改善手法
- 大規模システムの現代化戦略
-
レガシーコードからの脱却 -- ソフトウェアの寿命を延ばし価値を高める9つのプラクティス
- レガシーシステムの価値向上手法
- 実践的な改善アプローチ
-
エンジニアリング組織論への招待 -- 不確実性に向き合う指向と組織のリファクタリング
- 技術組織のマネジメント手法
- 不確実性への対処方法
-
プリンシプル オブ プログラミング -- 3年目までに身につけたい一生役立つ 101 の原理原則
- プログラミングの基本原則集
- 実践的な指針とパターン
-
Clean Architecture -- 達人に学ぶソフトウェアの構造と設計
- ソフトウェアアーキテクチャの原則
- システム設計の高レベルな考え方
-
エリック・エヴァンスのドメイン駆動設計
- DDD(ドメイン駆動設計)の原典
- 複雑なドメインの設計手法
-
セキュア・バイ・デザイン -- 安全なソフトウェア設計
- セキュリティを考慮した設計手法
- 安全なシステム構築の原則
-
ドメイン駆動設計入門 -- ボトムアップでわかる!ドメイン駆動設計の基本
- DDD の基本概念をわかりやすく解説
- 実践的な例を用いた入門書
-
ドメイン駆動設計 -- モデリング/実装ガイド
- DDD の実装テクニック
- 具体的なモデリング手法
-
ドメイン駆動設計 -- サンプルコード&FAQ
- DDD の実装例と一般的な疑問への回答
- 実践的なコード例
-
テスト駆動開発
- TDD(テスト駆動開発)の基本と実践
- テストファーストの開発手法
-
-
設計スキルを高める学び方
- 実践的な設計スキルを高めるための効率的な学び方
- 学習のための指針
- インプット2割、アウトプット8割
- 設計効果を必ず意識すること
- 悪魔の構造を見破る練習
- リファクタリングで大幅スキルアップ
- 行数の少ない private メソッドや static メソッドで
- 動くコードを書いたら、設計し直してからコミット
- まずは動くコードを早く書くことがおすすめ
- そして、コミットする前にあるべき構造を設計する
- 最初に書いたコードの代わりに、新たに設計したものを実装する
- 設計技術書でさらに高みを目指そう
-