Claude Media
Claude CodeでJavadocのdoclintエラーを直す — 警告の書き換えは人に回す

Claude CodeでJavadocのdoclintエラーを直す — 警告の書き換えは人に回す

-Xdoclint:allの指摘をerrorとwarningに分け、HTMLや参照の誤りだけをClaude Codeに直させる手順です。@param漏れなど意味の要る警告は人に回し、Stop hookで再発を防ぎます。

JavadocのdoclintエラーをClaude Codeに直させるなら、まず -Xdoclint:all,-missing で「ビルドを止めるエラー」だけを取り出します。& のエスケープ漏れや閉じていないタグ、解決できない @see は、機械的に直せます。@param の説明文や、コメントの無いメソッドへの説明は、意味を書く作業なので分けて扱います。

ここでは、Stop hookで再発を止める設定と、Claudeに触らせる範囲を決めるCLAUDE.mdの書き方を示します。出力例は、手元のJDK 17.0.3(Corretto)で小さなサンプルを実行して得たものです。

doclintの指摘は5つのグループに分かれる

DocLintは、ドキュメンテーションコメントの問題を5つのグループに分けて報告します。accessibility、html、missing、reference、syntax です。グループごとに有効・無効を切り替えられます。

グループ検出する例重大度の傾向
syntax検出する例未エスケープの < > &、不正なタグ重大度の傾向多くはエラー
html検出する例閉じていない要素、インライン要素の中のブロック要素重大度の傾向多くはエラー
reference検出する例@see や {@link} の参照先がない、@param の名前違い重大度の傾向多くはエラー
accessibility検出する例<img> の alt 抜け重大度の傾向多くはエラー
missing検出する例クラスのコメント欠落、@param や @return の漏れ重大度の傾向警告

missing だけが警告に寄るのは、生成されたHTMLを検証するツールがエラーにしないからです。「直さないとjavadocが失敗する」指摘と、「書けば良くなる」指摘が、グループで分かれていると考えると整理できます。

DocLintは入力を直しません。報告するだけです。直す作業が、Claude Codeの出番になります。

実際に出る指摘を見る

次の Calc.java に、意図的に問題を入れました。

package demo;
 
/**
 * Calculator with <b>helpers.
 * Returns a & b.
 */
public class Calc {
 
    /**
     * Adds two numbers.
     * @param a first
     * @return sum
     */
    public int add(int a, int b) {
        return a + b;
    }
 
    /**
     * Logs a message.
     * @return nothing
     * @see Missing
     */
    public void log(String msg) {
    }
 
    public int sub(int a, int b) { return a - b; }
}

javac -Xdoclint:all でも javadoc -Xdoclint:all でも、結果は同じでした。エラー4個と警告3個です。メッセージは日本語ロケールの出力を訳して示します。

行種別内容
5種別エラー内容HTMLエンティティが不正(&)
4種別エラー内容要素が閉じられていない(<b>)
20種別エラー内容@return の使い方が無効(戻り値がvoid)
21種別エラー内容参照が見つからない(@see Missing)
14種別警告内容b の @param がない
23種別警告内容msg の @param がない
26種別警告内容コメントがない(sub)

エラー4件は、どれも書く内容を考えなくても直せます。& は &amp; に、<b> は閉じるか {@code} などに換えます。voidメソッドの @return は消します。@see は、正しい参照先に直すか削ります。

警告3件は違います。@param b の説明は、b が何を表すかを知っている人しか書けません。sub に付ける説明も同じです。Claudeは名前から推測して書けますが、推測が合っているかは誰かが読んで確かめることになります。

エラー4件を直すとこうなる

機械的に直せる範囲は、サンプルでは次の差分になります。@see Missing は、参照先が実在しないので削る例にしました。実際のコードでは、正しいクラス名への修正になることも多いはずです。

前と後

エラー4件の直し方

BEFORE

直す前

/**
 * Calculator with <b>helpers.
 * Returns a & b.
 */
AFTER

直した後

/**
 * Calculator with <b>helpers</b>.
 * Returns a &amp; b.
 */

log メソッドのほうは、@return nothing と @see Missing の2行を消します。戻り値がvoidなので、@return は書けません。

