mcpField
J
java-coding-guideline

java-coding-guideline

github.com/gabia開発2151 回インストール14 週間前に更新
リポジトリ

java-coding-guideline について

java-coding-guidelineは、Java開発者向けのコーディング規約スキルです。スタイル、null処理、リソース管理に加え、外部境界と内部不変条件で例外とアサーションを使い分けます。

主な機能

  1. 01明示的インポートと空行の規則
  2. 02null許容アノテーションと型の選択
  3. 03コンストラクター、不変性、コンポジションの指針
  4. 04リソース管理と例外捕捉の規則
  5. 05信頼境界に応じた例外とアサーション

活用シーン

  1. 01Javaコードレビュー基準の統一
  2. 02null処理とリソース管理の実装
  3. 03入力検証で例外かアサーションかを判断

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/gabia/agent-skills --skill java-coding-guideline

インストール後、Claude Code で/java-coding-guidelineを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
async.md
javadoc.md
lombok.md
security-resources.md

詳細

Java コーディングガイドライン

重大:より具体的なスキルによって明示的に上書きされない限り、これらのルールはすべての Java コードに適用されます。

インポート文

コードの明瞭さと保守性を高めるため、ワイルドカードを使わず、常に明示的にインポートしてください。

✅ 推奨
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
❌ 非推奨
import static org.assertj.core.api.Assertions.*;

縦方向の間隔(読みやすさ)

クラス内の縦方向の間隔を統一し、差分を小さくするとともに、コードを見やすくしてください。

