前回はObserver・Strategy・Decoratorのデザインパターンを学びました。
今回は「クリーンコード」について解説します。 動くコードを書けるようになった次のステップとして、「読みやすく・変更しやすく・バグが起きにくい」コードを書くための考え方とテクニックをまとめます。
クリーンコードとは
「クリーンコード」とは、他の人(未来の自分を含む)が読んで理解しやすいコードのことです。ロバート・C・マーチン(Uncle Bob)が著書「Clean Code」で提唱した概念で、以下の特徴を持ちます。
・意図が明確に伝わる命名
・1つのことだけをするメソッド
・適切なコメント
・重複のないコード(DRY原則)
・適切な抽象化
命名のベストプラクティス
変数・メソッド・クラスの名前はコードの読みやすさに直結します。
// 悪い例: 何を表しているかわからない
int d = 7;
List<String> lst = new ArrayList<>();
void proc(List<String> l) { }
// 良い例: 意図が伝わる
int daysUntilExpiration = 7;
List<String> userNames = new ArrayList<>();
void sendEmailToAllUsers(List<String> userNames) { }
// 悪い例: 略語・略称
int cnt;
String usr;
boolean flg;
// 良い例: 略さない
int count;
String userName;
boolean isLoggedIn;
// boolean変数・メソッドは is/has/can で始める
boolean isValid = true;
boolean hasPermission = false;
boolean canEdit() { return true; }
// コレクションは複数形
List<User> users;
Map<String, Order> ordersByUserId;
メソッドの設計原則
// 悪い例: 1つのメソッドが多くのことをしている
void processOrder(Order order) {
// バリデーション
if (order == null) throw new IllegalArgumentException();
if (order.items().isEmpty()) throw new IllegalStateException();
// 在庫確認
for (var item : order.items()) { ... }
// 合計計算
int total = 0;
for (var item : order.items()) { total += item.price(); }
// メール送信
sendEmail(order.user(), total);
// DB保存
repository.save(order);
}
// 良い例: 1つのことだけする小さなメソッドに分割
void processOrder(Order order) {
validateOrder(order);
checkStock(order);
int total = calculateTotal(order);
notifyUser(order.user(), total);
saveOrder(order);
}
サンプルコード(Before/After比較)
ファイル作成
「Java50」フォルダに CleanCodeExample.java という名前でファイルを作成します

コード入力
以下のコードを入力します。
import java.util.List;
import java.util.stream.Collectors;
public class CleanCodeExample {
// ===== Before: 読みにくいコード =====
static class Before {
static List<String> get(List<String> l, int n) {
List<String> r = new java.util.ArrayList<>();
for (String s : l) {
if (s != null && !s.isEmpty() && s.length() > n) {
r.add(s.toUpperCase());
}
}
return r;
}
}
// ===== After: クリーンなコード =====
static class After {
static List<String> getLongWordsUpperCased(List<String> words, int minLength) {
return words.stream()
.filter(word -> isValidWord(word, minLength))
.map(String::toUpperCase)
.collect(Collectors.toList());
}
private static boolean isValidWord(String word, int minLength) {
return word != null && !word.isEmpty() && word.length() > minLength;
}
}
// ===== Before: マジックナンバー =====
static class BeforeNumbers {
static String getStatus(int code) {
if (code == 1) return "処理中";
if (code == 2) return "完了";
if (code == 3) return "エラー";
return "不明";
}
}
// ===== After: 定数・enumを使う =====
static class AfterNumbers {
enum OrderStatus {
PROCESSING(1, "処理中"),
COMPLETED(2, "完了"),
ERROR(3, "エラー");
final int code;
final String label;
OrderStatus(int code, String label) {
this.code = code;
this.label = label;
}
static OrderStatus fromCode(int code) {
for (OrderStatus status : values()) {
if (status.code == code) return status;
}
throw new IllegalArgumentException("不明なコード: " + code);
}
}
static String getStatus(int code) {
return OrderStatus.fromCode(code).label;
}
}
// ===== Before: ネストが深い =====
static class BeforeNested {
static String process(String input) {
if (input != null) {
if (!input.isEmpty()) {
if (input.length() <= 100) {
return input.trim().toUpperCase();
} else {
return "長すぎます";
}
} else {
return "空です";
}
} else {
return "nullです";
}
}
}
// ===== After: 早期リターンでネストを減らす =====
static class AfterNested {
static String process(String input) {
if (input == null) return "nullです";
if (input.isEmpty()) return "空です";
if (input.length() > 100) return "長すぎます";
return input.trim().toUpperCase();
}
}
public static void main(String[] args) {
List<String> words = List.of("hi", "hello", "java", "programming", "a");
System.out.println("--- Before vs After ---");
System.out.println("Before: " + Before.get(words, 3));
System.out.println("After: " + After.getLongWordsUpperCased(words, 3));
System.out.println("\n--- マジックナンバー ---");
System.out.println("Before: " + BeforeNumbers.getStatus(2));
System.out.println("After: " + AfterNumbers.getStatus(2));
System.out.println("\n--- ネストの解消 ---");
String[] inputs = {null, "", "hello", "a".repeat(101)};
for (String input : inputs) {
String before = BeforeNested.process(input);
String after = AfterNested.process(input);
System.out.println("Before: " + before + " / After: " + after);
}
}
}

