はじめに
株式会社Good Labでエンジニアをしている コータロー です。
日々、Java・SQL・Gitなどの技術情報や、新人エンジニア向けの学習ノウハウ、
AI活用についての情報を発信しています。
Good Labについて気になった方は、コーポレートサイトもぜひご覧ください。
▶コーポレートサイト
この記事は、新人〜2年目のJavaエンジニア向けに 「良いコードと悪いコードの違い」 を、現場でよく見る具体例とともに解説していくシリーズの第1回です。
シリーズを通して扱うテーマは以下の通りです。
| 回 | テーマ |
|---|---|
| #1(本記事) | 命名 |
| #2 | コメントの書き方 |
| #3 | マジックナンバー・定数化 |
| #4 | Null処理 |
| #5 | 早期リターン |
| #6 | メソッド分割 |
| #7 | ループ処理 |
| #8 | 例外処理 |
| #9 | ログ出力 |
| #10 | クラス設計 |
第1回は 命名 です。設計やアルゴリズムよりも前に、まず身につけてほしい基本中の基本ですが、現場で最も多く赤入れされるのも命名です。
この記事のゴール
この記事を読み終わると、以下ができるようになります。
- 「悪い命名」が将来どんなコストになるか説明できる
- 命名の3原則(役割・単位・状態)を意識して変数名を付けられる
- 演習問題を通じて、良い命名へリファクタリングできる
命名が雑なコードの「本当のコスト」
新人のコードをレビューしていると、こんな変数名をよく見かけます。
List<String> data = userRepository.findAllNames();
boolean flag = false;
String tmp = request.getParameter("name");
書いた本人はもちろん意味が分かっています。
ですが、3ヶ月後の自分や、別のメンバーが読んだとき、これらが何を指しているのか誰にも分かりません。
命名が雑なコードには、見えにくいコストが3つあります。
- 読解コスト:変数の宣言箇所まで遡らないと意味が分からない
- レビューコスト:レビュアーが「これは何ですか?」と毎回聞く
- バグコスト:単位や意味の取り違えがバグの温床になる
命名にかける時間はせいぜい10秒です。それをケチると、後で何十時間というコストが返ってきます。
押さえるべきは3原則だけ
新人〜2年目がまず身につけるべき命名の原則は、たった3つです。
- 役割で名付ける
- 単位や条件を含める
- Booleanは「状態」で書く
順番に見ていきます。
原則① 役割で名付ける
「型を表す名前」をやめて、「何が入っているか(役割)」を書きます。
悪い例
List<String> data = userRepository.findAllNames();
List<User> list1 = userRepository.findByStatus(1);
List<User> list2 = userRepository.findByStatus(0);
data、list1、list2 という名前は、変数の「型」しか伝えていません。
findByStatus(1) の 1 が何を意味するかも、呼び出し先の実装まで遡らないと分かりません。読み手の負担が一気に増えます。
良い例
List<String> activeUserNames = userRepository.findAllNames();
List<User> activeUsers = userRepository.findByStatus(STATUS_ACTIVE);
List<User> inactiveUsers = userRepository.findByStatus(STATUS_INACTIVE);
activeUsers なら、宣言を見なくても 「アクティブなユーザーが入っている」 ことが一目で分かります。
よくある罠:型名を変数名のサフィックスに付ける
userList、nameStr、countInt のように、型名をサフィックスに付ける命名もよく見ます。一見「親切」に見えますが、これは避けたほうがいい命名です。
理由は2つあります。
- 型は宣言箇所を見れば分かる(IDEのホバーでも分かる)
-
型が変わったとき、変数名と矛盾する(
ListからSetに変えたらuserListは嘘になる)
変数名には「型」ではなく「役割・意味」を込めましょう。
原則② 単位や条件を含める
数値や時間を扱う変数には、必ず単位を含めます。
悪い例
int timeout = 30;
int size = 1024;
long created = 1700000000L;
この timeout = 30 は秒でしょうか? ミリ秒でしょうか?
size = 1024 はバイト? KB? MB?
created = 1700000000L は何の時刻(UNIX時間? ミリ秒?)?
このコードを書いた本人にしか分かりません。
良い例
int timeoutSeconds = 30;
int maxFileSizeKb = 1024;
long createdAtEpochSeconds = 1700000000L;
単位を名前に入れるだけで、いずれ必ず起きる「単位の取り違えバグ」 がほとんど消えます。
単位を含めるべき変数の例
| 種類 | 推奨される命名 |
|---|---|
| 時間 |
timeoutSeconds、intervalMillis、expiresInMinutes
|
| サイズ |
maxFileSizeKb、bufferSizeBytes
|
| 通貨 |
priceJpy、amountUsd
|
| 割合 |
taxRatePercent、discountRatio
|
| 距離 |
distanceMeters、heightCm
|
「数値だけの変数」を見たら、単位を名前に込められないか必ず疑うくせをつけましょう。
単位がない数値は「条件」を込める
カウント値のように単位を持たない数値でも、「何のカウントか」を名前に込めます。
// 悪い例
int count = orders.size();
int size = users.size();
// 良い例
int pendingOrderCount = pendingOrders.size();
int activeUserCount = activeUsers.size();
count だけでは「何の件数か」が伝わりません。pendingOrderCount のように 「どんな条件で絞った件数か」 を名前に込めると、ひと目で意味が分かります。
原則③ Booleanは「状態」で書く
Boolean型に flag という名前を付けるのは、ほぼ確実に悪い命名です。
is / has / should / can のいずれかで始めます。
悪い例
boolean flag = user.getStatus() == 1;
if (flag) {
sendNotification(user);
}
flag という名前は 「真か偽か」しか伝えていません。
読み手は if (flag) を見ても、何の真偽を判定しているのか分かりません。
良い例
boolean isActiveUser = user.getStatus() == 1;
if (isActiveUser) {
sendNotification(user);
}
isActiveUser なら 「アクティブユーザーかどうか」 を判定していることが、ひと目で分かります。
if (isActiveUser) を読んだ瞬間に処理の意図が伝わります。
Booleanのプレフィックスの使い分け
| プレフィックス | 意味 | 例 |
|---|---|---|
is |
〜である |
isActive、isEmpty、isAdmin
|
has |
〜を持っている |
hasUnreadMessages、hasPermission
|
can |
〜できる |
canEdit、canDelete
|
should |
〜すべき |
shouldRetry、shouldSendEmail
|
ポイント:Booleanは「Yes / No で答えられる質問」になっているかを確認しましょう。
「isActive ですか? → Yes」のように、自然な疑問文として読めればOKです。
動作確認:3原則を全部適用したサンプル
3つの原則をすべて適用したコード例です。コピペでそのまま動かせます。
import java.util.List;
import java.util.ArrayList;
public class NamingDemo {
public static void main(String[] args) {
// 原則①:役割で名付ける
List<String> activeUserNames = new ArrayList<>();
activeUserNames.add("田中");
activeUserNames.add("佐藤");
System.out.println("アクティブユーザー数: " + activeUserNames.size());
// 原則②:単位や条件を含める
int timeoutSeconds = 30;
int maxFileSizeKb = 1024;
System.out.println("タイムアウト: " + timeoutSeconds + "秒");
System.out.println("最大ファイルサイズ: " + maxFileSizeKb + "KB");
// 原則③:Boolean は状態で書く
int status = 1;
boolean isActiveUser = status == 1;
if (isActiveUser) {
System.out.println("アクティブユーザーです");
}
}
}
期待する出力
アクティブユーザー数: 2
タイムアウト: 30秒
最大ファイルサイズ: 1024KB
アクティブユーザーです
避けるべきNGネーミング集
新人のコードでよく見る「悪い命名」のパターンと、その改善例をまとめました。
| 悪い命名 | なぜダメか | 良い命名の例 |
|---|---|---|
data |
何のデータか分からない |
userProfile、orderHistory
|
tmp |
一時的な何かとしか伝わらない |
originalUserName、backupOrders
|
info |
何の情報か曖昧 |
userProfileSummary、orderMetadata
|
manager |
何を管理しているか曖昧 |
userSessionManager、fileUploadManager
|
util / helper
|
機能がぼやける |
DateFormatter、CsvExporter
|
flag |
何の真偽か分からない |
isPaid、hasError
|
list1、list2
|
役割が伝わらない |
activeOrders、canceledOrders
|
result |
何の結果か分からない |
searchResult、validationErrors
|
obj、item
|
中身が何か伝えていない |
selectedProduct、uploadedFile
|
これらの単語そのものが悪いわけではなく、「単独で使うと意味が伝わらない」 のが問題です。
どうしても使いたい場合は、前後に意味のある単語をくっつけて補強しましょう。
半日溶かした実話
数年前、SESで参画していた現場で、こんなメソッドに出会ったことがあります。
public void process(User user, boolean flag1, boolean flag2, boolean flag3) {
if (flag1) {
// 何かの処理
}
if (flag2 && !flag3) {
// 別の何かの処理
}
// ...
}
書いた本人は退場済みで、flag1、flag2、flag3 がそれぞれ何を意味するのか、誰にも分かりません。
データベースの仕様書、画面定義書、ログを照合して、半日かけて意味を復元しました。
復元してみたら、それぞれの意味は以下でした。
-
flag1→ 「メール通知を送るか」(shouldSendEmail) -
flag2→ 「プレミアム会員か」(isPremiumUser) -
flag3→ 「初回ログインか」(isFirstLogin)
命名にかける時間はたった10秒です。
それをケチった結果、半日が消えました。
命名は「未来の自分とチームへの投資」です。
書いている瞬間は自分のためのコードでも、マージされた瞬間からチームのコードになります。
演習問題
難易度の見方
| マーク | 難易度 | 目安 |
|---|---|---|
| ⭐ | 基本 | 命名のルールを覚えれば解ける |
| ⭐⭐ | 応用 | 複数のルールを組み合わせる |
まずは自分で考えてから、模範解答を見てください!
問題1:役割で名付ける ⭐
次のコードの変数名を、意味が伝わるように修正してください。
int a = 100;
String b = "田中太郎";
double c = 78.5;
System.out.println("ユーザー名: " + b);
System.out.println("ユーザー数: " + a);
System.out.println("平均点: " + c);
模範解答
public class Exercise01 {
public static void main(String[] args) {
int userCount = 100;
String userName = "田中太郎";
double averageScore = 78.5;
System.out.println("ユーザー名: " + userName);
System.out.println("ユーザー数: " + userCount);
System.out.println("平均点: " + averageScore);
}
}
期待する出力
ユーザー名: 田中太郎
ユーザー数: 100
平均点: 78.5
ポイント:a、b、c のような1文字変数は、ループのインデックス(i など)以外では基本的に使わないようにしましょう。
問題2:単位を含めた命名にする ⭐
次のコードの変数名を、単位が伝わるように修正してください。
int retry = 500; // ミリ秒
int session = 30; // 分
long file = 2048L; // バイト
模範解答
public class Exercise02 {
public static void main(String[] args) {
int retryIntervalMillis = 500;
int sessionTimeoutMinutes = 30;
long fileSizeBytes = 2048L;
System.out.println("リトライ間隔: " + retryIntervalMillis + "ms");
System.out.println("セッション期限: " + sessionTimeoutMinutes + "分");
System.out.println("ファイルサイズ: " + fileSizeBytes + "バイト");
}
}
期待する出力
リトライ間隔: 500ms
セッション期限: 30分
ファイルサイズ: 2048バイト
ポイント:単位を変数名に含めると、後でこの変数を使う人が「ミリ秒?秒?」と迷うことがなくなります。
問題3:Booleanの命名を直す ⭐
次のBoolean変数を、is / has / can / should のいずれかを使った命名に直してください。
boolean a = true; // プレミアム会員かどうか
boolean b = false; // 未読メッセージがあるかどうか
boolean c = true; // プロフィール編集可能かどうか
boolean d = false; // リトライすべきかどうか
模範解答
public class Exercise03 {
public static void main(String[] args) {
boolean isPremiumUser = true;
boolean hasUnreadMessages = false;
boolean canEditProfile = true;
boolean shouldRetry = false;
System.out.println("プレミアム会員か: " + isPremiumUser);
System.out.println("未読メッセージがあるか: " + hasUnreadMessages);
System.out.println("プロフィール編集可能か: " + canEditProfile);
System.out.println("リトライすべきか: " + shouldRetry);
}
}
期待する出力
プレミアム会員か: true
未読メッセージがあるか: false
プロフィール編集可能か: true
リトライすべきか: false
ポイント:Booleanは「Yes / No で答えられる質問」になっているか確認しましょう。「isPremiumUser ですか? → Yes」のように読めればOKです。
問題4:メソッド全体の命名を直す ⭐⭐
次のメソッド・変数の命名は、すべて改善の余地があります。
3原則を踏まえて、メソッド名・変数名を直してください。
import java.util.List;
import java.util.ArrayList;
public class Sample {
public static void main(String[] args) {
List<Order> data = get();
int n = doIt(data);
System.out.println("処理した注文数: " + n);
}
static List<Order> get() {
List<Order> list = new ArrayList<>();
list.add(new Order(1, true));
list.add(new Order(2, false));
list.add(new Order(3, true));
return list;
}
static int doIt(List<Order> orders) {
int x = 0;
for (Order o : orders) {
if (o.flag()) {
x++;
}
}
return x;
}
}
class Order {
private final int id;
private final boolean f;
Order(int id, boolean f) {
this.id = id;
this.f = f;
}
boolean flag() {
return f;
}
}
模範解答
import java.util.List;
import java.util.ArrayList;
public class Exercise04 {
public static void main(String[] args) {
List<Order> pendingOrders = findPendingOrders();
int processedOrderCount = processOrders(pendingOrders);
System.out.println("処理した注文数: " + processedOrderCount);
}
static List<Order> findPendingOrders() {
List<Order> pendingOrders = new ArrayList<>();
pendingOrders.add(new Order(1, true));
pendingOrders.add(new Order(2, false));
pendingOrders.add(new Order(3, true));
return pendingOrders;
}
static int processOrders(List<Order> orders) {
int processedCount = 0;
for (Order order : orders) {
if (order.isPaid()) {
processedCount++;
}
}
return processedCount;
}
}
class Order {
private final int orderId;
private final boolean isPaid;
Order(int orderId, boolean isPaid) {
this.orderId = orderId;
this.isPaid = isPaid;
}
boolean isPaid() {
return isPaid;
}
}
期待する出力
処理した注文数: 2
改善ポイント
| 元の命名 | 改善後 | 理由 |
|---|---|---|
get() |
findPendingOrders() |
何を取得するかを明示(動詞 + 役割) |
doIt() |
processOrders() |
動作の中身が伝わる |
data |
pendingOrders |
役割で名付ける |
list |
pendingOrders |
型ではなく内容で名付ける |
n、x
|
processedOrderCount |
1文字変数を避けて意味を明示 |
o |
order |
ループ変数も意味のある名前に |
flag() |
isPaid() |
Booleanは is で始める |
f |
isPaid |
Boolean変数の状態を表す |
ポイント:メソッド名も「動詞 + 役割」で書きます。get() だけでは何を取得するか伝わりませんが、findPendingOrders() なら一目で分かります。
まとめ
新人〜2年目が押さえるべき命名の3原則は、以下の3つです。
- 役割で名付ける:型ではなく「何が入っているか」を書く
- 単位や条件を含める:時間・サイズ・通貨などは単位を名前に込める
-
Booleanは「状態」で書く:
is/has/can/shouldで始める
たったこの3つを意識するだけで、コードの読みやすさは劇的に変わります。
命名は「他人への配慮」であり、「未来の自分への投資」です。
書いている瞬間は自分のためのコードでも、マージされた瞬間からチームのコードになります。
10秒の命名で、後の半日を救えます。
次回予告
次回(#2)は 「コメントの書き方」 を扱います。
- コードで分かることをコメントに書いていないか
- 「WHY(なぜ)」を書けているか
- ドキュメントコメント(Javadoc)はどう書くか
を、Before / After 形式で解説していきます。
参考
- Oracle - Code Conventions for the Java Programming Language(公式)
- Google Java Style Guide
- リーダブルコード(オライリー・ジャパン)
@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!