🎉

Clean Code アジャイルソフトウェア達人の技まとめ

に公開

はじめに

※この内容は「Clean Code アジャイルソフトウェア達人の技」を読んで、自分の理解でまとめたものです。
※内容は書籍の趣旨と異なる解釈をしている可能性があります。あくまで私の理解・整理用メモです。
※誤りなどがあればご指摘いただけると幸いです。

第2章 意味のある名前

✅ わかりやすく区別できる名前を使う

  • customerInfocustomeraccountDataaccount のように違いが不明確な名前は避ける
  • 名前に本質的な意味の違いを持たせる。

✅ 発音可能な名前を使う

  • 発音できない名前(例:genymdhms)は会話での共有が難しい。
  • 会話が成り立つ名前(例:generationTimestamp)を選ぶ。

✅ 検索可能な名前を使う

  • 一文字の変数(例:a, x)や数字のみの名前(例:int 1 = 42)は検索に不便。
  • 検索性を意識して命名する
  • 1文字の変数は、小さなスコープのローカル変数のみ許容

✅ 名前への型情報の埋め込み(エンコーディング)を避ける

  • 接頭辞として m をつけたり(例:mName)、インターフェースに I をつける(例:IShapeFactory)のは避ける。
  • より自然な名前を使うべき:
    • ShapeFactory(インターフェース)
    • DefaultShapeFactoryShapeFactoryImpl(実装)

✅ クラス名には名詞を使う

  • クラス名は概念を表すべき。
  • ManagerProcessorDataInfo のような曖昧な名前は避ける。

✅ メソッド名には動詞を使う

  • メソッドは行動を表すため、動詞で始めるのが自然。

  • コンストラクタがオーバロードされている場合は、staticなファクトリメソッドを用意し、コンストラクタをprivateにする

public class Person {
    private String name;
    private int age;

    // コンストラクタを private にする
    private Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // ファクトリメソッド
    public static Person createWithName(String name) {
        return new Person(name, 0);
    }

    public static Person createWithNameAndAge(String name, int age) {
        return new Person(name, age);
    }

    public static Person createDefault() {
        return new Person("Unknown", 0);
    }
}
  • ファクトリメソッドにより生成目的が明確になる。
  • new を外部から直接呼ばせないことで、意図しない使い方を防ぐ

✅ 一つの概念には一つの言葉を

  • 同じような意味の単語(例:get, fetch, retrieve)を混在させない。
  • プロジェクト内で単語は統一する。

✅ 意味のある文脈を加える

  • firstName, lastName, street, houseNumber などの変数がバラバラに存在していると意味が伝わりにくい。
  • これらは Address クラスとしてまとめることで、文脈が明確になる。
public class Address {
    private String street;
    private String houseNumber;
    private String city;
    private String postalCode;
}

第3章 関数

✅ 関数は小さく!

  • 2〜4行で書くのが理想。20行を超えてはならない
  • 関数が短いことで読みやすく、保守しやすい

✅ ブロックとインデントを最小限に

  • if, while などの制御構文のブロックは1行で書く。
  • ネストを避ける。関数のインデントレベルは1〜2まで
  • ネスト構造を見たら、新しい関数に抽出するチャンス

✅ 関数は「一つのこと」だけを行う

  • 関数名が一つの抽象レベルで表現されるステップのみを含んでいれば、「一つのこと」をしている。
  • その関数の中でさらに説明が必要なら、一つ以上のことをしている可能性あり

✅ 一つの関数に一つの抽象レベル

  • getHtml() のような高レベルの関数内で .append("\n") などの低レベル処理を書くと、読みにくくなる
  • 抽象レベルを混在させない。

✅ 逓減規則(Step-down Rule)

  • 関数は抽象度が高いものから順番に並べる。
  • コードを上から読んでいくだけで、処理の全体像が自然に理解できる構造に。

