はじめに
「このコード、何してるんだっけ」と、自分が書いたコードに首を傾げた経験はありませんか。
コードは書く時間より、読まれる時間のほうがはるかに長いと言われています。
この記事では、未来の自分とチームメンバーのために、動くけど読みにくいコードを読みやすいコードに変えるパターンをBefore/After形式で紹介します。
① 変数名に意図を込める
Before
List<User> list = userRepository.findAll();
List<User> result = new ArrayList<>();
for (User u : list) {
if (u.getAge() >= 18 && u.isActive()) {
result.add(u);
}
}
return result;
list、result、u という変数名から何を意味するかは読み取れません。処理を追ってようやく「ああ、成人のアクティブユーザーを絞っているのか」とわかります。
After
List<User> allUsers = userRepository.findAll();
List<User> activeAdultUsers = new ArrayList<>();
for (User user : allUsers) {
if (user.getAge() >= 18 && user.isActive()) {
activeAdultUsers.add(user);
}
}
return activeAdultUsers;
変数名を変えただけで、処理の意図がすぐに伝わります。u という名前では、初めて読んだ人が「これは何を指しているのか」と一瞬立ち止まることになります。
② マジックナンバーを定数にする
Before
if (user.getAge() >= 18) {
// 処理
}
if (score >= 80) {
rank = "A";
} else if (score >= 60) {
rank = "B";
}
18・80・60 が何を意味するのか、コードだけでは判断できません。
After
private static final int ADULT_AGE = 18;
private static final int RANK_A_THRESHOLD = 80;
private static final int RANK_B_THRESHOLD = 60;
if (user.getAge() >= ADULT_AGE) {
// 処理
}
if (score >= RANK_A_THRESHOLD) {
rank = "A";
} else if (score >= RANK_B_THRESHOLD) {
rank = "B";
}
定数名がコメントの役割も果たします。また、値を変更するときに1箇所だけ修正すればよくなります。
③ 早期リターンでネストを浅くする
Before
public String getDisplayName(User user) {
if (user != null) {
if (user.isActive()) {
if (user.getNickname() != null) {
return user.getNickname();
} else {
return user.getName();
}
} else {
return "退会済みユーザー";
}
} else {
return "不明";
}
}
ネストが深く、どの return がどの条件に対応しているか追いにくくなっています。
After
public String getDisplayName(User user) {
if (user == null) return "不明";
if (!user.isActive()) return "退会済みユーザー";
if (user.getNickname() != null) return user.getNickname();
return user.getName();
}
異常系・例外的ケースを先にreturnして、本筋の処理を最後に書く形です。読む方向が「上から下」に一本化されて追いやすくなります。
④ 1つのメソッドに1つの責務を持たせる
Before
public void process(Long userId) {
// ユーザー取得
User user = userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("ユーザーが見つかりません"));
// ポイント計算
int point = 0;
if (user.getRank().equals("GOLD")) {
point = 1000;
} else if (user.getRank().equals("SILVER")) {
point = 500;
} else {
point = 100;
}
// メール送信
String message = "こんにちは " + user.getName() + " さん。" + point + "ptを付与しました。";
mailService.send(user.getEmail(), message);
// DB更新
user.setPoint(user.getPoint() + point);
userRepository.save(user);
}
「ユーザー取得」「ポイント計算」「メール送信」「DB更新」が1つのメソッドに詰め込まれています。テストも書きにくく、変更の影響範囲も広くなります。
After
public void process(Long userId) {
User user = findUserById(userId);
int point = calculatePoint(user.getRank());
addPointAndSave(user, point);
sendPointNotification(user, point);
}
private User findUserById(Long userId) {
return userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("ユーザーが見つかりません"));
}
private int calculatePoint(String rank) {
return switch (rank) {
case "GOLD" -> 1000;
case "SILVER" -> 500;
default -> 100;
};
}
private void addPointAndSave(User user, int point) {
user.setPoint(user.getPoint() + point);
userRepository.save(user);
}
private void sendPointNotification(User user, int point) {
String message = "こんにちは " + user.getName() + " さん。" + point + "ptを付与しました。";
mailService.send(user.getEmail(), message);
}
process() を読むだけで処理の流れが把握できます。各メソッドが独立しているのでテストも書きやすくなります。
⑤ Stream APIで意図を明確にする
Before
List<String> result = new ArrayList<>();
for (User user : users) {
if (user.isActive()) {
result.add(user.getName().toUpperCase());
}
}
After
List<String> result = users.stream()
.filter(User::isActive)
.map(user -> user.getName().toUpperCase())
.collect(Collectors.toList());
Stream APIを使うと「何を絞り込み(filter)、何に変換する(map)か」が宣言的に読み取れます。ループ変数や一時リストも不要になります。
⑥ コメントで「何をするか」ではなく「なぜするか」を書く
Before
// ユーザーを取得する
User user = userRepository.findById(userId).orElseThrow();
// ポイントを加算する
user.setPoint(user.getPoint() + point);
コードを読めばわかることを書いたコメントはノイズになります。
After
// 退会済みユーザーには付与しない仕様のため、activeチェックを行う
if (!user.isActive()) {
return;
}
// 小数点以下は切り捨て(仕様書X参照)
int point = (int) Math.floor(rawPoint);
「なぜそのコードが存在するか」「なぜその実装を選んだか」を書くと、後から読む人への情報量が増えます。
まとめ
| パターン | 一言まとめ |
|---|---|
| 変数名に意図を込める | 名前はコメントの代わりになる |
| マジックナンバーを定数にする | 数字に名前をつけると意図が伝わる |
| 早期リターン | 異常系を先に返して本筋を際立たせる |
| 1メソッド1責務 | 処理を分けると読みやすく・テストしやすくなる |
| Stream APIの活用 | 「何をしているか」が宣言的に読み取れる |
| コメントは「なぜ」を書く | 「何をしているか」はコードが語る |
コードを書いているとき、つい「動くかどうか」に集中してしまいがちです。しかし文章と同じように、後から読む人(未来の自分も含めて)にとってわかりやすいコードを意識することも大切です。
AIによるコード生成が普及してきた今だからこそ、人間が読んだときの可読性を意識してレビューする重要性は高まっていると感じています。