Java | 基礎文法:バッドパターン例(巨大メソッドなど)

Java Java
スポンサーリンク

バッドパターンの全体像

バッドパターンは「動くけど、壊れやすく、読みにくく、直しにくい」コードの形です。巨大メソッド、深いネスト、長すぎる引数、フラグ引数、マジックナンバー、神クラス(God Class)、グローバル可変状態、コピペ重複、雑な例外処理、早すぎる最適化などが典型です。問題は機能ではなく形にあり、形を正せばバグは減り、変更が楽になります。


巨大メソッド(長すぎ・責務過多)

何が悪いか

1つのメソッドに「入力検証、変換、計算、I/O、ロギング、エラーハンドリング」が全部入っていると、読みにくくテスト困難で、変更時に副作用が広がります。目安として、1メソッドは目的1つ、10〜30行程度に保ち、異質な処理は必ず分割します。

悪い例と直し方

悪い例:

int process(Order o, Path path, double rate) throws IOException {
    if (o == null || o.items().isEmpty()) throw new IllegalArgumentException();
    int subtotal = 0;
    for (Item it : o.items()) subtotal += it.price();
    int tax = (int) Math.round(subtotal * rate);
    int total = subtotal + tax;
    Files.writeString(path, "TOTAL=" + total);
    System.out.println("done");
    return total;
}
Java

良い形(責務ごとに分割):

int process(Order o, Path path, double rate) throws IOException {
    validate(o);
    int total = calcTotal(o, rate);
    writeTotal(path, total);
    logDone();
    return total;
}

void validate(Order o) {
    if (o == null || o.items().isEmpty()) throw new IllegalArgumentException("order empty");
}
int calcTotal(Order o, double rate) {
    int subtotal = o.items().stream().mapToInt(Item::price).sum();
    int tax = (int) Math.round(subtotal * rate);
    return subtotal + tax;
}
void writeTotal(Path path, int total) throws IOException {
    Files.writeString(path, "TOTAL=" + total);
}
void logDone() { System.out.println("done"); }
Java

深いネスト(if/else/for が入れ子地獄)

何が悪いか

ネストが深いと「どこで何が起きるか」が追いづらく、境界条件や例外パスを見落とします。早期リターン、ガード節、メソッド抽出で「左を浅く」保ちます。

悪い例と直し方

悪い例:

if (user != null) {
    if (!user.name().isBlank()) {
        if (user.active()) {
            send(user);
        } else {
            warn("inactive");
        }
    } else {
        warn("no name");
    }
} else {
    warn("null");
}
Java

良い形(ガードで浅くする):

if (user == null) { warn("null"); return; }
if (user.name().isBlank()) { warn("no name"); return; }
if (!user.active()) { warn("inactive"); return; }
send(user);
Java

長すぎる引数・フラグ引数

何が悪いか

引数が多い(5個以上)と理解が難しく、順番ミスが起きます。真偽フラグ(doFast=true など)は「メソッドに2つの動作」を押し込むサイン。引数オブジェクト化、ビルダー、動作を分けたメソッドへ切り出します。

悪い例と直し方

悪い例:

void export(String path, String format, boolean pretty, boolean includeHeader, int indent) { /* ... */ }
Java

良い形(設定オブジェクトで意味を束ねる):

record ExportConfig(String format, boolean pretty, boolean includeHeader, int indent) {}
void export(Path path, ExportConfig cfg) { /* ... */ }

// あるいは動作を分ける
void exportCompact(Path path, String format) { /* ... */ }
void exportPretty(Path path, String format, int indent) { /* ... */ }
Java

マジックナンバー・コピペ重複

何が悪いか

数字・文字列リテラルが散在すると、意味が伝わらず、変更漏れが誘発されます。重複したロジックは修正漏れの温床。定数化と共通メソッド抽出で「一箇所」に集約します。

悪い例と直し方

悪い例:

double priceWithTax(double price) { return price * 1.1; }   // 1.1 は何?
double fee(double amount) { return amount * 0.1; }          // 0.1 は何?
Java