switch の扱い方

  • switch低レベルの抽象ファクトリとして1回だけ使うべき。
  • 頻繁に switch を使うのは以下の原則に違反:
    • 単一責任の原則(SRP)
    • オープン・クローズドの原則(OCP)

❌ 悪い例:

public static int calculateSalary(Employee employee) {
    switch (employee) {
        case "Manager":
            return calculateManagerPay();
        case "Engineer":
            return calculateEngineerPay();
        case "Intern":
            return calculateInternPay();
        default:
            throw new IllegalArgumentException("Unknown employee");
    }
}

✅ 良い設計(ポリモーフィズム + 抽象ファクトリ):

abstract class Employee {
    public abstract boolean isPayDay();
    public abstract Money calculatePay();
    public abstract void deliverPay(Money pay);
}

class Manager extends Employee { ... }
class Engineer extends Employee { ... }
class Intern extends Employee { ... }

class EmployeeFactory {
    public static Employee createEmployee(String type) {
        switch (type) {
            case "Manager": return new Manager();
            case "Engineer": return new Engineer();
            case "Intern": return new Intern();
            default: throw new IllegalArgumentException("Unknown type");
        }
    }
}

public class SalaryCalculator {
    public static void processPay(Employee employee) {
        if (employee.isPayDay()) {
            Money pay = employee.calculatePay();
            employee.deliverPay(pay);
        }
    }
}
  • EmployeeFactoryswitch だけが変更点になるので、OCPは守られる。

✅ 引数は少なく!

  • 引数の数は0が理想、最大でも2まで
  • 3つ以上の引数は読みづらく、テストも困難。
  • 出力引数は使わないこと(混乱を招く)。

✅ フラグ引数はNG

printReport(true); // 一体何を意味しているのか分かりにくい
  • true/falseで処理が変わる = 複数の責務を持っているサイン。

✅ 引数オブジェクトを使う

  • 引数が多い場合はドメインオブジェクトにラップ。
report(String title, String author, String date); // ❌

report(ReportMeta meta); // ✅

✅ 副作用を避ける

  • 関数が意図しない処理を密かに行うことは避けるべき。
boolean checkPassword(String password) {
    session.initialize(); // ❌ セッションの初期化は副作用
    return password.equals("secret");
}

✅ 出力引数は避ける

public void appendFooter(Report report); // ❌
  • 「レポートを追記する」のか「レポートに追記する」のか不明。
report.appendFooter(); // ✅
  • オブジェクト自身が自身の状態を変える設計が望ましい。

✅ コマンド・クエリ分離原則(CQS)

  • 状態を変更する関数(コマンド)と、情報を返す関数(クエリ)を分ける
if (set("userName", "wakamatu57")) { ... } // ❌ わかりにくい

if (attributeExists("wakamatu57")) {
    setAttribute("userName", "wakamatu57"); // ✅
}

✅ tryブロックは分離する

  • try/catch/finally の中身は関数として外に出す。
try {
    saveRecord();
} catch (Exception e) {
    logError(e);
}
  • 正常系の処理とエラーハンドリングの責務を分離できる。

第4章 コメント

❌ コメントは“悪いコード”を隠す手段ではない

  • コメントは最終手段
    書く前にまずは「関数名」「変数名」「構造の改善」で表現できないかを考える。
  • コメントを書くということは「コードで意図を表現する努力を放棄した」というサインでもある。

✅ 良いコメント

種類 内容
真っ当なコメント 著作権、著者、法的情報など
意図の説明 例外的なコードや一見不自然な処理の理由を記述
明確化 意図が伝わりにくい標準ライブラリや外部コードの補足
TODOコメント 一時的な未実装箇所のメモ(※なるべく早く潰す)
強調 意図的な例外処理など、あえて筋が通らないように見える箇所の注意書き

❌ よくないコメント

