0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Javaの良いコード・悪いコード #2】コメントは「WHY」を書く ― 嘘をつかないコメントの3原則

0
Posted at

はじめに

株式会社Good Labでエンジニアをしている コータロー です。
日々、Java・SQL・Gitなどの技術情報や、新人エンジニア向けの学習ノウハウ、
AI活用についての情報を発信しています。

Good Labについて気になった方は、コーポレートサイトもぜひご覧ください。
コーポレートサイト

この記事は、新人〜2年目のJavaエンジニア向けに 「良いコードと悪いコードの違い」 を、現場でよく見る具体例とともに解説していくシリーズの第2回です。

テーマ
#1 命名
#2(本記事) コメントの書き方
#3 マジックナンバー・定数化
#4 Null処理
#5 早期リターン
#6 メソッド分割
#7 ループ処理
#8 例外処理
#9 ログ出力
#10 クラス設計

第2回は コメントの書き方 です。「コメントは丁寧に書きましょう」と教わった人ほど、現場で「悪いコメント」を量産してしまいがちです。コメントは、書きすぎても・書かなさすぎても、コードの寿命を縮めます。


この記事のゴール

この記事を読み終わると、以下ができるようになります。

  • 「悪いコメント」がなぜ生まれるか、そのコストを説明できる
  • コメントの3原則(コードで分かることは書かない/WHYを書く/Javadocは契約を書く)を意識して書ける
  • 演習問題を通じて、悪いコメントをリファクタリングできる

「悪いコメント」の本当のコスト

新人のコードをレビューしていると、こんなコメントをよく見かけます。

// ユーザー数を取得する
int count = users.size();

// 1を足す
i = i + 1;

// 価格を計算する
int price = calculatePrice(item);

書いた本人は「丁寧に書いている」つもりです。
ですが、これらのコメントは コードを読めば全部分かること しか書いていません。

このようなコメントには、見えにくいコストが3つあります。

  1. ノイズコスト:本当に重要なコメントが埋もれる
  2. 嘘コスト:コードが変更されてもコメントは取り残され、嘘になる
  3. 思考停止コスト:コメントを書くことが目的化し、設計が雑になる

特に厄介なのが 嘘コスト です。
コードは変更されても、コメントは「邪魔だから」とそのまま残されることが多く、半年後には コードとコメントが食い違っている状態 になります。これがバグの温床になります。


押さえるべきは3原則

新人〜2年目がまず身につけるべきコメントの原則は、以下の3つです。

  1. コードで分かることはコメントに書かない
  2. WHY(なぜ)を書く
  3. Javadocは「契約」を書く

順番に見ていきます。


原則① コードで分かることはコメントに書かない

最も多い「悪いコメント」が、コードを日本語に翻訳しているだけ のコメントです。

悪い例

// ユーザーリストを取得する
List<User> users = userRepository.findAll();

// ユーザー数をカウントする
int count = users.size();

// ループでユーザー名を出力する
for (User user : users) {
    System.out.println(user.getName());
}

これらはすべて「コードを読めば分かること」しか書いていません。
コメントを読む時間が完全に無駄になります。

良い例

List<User> users = userRepository.findAll();
int count = users.size();

for (User user : users) {
    System.out.println(user.getName());
}

「コメントを消した方が読みやすい」 ことが分かるはずです。
変数名・メソッド名がしっかり付いていれば、コメントは不要になります。

コメントを書く前に、まず命名を見直す

「コメントを書きたい」と感じたとき、まず疑うべきは 命名が悪い可能性 です。

// アクティブなユーザーだけ取り出す
List<User> list = filterUsers(users);

↑ こう書きたくなったら、コメントではなく 変数名・メソッド名 を見直しましょう。

List<User> activeUsers = filterActiveUsers(users);

これでコメントなしでも意図が伝わります。
良い命名は最高のコメントです。


原則② WHY(なぜ)を書く

コメントに書くべきは「コードが何をしているか(WHAT)」ではなく、「なぜそのコードになっているか(WHY)」 です。

悪い例