良い形(意味のある定数):

final class Tax {
    static final double RATE = 0.10;
    static double add(double price) { return price * (1 + RATE); }
    static double fee(double amount) { return amount * RATE; }
}
Java

重複の抽出:

int roundedTax(int subtotal, double rate) {
    return (int) Math.round(subtotal * rate);
}
Java

神クラス・グローバル可変状態

何が悪いか

「何でも知っていて何でもやる」クラスは変更影響が巨大化し、テストが難しく、並行性バグの原因になります。役割を分割し、依存は注入(DI)してテスト可能に。グローバルな可変状態は極力排除し、必要なら同期・不変オブジェクトで制御。

悪い例と直し方

悪い例:

class App {
    static Map<String, Integer> cache = new HashMap<>();
    // ここにI/O、計算、UI、DB、全てが詰まっている
}
Java

良い形(役割分割と依存注入):

final class Cache {
    private final Map<String, Integer> map = new HashMap<>();
    int getOrDefault(String k, int d) { return map.getOrDefault(k, d); }
}
final class Service {
    private final Cache cache;
    Service(Cache cache) { this.cache = cache; }
    /* 役割限定のメソッド群 */
}
Java

例外処理のバッドパターン(握りつぶし・不統一)

何が悪いか

例外を空 catch で無視すると原因が闇に消えます。場所ごとに対応がバラバラだと、再発と調査コストが増大。方針(再試行・包み直し・上位へ委譲)を決めて統一し、必ず原因連鎖(cause)を保持します。

悪い例と直し方

悪い例:

try {
    save();
} catch (Exception e) {
    // 何もしない
}
Java

良い形(原因を伝播):

try {
    save();
} catch (SQLException e) {
    throw new IllegalStateException("保存失敗 id=" + id, e); // cause を保持
}
Java

早すぎる最適化・マイクロ最適化

何が悪いか

測定せずに「速そう」に書くと、読みやすさと安全性を犠牲にし、効果がないことが多い。ボトルネックを計測してから、アルゴリズム・データ構造・I/O設計のレイヤで対処します。

悪い例と直し方

悪い例:

// String連結を自前のループで最適化したつもり
String s = "";
for (String t : list) s += t; // 実際は遅い
Java

良い形:

String s = String.join("", list); // or StringBuilder を使う
Java

隠れた副作用・暗黙の依存

何が悪いか

メソッドが「引数以外の外部状態」を書き換えると、呼び手が予測できずバグになります。副作用は明示し、純粋な計算と I/O を分離。必要なら「入力→出力」を関数として閉じる。

悪い例と直し方

悪い例:

int calcAndUpdate(int x) {
    global += x;  // 暗黙の副作用
    return global * 2;
}
Java

良い形:

int calc(int base, int x) { return (base + x) * 2; }
Java

リファクタリングの型(小さく安全に直す)

ステップと具体技術

  • 目的を1つに絞ってメソッド抽出(Extract Method)
  • 早期リターンでネストを削る
  • 引数オブジェクト化・ビルダー導入
  • 定数化・重複抽出・ユーティリティへ集約
  • 依存を注入し、テストスタブで検証
  • 例外方針を統一し、cause を保持

例:巨大メソッドを分割しテスト可能に

int process(Order o, Path path, double rate) throws IOException {
    validate(o);
    int total = calcTotal(o, rate);
    writeTotal(path, total);
    return total;
}
// 単体テストは calcTotal と validate を個別に検証
Java

仕上げのアドバイス(重要部分のまとめ)

巨大メソッドは責務分割、深いネストは早期リターンで浅く、長い引数は設定オブジェクト化、フラグ引数は動作分割。マジックナンバーと重複は定数化・抽出で一箇所に、神クラス・グローバル可変は役割分割と依存注入で解体。例外は cause を保って方針統一、最適化は計測してから——この型を習慣にすれば、「動くけど壊れやすい」から「読みやすく直しやすい」へ確実に進化します。

タイトルとURLをコピーしました