良いコード/悪いコードで学ぶ設計入門
1章
2章
3章
4章
5章
6章
- 条件分岐がネスト、何しているかわからない
- 早期リターンでネストを解消、条件ロジックと実行ロジックを分離
- interfaceを使う
- interface(抽象)に依存すると、型(クラス)判定用のロジックが不要になる。
- Switch文が重複すると、case文の実装漏れが起きる。
- interfaceを使うことで、切り替えが楽になる。
- ストラテジパターンを使うと良い。引数にinterfaceを指定する→引数に渡すクラスに応じて、処理を変えるという設計。
- 実装クラス側でメソッドを実装していないと、コンパイラが怒ってくれる利点もある。
- https://twitter.com/minodriven/status/1228896043435094016?s=46&t=7PLSkjioKczQuOdqRPNe8Q
- ポリシーパターンで条件判定ロジックを部品化し、再利用できる。
- laravelの認可で用いるPolicyと同じ設計
- 条件分岐が多い部分で使える。
- 条件分岐で使う各ロジックをルールクラスに切り出し→ポリシークラスからルールクラスを呼び出し、ルールを満たすか判断する設計。
- interfaceをよく理解していないと、リスコフの置換原則に違反する。
- interface を class に置換しても、動作するべきである
- フラグ引数で動作を切り替えるべきではない
- フラグを消して、動作ごとにメソッドを作成するべき。
- ストラテジパターン で動作の切り替えを行う
7章 コレクション
- 車輪の再発明、四角い車輪の再発明をしない
- 早期continueでループ処理をスキップする
- ファーストクラスコレクションには、コレクション型インスタンス変数を配置する。ロジックでそれらを操作して、高凝集に保つ
- コレクションを外部に渡す場合は読み取り専用にする
- 外部で追加されると、低凝集に陥る
8章 密結合
- 同じように見えるロジックでも、ドメイン知識が違えば共通化するべきではない。
- 継承は可能な限り避けるべき。
- 親クラスに依存する。親クラスの内容を常に気にして実装する必要が出る。
- 継承より委譲するべき。クラス内でprivateなインスタンス変数を持ち、呼び出すようにする。(コンポジション構造)
- インスタンス変数、メソッドの依存関係を図にする→影響スケッチ
- なんでもpublicにすると、他のパッケージやクラスから容易に参照される。
- クラス内では整合性を保っていたのに、他の部分で改竄されてしまう。
- クラスのサイズに気をつける。privateメソッドの数が多いクラスは、複数の責務を持ってしまっている可能性が高い。分割していくこと。
- 疎結合高凝集であって、密結合高凝集ではない。
- 表示を責務とするクラスの中に、表示以外のロジックが実装されている→スマートUI
- 巨大データクラス→様々なユースケースで使われる→無関係なデータを変更してしまい、バグになる可能性が生じる。
- メソッド内に一連の処理がダラダラと書き連ねてある構造→トランザクションスクリプトパターン。(手続型プログラミング)
- 神クラス→トランザクションスクリプトが重症化し、あらゆる責務が乱雑に絡み合っているクラス。不正検知ロジックが書きなぐられているため、不正が発生したことを追跡するのが困難。
- 単一責任の原則を遵守するクラスは100〜多くて200行に収まる。
9章 さまざまなアンチパターン
- どんな条件でも実行されないコード(デッドコード、到達不能コード)
- 可読性が低下する
- 意図があるのか疑い、混乱する
- 将来的に到達できるようになり、バグを及ぼす可能性がある
YAGNI
You aren’t going to need it.
- 実際に必要になった時にのみ実装する方針
- 使われなくなったロジックはデッドコードになる
- 先回りで作られたロジックは仕様にない→複雑
マジックナンバー
ロジック内に直接書き込まれている意味不明な数値
実装者本人にしか意図を理解できない
複数の箇所でマジックナンバーは実装されがち→重複コード
文字列型執着
単一の文字列変数に複数の値を格納すること
string $book = "Title,1997,08,19,Steve Jobs";
わかりにくい
別々の変数に格納する
グローバル変数
どこからでも参照・操作可能な変数
多くのロジックで参照・変更される→値がどこで、どのタイミングで書き変わったか把握するのが困難
排他制御が必要になる場合もある(同時に操作される可能性が生じるため)→ロック、パフォーマンス低下、デッドロック
巨大データクラスもさまざまなデータを保有→多くの箇所から参照される→グローバル変数と同様の性質
null問題
nullが入り込む前提でロジックを組みたくない
至る所でnullチェックするのは手間
nullチェックが漏れるとバグになる
null を返さない、渡さない
「未設定状態」も状態の1つとして扱う
null安全にする
null 非許容型
例外の握り潰し
例外を捕捉しても何もしないこと
エラーが起こっても、外から検知できない
このような不正は、しばらく経ってからサービスの利用者によって発見・報告される
開発者はデータベースやログ、コードを追いかけることになり、疲労する
問題検出時にはすぐに気づけるようにする
ロギング、リカバリ
メタプログラミング
プログラム実行時に、そのプログラムの構造自体を制御する技術
クラス構造を読み書きできる機構(=リフレクション)
ハードコード
メタ情報(パッケージ名・クラス名)からインスタンスを生成するなど
パッケージ・クラスが文字列として扱われている=ハードコード
静的解析の効果がなくなる
技術駆動パッケージング
パッケージの区切り方、フォルダの分け方
どれがどれに強く関連し合うかわからない
サンプルコードのコピペ
鵜呑みにするな
銀の弾丸
闇雲に自分が知っている便利な手法を使うと、逆に問題が深刻化するケースがある
ソフトウェア開発に銀の弾丸はない
10章
11章 コメント
退化コメント
コードに比べ、コメントはメンテナンスされにくい
コメントが偽情報として振る舞うようになる→退化コメント
ロジックの挙動をそのままなぞると、更新頻度は高いのに情報量は少ない→退化しやすい
コメントで命名を誤魔化す
コメントで補足説明しているから、適当な命名でいいや。→ダメです
補足説明ではなく、メソッド名や変数名自体をブラッシュアップする
意図、仕様変更時の注意点を伝える
コードを読む時、書き換える時の自分の気持ちを考えればわかる
どう言う意図でこのコードは書かれているのか
どう変更したら安全性を保てるか
ドキュメントコードを使う
当然
phpdoc
言語によっては、ドキュメント化してくれるツールもある
12章 メソッド
自身のクラスのインスタンス変数を使う
他のクラスのインスタンス変数(プロパティ)を変更するメソッドはダメ。
低凝集に陥る
class ActorManager
{
// ゲームキャラの位置を移動する。
shift(Location $location,int $shiftX,int $shiftY): void
{
$location->x += $shiftX;
$location->y += $shiftY;
}
}
// これは Location クラスのインスタンス変数を変更している。
// Location クラスで location->x を変更するメソッドを定義するべき
不変をベースに、予期せぬ動作を防ぐ
プロパティが別の場所で更新されるなどによって、予期しない動作になることがある
プロパティを不変にする。
尋ねるな、命じろ
あるクラスが別のクラスの状態を判断したり、状態に応じて他のクラスの値を変更したりするのはダメ(低凝集)
getter, setterはあまり良くない
コマンドクエリ分離
状態の変更と取得を、同時に行わない。
CQS
コマンド(変更)またはクエリ(問い合わせ)のどちらかを実行する(CQRSとは別)
引数
引数は不変にする。入力値を変更すると意味が推測しづらい
フラグ引数は使わない
ストラテジパターンなどを使う
nullを返さない
Exception, nullチェックに繋がる
出力引数は使わない
入力値として使う。
引数は可能な限り少なく
引数が多い=処理内容が多い
戻り値
型を使う
エラーは例外として返す。
値の意味は一つ
staticメソッドは、自身のクラスのインスタンス変数を操作できない。→低凝集にならないようにする。