4
0

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の良いコード・悪いコード #1】命名で寿命の長いコードを書く3原則

4
Posted at

はじめに

株式会社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つあります。

  1. 読解コスト:変数の宣言箇所まで遡らないと意味が分からない
  2. レビューコスト:レビュアーが「これは何ですか?」と毎回聞く
  3. バグコスト:単位や意味の取り違えがバグの温床になる

命名にかける時間はせいぜい10秒です。それをケチると、後で何十時間というコストが返ってきます。


押さえるべきは3原則だけ

新人〜2年目がまず身につけるべき命名の原則は、たった3つです。

  1. 役割で名付ける
  2. 単位や条件を含める
  3. Booleanは「状態」で書く

順番に見ていきます。


原則① 役割で名付ける

「型を表す名前」をやめて、「何が入っているか(役割)」を書きます。

悪い例

List<String> data = userRepository.findAllNames();
List<User> list1 = userRepository.findByStatus(1);
List<User> list2 = userRepository.findByStatus(0);

datalist1list2 という名前は、変数の「型」しか伝えていません。
findByStatus(1)1 が何を意味するかも、呼び出し先の実装まで遡らないと分かりません。読み手の負担が一気に増えます。

良い例

List<String> activeUserNames = userRepository.findAllNames();
List<User> activeUsers = userRepository.findByStatus(STATUS_ACTIVE);
List<User> inactiveUsers = userRepository.findByStatus(STATUS_INACTIVE);

activeUsers なら、宣言を見なくても 「アクティブなユーザーが入っている」 ことが一目で分かります。

よくある罠:型名を変数名のサフィックスに付ける

userListnameStrcountInt のように、型名をサフィックスに付ける命名もよく見ます。一見「親切」に見えますが、これは避けたほうがいい命名です。

理由は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;

単位を名前に入れるだけで、いずれ必ず起きる「単位の取り違えバグ」 がほとんど消えます。

単位を含めるべき変数の例

種類 推奨される命名
時間 timeoutSecondsintervalMillisexpiresInMinutes
サイズ maxFileSizeKbbufferSizeBytes
通貨 priceJpyamountUsd
割合 taxRatePercentdiscountRatio
距離 distanceMetersheightCm

「数値だけの変数」を見たら、単位を名前に込められないか必ず疑うくせをつけましょう。

単位がない数値は「条件」を込める

カウント値のように単位を持たない数値でも、「何のカウントか」を名前に込めます。

// 悪い例
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 〜である isActiveisEmptyisAdmin
has 〜を持っている hasUnreadMessageshasPermission
can 〜できる canEditcanDelete
should 〜すべき shouldRetryshouldSendEmail

ポイント: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 何のデータか分からない userProfileorderHistory
tmp 一時的な何かとしか伝わらない originalUserNamebackupOrders
info 何の情報か曖昧 userProfileSummaryorderMetadata
manager 何を管理しているか曖昧 userSessionManagerfileUploadManager
util / helper 機能がぼやける DateFormatterCsvExporter
flag 何の真偽か分からない isPaidhasError
list1list2 役割が伝わらない activeOrderscanceledOrders
result 何の結果か分からない searchResultvalidationErrors
objitem 中身が何か伝えていない selectedProductuploadedFile

これらの単語そのものが悪いわけではなく、「単独で使うと意味が伝わらない」 のが問題です。
どうしても使いたい場合は、前後に意味のある単語をくっつけて補強しましょう。


半日溶かした実話

数年前、SESで参画していた現場で、こんなメソッドに出会ったことがあります。

public void process(User user, boolean flag1, boolean flag2, boolean flag3) {
    if (flag1) {
        // 何かの処理
    }
    if (flag2 && !flag3) {
        // 別の何かの処理
    }
    // ...
}

書いた本人は退場済みで、flag1flag2flag3 がそれぞれ何を意味するのか、誰にも分かりません。
データベースの仕様書、画面定義書、ログを照合して、半日かけて意味を復元しました。

復元してみたら、それぞれの意味は以下でした。

  • 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

ポイントabc のような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 型ではなく内容で名付ける
nx processedOrderCount 1文字変数を避けて意味を明示
o order ループ変数も意味のある名前に
flag() isPaid() Booleanは is で始める
f isPaid Boolean変数の状態を表す

ポイント:メソッド名も「動詞 + 役割」で書きます。get() だけでは何を取得するか伝わりませんが、findPendingOrders() なら一目で分かります。


まとめ

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

  1. 役割で名付ける:型ではなく「何が入っているか」を書く
  2. 単位や条件を含める:時間・サイズ・通貨などは単位を名前に込める
  3. Booleanは「状態」で書くis / has / can / should で始める

たったこの3つを意識するだけで、コードの読みやすさは劇的に変わります

命名は「他人への配慮」であり、「未来の自分への投資」です。
書いている瞬間は自分のためのコードでも、マージされた瞬間からチームのコードになります。
10秒の命名で、後の半日を救えます。


次回予告

次回(#2)は 「コメントの書き方」 を扱います。

  • コードで分かることをコメントに書いていないか
  • 「WHY(なぜ)」を書けているか
  • ドキュメントコメント(Javadoc)はどう書くか

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


参考


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

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?