コンパイル
javac CleanCodeExample.java

実行
java CleanCodeExample
実行結果は次のようになります。
--- Before vs After ---
Before: [HELLO, JAVA, PROGRAMMING]
After: [HELLO, JAVA, PROGRAMMING]
--- マジックナンバー ---
Before: 完了
After: 完了
--- ネストの解消 ---
Before: nullです / After: nullです
Before: 空です / After: 空です
Before: HELLO / After: HELLO
Before: 長すぎます / After: 長すぎます

その他のベストプラクティス
コメントの書き方
// 悪い例: コードの繰り返し(コードを読めばわかる)
// iに1を足す
i++;
// 良い例: 「なぜ」そうするかを説明する
// うるう年の計算では2月を28日として扱う必要があるため+1する
i++;
例外の扱い方
// 悪い例: 例外を握りつぶす
try {
doSomething();
} catch (Exception e) {
// 何もしない
}
// 良い例: 例外を適切に処理するかログに残す
try {
doSomething();
} catch (IOException e) {
logger.warning("ファイル操作に失敗しました: " + e.getMessage());
throw new RuntimeException("処理に失敗しました", e);
}
定数の扱い方
// 悪い例: マジックナンバー
if (score >= 60) { ... }
Thread.sleep(1000);
// 良い例: 定数として名前をつける
static final int PASSING_SCORE = 60;
static final long ONE_SECOND_MS = 1000L;
if (score >= PASSING_SCORE) { ... }
Thread.sleep(ONE_SECOND_MS);
コードの重複を避ける(DRY原則)
// 悪い例: 同じロジックが複数箇所に
double calcCircleArea(double r) { return 3.14159 * r * r; }
double calcCirclePerimeter(double r) { return 2 * 3.14159 * r; }
// 良い例: 定数として1か所に
static final double PI = Math.PI;
double calcCircleArea(double r) { return PI * r * r; }
double calcCirclePerimeter(double r) { return 2 * PI * r; }
まとめ
クリーンコードの基本は「意図が伝わる命名」「1つのことだけするメソッド」「マジックナンバーを使わない」「ネストを減らす早期リターン」「DRY原則(重複をなくす)」の5つです。どれも「動くコード」を「読めるコード」に変えるための工夫です。コードは書く時間より読まれる時間の方がはるかに長いため、読みやすさへの投資は必ず報われます。今日からコードレビューやリファクタリングの際にこれらの観点を意識してみてください。
2026/08/10(月) ぱんまる


コメント