種類 内容
冗長なコメント そのままコードを読めばわかる内容(≒ノイズ)
誤解を招くコメント 実際のロジックと食い違う説明
命令コメント @param などJavadocで代替可能なもの(強制的なスタイル指示)
日誌コメント Gitに履歴があるので不要
ノイズコメント // increment i のような意味が明白なコメント
とじかっこコメント // end if// } など:関数が長すぎる兆候、リファクタ推奨
コメントアウトされたコード Gitで履歴管理されているため削除するべき
非局所的な情報 他ファイルや他クラスに依存した説明。保守性が落ちる
多すぎる情報 履歴・議論の経緯などはドキュメントやPRに記録すべき

🎯 結論:コメントではなくコードで語る

  • 読みやすいコード > 説明が必要なコード
  • 名前、関数の構造、責務の分離によって「コメント不要なコード」を目指すこと。

以下に「第5章 書式化」の内容をマークダウン形式でまとめました。読みやすさや構造を意識して整理しています。

第5章 書式化

✍️ コードの整形は「読みやすさ」のためにある

コードの読みやすさ=保守性の高さ
美しく整ったコードは、変更やバグ修正もしやすくなる。


🧱 縦方向の書式化(Vertical Formatting)

✅ ファイル全体の長さ

  • 1ファイルあたり200行〜最大でも500行程度が目安。

✅ 新聞スタイル(新聞の見出しのように)

  • 上から下に読む構造。
  • 高レベルな概念(全体像) → 低レベルな詳細(実装) の順に記述する。

✅ 垂直概念分離性

  • セクションごとに空行を挟むことで視認性を高める。
  • 例:パッケージ宣言、インポート、クラス定義、各メソッドの間には空行を。

✅ 垂直密度

  • 関連性の高い変数や関数は近くに置くことで理解しやすくなる。
  • コメントアウトされたコードなどが間にあると「関連の引き裂き」が起こるため注意。

✅ 垂直距離

  • 変数宣言は使用される位置の近くに置く。
  • インスタンス変数はクラスの先頭にまとめておく。
  • メソッドの順番は上から順に読むように設計する(呼び出し元→呼び出し先)。

✅ 垂直方向の並び順

  • 高レベル(抽象的)なメソッドが上にくる
  • 例:
    public void mainProcess() {
        doTask();
    }
    
    private void doTask() {
        // 実処理
    }
    

📏 横方向の書式化(Horizontal Formatting)

  • 1行の長さは100〜120文字以内を目安に。
  • 長くなりすぎると、横スクロールや視線の移動が大きくなり、可読性が下がる。

6章 オブジェクトとデータ構造

🔸 データ抽象

  • public なインスタンス変数を定義するのではなく、private にして、メソッド経由でアクセスするのが望ましい。
  • しかし、単なるセッター/ゲッターの提供は意味がなく、ナンセンス
  • 抽象的なインターフェースを公開し、内部データの詳細を隠蔽したまま、利用者に操作させるのが良い。

🔸 データ/オブジェクトの非対称性

  • オブジェクト
    • データを隠蔽し、データを操作する 機能(振る舞い)を提供
    • クラス(型)を 追加するのは容易だが、関数(メソッド)の追加は 困難
  • データ構造
    • データをそのまま 公開し、意味のある関数を持たない
    • クラスの追加は 困難だが、関数の追加は 容易

🔸 デメテルの法則(Law of Demeter)

「知る必要のないことを知ってはいけない」

  • クラス C のメソッド f は次のようなメソッド呼び出しだけを行うべき:
    1. C 自身のメソッド
    2. f 内で生成されたオブジェクトのメソッド
    3. f の引数で渡されたオブジェクトのメソッド
    4. C のインスタンス変数に保持されたオブジェクトのメソッド

🚨 電車の衝突(Train Wreck)

order.getCustomer().getAddress().getZipCode();
  • このような連鎖的な呼び出しは、デメテルの法則に違反しており、オブジェクト内部構造への依存度が高くなってしまう。

🔸 データ転送オブジェクト(DTO)

  • 関数を持たず、public な変数だけを持つデータ保持用のクラス
  • 結果のやり取り、データの伝達に便利。