// 100を掛ける
price = price * 100;

「100を掛ける」はコードを見れば分かります。
読み手が知りたいのは 「なぜ100を掛けるのか」 です。

良い例

// 表示用に円→銭単位へ変換する(DB保存値は銭単位のため、UI入力時に100倍する)
price = price * 100;

このコメントがあれば、半年後にこのコードを見た別のメンバーが「なんで100倍してるんだ?」と悩む時間が消えます。

WHYを書くべき典型パターン

パターン コメントの例
業務ルールの根拠 // 法人会員は2024年4月の規約改定で5%割引対象になった
技術的な制約 // 旧APIが502を返すため、リトライではなく1秒待ってから再呼び出しする
数値の根拠 // 30秒は本番のAPI応答P99が約20秒のため、余裕を持って設定
一見不要に見える処理 // nullチェックは外部APIが稀にnullを返すため必要(仕様書5.3章)
一時的な回避策 // ライブラリのバグでString.formatが失敗するため、自前で実装(issue#1234)

ポイント:将来のメンバーが「なぜこんなことを?」と疑問に思いそうな箇所にだけ、WHYを残します。


原則③ Javadocは「契約」を書く

メソッドの上に書くJavadocは、そのメソッドを使う側との 「契約」 を書く場所です。

契約とは何か

「契約」とは、メソッドを使う側が守るべき前提(事前条件)と、メソッドが保証すること(事後条件)を明文化したものです。具体的には以下を書きます。

  • @param:引数の意味と制約(null許容か、範囲、単位)
  • @return:戻り値の意味とnull可能性
  • @throws:どんな状況で例外を投げるか

悪い例

/**
 * ユーザーを取得する
 */
public Optional<User> findUser(long userId) {
    // ...
}

「ユーザーを取得する」は、メソッド名 findUser で分かります。
何の追加情報も伝わっていません。

良い例

/**
 * ユーザーIDからユーザーを取得する。
 *
 * @param userId 検索対象のユーザーID。正の値を指定すること
 * @return 該当ユーザー。存在しない場合は空のOptional
 * @throws IllegalArgumentException userIdが0以下の場合
 */
public Optional<User> findUser(long userId) {
    // ...
}

このJavadocなら、メソッドを使う側は 実装を読まなくても安全に呼び出せます

Javadocを書くべきメソッド

新人のうちは、以下のメソッドにJavadocを書く習慣をつけましょう。

  • public のメソッド(他クラスから呼ばれる)
  • protected のメソッド(サブクラスから呼ばれる)
  • 引数に制約があるメソッド(null禁止、範囲制限など)
  • null を返す可能性があるメソッド
  • 例外を投げる可能性があるメソッド

逆に、private で、引数の制約も特殊な戻り値もないメソッドには、無理にJavadocを書く必要はありません。


動作確認:3原則を全部適用したサンプル

3つの原則をすべて適用したコード例です。コピペでそのまま動かせます。

import java.util.List;
import java.util.ArrayList;

public class CommentDemo {
    public static void main(String[] args) {
        // 原則①:コードで分かることはコメントに書かない
        List<String> activeUsers = new ArrayList<>();
        activeUsers.add("田中");
        activeUsers.add("佐藤");
        int activeUserCount = activeUsers.size();
        System.out.println("アクティブユーザー数: " + activeUserCount);

        // 原則②:WHYを書く
        // 旧課金APIは月末締めで一斉に呼び出すと502を返すため、月初に分割実行する
        int billingDayOfMonth = 5;
        System.out.println("課金実行日: " + billingDayOfMonth + "日");

        // 原則③:Javadocは契約を書く(メソッド呼び出し例)
        int taxIncludedPrice = calculateTaxIncluded(1000);
        System.out.println("税込価格: " + taxIncludedPrice + "円");
    }