ルール
  • 次の箇所の間には、空行をちょうど1行入れてください。
    • フィールドのまとまりと次のフィールドのまとまり(グループ化されている場合)
    • フィールドと最初のコンストラクター
    • コンストラクターとメソッド
    • メソッドと次のメソッド
  • また、クラスの開始 { の直後(最初のフィールドまたは初期化子の前)にも空行を1行入れてください。ただし、クラスが意図的に空の場合は除きます。
✅ 推奨
public final class SomeClass {

    @NonNull
    private final String fieldA;

    @NonNull
    private final String fieldB;

    public void someFunc() {
        // some action
    }

    public String getFieldA() {
        return fieldA;
    }
}
❌ 非推奨
public final class SomeClass {
    @NonNull
    private final String fieldA;
    @NonNull
    private final String fieldB;
    public void someFunc() {
        // some action
    }
    public String getFieldA() {
        return fieldA;
    }
}

コンストラクター

プライマリーコンストラクターを1つだけ使用し、補助コンストラクターからは this() を使って委譲してください。

✅ 推奨
public final class SomeClass {

    private final int field1;
    private final int field2;

    public SomeClass() {
        this(1, 2);
    }

    public SomeClass(int field1, int field2) {
         this.field1 = field1;
         this.field2 = field2;
    }
}
❌ 非推奨
public final class SomeClass {

    private final int field1;
    private final int field2;

    public SomeClass() {
        this.field1 = 1;
        this.field2 = 2;
    }

    public SomeClass(int field1, int field2) {
         this.field1 = field1;
         this.field2 = field2;
    }
}

Optional の使用

戻り値やパラメーターとして Optional を使用するのは避け、代わりに @Nullable アノテーションを使用してください。

✅ 推奨
public final class SomeClass {

    @Nullable
    public Integer someNullable() {
        if (something) return null;
        return 1;
    }

    public void func(@Nullable Integer param) {
        // do something
    }
}
❌ 非推奨
public final class SomeClass {

    @NonNull
    public Optional<Integer> someNullable() {
        if (something) return Optional.ofNullable(null);
        return Optional.ofNullable(1);
    }

    public void func(@NonNull Optional<Integer> param) {
        // do something
    }
}

ラッパー型とプリミティブ型

  • null が想定される場合はラッパー型を使用してください
  • null が発生すべきでない場合はプリミティブ型を使用してください
✅ 推奨
public final class SomeClass {

    private final int field1;

    @Nullable
    private final Long field2;

    public SomeClass(int field1, @Nullable Long field2) {
        this.field1 = field1;
        this.field2 = field2;
    }
}
❌ 非推奨
public final class SomeClass {

    @NonNull
    private final Integer field1;

    @Nullable
    private final Long field2;

    public SomeClass(@NonNull Integer field1, @Nullable Long field2) {
        this.field1 = field1;
        this.field2 = field2;
    }
}

null の比較

  • 一般的な場合は A == null を使用してください
  • Stream API または関数型プログラミングのコンテキストでは Objects.isNull を使用してください
✅ 推奨
final Integer someVariable = null;

if (someVariable == null) {
    // do something
}

List<Integer> lists = new ArrayList<>();
lists.stream()
  .filter(Objects::isNull)
  .collect(Collectors.toList());
❌ 非推奨
final Integer someVariable = null;

if (Objects.isNull(someVariable)) {
    // do something
}

List<Integer> lists = new ArrayList<>();
lists.stream()
  .filter(p -> p == null)
  .collect(Collectors.toList());

Nullability アノテーション

  • JSpecify アノテーションを使用してください
  • private メソッドも含め、すべてのメソッドに @Nullable と @NonNull を適用してください
  • クラスレベルまたはパッケージレベルのアノテーションは避けてください(Lombok との互換性に問題があるため)
✅ 推奨
public final class SomeClass {

    @Nullable
    private final Integer field1;

    @NonNull
    private final String field2;

    @NonNull
    public String func() {
        return process();
    }

    @Nullable
    private String process() {
        if (field1 == null) return null;
        return "awesome";
    }
}
❌ 非推奨
public final class SomeClass {

    private final Integer field1;
    private final String field2;

    public String func() {
        return process();
    }

    private String process() {
        if (field1 == null) return null;
        return "awesome";
    }
}

パッケージ名

Google Java Style Guide の規則に従い、小文字と数字のみを使用してください。

✅ 推奨
package com.example.project.initscript;
❌ 非推奨
package com.example.project.initScript;

静的ファクトリーメソッド

of の命名規則のみを使用してください。複数の型がある場合は、of<Postfix> の形式を使用してください。

✅ 推奨
public final class SomeClass {

    private final int field1;

    private SomeClass(int field1) {
        this.field1 = field1;
    }

    public static SomeClass ofDefault() {
        return new SomeClass(1);
    }

    public static SomeClass ofSpecial() {
        return new SomeClass(2);
    }
}
❌ 非推奨
public final class SomeClass {

    private final int field1;

    private SomeClass(int field1) {
        this.field1 = field1;
    }

    public static SomeClass from() {
        return new SomeClass(1);
    }

    public static SomeClass valueOf() {
        return new SomeClass(2);
    }
}

リソース管理

AutoCloseable リソースには必ず try-with-resources を使用してください。ファイナライザーには決して依存しないでください。

Try-With-Resources
✅ 推奨
public String readFile(@NonNull Path path) throws IOException {
    try (var reader = Files.newBufferedReader(path)) {
        return reader.lines().collect(Collectors.joining("\n"));
    }
}

// Multiple resources
public void copyStream(@NonNull InputStream in, @NonNull OutputStream out) throws IOException {
    try (in; out) {  // Java 9+: can use already-declared variables
        in.transferTo(out);
    }
}
❌ 非推奨
public String readFile(@NonNull Path path) throws IOException {
    BufferedReader reader = Files.newBufferedReader(path);
    try {
        return reader.lines().collect(Collectors.joining("\n"));
    } finally {
        reader.close();  // The original exception may be suppressed if one occurs here
    }
}

継承よりコンポジション

継承よりコンポジションを優先してください。

✅ 推奨
public final class SomeFeature {
    public void func() { /* ... */ }
}

public final class SomeClass {

    private final SomeFeature feature;

    public void func2() {
        feature.func();
        // do something
    }
}
❌ 非推奨
public class SomeFeature {
    public void func() { /* ... */ }
}

public final class SomeClass extends SomeFeature {

    public void func2() {
        super.func();
        // do something
    }
}

不変オブジェクト

拡張やフレームワークの要件で必要な場合を除き、すべてのオブジェクトで final キーワードを使用してください。

✅ 推奨
public final class SomeClass {
    // ...
}
❌ 非推奨
public class SomeClass {
    // ...
}

アサーションと例外

信頼境界と、呼び出し元が復旧を担う責任に基づいて、アサーションと例外を使い分けてください。このルールはクラスやメソッド全体ではなく、各条件に適用します。

Java のアクセス修飾子やコンポーネント名から境界を推測しないでください。public メソッドが内部 API の場合もあれば、private パーサーが外部入力を処理する場合もあります。

判断表
条件対応方法
外部からの不正なリクエスト、コマンド、メッセージ、または公開 API の引数例外をスローする
実行時設定が無効例外をスローする
ネットワーク、データベース、ファイルシステム、または外部サービスの障害例外をスローする
セキュリティ、認可、またはデータ整合性のチェック例外をスローする
呼び出し元が障害を変換、再試行、追跡、記録、またはその他の方法で処理する必要がある例外をスローする
内部 API における、プログラマーが制御する事前条件または事後条件assert を使用する
内部の配線またはディスパッチによって保証される関係assert を使用する
実装が正しければ到達しない状態assert を使用する
外部境界を越える場合は例外をスローする

有効な実行時動作中に障害が発生する可能性がある場合、または呼び出し元がその障害に対応できる必要がある場合は、例外をスローします。外部境界には次のものがあります。

  • HTTP、RPC、XML-RPC、CLI、メッセージ、イベントのハンドラー
  • 独立して開発された利用者が呼び出す公開ライブラリ API
  • ユーザーまたはテナントから提供される入力
  • 実行時設定
  • 通常の動作中に発生する可能性がある、ネットワーク、データベース、ファイルシステム、外部サービスからの予期しないデータや無効なデータ

アサーションが有効な場合でも、例外を使用します。外部から見える動作を JVM の -ea オプションに依存させてはいけません。

障害の内容が伝わる例外の型を選びます。

  • 呼び出し元から渡された引数が無効な場合は IllegalArgumentException を使用します
  • 現在の実行時状態では操作を実行できない場合は IllegalStateException を使用します
  • 呼び出し元が区別する必要のある運用上の障害には、ドメイン固有の例外を使用します
  • I/O 障害を意図的に非チェック例外として公開する場合は UncheckedIOException を使用します

外部から見える例外は @throws でドキュメント化します。

内部不変条件をアサートする

内部 API では、プログラマーが管理する事前条件、事後条件、不変条件に assert を使用します。これには、信頼できるアプリケーションデータを処理する内部パブリッシャー、ジェネレーター、コンバーター、パーサーが含まれます。

アサーションを使用する一般的なケースは次のとおりです。

  • 内部リゾルバーが空白でないキーを生成する必要がある
  • ジェネレーターまたはコンバーターが空でない結果を生成する必要がある
  • 関連するドメインオブジェクトが互いを参照する必要がある
  • 永続化処理で ID が設定される必要がある
  • 挿入直後のクエリーで、挿入したエンティティが返される必要がある
  • インターセプターが内部コンテキスト値を設定している必要がある
  • 内部のディスパッチが正しければ、ある分岐に到達しない必要がある
public void publish(@NonNull String objectKey, @NonNull String generatedScript) {
    assert !objectKey.isBlank();
    assert !generatedScript.isBlank();

    objectStore.put(objectKey, generatedScript);
}

本番環境ではアサーションが無効になっている場合があります。そのため、次の点を守ります。

  • 外部入力の検証にアサーションを使用しないでください
  • 認可やセキュリティのチェックにアサーションを使用しないでください
  • 実行時のリカバリーが必要な条件にアサーションを使用しないでください
  • アサーション式に副作用を含めないでください
  • 通常の制御フローとして AssertionError を捕捉しないでください
  • アサーションが無効でも、コードの正確性とセキュリティを保ってください

不変条件が自明でない場合は、アサーションメッセージを追加します。

assert collector.getAgentId() == agent.getId() : "collector must own the agent";
条件ごとにルールを適用する

同じメソッド内で、アサーションと例外の両方を使用する場合があります。

public void resize(@NonNull Pool pool, @NonNull Bucket bucket, long requestedSize, long minimumSize) {
    assert pool.getId() == bucket.getPoolId();
    assert pool.getType() == PoolType.OBJECT_STORAGE;

    if (requestedSize < minimumSize) {
        throw new IllegalArgumentException("requested size is smaller than the minimum size");
    }
}

プールの関係性は内部ルーティングの不変条件です。要求されたサイズは実行時引数であり、呼び出し元が正しくない値を渡すことは十分にあり得ます。

内部 API でも、S3 のアップロード失敗、データベースエラー、ネットワークタイムアウトなど、運用上の障害には例外をスローする必要があります。内部 API であるというだけで、想定される実行時障害がアサーションに変わるわけではありません。

一度検証してから、内部ではアサートする

外部データは境界で例外を使用して検証します。信頼できる内部データに変換した後は、伝播する不変条件を記録するためにアサーションを使用します。

パーサーにも同じルールが適用されます。

  • 外部または信頼できないコンテンツを解析するときは、解析例外をスローします
  • 同じアプリケーションで生成および検証されたコンテンツを解析するときは、前提条件をアサートします
例外のログ出力

例外をスローしても、すべてのレイヤーでログを出力する必要はありません。元の原因を保持し、操作を処理、変換、再試行、または終了できるレイヤーまで例外を伝播させます。各レイヤーでログを出力してから再スローするのではなく、その処理境界で一度だけ例外をログに記録します。


例外の捕捉

必要最小限の例外だけを捕捉します。

✅ 推奨
try {
    // do something
} catch (IllegalArgumentException e) {
    // do catch
}
❌ 非推奨
try {
    // do something
} catch (RuntimeException e) {
    // do catch
}
AutoCloseable の実装

AutoCloseable を実装する場合:

  1. close() をべき等(複数回呼び出しても安全)にします
  2. close() から例外を決してスローしないでください(抑制された例外に関する問題があるため)
  3. 例外をスローする代わりに、エラーをログに記録します
✅ 推奨
public final class DatabaseConnection implements AutoCloseable {

    @NonNull
    private final Connection connection;

    private volatile boolean closed = false;

    @Override
    public void close() {
        if (closed) return;  // Idempotent: safe to call multiple times

        closed = true;
        try {
            connection.close();
        } catch (SQLException e) {
            // Log only; do not throw (exceptions in close can suppress the original)
            logger.warn("Failed to close connection", e);
        }
    }
}
❌ 非推奨
public final class DatabaseConnection implements AutoCloseable {

    @NonNull
    private final Connection connection;

    @Override
    public void close() throws SQLException {
        connection.close();  // Not idempotent; exception propagates
    }
}

日付/時刻 API

java.time(JSR-310)のみを使用してください。新しいコードでは java.util.Date と java.util.Calendar の使用は禁止されています。

型の選択
用途型例
保存/転送(UTC)InstantAPI のタイムスタンプ、DB への保存
ユーザーへの表示ZonedDateTimeUI に表示する時刻
日付のみLocalDate誕生日、有効期限
時刻のみLocalTime営業時間、アラーム
期間Duration / Period経過時間、日付の差
✅ 推奨
public final class Event {

    @NonNull
    private final Instant createdAt;  // UTC timestamp

    @NonNull
    private final LocalDate eventDate;  // Date only

    @NonNull
    private final ZoneId timeZone;  // Display time zone

    @NonNull
    public ZonedDateTime displayTime() {
        return createdAt.atZone(timeZone);
    }
}

フォーマッターの再利用(スレッドセーフ):

public final class DateFormats {

    // DateTimeFormatter is thread-safe, so reuse as constants
    public static final DateTimeFormatter ISO_DATE = DateTimeFormatter.ISO_LOCAL_DATE;

    public static final DateTimeFormatter ENGLISH_DATE = DateTimeFormatter.ofPattern("MMM dd, yyyy");

    private DateFormats() {}
}
❌ 非推奨
public final class Event {

    @NonNull
    private final Date createdAt;  // Do not use java.util.Date

    @NonNull
    private final Calendar calendar;  // Do not use java.util.Calendar

    @NonNull
    public String format() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");  // Not thread-safe
        return sdf.format(createdAt);
    }
}
レガシー API との連携(境界でのみ)