🔸 アクティブレコード(Active Record)

  • DTOに save()find() などの 永続化用メソッドを追加した構造
  • ただし、ビジネスロジックはこの中に書くべきではない
  • ビジネスロジックは別のエンティティクラスに分離するべき。

以下は、第7章「エラー処理」 の内容をマークダウン形式に整えたものです:


7章 エラー処理

✅ リターンコードではなく例外を使用する

  • リターンコードによるエラー処理は、呼び出し元のコードを煩雑にしやすい
  • 例外(try-catch)を使うことで、エラー処理と通常処理を分離できる

✅ 最初に try-catch-finally を書く

  • まずは例外が発生することを前提に catchに入るテストコードを書く
  • その後、try ブロックに必要なロジックを追加する。
  • 例外処理が明確で、テストしやすいコードになる。

✅ 非チェック例外(RuntimeException)を使う

  • チェック例外(例:IOException)は関数の呼び出し階層すべてに影響を与える。
  • 非チェック例外であれば、高レベル関数を変更せずに例外処理ができる

✅ 呼び出し元が必要とする例外クラスを定義する

  • サードパーティの関数からの例外を、アプリケーション独自の例外でラップすると便利。

✅ 正常ケースのフローを定義する

  • catch 節の中に重要なロジックがあると、正常な流れが分断されて読みづらい
  • **「スペシャルケースパターン」**を用いて、例外を回避する設計が望ましい。

❌ Before: null チェックで分岐

function getCustomer(customerId) {
  return database.find(customerId); // 見つからなければ null
}

const customer = getCustomer(id);

if (customer) {
  console.log(customer.getName());
  console.log(customer.getDiscount());
} else {
  console.log("ゲスト");
  console.log(0.0);
}

✅ After: スペシャルケースパターン

class Customer {
  getName() {
    return "普通の顧客の名前";
  }

  getDiscount() {
    return 0.1;
  }
}

class UnknownCustomer extends Customer {
  getName() {
    return "ゲスト";
  }

  getDiscount() {
    return 0.0;
  }
}

function getCustomer(customerId) {
  const customer = database.find(customerId);
  return customer ? customer : new UnknownCustomer();
}

const customer = getCustomer(id);
console.log(customer.getName());
console.log(customer.getDiscount());
  • 呼び出し元は CustomerUnknownCustomer かを意識する必要がなく、コードがシンプルに保たれる

null を返さない

  • null チェックが多発すると、読みづらく、バグの温床に
  • 代わりに以下の方法を検討:
    • 例外をスローする
    • スペシャルケース(例:UnknownCustomer)を返す
    • 空のリストやオブジェクトを返す

null を渡さない

  • null を渡すと、nullチェック + 本来のロジックという二重の責任が生じる。
  • 単一責任の原則に反するため、原則として禁止

8章 境界

✅ サードパーティのコードを使用する

  • サードパーティのフレームワークやパッケージは便利だが、特定のニーズに特化したインターフェースを提供すべき。
  • 例えば、Sensorsクラス内で Map を使う場合、外部には Map の機能を公開しない
    • 必要なメソッドのみ公開して、内部の実装に依存しないようにする。
public class Sensors {
    private Map<String, Sensor> sensorMap = new HashMap<>();

    public void addSensor(String id, Sensor sensor) {
        sensorMap.put(id, sensor);
    }

    public Sensor getSensor(String id) {
        return sensorMap.get(id);
    }

    public boolean containsSensor(String id) {
        return sensorMap.containsKey(id);
    }
}
  • こうすることで、変更の影響を最小限に抑えることができる。

✅ まだ存在しないコードを使用する

  • 開発中のAPIをインターフェースで定義し、利用者側のクラスを先に作成することでコードが整理される
  • 実際の具象クラスは、インターフェースを実装する形で依存するようにする。
    • これがアダプターパターン

