📕 良いコード/悪いコードで学ぶ設計入門 ―保守しやすい 成長し続けるコードの書き方
https://scrapbox.io/files/6569611f150218002433eeda.png
この本を読むモチベ
フロント/バック両方に使える技術を学びたい
OOPわかったつもりでいるけどどう実践で使えば?を解消したい
1章
👿意味不明な命名
技術駆動命名
連番命名(SIerにありがちなきが..)
👿理解を困難にする条件分岐のネスト
ちょっとここ混乱した c.koide.icon
ミノ駆動本:
データを保持するだけのクラスをデータクラス
アンチパターン「データクラス」
Pythonの@dataclass :
データ(プロパティ)だけでなく、データにまつわるメソッドもかく事ができる
普通のClass記法に比べて、分量減る
__init__ , __eq__ , __repr__が自動で定義される
__eq__ は == の 呼び出したタイミング...といった具合で自動で呼び出される
...などなど
@dataclassをつかっててもデータしか定義してなければ、ミノ駆動さんのいう「データクラス」に該当する
DTOは悪?一概に悪とはいえなそうだけどな... c.koide.icon 例)契約金額のデータクラスと契約を管理するクラスがある場合で、税込金額を計算するロジックが契約を管理するクラスで定義されている
👿 消費税関係の仕様変更が発生した場合、別々に定義していると修正漏れが発生する
関連するデータやロジック同士が分散し、バラバラになることを低凝集という 重複コード、修正漏れ、可読性低下
初期化をしないと使い物にならないクラス、未初期化状態が発生しうるクラス
code:java
ContractAmount amount = new ContractAmount(); // 本来はここで引数を与えるべき
System.out.println(amount.salesTaxRate.toString()); // NullPointerExceptionになる
不正値の混入
仕様が正しくない状態(注文数がマイナス値など)
code:java
ContractAmount amount = new ContractAmount();
amount.salesTaxRate = new BigDecimal("-0.1");
2章 設計の初歩
省略せずに意図が伝わる名前
再代入をしない(目的ごとの変数を用意する)
メソッドに切り出す
関連し合うデータとロジックをクラスにまとめる
code:java
class HitPoint {
private static final int MIN = 0;
private static final int MAX = 999;
// ガード節
hitPoint(final int value) {
if (value < MIN) throw new IllegalArgumentException(MIN + "以上を指定してください");
}
// ダメージを受ける
HitPoint damage(final int damageAmount) {..}
// 回復
hitPoint recover(final int recoveryAmount){..}
}
3章 クラス設計 (ボリューミーかな)
OOP
関心の分離がしやすい
💡クラス単体で正常に動作するように設計
クラス設計とは、インスタンス変数を不正状態に陥らせないためのしくみづくり
良いクラスの構成要素
1. インスタンス変数
2. インスタンス変数を不正状態から防御し、正常に操作するメソッド
自己防衛責務
code:java
class Money {
// 4. インスタンス変数を不変(イミュータブル)にする
final int amount;
final Currency curency;
// 5. 引数も不変にする
Money(final int amount, final Currency currency) {
// 1. ガード節で不正値を防ぐ
if (amount < 0) {
throw new IllegalArgumentException("金額には0以上を指定してください。");
}
if (currency == null) {
throw new NullPointerException("通貨単位を指定してください。")
}
// 1. コンストラクタで確実に正常値を設定 (生焼けオブジェクトを防ぐ)
this.amount = amount;
this.currency = currency;
}
// 3. 計算ロジックをデータ保持側に寄せる
// 6. Money型だけ渡せるようにする
void add(final Money other) {
// 7. 独自の型を用いると異なる通貨単位での加算を防止できる
if (!currency.equals(other.currency)) {
throw new IllegalArgumentException("通貨単位が違います。")
}
final int added = amount + other.amount;
return new Money(added, currency);
}
}
4の補足
code:java
// finalで定義しないとインスタンス変数が上書きされてしまう
money.amount = originalPrice;
...
if (specialServiceAdded) {
money.add(additionalServiceFee);
...
if (seasonOffApplied) {
money.amount = seasonPrice();
}
}
変更したい場合は新しいインスタンスを作成する
code:java
class Money {
..
Money add(int other) {
int added = amount + other;
return new Money(added, currency);
}
}
5の補足
code:java
final int ticketCount = 3; // チケット枚数
money.add(ticketCount); // 意図が異なる値をセットすることができてしまう
プリミティブ型より独自の型を!
システムの仕様に必要なメソッドのみ定義する
例)金額の乗算など...(会計システムでそのメソッド使う?)
必要な手続き = メソッド(上記の場合だと add)をのみを外部へ公開する
感想 c.koide.icon
GoFのデザインパターンとはまた別にある?
ファーストクラスコレクションは業務でもみかけた記憶
最初違和感はあったけどコレクションに対する操作をまとめられて便利だなと思った
4章 不変の活用
変数の値を変更するなど状態変更できることを 可変(ミュータブル) インスタンス変数を使いまわさない
code:java
AttackPower attackPower = new AttackPower(20);
Weapon weaponA = new Weapon(attackPower);
Weapon weaponB = new Weapon(atackPower);
// 仕様で 武器Aの攻撃力だけ変えたいのに、武器Bの攻撃力もあがってしまった
weaponA.attackPower.value = 25;
System.out.println('Weapon A attack power:'+weaponA.attackPower.value); //25
System.out.println('Weapon B attack power:'+weaponB.attackPower.value); //25
以下のようにする
code:java
AttackPower attackPowerA = new AttackPower(20);
AttackPower attackPowerB = new AttackPower(20);
Weapon weaponA = new Weapon(attackPowerA);
Weapon weaponB = new Weapon(atackPowerB);
5章
- インスタンス変数は不変にしよう
- インスタンスの使い回しはやめよう (AttackPower.value をfinalにするとどのみち、できない)
- 値を変更する場合は、変更用のメソッドをはやして新しいインスタンス変数をいれるようにする
過剰に作るのもよくないよね
6章