42
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

「動くコード」と「読めるコード」の違いをBefore/Afterで見る

42
Posted at

はじめに

「このコード、何してるんだっけ」と、自分が書いたコードに首を傾げた経験はありませんか。
コードは書く時間より、読まれる時間のほうがはるかに長いと言われています。
この記事では、未来の自分とチームメンバーのために、動くけど読みにくいコードを読みやすいコードに変えるパターンを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;

listresultu という変数名から何を意味するかは読み取れません。処理を追ってようやく「ああ、成人のアクティブユーザーを絞っているのか」とわかります。

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";
}

188060 が何を意味するのか、コードだけでは判断できません。

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によるコード生成が普及してきた今だからこそ、人間が読んだときの可読性を意識してレビューする重要性は高まっていると感じています。

42
26
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
42
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?