Good Code, Bad Code 〜持続可能な開発のためのソフトウェアエンジニア的思考(Tom Long)
書籍情報
- 著者:Tom Long(著), 秋勇紀(訳), 高田新山(訳), 山本大祐(訳)
- 発行日:2023-01-28
- ISBN:9784798068169
- URL:https://www.shuwasystem.co.jp/book/b620733.html
書籍目次
- 監訳者まえがき
- 訳者まえがき
- 本語翻訳に際しての謝辞
- 訳者・監訳者プロフィール
- Part 1 理論編
- Chapter 1 コードの品質
- 1.1 コードがソフトウェアになるまで
- 1.2 コードの品質のゴール
- 1.2.1 正しく動くこと
- 1.2.2 正しく動作し続けること
- 1.2.3 要件の変更に対応しやすいこと
- 1.2.4 車輪の再発明をしないこと
- 1.3 コード品質の柱
- 1.3.1 コードを読みやすくする
- 1.3.2 想定外の事態をなくす
- 1.3.3 誤用しにくいコードを書く
- 1.3.4 コードをモジュール化する
- 1.3.5 コードを再利用、汎用化しやすくする
- 1.3.6 テストしやすいコードを書き、適切にテストする
- 1.4 高品質なコードを書くことは、開発のスピードを遅らせるのか
- まとめ
- Chapter 2 抽象化レイヤー
- 2.1 擬似コードでのnullの扱い方
- 2.2 なぜ抽象化レイヤーを作るのか
- 2.2.1 抽象化レイヤーとコード品質の柱
- 2.3 コードのレイヤー
- 2.3.1 APIと実装の詳細
- 2.3.2 関数
- 2.3.3 クラス
- 2.3.4 インターフェイス
- 2.3.5 レイヤーが薄すぎるとき
- 2.4 マイクロサービスとは何か
- まとめ
- Chapter 3 コードでの契約
- 3.1 あなたのコードと他のエンジニアのコード
- 3.1.1 あなたにとっては明確なことでも、他の人にとっては明確ではない
- 3.1.2 他のエンジニアは、不注意であなたのコードを壊す
- 3.1.3 あなたはやがて、あなたのコードのことを忘れる
- 3.2 どうやってあなたのコードの使い方を理解するのか
- 3.2.1 名前に注目する
- 3.2.2 データの型に注目する
- 3.2.3 ドキュメントを読む
- 3.2.4 直接尋ねる
- 3.2.5 コードを読む
- 3.3 コードでの契約
- 3.3.1 契約の中の細則
- 3.3.2 細かいコメントに頼りすぎない
- 3.4 検査とアサーション
- 3.4.1 検査
- 3.4.2 アサーション
- まとめ
- 3.1 あなたのコードと他のエンジニアのコード
- Chapter 4 エラー
- 4.1 回復可能性
- 4.1.1 回復可能なエラー
- 4.1.2 回復不可能なエラー
- 4.1.3 エラーから回復可能かどうかは、呼び出し元だけが知っている
- 4.1.4 呼び出し元が回復可能なエラーを呼び出し元に認識させる
- 4.2 堅牢性 vs 失敗
- 4.2.1 早い失敗
- 4.2.2 目立つ失敗
- 4.2.3 回復可能かどうかの範囲
- 4.2.4 エラーを隠さない
- 4.3 エラーを通知する方法
- 4.3.1 復習:例外
- 4.3.2 明示的: 検査例外
- 4.3.3 暗黙的:非検査例外
- 4.3.4 明示的:null許容型の戻り値
- 4.3.5 明示的:Result型の戻り値
- 4.3.6 明示的:エラーを示す戻り値
- 4.3.7 暗黙的:Promise型かFuture型
- 4.3.8 暗黙的:マジックバリューを返す
- 4.4 回復不可能なエラーを通知する
- 4.5 呼び出し元が回復したい可能性のあるエラーを通知する
- 4.5.1 非検査例外を利用すべきという意見
- 4.5.2 明示的なテクニックを利用すべきという意見
- 4.5.3 筆者の意見:明示的なテクニックを使う
- 4.6 コンパイラーの警告を無視しない
- まとめ
- 4.1 回復可能性
- Chapter 1 コードの品質
- Part 2 実践編
- Chapter 5 コードを読みやすくする
- 5.1 わかりやすい名前を使う
- 5.1.1 わかりづらい名前は、コードを読みにくくする
- 5.1.2 コメントは、わかりやすい名前の不適切な代替である
- 5.1.3 解決策:名前をわかりやすくする
- 5.2 コメントを適切に使う
- 5.2.1 冗長なコメントは有害である
- 5.2.2 コメントは、必ずしも読みやすいコードの代わりにはならない
- 5.2.3 コメントは、コードが存在する理由を説明するのに最適である
- 5.2.4 コメントは、役立つトップレベルの要約を提供できる
- 5.3 コードの行数にこだわらない
- 5.3.1 簡潔ではあるものの理解できないコードは避ける
- 5.3.2 解決策:より多くの行が必要だとしても、コードを読みやすくする
- 5.4 一貫したコーディングスタイルにこだわる
- 5.4.1 一貫性のないコーディングスタイルは混乱を招くリスクがある
- 5.4.2 解決策:スタイルガイドを採用して、それに従う
- 5.5 ネストの深いコードは避ける
- 5.5.1 深くネストしたコードは読みにくい
- 5.5.2 解決策:ネストを最小限に抑えるようにコードを再構築する
- 5.5.3 ネストは多くのことをやりすぎている結果である
- 5.5.4 解決策:コードを小さな関数に分割する
- 5.6 関数の呼び出しを読みやすくする
- 5.6.1 引数が解読困難な場合がある
- 5.6.2 解決策:名前付き引数を使用する
- 5.6.3 解決策:説明的な型を使用する
- 5.6.4 適切な解決策がない場合もある
- 5.6.5 IDEはどうだろうか?
- 5.7 説明のない値を使用しない
- 5.7.1 説明のない値は混乱を招く可能性がある
- 5.7.2 解決策:適切な名前の定数を使用する
- 5.7.3 解決策:適切な名前の関数を使用する
- 5.8 無名関数を適切に使用する
- 5.8.1 無名関数は小さなものへの使用に最適である
- 5.8.2 無名関数は読みにくい
- 5.8.3 解決策:代わりに名前付き関数を使用する
- 5.8.4 巨大な無名関数は問題になる可能性がある
- 5.8.5 解決策:巨大な無名関数を名前付き関数に分割する
- 5.9 すばらしい新しい言語機能を適切に使用する
- 5.9.1 新しい機能でコードを改善できる可能性がある
- 5.9.2 よく知られていない機能は混乱を招く可能性がある
- 5.9.3 処理に最適なツールを使用する
- まとめ
- 5.1 わかりやすい名前を使う
- Chapter 6 想定外の事態をなくす
- 6.1 マジックバリューを戻り値に使わない
- 6.1.1 マジックバリューはバグにつながる可能性がある
- 6.1.2 解決策:null、オプショナル、エラーを返す
- 6.1.3 マジックバリューは思いがけず発生することがある
- 6.2 nullオブジェクトパターンを適切に使用する
- 6.2.1 空のコレクションを返すことでコードが改善することもある
- 6.2.2 空の文字列を返すと、問題が発生することがある
- 6.2.3 より複雑なnullオブジェクトは想定外の事態を起こす可能性がある
- 6.2.4 nullオブジェクトの実装が想定外の事態を起こす可能性がある
- 6.3 予期せぬ副作用の発生を避ける
- 6.3.1 明白に意図的な副作用は問題ない
- 6.3.2 予期せぬ副作用は問題になる可能性がある
- 6.3.3 解決策:副作用を回避するか明白にする
- 6.4 入力パラメーターの変更に注意する
- 6.4.1 入力パラメーターを変更すると、バグが発生する可能性がある
- 6.4.2 解決策:変更する前にコピーする
- 6.5 誤解を招くような関数の書き方を避ける
- 6.5.1 極めて重要な入力値が欠けているときに何もしないと、想定外の事態を起こす可能性がある
- 6.5.2 解決策:重要な入力値を必須にする
- 6.6 将来的にも有効に使い続けられるように設計された列挙型処理
- 6.6.1 将来の列挙値を暗黙的に処理すると、問題が発生する可能性がある
- 6.6.2 解決策:全ケースを網羅したswitch文を使用する
- 6.6.3 デフォルトケースに注意する
- 6.6.4 警告:別のプロジェクトの列挙型に依存する
- 6.7 これらのすべてをテストで解決することはできないか?
- まとめ
- 6.1 マジックバリューを戻り値に使わない
- Chapter 7 誤用しにくいコードを書く
- 7.1 すべてを不変にすることを検討する
- 7.1.1 可変クラスは誤用されやすい
- 7.1.2 解決策:構築時のみに値を設定する
- 7.1.3 解決策:不変性を実現するデザインパターンを使用する
- 7.2 すべてを深く不変にすることを検討する
- 7.2.1 深い可変性は誤用につながる可能性がある
- 7.2.2 解決策:すべてを防御的にコピーする
- 7.2.3 解決策:不変のデータ構造を使用する
- 7.3 あまりにも汎用的なデータ型を避ける
- 7.3.1 あまりにも汎用的な型を使うと、コードを誤用する可能性がある
- 7.3.2 ペア型は誤用しやすい
- 7.3.3 解決策:専用の型を使用する
- 7.4 時間の扱い
- 7.4.1 整数で時刻を表すと、問題が発生する可能性がある
- 7.4.2 解決策:時間に適切なデータ構造を使用する
- 7.5 データに対して信頼できる唯一の情報源を持つ
- 7.5.1 2つ目の信頼できる情報源の存在が、不正な状態につながる可能性がある
- 7.5.2 解決策:一次データを信頼できる唯一の情報源として使用する
- 7.6 ロジックに対する信頼できる唯一の情報源を持つ
- 7.6.1 ロジックに対する信頼できる情報源が複数あるとバグが発生する可能性がある
- 7.6.2 解決策:信頼できる唯一の情報源を持つ
- まとめ
- 7.1 すべてを不変にすることを検討する
- Chapter 8 コードをモジュール化する
- 8.1 依存性注入(DI)の使用を検討する
- 8.1.1 ハードコーディングされた依存関係は問題になる可能性がある
- 8.1.2 解決策:DIを使用する
- 8.1.3 DIを念頭に置いてコードを設計する
- 8.2 インターフェイスに依存する
- 8.2.1 具体的な実装に依存することは変更への対応力を制限する
- 8.2.2 解決策:できる限りインターフェイスに依存する
- 8.3 クラスの継承に注意
- 8.3.1 クラスの継承は問題になる可能性がある
- 8.3.2 解決策: コンポジションを使用する
- 8.3.3 真のis-a関係とは何か?
- 8.4 クラスは自分自身に関心を持つべき
- 8.4.1 他のクラスに関心を持ちすぎると、問題が起きる可能性がある
- 8.4.2 解決策:クラスが自分自身に関心を持つようにする
- 8.5 関連データをまとめてカプセル化する
- 8.5.1 カプセル化していないデータは扱いにくい
- 8.5.2 解決策:関連データをオブジェクトまたはクラスにグループ化する
- 8.6 戻り値の型から実装の詳細が漏洩することに注意する
- 8.6.1 戻り値の型から実装の詳細が漏洩すると、問題になる可能性がある
- 8.6.2 解決策:抽象化レイヤーに適した型を返す
- 8.7 例外の中から実装の詳細が漏洩することに注意する
- 8.7.1 例外から実装の詳細が漏洩すると、問題が発生する可能性がある
- 8.7.2 解決策: 例外を抽象化レイヤーに適したものにする
- まとめ
- 8.1 依存性注入(DI)の使用を検討する
- Chapter 9 コードを再利用、汎用化しやすくする
- 9.1 想定に注意する
- 9.1.1 コードを再利用すると、想定がバグにつながる可能性がある
- 9.1.2 解決策:不必要な想定を避ける
- 9.1.3 解決策:想定が必要な場合は強制する
- 9.2 グローバル状態に注意する
- 9.2.1 グローバル状態がコードを安全に再利用できなくする可能性がある
- 9.2.2 解決策: 共有状態をDIする
- 9.3 デフォルトの戻り値を適切に使用する
- 9.3.1 下位レイヤーでデフォルトの戻り値を使うと、再利用しづらくなる可能性がある
- 9.3.2 解決策:より上位レイヤーでデフォルト値を提供する
- 9.4 関数パラメーターの的を絞る
- 9.4.1 不必要なものを受け取る関数は再利用しづらい
- 9.4.2 解決策:関数が必要なものだけを受け取るようにする
- 9.5 ジェネリクスの使用を検討する
- 9.5.1 特定の型に依存すると、汎用性が低くなる
- 9.5.2 解決策: ジェネリクスを使用する
- まとめ
- 9.1 想定に注意する
- Chapter 5 コードを読みやすくする
- Part 3 ユニットテスト編
- Chapter 10 ユニットテストの原則
- 10.1 ユニットテストの原則
- 10.2 よいユニットテストとは?
- 10.2.1 破損を正確に検出する
- 10.2.2 実装の詳細にとらわれない
- 10.2.3 よく説明された失敗
- 10.2.4 わかりやすいテストコード
- 10.2.5 簡単かつ迅速に実行する
- 10.3 パブリックAPIに注目しても重要な動作は無視しない
- 10.3.1 重要な動作がパブリックAPIの外部にある可能性もある
- 10.4 テストダブル
- 10.4.1 テストダブルを使用する理由
- 10.4.2 モック
- 10.4.3 スタブ
- 10.4.4 モックとスタブが問題になる可能性がある
- 10.4.5 フェイク
- 10.4.6 モックに関する学派
- 10.5 テストのフィロソフィーから選択する
- まとめ
- Chapter 11 ユニットテストの実践
- 11.1 ただ関数をテストするのではなく動作をテストする
- 11.1.1 1関数につき1テストケースでは不十分である
- 11.1.2 解決策: 個々の動作をテストすることに焦点を合わせる
- 11.2 テストのためだけに公開するのは避ける
- 11.2.1 プライベート関数をテストするのは悪いアイデアである
- 11.2.2 解決策:パブリックAPI経由のテストを選ぶ
- 11.2.3 解決策:コードを小さい単位に分ける
- 11.3 一度に1つの動作のみをテストする
- 11.3.1 一度に複数の動作をテストすると、テストが不十分になる
- 11.3.2 解決策:それぞれのシナリオごとにテストケースを作る
- 11.3.3 パラメタライズドテスト
- 11.4 共通のテストのセットアップを適切に使う
- 11.4.1 状態の共有は問題になる可能性がある
- 11.4.2 解決策:状態の共有を避けるかリセットする
- 11.4.3 構成の共有は問題になる可能性がある
- 11.4.4 解決策:テストケースごとに重要な構成を定義する
- 11.4.5 いつ構成の共有をするのが適切か
- 11.5 適切なアサーションマッチャーを使う
- 11.5.1 不適切なマッチャーは失敗の不十分な説明につながる
- 11.5.2 解決策:適切なアサーションマッチャーを使う
- 11.6 DIを使ってテスタビリティを補強する
- 11.6.1 ハードコーディングされた依存関係はテストを不可能にする
- 11.6.2 解決策: DIを利用する
- 11.7 テストについての最後の言葉
- まとめ
- 11.1 ただ関数をテストするのではなく動作をテストする
- Chapter 10 ユニットテストの原則
- Appendix 付録
- Appendix A チョコレートブラウニーのレシピ
- チョコレートブラウニーのレシピ
- 材料
- 作り方
- Appendix B null安全とオプショナル
- B.1 null安全の使用
- B.1.1 nullチェック
- B.2 オプショナルの使用
- Appendix C 追加のコード例
- C.1 ビルダーパターン
- Appendix A チョコレートブラウニーのレシピ
- 訳者あとがき