Date を必要とするレガシー API と連携する場合は、境界で変換してください。

// Instant → Date (boundary layer only)
Date legacyDate = Date.from(instant);

// Date → Instant (convert immediately on receipt)
Instant instant = legacyDate.toInstant();

その他のガイドライン

ビルダーパターン

API の一貫性を保つため、Lombok 形式のビルダーを使用してください。

変数とメソッドの命名
  • JVM 10 以降の var キーワードを使用してください
  • 意味の分かる変数名を使用してください
    • ただし、変数名、関数名、フィールド名が長くなりすぎないようにしてください
  • メソッド名には動詞を使用してください
  • ゲッターを除き、get 接頭辞の使用は避けてください
キャメルケースの規則

大文字と小文字が混在する単語や略語にはキャメルケースを適用してください(例:「MultiNIC」は「MultiNic」にします)。

制御フロー文

波括弧を使わず、単純な if 文には1行構文を使用してください。

✅ 推奨
if (someCondition) doSomething();
❌ 非推奨
if (someCondition) {
    doSomething();
}
コメント
  • 特別な場合にのみコメントを使用してください
  • TODO コメントは、すぐに取り組むタスクに使用してください
  • コードの内容を繰り返す不要なコメントは避けてください

関連項目

リファレンスガイド

各トピックを詳しく理解するには、以下を参照してください。

コメント