    /**
     * 税抜価格から税込価格を計算する(消費税10%、円未満切り捨て)。
     *
     * @param priceExcludingTaxJpy 税抜価格(円)。0以上を指定すること
     * @return 税込価格(円)。円未満は切り捨て
     * @throws IllegalArgumentException 引数が負の値の場合
     */
    static int calculateTaxIncluded(int priceExcludingTaxJpy) {
        if (priceExcludingTaxJpy < 0) {
            throw new IllegalArgumentException("税抜価格は0以上を指定してください: " + priceExcludingTaxJpy);
        }
        return (int) (priceExcludingTaxJpy * 1.10);
    }
}

期待する出力

アクティブユーザー数: 2
課金実行日: 5日
税込価格: 1100円

避けるべきNGコメント集

新人のコードでよく見る「悪いコメント」のパターンと、その対処をまとめました。

悪いコメント 何が問題か 対処
// ユーザーを取得 コードで分かる 削除する
// TODO: あとで直す いつ・誰が・何を直すか不明 Issue番号と日付を含める:// TODO(#1234, 2026-06): バリデーション追加
// 修正:2024/3/15 田中 git logで分かる 削除する(変更履歴はGitに任せる)
// 古い実装 だけ書かれたコメントアウト いつ消していいか分からない コードごと削除する(必要ならGitで復元)
// 重要!注意! だけの強調 何が重要か伝わらない 具体的な理由を書く
// nullチェック 何のためのnullチェックか不明 「なぜnullになりうるか」を書く
// 三項演算子で分岐 構文の説明 削除する
Javadoc未記載の public メソッド 使う側が実装を読む羽目になる @param@return@throws を書く

特に TODO コメントを書きっぱなしにしない ことは重要です。
TODOを書くなら、必ず以下を含めます。

  • Issue番号:トラッキング用
  • 日付:いつ書かれたTODOか
  • やること:具体的な作業内容
// TODO(#2345, 2026-05-20): リファクタ後はこのメソッドを削除する

半日溶かした実話:嘘をついていたコメント

数年前、SES案件で参画していた現場で、こんなコードに出会いました。

// 消費税10%を掛けて税込価格を返す
public int calculateTaxIncludedPrice(int price) {
    return (int) (price * 1.08);
}

コメントには「10%を掛ける」と書いてあるのに、コードは 「1.08倍(つまり8%)」 していました。
書かれたのは消費税が8%だった時代で、その後消費税が10%に上がった際に コメントだけが書き換えられて、コードはそのまま だったのです。

このバグに気づいたのは、本番リリース後でした。
原因調査・影響範囲確認・修正・データ修正・お客様への説明、すべて含めて 2日半 が消えました。

このとき学んだのは、シンプルな教訓です。

コメントは嘘をつく。コードだけが真実を語る。

だからこそ、コメントは必要最小限にして、本当に必要な箇所にだけ書く ことが重要なのです。
書かれたコメントが少ないほど、メンテナンスする側も「このコメントは信頼できる」と判断できます。


演習問題

難易度の見方

マーク 難易度 目安
基本 原則を覚えれば解ける
⭐⭐ 応用 複数の原則を組み合わせる

まずは自分で考えてから、模範解答を見てください!


問題1:不要なコメントを削除する ⭐

次のコードから「不要なコメント」をすべて削除してください。

import java.util.List;
import java.util.ArrayList;

public class Sample {
    public static void main(String[] args) {
        // ユーザー名のリストを作成する
        List<String> userNames = new ArrayList<>();
        // ユーザーを追加する
        userNames.add("田中");
        userNames.add("佐藤");

        // ユーザー数を取得する
        int userCount = userNames.size();

        // ユーザー数が0より大きい場合
        if (userCount > 0) {
            // メッセージを出力する
            System.out.println("ユーザーが存在します: " + userCount + "人");
        }
    }
}
模範解答
import java.util.List;
import java.util.ArrayList;

public class Exercise01 {
    public static void main(String[] args) {
        List<String> userNames = new ArrayList<>();
        userNames.add("田中");
        userNames.add("佐藤");

        int userCount = userNames.size();

        if (userCount > 0) {
            System.out.println("ユーザーが存在します: " + userCount + "人");
        }
    }
}

期待する出力

