📚

Good Code, Bad Code ~持続可能な開発のためのソフトウェアエンジニア的思考を読んで、身につけたいと思ったこと

2024/03/01に公開

読んだきっかけ

  • ITエンジニア本大賞2024の技術書部門ベスト10に選出されたことで気になり、ちょうど自分のコード設計に関する知見が足りていないと感じていたため、読むことにしました。

本の概要

  • 堅牢で信頼性が⾼く、保守しやすいコードを書くための概念と実用的なテクニックに関して、タイトルにもある通り、良いコードと悪いコードの例を交えながら丁寧に解説されています。
  • 本書の流れとしては、まず「高品質なコードを書く」ために、以下の4つのゴールを設定しています。
     1. 正しく動くこと
     2. 正しく動作し続けること
     3. 要件の変更に対応しやすいこと
     4. 車輪の再発明をしないこと
  • そして、これらのゴールを達成するための戦略として、以下の「コード品質の6つの柱」に沿ってアドバイスが述べられています。
     1. コードを読みやすくする
     2. 想定外の事態をなくす
     3. 誤用しにくいコードを書く
     4. コードをモジュール化する
     5. コードを再利用、汎用化しやすくする
     6. テストしやすいコードを書き、適切にテストする

身につけたいと思ったこと

コードの契約において、いかに強制的に意図を守らせるのかを考える

  • 他のエンジニアがどのようにコードを壊したり誤用したりするかを考えて、それらが起こる可能性を最小限に留める。
  • 細かいコメントは契約を遵守させる方法として信頼できない。コンパイラーを使用した契約の強制が最も信頼でき、それが不可能ならば、検査とアサーションによる強制が代替案である。

早い失敗と目立つ失敗でエラーを隠蔽しないようにする

  • エラーを隠蔽してしまうと、どこでどのような問題が起きたのかを把握することができなくなり、後々困ることになる。

想定外の事態をなくすような実装にする

  • マジックバリュー(空文字や-1)を戻り値に使うとバグに繋がる可能性があるので、null、オプショナル、エラーなどを返す。
  • 入力パラメータの変更は想定外のリスクが生じ得るので、できる限り変更しない。
  • 列挙型を使う際は、今後値が追加されることを想定した実装にする。(例として、switch文で確認していない列挙値が存在して、default句に入った場合はエラーをスローするようにする等)

不変性でいかに防御するかを考える

  • 不変性を実現するデザインパターン(ビルダーパターン、コピーアンドライトパターン)を取り入れる。
  • 2つ目の信頼できる情報源の存在によって、不正な状態につながる可能性があるため、信頼できる唯一のデータやロジックを持つ。(例として、コンストラクタには一次データのみを渡し、派生データは渡さずに都度計算するようにする等)

DIを念頭に置いてコードを設計する

  • グローバル状態によってコードを安全に再利用できなくなる可能性があるため、共有状態はDIで依存関係を切り離す。
  • ハードコーディングされた依存関係はテストを不可能にするため、DIを使ってフェイクなどの注入ができるようにして、テスタビリティを補強する。

継承ではなく、コンポジッションも選択肢の一つに入れる

  • 継承した場合、継承先クラスが必要のない継承元クラスのパブリックメソッドも公開してしまい、そのパブリックメソッドが他の箇所で使用されることになっていた場合、コードを変更しづらくする可能性があるので、コンポジッション(別のクラスを拡張するのではなく、そのクラスのインスタンスを内部に含める)の使用を考える。
  • 真のis-a関係がない場合はコンポジッションを使うのが望ましく、真のis-a関係がある場合はコードによって適切な方を判断する。

他のクラスに関心を持ちすぎないようにする

  • 他のクラスに関心を持ちすぎると、他のクラスの変更による影響を受けやすくなるので、他のクラスについて持っている前提は最小限にする。(デメテルの法則)

可能ならプロダクションの依存関係を使用するべきで、次の選択肢はフェイク、モックとスタブは最後の手段としてのみ使用する

  • できるだけテストダブル(モック、スタブ、フェイクの総称)は使用せず、プロダクションの依存関係を使用する。
  • モックとスタブは実態からかけ離れたテストにつながる可能性や、実装の詳細を密接に結びつける可能性がある。
  • フェイクはテストをよりプロダクションに近いものにでき、実装の詳細からテストを切り離すこともできる。

テストにおけるセットアップの共有はリスクもあるため、重複した方が安全である可能性も考える

  • 別のテストケースにも適応させるために、共有のセットアップが意図しない形で書き換えられてしまう恐れがある。
  • セットアップした特定の値や状態にテストケースが依存する場合、テストケースごとに構成を定義した方が安全である。
  • コードの重複が面倒であれば、ヘルパー関数(依存しない値や状態のみを共通化)により問題を回避できる。

プライベートメソッドをテストしたくなった場合、コードをより小さい単位に分割できないか考える

  • テストのためだけにプライベートメソッドを公開するのは避けるべきであり、パブリックメソッドを使用して、実装の詳細ではなく重要な動作をテストする。(ただし、パブリックメソッドに注目するにしても、重要な動作がパブリックメソッド以外にあった場合、ユニットテストでは無視しないようにする。)
  • プライベートメソッドを公開してテストしたくなった場合は、プライベート関数が複雑なロジックを持っている可能性があるので、コードをより小さい単位のクラスなどに分けることを考える。

まとめ

コーディングのベースとなる知見が網羅されており、非常に有益な良書でした。
経験が3年以内のソフトウェアエンジニアを対象としているので、既知の内容も多少ありましたが、例を交えて具体的なユースケースをイメージしながら理解することができたので、なんとなくは感じているけど、なかなか言語化しにくいようなことが腹落ちした感覚でした。
どのようにして堅牢性を保って、持続可能な開発をしていくかという観点で勉強になりました。

Discussion