あるべきを知ってバグを生む構造に気付く!「良いコード/悪いコードで学ぶ設計入門 保守しやすい成長し続けるコードの書き方」を読んで
設計の入門書として名高い、ミノ駆動(@MinoDriven)さん著「良いコード/悪いコードで学ぶ設計入門 ―保守しやすい 成長し続けるコードの書き方」を読みました。
本書について
著者があるべき設計をするために活用している”オブジェクト指向設計の実践的ノウハウを記した本”です。
本書には各章の解説内容と「入門・実践・応用」のどれに該当するのかが一覧でまとめられているページがあるので、自分の読みたい内容・段階に応じて読み進めることができます。
▼本書について著者本人が書いているnoteがこちら
『良いコード/悪いコードで学ぶ設計入門 』を出版します
感想
これまで僕が読んできた技術書と違うと感じた点は、知識をまとめた教科書のようなものではなく、設計について著者自身の考え方が書いてあるため「私はこう考えている。あなたはどうする?」というような、著者と対話をしているような感覚になる技術書だと感じました。
このように、技術的なことだけでなく、技術者としてあるべき意識・気持ちの部分についても書かれていることから人間味を感じる書籍でした。
僕は本書を読んで"永く続くサービス開発がしたい"と改めて思いました。以前のnoteにも書きましたが、”技術で誰かの課題を解決できること”がエンジニアのやりがいだと思っています。そして、誰かの課題を解決し続けるためには、そのためのサービスを提供し続ける必要があると思っています。
そのためにも「適切な”設計”をして生産性の高い状態(機能追加・改修や不具合の調査・対応が容易であること)を維持し続けられること」が必要だと思いました。
内容
ここからは覚えておきたい内容や感じたことのまとめになります!
可変を減らして、不変を増やす
オブジェクト指向設計の基本を解説している章。不変なら”どこで値が変更されているのか”を実装者が意識する必要がなくなる。影響範囲を限定的にすることができる。
■正常値をコンストラクタで設定するようにする
▼初期値に「名前(string)」「age(int)」を設定できるPlayerクラス
class Player {
public string $name;
public int $age;
public function __construct(string $name, int $age){
$this->name = $name;
$this->age = $age;
}
}
このクラスだと初期値に『空文字(’’)』『-10』等の不正な値を設定できてしまう。そこで construct() 内でバリデーションを行い、不正な値が入ってきた場合は例外を投げる。
▼改善例
class Player {
public string $name;
public int $age;
public function __construct(string $name, int $age){
if ($name === '') {
throw new Exception('$nameを入力してください');
}
if ($age < 0) {
throw new Exception('$ageには0以上を入力してください');
}
$this->name = $name;
$this->age = $age;
}
}
■finalを使うことで不変にする
PHPではプロパティにfinalはつけられない。(クラスとメソッドにはつけられる)(参考)。初期化した後は"読み込み可、書き込み不可”のreadonlyが使える。(PHP8.1以降)
▼readonly使用例
class Money {
public readonly int $amount; // ←設定箇所
public function __construct(int $amount){
$this->amount = $amount; // 値の設定が可能なのはここだけ
}
}
$money = new Money(1000);
var_dump($money->amount); // 読み込み可能
readonlyを使うと値の書き込みが出来ない。変更したい場合は新しいインスタンスを生成して返すようにする。
▼例えば、値を加算するadd()メソッドでは新しいインスタンスをnewしてreturnする
class Money {
public int $amount;
public function __construct(int $amount) {
$this->amount = $amount;
}
public function add(int $other){
$added = $this->amount + $other;
return new Money($added); // ←この部分
}
}
$money = new Money(1000);
$money = $money->add(1500);
var_dump($money->amount); // 2500が出力される
■パラメータ渡し間違い防止のために型を使う
プリミティブ型(プログラミング言語が用意している基本データ型。intやstring)をパラメータで受け取らずに、型で受け取るようにする。
例えば、add()のパラメータをint型で受け取れるようにしているとクラスとは無関係の値であっても、int型の変数であれば受け取ることができてしまう。
▼int型で受け取れるクラス
public function add(int $other){
$added = $this->amount + $other;
return new Money($added);
}
...
$money = $money->add(1); // ← int型であれば設定可能
そこで、クラスをパラメータとして受け取るようにすることで、ただのint型の変数ではなく、”クラスに設定する意味のあるint型の変数”として実装者側のミスを防ぐことができる。
class Money {
public readonly int $amount;
public function __construct(int $amount){
$this->amount = $amount;
}
public function add(Money $other){
$added = $this->amount + $other->amount;
return new Money($added);
}
}
$money = new Money(1000);
// 間に処理がある想定
$money = new Money(500); // ← 量として設定する意味のある「500」という数字
$money = $money->add($money);
var_dump($money->amount);
■可変にしてよいケース
不変にすると値変更のためにインスタンス生成が必要になるため、値変更やインスタンス生成がパフォーマンスに問題がある場合は可変にすることも検討する。(大量データの高速処理や画像処理など、パフォーマンスに問題が出る場合など)
設計の問題を解決する設計パターン
・ストラテジパターン
・ポリシーパターン
→この部分は、具体的な問題と解決策が書いてあるので何度も読み直したい
早期return, continue, breakを使ってネストを解消する
1.早期return
いつ使うか:条件分岐が1つ
例:1点の商品に対して購入可能か確認したい
2.早期continue
いつ使うか:
・条件分岐が複数
・全ての値に対して条件分岐処理を通したい
例:
複数の商品に対して購入可能な商品を確認し、
全ての商品に対して購入可能かどうかチェックしたい
3.早期break
いつ使うか:
・条件分岐が複数
・条件に合致しないものがあれば処理を中断したい
例:
複数の商品に対して購入可能な商品を確認し、
1つでも購入不可なものがあれば処理を中断したい
nullを返さない。確実にエラーを検知できるようにする
・nullを返さない、渡さない
・確実にエラーを検知できるようにする
ログ出力だけでなく、エラー通知処理を別レイヤーに対して要求する等
そのクラスが何の目的で存在するかを考える
■目的ベースで命名する
ECサイトの設計を考えたときに商品(Product)をどう設計するか。
目的に沿った命名は”無関係なロジックの排除”、”可読性の向上”に繋がる。機能追加や改修時に関連するクラス数が減ることで”機能追加や仕様変更時に考慮することが減る”。
“目的に沿った命名をすること”は、モデリング(現実世界のものをシステム内で扱えるように置き換えること)にも重要。これは、13章モデリング -クラス設計の土台- でも記載されている。
あるべき姿を整理してからリファクタリングする
本章では、実際のソースコードを例にリファクタリングの手順について書かれている。読みながら実際に手を動かすことで”どの点を改善すべきか”という視点のヒントが得られそう。(リーダブルコードの解説と共通している内容も何点かあった)「ユニットテストの重要性」「テストコード例」も書かれている。また、コラムでは著者のRailsアプリのリファクタリングにおける難しい点や進め方が書いてあり、面白かった。
リファクタリングで重要なのは”あるべき姿はどのようなものか”を考えて、その姿に近づけていくこと。それが整理できていないと「ネストを無くせたからヨシ!」「メソッド名を分かりやすくできたからヨシ!」といった、表面上のリファクタリングのみになりそう。これはコードレビューでも役立つ視点。
コードメトリクスから改善点を見つける
▼コードメトリクス(コードの品質を表す指標)
行数
・メソッド:10行以内
・クラス:100行以内
rubyの場合はコード解析ライブラリ「RuboCop」で可能。phpなら「phan」「phpstan」か?
循環的複雑度
コードの複雑さを表す指標。ネストやループ処理などが原因。本書ではMathWorks社の目安が取り上げられている。(https://jp.mathworks.com/discovery/cyclomatic-complexity.html)
凝集度
モジュール内におけるデータとロジックの関係性の強さを表す指標
結合度
モジュール間における依存の度合いを表す指標
■コードメトリクス分析ツール
・Code Climate Quality(https://codeclimate.com/quality/)
・Understand(https://www.techmatrix.co.jp/product/understand/)
■費用対効果が高いところから改善を行う
リファクタリングは無限にできるが時間は有限。では、どこが”費用対効果が高いところ”なのか?
■費用対効果が高いのはどこか
1.パレートの法則(80:20の法則)
全体の数値の8割は全体を構成する要素の2割が生み出している。2割に当たる”重要な機能・重点的に使用される機能・変更頻度が高い機能”に着目する。
2.コアドメイン
サービスの核となるビジネス領域。ビジネス上、価値がある部分に着目する。選定にはビジネスの知識(サービスが解決したい課題、本質は何か」を見通す力)が必要。
本章で挙げられているポイントはすぐにでも開発で確認したい。まず、現状のコードに課題があるかないかを知る必要がある。
敬意と礼儀を持ったレビュー
▼参考になるサイト
・google/eng-practices
https://github.com/google/eng-practices
・Respectful Code Reviews
https://chromium.googlesource.com/chromium/src/+/HEAD/docs/cr_respect.md
まとめ
本書にも書いてありますが、本書は"設計の入門書"です。そのため他分野の書籍を読むことでさらに知識を深めることができます。本書を読んだことで、他の本を読むことへのハードルが低くなると思いました。側に置いておき、設計やリファクタリングをするときにはまた手に取って読もうと思います。
それでは!