ユーザーが存在します: 2人

ポイント:すべてのコメントが「コードを日本語に翻訳しているだけ」なので削除します。変数名・メソッド名がしっかり付いていれば、コメントは不要です。


問題2:WHYを書く ⭐

次のコードのコメントは「WHAT(何をしているか)」しか書かれていません。
このコードの「WHY(なぜそうしているか)」をコメントとして補ってください。

前提:このメソッドは「2026年4月の周年キャンペーン中だけ、定価から5%引く」という業務仕様で実装されています。

public class Sample {
    public static void main(String[] args) {
        int discountedPrice = applyCampaignDiscount(1000);
        System.out.println("割引後価格: " + discountedPrice + "円");
    }

    // 0.95を掛ける
    static int applyCampaignDiscount(int originalPriceJpy) {
        return (int) (originalPriceJpy * 0.95);
    }
}
模範解答
public class Exercise02 {
    public static void main(String[] args) {
        int discountedPrice = applyCampaignDiscount(1000);
        System.out.println("割引後価格: " + discountedPrice + "円");
    }

    // 2026年4月の周年キャンペーン期間中のみ、定価から5%引く
    // 期間終了後はマーケ部の判断でキャンペーン自体を撤去する想定
    static int applyCampaignDiscount(int originalPriceJpy) {
        return (int) (originalPriceJpy * 0.95);
    }
}

期待する出力

割引後価格: 950円

ポイント

  • 「0.95を掛ける」というコメントは、コードを読めば分かる無意味な情報
  • 代わりに「なぜ5%引きなのか(業務ルールの根拠)」と「いつまでの処理なのか(一時的な処理であることの明示)」を書く
  • 将来のメンバーが「このコードはまだ必要か?」を判断できるようになる

問題3:Javadocを書く ⭐

次のメソッドにJavadocを付けてください。
@param@return@throws を含めます。

import java.util.Optional;
import java.util.HashMap;
import java.util.Map;

public class Sample {
    private static final Map<Long, String> USER_DB = new HashMap<>();

    public static void main(String[] args) {
        USER_DB.put(1L, "田中");
        Optional<String> user = findUserNameById(1L);
        System.out.println("ユーザー1: " + user.orElse("見つかりません"));
    }

    // ここにJavadocを書く
    static Optional<String> findUserNameById(long userId) {
        if (userId <= 0) {
            throw new IllegalArgumentException("userIdは正の値を指定してください: " + userId);
        }
        return Optional.ofNullable(USER_DB.get(userId));
    }
}
模範解答
import java.util.Optional;
import java.util.HashMap;
import java.util.Map;

public class Exercise03 {
    private static final Map<Long, String> USER_DB = new HashMap<>();

    public static void main(String[] args) {
        USER_DB.put(1L, "田中");
        USER_DB.put(2L, "佐藤");

        Optional<String> user = findUserNameById(1L);
        System.out.println("ユーザー1: " + user.orElse("見つかりません"));

        Optional<String> notFound = findUserNameById(99L);
        System.out.println("ユーザー99: " + notFound.orElse("見つかりません"));
    }

    /**
     * ユーザーIDからユーザー名を取得する。
     *
     * @param userId 検索対象のユーザーID。正の値を指定すること
     * @return ユーザー名。該当ユーザーが存在しない場合は空のOptional
     * @throws IllegalArgumentException userIdが0以下の場合
     */
    static Optional<String> findUserNameById(long userId) {
        if (userId <= 0) {
            throw new IllegalArgumentException("userIdは正の値を指定してください: " + userId);
        }
        return Optional.ofNullable(USER_DB.get(userId));
    }
}

期待する出力

ユーザー1: 田中
ユーザー99: 見つかりません

ポイント

  • @param には「引数の意味」と「制約(正の値)」を書く
  • @return には「戻り値の意味」と「null可能性(Optionalが空になる条件)」を書く
  • @throws には「どんな場合に例外を投げるか」を書く
  • これだけ書けば、呼び出し側は実装を読まなくても安全に使える

