Open21
良いコード/悪いコードで学ぶ設計入門―保守しやすい 成長し続けるコードの書き方を読む
これ読む
プリンシプルオブプログラミングとかオブジェクト指向設計実践ガイドとかドメイン駆動設計とかは読了しているので多分重なる部分もたくさんありそう。
関係ないけどBobおじさんシリーズも読みたい。けど買うタイミングと時間を失ってる。
全17章
結構モリモリだ
1章 ダメなコード
-
よくない命名
- 予約語とかIntみたいな型名とか(intValueとか)
- 連番 (method001とか)
-
ネストされた条件分岐
-
データクラス(データしか持たないクラス)
-
低凝集
-
重複コード
-
修正漏れ(重複コードの修正漏れ等)
-
低可読性
-
未初期化オブジェクト
- 初期化がされなくても使えてしまうオブジェクト
- 初期化しても正しく動かないオブジェクト
- 不正な値での初期化が可能なオブジェクト
一般常識なところ
2章 設計の初歩
- 省略した変数を使わない
- aとかpとか
- 変数の使い回し(再代入)
- 引いてはイミュータブルにすることの大事さ
- 意味のあるまとまり、ロジックのあるまとまりにまとめる(関数化)
- わかりやすい変数名、ロジックにする
- データとロジックをクラスにまとめる
変わらず一般常識なところ
3章 クラス設計
- クラス単体で動くようにする
- メソッドとインスタンス変数を使用する
- 状況によるが基本原則、どちらかが欠けてるなどない
- 自己防衛責務を全てのクラスで備える
- コンストラクタで初期化
- ガード節で弾く
- 関連するロジック(計算など)をクラスに持たせる
- イミュータブルにする
- 変数をfinalにする
- 引数もfinalにする
- 値を渡すものはプリミティブ型にしない。値のクラスを作る。
- メソッドの引数に対してもバリデーションをかける
- 現実の営みと関係のないメソッドを追加しない
- 数字(int)は四則演算が可能だがMoney(お金)クラスは乗算は起こり得ない。
→クラス設計はインスタンス変数を不正状態に陥らせないための仕組み
- 構造の問題解決に役立つパターン
- 完全コンストラクタ
- 値オブジェクト
- ストラテジー
- ポリシー
- ファーストコレクション
- スプラウトクラス
memo
- イミュータブル時の変更は?
新しくクラスのインスタンスを生成し直してをreturnする
4章 イミュータブル
- 再代入(破壊的代入)を避ける
- finalをつける
- フールプールの立場をとる
- 変更系はクラスのインスタンスを生成し直してをreturnする
- どうしてもミュータブル(可変)をとるケース
- パフォーマンスに関わるケース(大量のデータ処理やループでの変更の多い処理など)
- スコープが局所的なケース(ループカウンタやローカル変数など)
memo
- ミューテーター
状態変更を発生させるメソッド
5章 低凝集
- staticメソッドの誤用
- インスタンス変数を使う構造にする
- インスタンス変数とそのインスタンス変数を用いるロジックを同じクラス内に閉じ込めた構造が高凝集
- staticメソッドでなくてもインスタンス変数を使ってないメソッドがクラスにあったら注意する
- 初期化ロジックの分散
- 標準会員のポイントと特別会員のポイントで初回付与ポイントが違う時に呼び出し側でコンストラクタにポイントを渡すのではなく、コンストラクタをprivateにしてファクトリメソッドでポイントの責務をクラスに閉じ込め凝集性を高める(生成ロジックが大量にある場合はファクトリクラスも検討する)
- CommonやUtilクラスを無闇に作らない
- 横断的関心事はstaticに作っても問題ないことが多い
- ログ出力
- エラー検出
- デバッグ
- 例外処理
- キャッシュ
- 同期処理
- 分散処理
- 結果を返すための出力引数を使わない(引数にクラス型の変数を取得し、その中の値を変える等)
- 多すぎる引数を避ける
- プリミティブ型を中心に設計しない
- 意味のある単位でクラスを作っていく
- デメテルの法則に気をつける
- Don't tell ask.
- 呼び出し側がメソッドを呼んでその状態に応じてプログラミングするのではなく、呼び出し側の状態に対応するクラスやメソッドをただ呼ぶだけにする
memo
- 凝集度
モジュール内におけるデータとロジックの関係性の強さ - staticメソッドの使い所
- 凝集度とか関係ないところ(ファクトリメソッドなど)
6章 条件分岐
- ネストを深くしない
- 早期returnで条件に当てはまらない場合はすぐ抜ける
- 属性をswitchで分岐してロジックを記述するのは怪しい匂いの元
- typeなどで分岐していくとtypeの記述漏れ等が発生する
- 他プログラマーが真似たり、同様の分岐ロジックなどが氾濫する可能性もある
- 属性による分岐分けはinterfaceで対応する(ポリモーフィズム)→ストラテジーパターン
- 会員種別(ゴールドやシルバー会員など)を分けるにはポリシーパターンが有効
- 条件の部品か
- 部品化した条件を組み替えてのカスタマイズ
- 引数によって処理を変えるフラグ引数は悪手
- メソッドは単機能になるように設計する。フラグで分岐させない。
7章 コレクション
- わざわざループを回して自前でコレクション処理をしない。言語に存在する標準関数を使う(車輪の再開発)
- 早期continue、breakでネストを減らす
- 条件に当てはまったら処理をする、ではなく当てはまらなかったら次のループに移行する。
- ファーストコレクションを利用し低凝集を解決する
8章 密結合
- ビジネスロジックに関わるメソッドを流用すると流用された側の修正によって流用した側のメソッドに影響が及ぶ
- 単一責任の原則
- クラスが担う責任はたった一つに限定する
- 責務が単一になるようにクラスを設計する
- DRYは重複コードであれば必ず繰り返してはいけないというわけではない
- 全ての知識はシステム内において、単一、かつ明確な、そして信頼できる表現になっていなければならない
- 継承はかなり中して扱わないと危険。基本的に継承は推奨しない。
- 継承より委譲
- ある継承クラスにとって関係があって、別の継承クラスには無関係なメソッドなどが出てくると危険の象徴(どこからどこまで関係があるのかわからないため)
- 依存関係の図を影響スケッチと呼び、図式化して関係を把握するのもあり
- アクセス修飾子はpackage privateを基本にする
- privateメソッドが大量にあるクラスは異なる責務が混じり込んでる可能性が高い。適宜クラスを分けて責務ごとにクラス、メソッドを分ける。
- 表示関連のクラスに表示以外の責務のロジックをのせない
- トランザクションスクリプトパターンを避ける
- 手続的なメソッドの羅列をつくらない
- 神クラスは作らない(責務を分けないでひたすら一つのメソッドに全てを書いていく)
- 責務ごとにロジックを分ける
9章 安全性をそこなう設計
- デッドコード
- 到達しないコード。可読性の低下、読み手に実行条件があるのではないかと混乱させる。
- また今まで実行されてなかったが仕様変更によって実行されてバグを生む原因になる。
- マジックナンバー
- 文字列型への固執
- "a,b,c"みたいにカンマ区切りでsplit変数を使って利用するなど(CSVはフォーマットなので別)
- グローバル変数やデータクラス
- どこからでも書き込めるというのはバグの温床になりやすい
- なるべくNullを扱わないようにする
- nullをreturnしない。初期値にnullを入れない。
- 例外の握り潰し
- メタプログラミング
- MVCSなどの技術駆動の単位でパッケージングをしない
- ビジネスロジック単位でパッケージングする
10章 名前設計
- 正しくクラス分け(ネーミングする)
- ユーザークラスや商品クラスなどは定義が大きすぎる。もっと明確かつ焦点を当てた特定の名前を持つクラス名とつける。
- 関心事ごとに分離する
- 命名に大事なポイント
- 具体的で、意味範囲が狭く、特化した名前
- 存在ベースではなく、目的ベース
- 関心事を分析する
- 声に出してみる
- ラバーダッキング
- 問題が発生した時に誰かに声に出して説明すると自己解決する
- ユビキタス言語
- チーム全体で意図を共有する言葉。またクラスやメソッドなどもそれに統一する。
- 開発者のみで通じる言葉だったりビジネスサイドだけで通じる言葉は存在しないようにする。
- ラバーダッキング
- 利用規約を読む
- 違う名前に置き換えられないか検討する
- 疎結合高凝集になるかチェックする
- 仕様変更時は適宜名前も変える
- 最初は個人顧客を扱う顧客クラスがあったとして途中で法人顧客も扱うようになったら両方のビジネスロジックを一つにしないで二つにクラスを分割する
- 会話に出てくる名称がクラスとして存在しない場合は要注意
- 例えばユーザーの状態によってゴールド会員とかある場合にユーザークラスだけで取り扱うのは危険
- 意図が伝わらない名前は気をつける
- tmpだらけとか、aとかbとか
- 驚き最小の原則
- 使う側が予想した通りに、予想外な驚きが最小になるように設計する
- countは要素の数を返すだけ。他の処理を交えない
- クラスが巨大化する名前は避ける
- ◯◯Managerクラス、◯◯Processorクラス、◯◯Controllerクラス、◯◯Serviceクラスなど
- コンテキスト(状況)の違いを考慮する
- 車といっても販売(車本体の価格)を扱うものと、車を使った配送とを同じクラスにすると車クラスが肥大化する。車の販売と車の配送をパッケージごとに分離する。
- 可能な限り動詞1文字になるようなメソッド命名をする
- 目的語がクラス名となって、それがメソッド(動詞)を実行すると言う形
-
クラス名 is 状態
やクラス名 動詞
が自然な英語になるようにする - 省略された名前はロジックがわかりにくくなるので省略しない
11章 コメント
- 退化コメントになるものは書かない
- コメントに書いた時点のものとその中身の動作に差異が生まれコメントが古くなる状態のこと
- コメントは修正されにくく、呼び出し側の修正までされないことが多いため
- コメントにより命名が蔑ろになる
- コメントを書くのであれば将来ロジック変更になる時どこで何をすればいいのかを書く
12章 メソッド
- クラスのインスタンス変数を必ず使うこと
- 変数は不変をベースにする
- 呼び出す側がクラスの中身や状態を判断してプログラムをしない。ただクラスに命じるだけになるようにする。(Tell, Don't ask)
- CQS(コマンドクエリ分離)
- 取得と変更を同じメソッドに書かないようにする
- 引数
- 不変にする
- フラグは使わない
- nullを使わない
- 出力引数は使わない
- 引数の数はなるべく少なく
- エラーはエラー値やNULLで返さない。言語にもよるが基本は例外で返す。
- エラーを値やエラー用のデフォルト値として返すとそれ自体に意味を持ってしまい、その値だったらという処理を呼び出し側が定義する必要が出てきてしまう(ダブルミーニング)
- NULLも同様。
13章 モデリング
- 特定の目的達成のために最低限考慮が必要な要素を備えたものがモデル
- 現実世界の出の物理的な存在と情報システム上のモデルが1:1になるとは限らない
- Userというのはプロフィールやアカウント情報(ID/PASSなど)や所属している会社などの情報等、システムの観点で見ると複数にまたがる
- そのためUserという大きすぎる枠組みだと解釈次第で全てUserになってしまう
- 目的達成するための目的駆動で名前設計をするようにして解決する
- 特定の目的に特化して設計することで変更に強い構造になる(単一責任の原則)
- モデルの見直し方
- モデルが達成しようとしている目的を全て洗い出す
- 目的特化したモデリングをする
- モデルに目的以外の要素があったら見直す
- モデル=クラスは必ずしも成り立たない。モデルは1:Nのクラスから構成される。
- 機能性の革新に貢献するモデルをドメイン駆動設計で深いモデルという
14章 リファクタリング
- Unitテストのコードを書く
- 仕様化テスト
- 試行リファクタリング
- IDEのリファクタリング機能を使う
- リファクタリングで気をつけること
- 機能追加とリファクタリングを同時にやらない
- スモールステップで実施する
- 無駄な仕様は削除することも検討する
15章 設計の意義と設計への向き合い方
- 変更が困難で壊れやすいコードをレガシーコードという
- レガシーコードが蓄積されている状態を技術的負債と呼ぶ
- 可読性などの一連の品質指標をコードメトリクス(ソフトウェアメトリクス)と呼ぶ
- 行数が多い場合はメソッドやクラスの分割を検討する
- 循環的複雑度(サイロマティック複雑℃)。コードの構造的な複雑さの指標。
- 凝集度
- 結合度
- チャンク
16章と17章は振り返りと書籍紹介
全体読んでの感想
全体的に今まで設計について勉強してこなくて、やり始めてみようと思ってる人向けの書籍だった
基本はどの設計についての書籍でも言っているように高凝集、低結合、SOLIDが基本。
そこらへんしっかり出来てる人はこの本はスキップしてもいいかもしれない。逆に自信ない人はお薦めできそう。
ちょっと気になった点は、
同じことを何度か繰り返すので内容に対してボリュームが増えてしまってる(大事だから繰り返してるともとれる)
この設計は実はよくない設計です。後に説明します。
といった後に、その説明が出てくるまでに長すぎるのと、ずっとその例を使い続けるので気になってしまう。よくない設計ならその時点で説明して後々の例でも出ないようにしたほうがいいと個人的には思った。