✅ きれいな境界

  • システム全体のコードがサードパーティの詳細処理を呼び出すのは避けるべき。
  • サードパーティとやりとりする部分を小さな境界に閉じ込め、その範囲だけに依存させる。

了解!9章の内容を、前回までと同じスタイルでマークダウンにまとめつつ、
良い例は ✅ 緑チェック、悪い例は 🚫 赤バツ に切り分けて整理したよ。


9章:単体テスト


✅ TDD(テスト駆動開発)三原則

  1. 失敗する単体テストを書く前に、製品コードを書かない
  2. コンパイルが通り、失敗するテストを書くまでは次のテストを書かない
  3. 失敗中のテストがパスするまで、プロダクションコードを追加しない

✅ テストは「変更可能性」を高める

  • テストがあることで、リファクタリングや機能追加が怖くなくなる
  • テストがないと、動いてるコードを壊すのが怖くて手が出せない。

✅ クリーンテスト構造(構築-操作-検査)

テストは以下の3ステップ構成にすると読みやすい:

構築:テストデータや状態を準備する  
操作:テスト対象の処理を呼び出す  
検査:期待どおりの結果かをassertで確認する  

✅ ドメイン特化テスト言語(DSL)

  • テストコード内に専用関数やユーティリティを定義して、読みやすく表現する
  • メリット:
    • テストが自己記述的になる
    • 読んで理解しやすい
    • 実装の変更にテストが強くなりすぎない

✅ 二重規範を避ける

assertTrue(condition)assertEquals(expected, actual) など、引数の順番で迷うようなコード

✅ 専用アサート関数を作って意味が明確な名前で包む

// 悪い例
assertTrue(user.isLoggedIn());

// 良い例
assertUserIsLoggedIn(user);

パフォーマンスより読みやすさ・意図の明確さを優先してOK


❌ 1つのテストに複数のassertをすべきではない

  • assertはなるべく少なめが理想
  • ただし「1つだけにする必要はない」
     → 1つの概念に対して複数のassertはOK

10章:クラス


✅ クラスの構成ルール

  • インスタンス変数の順番:

    • public staticprivate staticprivate
    • publicなインスタンス変数は禁止
  • メソッドの並び順:

    • publicprivateユーティリティprivate
    • 上から順に自然に読めるように構成

✅ クラスは小さく

  • 関数の小ささ=行数
  • クラスの小ささ=責務の数で判断

🚫 名前が曖昧なクラス名(例:Manager, Processor, Super)はNG
✅ クラス名はその責務がハッキリ伝わるものにする

  • クラスの簡単な説明に「そして」「しかし」が出てくるなら責務が多すぎる証拠

✅ 単一責任の原則(SRP)

  • クラスの変更理由は1つだけにするべき
  • 「なんでも屋クラス」はやめて、小さなクラスをいっぱい作る方が良い

✅ 凝集性を高める

  • メソッドがクラスのインスタンス変数を多く使っている → 凝集性が高い
  • 凝集性が高いと、そのクラスが1つのまとまりとしてしっかり機能している証拠

✅ 凝集性に気を配ると大量の小さなクラスが生まれる

  • 大きなメソッドを小さく分割する
  • 引数で変数を渡しあうくらいなら → インスタンス変数にすべき
  • インスタンス変数が増えすぎると、凝集性が下がる

✅ 上記のような場合は、別クラスに切り出すのがいい


✅ 変更しやすさを設計でカバー(OCP)

  • クラスに新機能を追加したいけど、既存機能は壊したくない場合は
    • 抽象クラスやインターフェースを用意
    • 新しいクラスを継承して追加
    • 既存コードに手を加えず拡張できる(OCP:Open/Closed Principle)

✅ 依存から切り離す(DIP)

依存性逆転の原則:

  • 上位モジュールは下位モジュールに依存しない
  • 両者ともに「抽象に依存」すべし
  • 詳細(具象)は抽象に依存すべし

Discussion