問題4:悪いコメント混在のメソッドを改善する ⭐⭐

次のメソッドには「不要なコメント」「WHYのないコメント」「Javadoc未記載」の問題がすべて含まれています。
3原則を踏まえて、コメントを修正してください。

import java.util.List;
import java.util.ArrayList;

public class Sample {
    public static void main(String[] args) {
        List<Integer> prices = new ArrayList<>();
        prices.add(1000);
        prices.add(2000);
        prices.add(3000);

        int total = calculateTotalWithTax(prices);
        System.out.println("税込合計: " + total + "円");
    }

    // 税込合計を計算する
    static int calculateTotalWithTax(List<Integer> priceListJpy) {
        // 合計を入れる変数を0で初期化
        int subtotal = 0;

        // priceListJpyをループする
        for (Integer price : priceListJpy) {
            // nullまたは負の値をチェックする
            if (price == null || price < 0) {
                // 例外を投げる
                throw new IllegalArgumentException("価格は0以上を指定してください: " + price);
            }
            // subtotalにpriceを足す
            subtotal += price;
        }

        // 1.10を掛けて返す
        return (int) (subtotal * 1.10);
    }
}
模範解答
import java.util.List;
import java.util.ArrayList;

public class Exercise04 {
    public static void main(String[] args) {
        List<Integer> prices = new ArrayList<>();
        prices.add(1000);
        prices.add(2000);
        prices.add(3000);

        int total = calculateTotalWithTax(prices);
        System.out.println("税込合計: " + total + "円");
    }

    /**
     * 商品価格リストの税込合計金額を計算する(消費税10%、円未満切り捨て)。
     *
     * @param priceListJpy 税抜価格のリスト(円)。各要素は0以上であること
     * @return 税込合計金額(円)
     * @throws IllegalArgumentException リストにnullまたは負の値が含まれる場合
     */
    static int calculateTotalWithTax(List<Integer> priceListJpy) {
        int subtotal = 0;
        for (Integer price : priceListJpy) {
            if (price == null || price < 0) {
                throw new IllegalArgumentException("価格は0以上を指定してください: " + price);
            }
            subtotal += price;
        }
        return (int) (subtotal * 1.10);
    }
}

期待する出力

税込合計: 6600円

改善ポイント

元のコメント 対処 理由
// 税込合計を計算する → Javadocに昇格 メソッドの「契約」として正式に書く
// 合計を入れる変数を0で初期化 削除 コードで分かる
// priceListJpyをループする 削除 コードで分かる
// nullまたは負の値をチェックする 削除 コードで分かる
// 例外を投げる 削除 コードで分かる
// subtotalにpriceを足す 削除 コードで分かる
// 1.10を掛けて返す 削除 コードで分かる(消費税10%という情報はJavadocに記載済み)

ポイント

  • WHATを書いたコメントはすべて削除
  • 全体としての「契約」(消費税率・例外条件)はJavadocに集約
  • 結果として コメントの量は減ったのに、伝わる情報量は増えた

まとめ

新人〜2年目が押さえるべきコメントの3原則は、以下の3つです。

  1. コードで分かることはコメントに書かない:WHATを書かない、命名で表現する
  2. WHY(なぜ)を書く:業務ルールの根拠、技術的な制約、数値の根拠を残す
  3. Javadocは「契約」を書く@param@return@throws を書く

コメントは「丁寧に書く」のではなく、「必要なものだけ書く」 のが正解です。
書かれたコメントが少ないほど、メンテナンスする側も「このコメントは信頼できる」と判断できます。

コメントは嘘をつく。コードだけが真実を語る。

だからこそ、コメントを書く前に 「これは本当に必要か?」 を一度立ち止まって考える習慣をつけましょう。


次回予告

次回(#3)は 「マジックナンバー・定数化」 を扱います。

  • if (status == 3) のような数値リテラルがなぜ問題か
  • 定数(static final)への切り出し方
  • enumを使うべき場面の見極め方

を、Before / After 形式で解説していきます。


参考


@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!

0
1
0

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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?