ここで注意したいのは、エラーが消えたからといって文書として正しくなったとは限らないことです。Returns a & b. の元の意図が「aとbの論理積」だったのか、単なる「aとb」だったのかは、コメントからは分かりません。エスケープは機械的に直せても、文の意味までは検証されません。

グループを外して「直してよい指摘」だけを返す

missing を外すと、エラーだけが残ります。

javadoc -Xdoclint:all,-missing -quiet -d build/doclint-out \
  -sourcepath src/main/java -subpackages com.example

同じサンプルに -Xdoclint:all,-missing を付けて実行すると、上の4つのエラーだけが出て、警告3件は消えました。終了コードは、エラーがあれば1、無ければ0です(別の小さなサンプルで確認)。

Mavenを使う場合、maven-javadoc-plugin の doclint パラメーターに同じ値を渡せます。ユーザープロパティ名も doclint です。failOnError の既定値は true で、failOnWarnings は3.0.1から使え、既定値は false です。警告まで失敗扱いにする設定は、missing の警告を人が片付け終わってから入れるほうが安全です。

pom.xmlでは、次のように書きます。

<plugin>
  <artifactId>maven-javadoc-plugin</artifactId>
  <configuration>
    <doclint>all,-missing</doclint>
    <failOnError>true</failOnError>
  </configuration>
</plugin>

hookで呼ぶコマンドも、この設定と同じ値にそろえます。mvn javadoc:javadoc をhookから呼ぶ形にすれば、チェック対象の食い違いがなくなります。ただし、Mavenの起動はjavadoc単体より時間がかかります。

Stop hookでエラーが残る間は止まらせない

Claudeが作業を終えようとしたとき、doclintのエラーが残っていれば止める設定にします。Stop hookが終了コード2で終わると、stderrがClaudeに返り、会話が続きます。

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/doclint.sh",
            "timeout": 120
          }
        ]
      }
    ]
  }
}

スクリプトは次のとおりです。

#!/bin/bash
# .claude/hooks/doclint.sh
input=$(cat)
# 直前のStop hookで続行中なら、無限ループを避けて通す
if [ "$(echo "$input" | jq -r '.stop_hook_active')" = "true" ]; then
  exit 0
fi
 
out=$(javadoc -Xdoclint:all,-missing -quiet \
  -d "$(mktemp -d)" -sourcepath src/main/java \
  -subpackages com.example 2>&1)
if [ $? -ne 0 ]; then
  echo "$out" | head -40 >&2
  exit 2
fi

stop_hook_active を見ているのは、Stopのhookが続けて同じ理由で止め続けないようにするためです。この値が true のときは、Claude Codeがすでにstop hookの結果で続行している最中です。また、連続して続行できる回数にも上限があり、既定では8回続けて止めたあとは次のブロックが無視されます。上限は CLAUDE_CODE_STOP_HOOK_BLOCK_CAP で変えられます。

ガードを入れた結果、直し切れないエラーがあっても、Claudeは2回目には止まれます。残りは最後の出力に出るので、人が読んで判断します。

head -40 で切っているのは、大きなモジュールで指摘が数百件出たとき、Claudeのコンテキストをdoclintのログでいっぱいにしないためです。

CLAUDE.mdで触らせる範囲を決める

hookが「エラーを残さない」を保証しても、直し方は別問題です。たとえばエラーを消すために、@SuppressWarnings("doclint") をクラスに付けてしまう直し方もあります。DocLintは、この注釈があるとその宣言と中身のメッセージをエラーも含めて黙らせます。しかも、一部だけ再び有効にすることはできません。

そこで、CLAUDE.mdに範囲を書きます。

## Javadoc
- `javadoc -Xdoclint:all,-missing` のエラーだけを直す
- 直してよいのはコメント内のHTML、`&` と `<` `>` のエスケープ、
  存在しない `@see` `{@link}` の参照、voidメソッドの `@return`
- `@SuppressWarnings("doclint")` は付けない
- コメントが無いメソッドへの説明や `@param` の説明文は書かない。
  該当箇所の一覧だけを最終の返答に出す
- コード(シグネチャ・本体)は変更しない

最後の2行が、意味の書き換えを人に回す部分です。@param の追加は警告側ですから、Claudeがやるのは「どのメソッドのどの引数が足りないか」を一覧にするところまでです。

missingを片付けるときの進め方

