バッドパターンの全体像
バッドパターンは「動くけど、壊れやすく、読みにくく、直しにくい」コードの形です。巨大メソッド、深いネスト、長すぎる引数、フラグ引数、マジックナンバー、神クラス(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 を保って方針統一、最適化は計測してから——この型を習慣にすれば、「動くけど壊れやすい」から「読みやすく直しやすい」へ確実に進化します。
