はじめに
株式会社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つあります。
- ノイズコスト:本当に重要なコメントが埋もれる
- 嘘コスト:コードが変更されてもコメントは取り残され、嘘になる
- 思考停止コスト:コメントを書くことが目的化し、設計が雑になる
特に厄介なのが 嘘コスト です。
コードは変更されても、コメントは「邪魔だから」とそのまま残されることが多く、半年後には コードとコメントが食い違っている状態 になります。これがバグの温床になります。
押さえるべきは3原則
新人〜2年目がまず身につけるべきコメントの原則は、以下の3つです。
- コードで分かることはコメントに書かない
- WHY(なぜ)を書く
- 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つです。
- コードで分かることはコメントに書かない:WHATを書かない、命名で表現する
- WHY(なぜ)を書く:業務ルールの根拠、技術的な制約、数値の根拠を残す
-
Javadocは「契約」を書く:
@param、@return、@throwsを書く
コメントは「丁寧に書く」のではなく、「必要なものだけ書く」 のが正解です。
書かれたコメントが少ないほど、メンテナンスする側も「このコメントは信頼できる」と判断できます。
コメントは嘘をつく。コードだけが真実を語る。
だからこそ、コメントを書く前に 「これは本当に必要か?」 を一度立ち止まって考える習慣をつけましょう。
次回予告
次回(#3)は 「マジックナンバー・定数化」 を扱います。
-
if (status == 3)のような数値リテラルがなぜ問題か - 定数(
static final)への切り出し方 - enumを使うべき場面の見極め方
を、Before / After 形式で解説していきます。
参考
- Google Java Style Guide - Javadoc
- How to Write Doc Comments for the Javadoc Tool(Oracle公式)
- リーダブルコード 第5章「コメントすべきことを知る」(オライリー・ジャパン)
@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!