エラーが0になってから、-Xdoclint:all に戻して警告を数えます。件数が多ければ、パッケージごとに分けます。DocLintには -Xdoclint/package: オプションがあり、パッケージ名か * 付きの接頭辞を指定して、対象を絞ったり除外したりできます。

Claudeに下書きを書かせる場合は、1回の依頼を1クラスに絞ると、差分が読みやすくなります。書かれた説明は、メソッド本体を見て内容が合っているか確かめます。名前から推測した説明は、読み流すと間違いに気づけません。

公開APIの一部だけが対象なら、@SuppressWarnings("doclint:missing") を宣言に付けて意図的に外す手もあります。この場合は付けた理由を人が決めます。注釈をClaudeに任せると、エラーを消すために使われるおそれがあるからです。

編集のたびに見たいときはPostToolUse

Stop hookは、作業の最後にまとめて見る形です。編集の直後に指摘を返したいなら、PostToolUseを使います。このイベントはツールの成功後に動き、終了コード2で終わるとstderrがClaudeに渡ります。ツールはすでに実行済みなので、ブロックはされません。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "if": "Edit(*.java)",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/doclint.sh"
          }
        ]
      }
    ]
  }
}

if には、Edit(*.java) のような権限ルール形式を1つだけ書けます。.java 以外の編集ではスクリプトが起動しません。if に書けるルールは1つだけなので、複数の条件を足したいときはハンドラーを分けます。

編集のたびに javadoc を回すと、大きなプロジェクトでは待ち時間が増えます。まずはStop hookだけで始め、遅さが気にならない規模なら足す、という順が現実的です。

javacとjavadocでの動きの違いと限界

DocLintは、javac と javadoc のどちらからも呼べます。違いは、チェックする対象の決まり方です。

  • javadoc から呼ぶと、生成されるドキュメントに使われるコメントを既定でチェックします。直列化できるクラスの private メンバーも、Serialized Formのページに載るなら対象になることがあります
  • javac から呼ぶと、-Xdoclint 系のオプションだけでチェック対象が決まります

つまり、同じソースでも、javadoc の -public などの可視性オプションによって指摘の数が変わります。CIで使うコマンドと、hookで使うコマンドは揃えておくと、「手元では通るのにCIで落ちる」を避けられます。

もう1つの限界があります。DocLintはHTMLの完全な検証器ではありません。よくある誤りを、見つけやすい形で報告するという位置づけです。生成後のHTMLを検証する外部ツールのほうが完全な検査ができる一方、問題がビルドのどこで入ったかは探しにくくなります。DocLintは、リンク切れやカスタムtagletの問題までは検出しません。

doclintが通っても、生成されたドキュメントのリンクが切れていないとは言えません。この点は、Claudeへの依頼文で「doclintが0件なら完了」と書かないほうがよい理由になります。完了の条件は、hookが見ている範囲と同じ言葉で書いてください。

似た作業との違い

警告を失敗扱いにして直させる型は、ほかの言語でも使えます。TypeDocの警告では、@link の解決とexportされていない型で、直してよい場所が分かれました。Sphinxではsphinx-build -Wで警告を失敗にすることで、同じ流れが作れます。

未記載の関数に説明を足す作業は、Pythonのdocstringでも「意味を書く側」にあたります。Javaでは missing グループがそれに近い位置です。

Javaのバージョンを上げる作業は、OpenRewriteを使ったJava 21への移行で扱っています。移行が済んでからdoclintを回すと、指摘がコメント由来のものに絞られます。

まとめ

doclintの指摘は、エラーとして直せるものと、意味を書く必要がある警告に分かれます。前者は -Xdoclint:all,-missing とStop hookで、Claudeに機械的に直させられます。後者は一覧を出させて、人が説明を決めます。

最初に確かめるのは、手元で -Xdoclint:all と -Xdoclint:all,-missing の件数がどう違うかです。その差が、人に回す作業量になります。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. docs.oracle.com/en/java/javase/21/docs/specs/man/javadoc.html
  2. docs.oracle.com/en/java/javase/21/javadoc/javadoc.html
  3. maven.apache.org/plugins/maven-javadoc-plugin/javadoc-mojo.html
  4. code.claude.com/docs/en/hooks
この記事を共有:XはてブLinkedIn