【読書ログ】良いコード/悪いコードで学ぶ設計入門 ―保守しやすい 成長し続けるコードの書き方

updated_at: 2022-08-26

概要

良いコード/悪いコードで学ぶ設計入門 を読んで学びになったことをつらつらと書いていく。
改めてコードの書き方について見直すきっかけとなった。
また、デザインパターンがあまり理解できていないのでそこを学びたいと思った。

内容
1章: 悪しき構造の弊害を知覚する

低凝集は避ける
データやロジックが分散して低凝集になっていると人間が認知できなくなる。
具体的に言うと、ある操作のロジックを追加するときに複数箇所を修正しないといけない場合、変更漏れが起こる可能性が高い。
そうなると、対応したのに対応していないというクレームが上がってきて追加修正しないといけない。信頼も失ってしまうので、
また、それを知らずに重複コードを書いてしまう可能性や可読性の低下を招いてしまう。さらに、そのコード動作前に初期化処理が必要な場合、それを忘れてしまってnullに対してロジックを実行してしまって実行時エラーになったりとか。
個人だけでなくチーム全体の生産性にも悪影響なので、そういったコードは書かない方が良いなと改めて感じた。

2章: 設計の初歩

変数への再代入を避ける
変数を使い回すと期待通りの出力にならない可能性があり、バグを埋め込んでしまうことになる。
読み手も混乱させてしまうので、変数を使い回さずに目的ごとに変数を用意して処理した方が良い。

3章: クラス設計

クラス設計のエッセンス

  • クラスが単体で正常動作するように疎結合を保つ(高凝集)
  • 複雑な初期設定をせずとも使えるようにする
  • 外部公開メソッドは慎重に


コンストラクタで確実に正常値を設定する
これによりインスタンス化する際に正常値が確実に設定されることを担保できる

メソッド引数やローカル変数を不変にする
途中で値が変化すると追うのが難しくなるのでバグを引き起こしてしまう

値の渡し間違いを型で防止する
関数は通貨の数字が渡されるのを期待しているのに、number型で定義しているので通過以外の数字が渡ってきてしまう

// NG
const totalMoney = (money: number) => {
 // 処理
}
totalMoney(3)
// OK
const totalMoney = (money: Money) => {
  // 処理
}
totalMoney(3)


上記例ではMoney型の変数を渡しているがこの変数は「完全コンストラクタ + 値オブジェクト(値そのものとクラスとして切り出し表現する」でガード節で不正値を弾き、正常値だけを持つインスタンスを生成するためのコンストラクタ。オブジェクト指向設計の基本形。

異常値をいかに生み出さないかに焦点が置かれていると思った。
クラス設計とは、インスタンス変数を不正状態に陥らせないための仕組みづくりと言っても過言ではありませんと筆者が述べているように、いかに異常値を防ぐかがポイント。
確かに普通に考えて設計図から意図しないものが生まれてきたら設計図の意味を成してないよなと思う。

4章: 不変の活用

副作用のデメリット
副作用とは、関数が戻り値を返す以外に外部の状態(変数など)を変更すること
副作用ってどこまで許容するべきなのかは割と難しいかもしれないと感じる。
影響範囲を留めるべきというのは分かるが関数内で引数を操作して返すというのは割とやっている気がする。
クラスやサービス横断して副作用を生み出すのは良くなさそう。

5章: 低凝集

モジュール内のデータとロジックの関係性の強さ

共通処理クラスを安易に作らない
CommonとかUtilとか神クラスとか
ログ出力やエラー処理、デバッグや例外処理などの横断的関心事であれば
共通処理として切り出しても良いが、それ以外の場合は本当に共通処理が必要か考えた方が良い。
共通処理という名の雑多なロジック生まれがち。

引数は少なく
引数が多すぎると低凝集に陥りがち。
引数が多いとそれだけ処理内容が増えるのでロジックの複雑化や重複コードにつながってしまう。
そういう場合、データを引数として扱うのではなくそのデータをインスタンス変数としてもつクラスへと変更してみる。
この考え方は学びになった。
メソッド内で複数のオブジェクトを操作している状態から、クラスとして切り出し関心事を分離していく。
そうすることで全体として見通しが良くなるなと感じた。

尋ねるな命じろ
他のオブジェクトの状態を問い合わせるのではなく、呼び出し側はただメソッドを呼び出すだけにする。
オブジェクトの状態を判定するロジックなどは呼ばれる側で実装するべき。
こうすることでロジックの高凝集につながる。
この考え方はAPI設計に通ずるものがあると感じた。外部インターフェースとして呼び出すものを与えるだけで、内部状態は気にさせない。呼び出す時も余計なロジックを考えずに済む。

6章: 条件分岐

この章は条件分岐はinterfaceに書き換えると良いということが学びになった。

8章: 密結合

継承はなるべく非推奨とのこと。サブクラスがスーパークラスに依存するので密結合やロジック混乱などになりがちとのこと。
書籍には「ある継承クラスにとっては関係があっても、別の継承クラスには無関係なメソッドが登場し始めると問題」とあったけれど、これは単一責任の原則を破っているからだと思う。
Qiitaとかに参考記事があったけれど、共通メソッドや変数を共有するためなどの安易な継承は避けるべきだというのを見て納得した。

10章: 名前設計

書籍では目的駆動名前設計を推してる。
目的ベースでの命名。確かに商品や顧客にもいろいろな切り口があるのでしっかりと目的を検討して区別する必要ある。
ドメイン理解と元に切り分ける大事さ。
特に印象に残ったのが、「形容詞で区別が必要なときはクラス化のチャンス」ということ。
正しくその通りだなと思ったので、今後意識していきたいなと考えている。

12章 メソッド

CQS(コマンドクエリ分割)

メソッドはコマンド(変更)またはクエリ(問い合わせ)のどちらか一方だけを行うようにするという考え方らしい。
これは自然と意識するようにしてるなと思う。パターン名があったというのが発見かも。

13章 モデリング

10章名前設計の章と被るところだが、任意のモデルが適切に分割されていることが大事だなと思う。
単一責任の原則をどれだけ意識できているか?自分達のドメインでCustomerモデルはどういう種類の顧客を扱っているのか?
それをするには事業ドメインを深く理解することが大事。

16章 設計を妨げる開発プロセスとの戦い

色々記述されているが「既存コードを信用せず、冷静に正体を見破る」を意識したい。
既存実装を模倣することが多いけれど、その実装は本当にベストなのか?は今一度冷静になって見極める意識を持ちたい。

まとめ

今のチームが拡大するにあたって保守性を第一に考えたいと思っていて、設計周りをきちんと抑えておきたかったので読んだ。
筆者の主張がいくらか強い部分もあったけれど、クラス設計やCQSなど勉強になった。今一度良いコードとは何かを考え直すきっかけになったと思う。次はドメイン駆動設計やデザインパターンをちゃんと抑えたい。