Javadoc ドキュメンテーションコメントの書き方

  • 533
    Like
  • 0
    Comment

はじめに(モチベーション)

こんな話があります。あるソフトウェア企業が一人の技術者の採用を決めました。その決め手となった理由は、「公開しているオープンソースソフトウェアのドキュメントが素晴らしかったから」です。彼らは、作成されたドキュメントを見ただけで、その人には技術力がある、一緒に働いて欲しいと判断したのです。

ある国の言語を学ぶために読み書きの練習が必要であるのと同様に、コーディング技術をつけるには、多くの良質なコードを読み、多くのコードを書くことが必要です。設計ドキュメントを書くのも同じことです。日頃から分かりやすいドキュメントを書く鍛錬を怠らず、長年の経験を積んでいかなければ、良質なドキュメントを書く力は身に付きません。今日からドキュメンテーションコメントをバリバリ書いて、ドキュメンテーション力を付けていきましょう。

    Let's Enjoy ドキュメンテーションコメント!

ドキュメンテーションコメントとは?

API の使用方法を表すコメント(=ドキュメンテーションコメント)

ドキュメンテーションコメントは「APIの仕様」であり、主に API を使用する人のために記述するものです。そのため、通常は public あるいは protected なメソッドや、フィールドが記述の対象となります。ドキュメンテーションコメントを見ただけで、その API を正しく使えるようになっている必要があります。ある API を使おうと思った時に、そのメソッドの実装を見なければ呼び出し方が分からないようであれば、正しく説明ができていないということです。

Java の ドキュメンテーションコメントは /** */ という形式の Javadoc フォーマットで記述します。C# では ///、Python では """ と、いろいろ書き方はありますが、何を書くべきかという本質的な部分はどの言語でも同じです。Javadoc フォーマットでコメントを記述しておくと、javadoc コマンドを使ってドキュメントファイルを生成できます(一般的に HTML 形式で出力します)。javadoc コマンドは、デフォルトで public/protected メソッドのみドキュメントを生成するようになっています。

API の実装方法を表すコメント(=通常コメント)

そのメソッドを「どのように実装しているのか」を示すコメントは、実装者のための通常コメントであり、ドキュメンテーションコメントとしては出力しなくてもよいものです。private メソッドとして実装される部分は、この種のコードに該当します。private メソッドを使用できる状況というのは、つまりは、そのクラスのコードを参照しながら作業しているということなので、ドキュメントとして出力する必要はないということです。これは、private なフィールドにコメントが必要ないということではないので注意してください。詳細実装に必要なコメントは private なフィールドに対しても記述しておくべきです。Java では、通常のコメントは ///* */ を使って記述します(もちろん Javadoc 形式 /** */ でも記述可能です)。

コメントを記述するときは、API 実装者のためのコメント(実装方法)と、API 使用者のためのコメント(使用方法)のどちらを書いているのかを常に意識しましょう。

なぜドキュメンテーションコメントが必要なのか?

一般公開する目的で作られたライブラリにドキュメントが必要なのは明らかです(中には、「コード読め」、「試しながら使え」、といったやる気なしライブラリもありますが)。一方で、内製のアプリケーションを作成するときにも、ドキュメンテーションコメントを記述すべき理由があります。

多人数での開発効率を上げるため

大規模なソフトウェアを開発する場合、多人数でコードを共有して修正していくことはよくあることです。他の人が作成した API を呼び出すこともありますし、その API を修正しなければならないこともあります。このとき、API 仕様が明確になっていないと、正しく API を呼び出すことはできませんし、既存のコードを正しく修正することもできません。その結果、全体のコード品質は低下し、メンテナンスコストは増大していきます。

Aさん「このメソッドの動きよく分からないんだけど、誰が作ったの?」
Bさん「Cさんか、Dさんじゃない?」
Cさん「うーん、これ何だったけ・・・忘れた!」
Dさん「I'm not sure, E-san knows about it.」
Eさん「这是什么?我不太记得」
   ・・・
Aさん「しょうがない、コード分析するか・・・」

そもそも、そのメソッドの作成者が近くにいない場合、こういった確認すら行えません。結局、あるメソッドを使うために、そのメソッドの実装を時間をかけて分析することになるため、複数人で開発していることが、逆に開発効率を悪化させてしまいます。つまり、簡単に言うと、

    「仕様の明確でないメソッドを作るのは迷惑行為です!」

ドキュメンテーションコメントによって API 仕様が明確にされていれば、こういった無駄なやりとりがなくなるため、開発効率もコード品質も上がります。下記のグラフは、開発メンバの人数と、生産性の関係を表しています。

生産性
  |                    *** ドキュメンテーション
  |                 ***    コメントのあるプロジェクト
  |              ***
  |           ***
  |        ***      ............. ドキュメンテーション
  |     ***  .......              コメントのないプロジェクト
  |   **.....
  | **
  |*
  +---------------------------------------------------- 開発メンバ数

仕様の不明確な API が溢れているプロジェクトに新しい実装メンバを投入しても、開発効率はうまく上がっていきません。すべての API の仕様が明確になっていれば、不具合が見つかった場合でも、各メソッドが何を実現すべきかが分かるので、別の人が実装を引き継いで修正していくことが可能になります。クラスの責務が明らかになっていれば、そのクラスにどのようなメソッドを追加すべきなのか、すべきではないのかも分かるため、メンテナンス不能な巨大クラスが作られるのを防ぐことができます。

後継者のため

プログラムはある瞬間、あるリリースバージョンだけ動けばいいというものではありません。長期的にコード品質を維持していくために、ドキュメンテーションコメントは必須だと考えましょう。あなたの書いたコードを、今後数年にわたってメンテナンスしていくのは他の人かもしれません。

    「自分にしか理解できないコードを書くことは迷惑行為です!」

チームリーダやマネージャの立場の人は、ソフトウェアの動作だけを見て、そのソフトウェアの品質を評価するのではなく、コードそのものの品質やメンテナンス性まで見るべきです。実際のところ、コードを深くまで読む時間はないでしょうから、その代わりに、生成されたドキュメントをざっと見てみれば、コードの品質も分かるものです。

ユニットテストを正しく記述するため

どのようなユニットテストを記述すべきかは、API 仕様が明確になっていれば、自ずと明らかになります。そもそも、API の仕様を説明できないのであれば、正しいユニットテストを書くことはできません。まずは、API の仕様をドキュメンテーションコメントという言葉で説明し、それを具体的にユニットテストの形にブレイクダウンする、という流れがおすすめです(少なくとも、両方とも存在しないという状況にはならないようにしましょう)。

多人数でのプロジェクトの場合、他の人が書いたメソッドのテストコードを作成しなければならないケースもあるでしょう(もちろん望ましいプロセスではありません)。そのためには、パラメータの定義域などの詳細情報が必要になります。それらを含めた API 仕様がちゃんと記述されていれば、他の人にバトンタッチしても、正しくユニットテストコードを作成することができます。逆に API 仕様が記述されていなかったとしたら、何を頼りにしてテストコードを書けばいいのでしょうか?メソッドの実装を見て、必ず成功するテストコードを書きますか?それは、そのメソッドの実装に対して、うまく動作する範囲の入力を与えているだけで、そのテストコードにあまり意味はありません。テストコードは、そのメソッドが正しく動作しているかを調べるためのものであって、メソッドの変更を検出するものではありません。テストコードの前に、必ずそのメソッドの仕様があるはずです。

オープンソースの世界で生きていくため

自分ひとりで作成しているソフトウェアであったとしても、Sourceforge、GitHub といったサイトでコードを管理するのであれば、そのソフトウェアの使い方、ドキュメンテーションコメントを分かりやすく記述しておくべきです。ドキュメンテーションコメントは、オープンソースのコードを読むときの手掛かりになります。世界中の人から、改善案や不具合修正などのコントリビューションを受けやすくなります。他の人のために書いたドキュメントは、自分のためのフィードバックとなって返ってきます。

いつ、誰が書くべきか?

そのクラス、メソッドを実装するときに、その実装者がドキュメンテーションコメントを書くべきです。とりあえず実装しておいて、後からドキュメンテーションコメントをまとめて書こうという姿勢はよくありません。そのメソッドを実装しようとしているときが、その API の仕様を一番理解しているときなので、そのタイミングでドキュメンテーションコメントを記述してしまうのが一番効率がよいです。後になって、誰が実装したのかも分からないクラス、メソッドの仕様を定義するなんてゾッとします。ドキュメントは実装時に記述しておかないと、結局最後まで記述されないものが出てきます。

何を書くべきか?

クラス名や、メソッドのシグネチャを見ただけでは分からない情報がたくさんあるはずです。例えば、ReentrantLock という名前のクラスがあったとします。その名前だけを見て、そのクラスの役割や、どのように使うものなのか分かるでしょうか?そのようなことをドキュメンテーションコメントとして記述します。実装者がどう実装しているかではなく、利用者がどう利用するべきかという観点で書きましょう。

ユニットテストのコードは、ドキュメンテーションコメントの代わりにはなりません。「使い方はユニットテストのコードを参照」というのは避けてください。テストコードは、あくまで API 仕様を満たしていることをテストするため、API 仕様の説明を補うためにあるべきす。ドキュメンテーションコメントが書きにくい、ユニットテストも書けないと感じたら、まずは設計のまずさを疑ってみることをお勧めします(複数の責務が混ざってしまっていないかは特に気を付けるべきポイントです)。

メソッドのコメント

メソッドのドキュメンテーションコメントには、何のためのメソッドなのか、どのように使用するのか、どういった副作用があるのかなどを記述します。

メソッドのパラメータに関するコメント

どういった入力を与えることができるのか、想定範囲(定義域)を超えた値を渡した場合に何が起こるのかなどを明確にします。渡されたオブジェクトの内容が変更される(副作用がある)場合は、必ず分かるように記述しましょう。

  • パラメータとして null を渡せるのか?渡した場合に何が起こるのか?(オブジェクト、String、配列型のパラメータが該当します)
  • パラメータとして空の文字列 ("") を渡しせるのか?渡した場合に何がおこるのか?
  • サイズ 0 のリスト、配列を渡せるのか?渡した場合に何が起こるのか?
  • パラメータに渡せる数値の範囲は? 0 や負の値を渡せるのか? 小さすぎる値、大きすぎる値を渡せるのか?
  • 文字列形式で、アルゴリズム名や定数値を渡す場合、どのような値を渡せるのか?
  • パラメータで渡したオブジェクトは変更されるのか?(Java の final は C++ の const と全然違うよ!)

パラメータに null を渡せるかどうかは、例えば、下記のように記述しておけばよいでしょう。

 * @param ... , not {@code null}
 * @param ... . Cannot be {@code null}.
 * @param ... . May be {@code null}, in which case ...

メソッドの戻り値に関するコメント

どのようなときに、どういった戻り値を返すのかを記述します。特に、null を返すことがあるのであれば、それはどんな場合かを明確に書いておくべきです。メソッドのシグネチャを見ただけでは、そのメソッドの呼び出し側で null チェックをすべきかどうかは分からないので、必ずドキュメンテーションコメントとして記述する必要があります(そもそも、安易に null を返すようなメソッドを作るべきではありませんが、それはまた別のお話)。例えば、戻り値の説明に、下記のような説明があるだけで、null に関する扱いは明確になります。

 * @return ... . Returns {@code null} if ...
  • 空文字 と null のどちらを返すのか?
  • 空リストと null のどちらを返すのか?
  • どんな範囲の値を返すのか? 0 や負の値を返すことがあるのか?
  • オブジェクトを返す場合、呼び出し元でそのオブジェクトの内容を変更しても問題ないか?(primitive な型の値や、immutable なオブジェクトを返すのであれば問題なし)
  • 配列やコレクションを返す場合、その要素はソートされているのか?

一意の ID として数値を返すような場合でも、その値域が明らかになっていると、呼び出し側のコーディングが楽になることがあります。例えば、0 以上の値が返されることが明らかになっているのであれば、呼び出し側で、その値を配列インデックスとして扱える可能性があります。負の値を渡してはいけないメソッドにも、値のチェックをせずにそのまま渡すことができます。

メソッド全般に関するコメント

  • そのメソッドはスレッドセーフなのか?
    • ただし、通常はスレッドセーフかどうかはクラス単位で設計すべきなので、クラスのコメントとして記述すべきです。
  • そのメソッドを呼び出すことにより、どのような副作用があるのか?
    • あるメソッドを呼び出すと、別のメソッドの振る舞いが変わるなど(こういったものはクラスのコメントとしても記述すべきです)。
  • 例外が発生する場合は、どのような条件で発生するのか? (@throws)
    • 例外の回避のために特別な処理が必要な場合は、その方法を明記。
    • よくない例: @throws XxxException ○○例外が発生した場合
    • 例外が発生する「条件」が明記されていません
    • よくない例: @throws XxxException 不正な引数が渡された場合
    • 「不正な引数」とは何なのかが明記されていません
  • 非推奨メソッドである場合はその理由と代替案 (@deprecated)
  • メソッド呼び出し前の事前条件
  • メソッド呼び出しに失敗したときに何が起こるか
  • 似たようなメソッドとの使い分け基準

メソッドのパラメータとして null を渡せないことや、メソッドが null を返さないことを毎回記述するのは煩雑だというときは、プロジェクト全体で下記のようなルールを作っておくのもよいでしょう。

  • ドキュメントに記載がない限り、メソッドのパラメータに null は渡してはいけない。
    • null を渡してしまった場合は、メソッドの呼び出し側の不具合とみなす。
    • メソッドの実装側では null が渡されないことを前提に実装してよい。null が渡された場合に、明示的に NullPointerExceptionIllegalArgumentException を投げるのもあり。
  • ドキュメントに記載がない限り、メソッドは null を返さない。
    • メソッドの呼び出し側は、戻り値の null チェックを行う必要はない。
    • メソッドが List 型を返すようなケースでは、null ではなく Collections.emptyList() を返すようにする。
    • 独自オブジェクトを返すようなケースでは、Null オブジェクトパターンを適用し、不用意に null を返さない。

メソッドのオーバライド版が存在する場合は、何らかの目的のあるために作ったはずですので、ドキュメントの内容からもそれが分かるように記述しましょう。概要説明もコピペするのではなく、どのように使い分けるべきかが分かるように記述すべきです。

クラスのコメント

クラスのドキュメンテーションコメントには、そのクラスの目的、責務、特徴などを記述します。また、クラスの全体的な使用方法に制約がある場合(例えば、最初に init() を呼ばないと、別のメソッドが動作しないなど)、各メソッドの説明に記述するだけでは分かりにくいので、そのクラスの使い方として記述しておくべきです(ちなみに、このようなケースでは IllegalStateException 例外を適用できます)。

複数メンバで開発している場合は、クラスの責務を記載しておくことは特に重要になります。クラスの責務が明確になっていないと、別の開発者が、関係のないメソッドをそこに追加してしまいます。最終的に、数千行を超える何でも入りクラスになってしまい、メンテナンス性の悪いコードができてしまいます。クラスのドキュメントは、そのクラスを作成する人が必ず記載するようにしましょう。一方で、メソッド、フィールドを追加する人は、必ずそのクラスの責務が何なのかを確認し、本当にそのクラスに追加すべきものなのかをよく考えるようにしましょう。 :arrow_right: 単一責任の原則 (SRP)

他にも、クラスの説明には以下のようなことを記述します。

  • そのクラスはスレッドセーフ(複数スレッドからオブジェクトを扱える)なのか?
    • 特に記載がない場合、そのクラスはスレッドセーフに実装されているとみなす
    • 代替となるスレッドセーフ版クラスへのリンク(例: Java の HashMapHashtable
  • そのクラス内で使う用語の定義(パッケージ全体で使用する用語であれば、パッケージドキュメントに書くべきです)
  • 別のクラスとの関連、およびそのクラスドキュメントへのリンク。
  • インスタンス化方法が特殊なのであれば、その方法と、その方法で生成する理由。インスタンス化に別のファクトリクラスが必要なのであれば、その方法とドキュメントへのリンク
  • 使用方法が難しいのであれば、サンプルコード (<pre>)
  • 存在するのであれば、クラスの型パラメータの説明 (@param <T>)
  • 継承することを想定した abstract クラスであれば、どのように実装すべきか
  • オブジェクトの状態によって全体の振る舞いが変化するのであれば、その説明
  • 不具合があることが分かっていて、当面直せそうにないものであれば、それによる制約
  • シリアライズの対象外となるデータの説明

例: ArrayList のコメント

/**
 * ArrayList is an implementation of {@link List}, backed by an array.
 * All optional operations including adding, removing, and replacing elements are supported.
 *
 * <p>All elements are permitted, including null.
 *
 * <p>This class is a good choice as your default {@code List} implementation.
 * {@link Vector} synchronizes all operations, but not necessarily in a way that's
 * meaningful to your application: synchronizing each call to {@code get}, for example, is not
 * equivalent to synchronizing the list and iterating over it (which is probably what you intended).
 * {@link java.util.concurrent.CopyOnWriteArrayList} is intended for the special case of very high
 * concurrency, frequent traversals, and very rare mutations.
 *
 * @param <E> The element type of this list.
 * @since 1.2
 */
public class ArrayList<E> extends AbstractList<E> implements Cloneable, Serializable, RandomAccess {

インタフェースのコメント

インタフェースのドキュメントは、どう振る舞うのかを記述すべきで、どう実装すべきかは実現クラス側の責務であることに注意しましょう。インタフェースが定義する API のコメントは、その実現クラスのコメントよりも慎重に記載する必要があります。例えば、あるインタフェースの API が戻り値として List を返すとします。この戻り値の仕様が明確に記述されていないと、ある実装クラスは null を返したり、また別の実装クラスは空リストを返したり、バラバラの実装をしてしまいます。これでは、その API の使用者は、どのように戻り値をハンドルすべきかが分かりません。典型的なパターンとして、あちこちに null チェックが入ることになり、コードの見通しが悪くなり、実行効率も悪くなってしまいます。

形容詞の名前が付くインタフェースは、何らかの付加価値を表しています。中には、メソッドをひとつも持たないマーカーインタフェースとして定義することもあります(例: Serializable, Cloneable)。このような場合は、それを実現したクラスで何が可能になるのかをドキュメントとして記述します。

パッケージのコメント

Java プロジェクトの場合、クラスが増えてくるとパッケージとしてグルーピングをすることになるでしょう。パッケージのドキュメンテーションコメントには、そのパッケージがどのようなクラスを提供するのかを記述します。パッケージを作成するときは、そのディレクトリに package-info.java という、パッケージのコメントを記述するためのファイルを作成できます。新規に Java パッケージを作成する人は、同時に package-info.java を書く責任を持ってください。

どんなクラスを含めるためのパッケージなのかを一番よく分かっているのは、そのパッケージを作った本人です。パッケージを作成する人は、まずはそのパッケージがどんな役割を持っているかを示すドキュメントを書くようにしましょう。他のプロジェクトメンバは、そのドキュメントを見て、新しいクラスをそのパッケージに含めるべきかどうかを判断できるようになります。

Javadoc の文法など

パッケージ概要や、クラス概要、メソッド概要(最初のピリオドまでの文章)は、ドキュメントの一覧のページに表示されることになるので、特に簡潔かつ用途が分かるように書きましょう。

書き方に迷ったら、Java のコアライブラリのコードを参考にするとよいです。
Java のコアライブラリであれば、Javadoc の文法的にも正しく書かれているはずなので、安心して真似できます。

ドキュメンテーションコメントの構成

ドキュメンテーションコメントの構成は、大きく、説明文を記述するセクション (description block) と、0 個以上のブロックタグ (block tags) を配置するセクションの 2 つに分かれます。ブロックタグの後ろに、説明文 (description block) を配置することはできないので、必ず下記のような構成になります。

/**
 * Description(概要説明 +詳細説明)
 *
 * block tags...(ブロックタグ)
 */

Javadoc ドキュメンテーションコメント内では、ブロックタグとインラインタグを使用して、別ドキュメントへリンクしたり、表示の制御を行うことができます。

ブロックタグ
`@タグ名 ...` のように、行頭から記述します。メソッドのパラメータの説明、例外の説明、外部ドキュメントへのリンク、バージョン情報などを記述できます。
インラインタグ
各文章の中で `{@code Hoge}` のように中括弧(curly brace) で囲んで記述し、主に表示上の体裁を整えるために使用します。

概要説明+詳細説明コメントの書き方

すべての Javadoc コメントの共通ルールとして、コメントの最初の一行(ピリオドで終わるまで、あるいは、最初にブロックタグが現れるまで)は、概要説明 (summary description)を記述します。必要であれば、その後ろに詳細説明を記述できます。

/**
 * Specifies whether or not date/time parsing shall be lenient.
 * With lenient parsing, the parser may use heuristics to interpret inputs
 * that do not precisely match this object's format. With strict parsing,
 * inputs must match this object's format.
 * ...

上記の例では、一行目が「概要説明」、二行目以降が「詳細説明」として扱われます。「概要説明」の一文は、パッケージ一覧、クラス一覧、メソッド一覧のページにまとめて表示されるので、ひと目で使い方がわかるように、必要な情報を簡潔に記述する必要があります。「概要説明」は完全な文の形になっていることは少なく、簡潔に記述するため、通常は動詞句や名詞句の形で記述されます(主語は明らかなので冗長なことが多い)。

  • 概要説明が動詞句になるもの -- メソッド
  • 概要説明が名詞句になるもの -- クラス、インタフェース、フィールド、メソッドのパラメータ

メソッドがオーバライドされている場合は、どう使い分けるかが分かるように概要説明を記述するべきです。

  • CopyOnWriteArrayList() -- Creates an empty list.
  • CopyOnWriteArrayList(E[] toCopyIn) -- Creates a list holding a copy of the given array.
  • CopyOnWriteArrayList(Collection<? extends E> c) -- Creates a list containing the elements of the specified collection, in the order they are returned by the collection's iterator.

文章の中に現れるピリオドが、概要説明の終わりかどうかを判断する基準は、後ろにスペース、あるいは改行が続いていることです。この判断基準を無効にするためには、{@literal} インラインタグが使用できます(例: {@literal 10 p.m.})。ピリオドの直後のスペースを &nbsp; に置き換えるという方法もありますが、{@literal} インラインタグを使ったほうが直感的です。

メソッドのコメントの書き方

下記は、典型的なメソッドの Javadoc コメントの例です。

/**
 * Inserts the objects in the specified collection at the specified location
 * in this List. The objects are added in the order they are returned from
 * the collection's iterator.
 *
 * @param index the index at which to insert
 * @param collection the collection of objects
 * @return {@code true} if this {@code ArrayList} is modified, {@code false} otherwise
 * @throws IndexOutOfBoundsException when {@code location < 0 || location > size()}
 */
@Override
public boolean addAll(int index, Collection<? extends E> collection) {

メソッドの概要説明は、三単現の s を付けた動詞で始め、ピリオドで終わります。その後に詳細説明が続く場合は、通常の文章(主語+述語.)の形で記述していきます。

/**
 * Registers the text to display in a tool tip. Second sentence ...

一方で、タグブロックの @param@return@throws の後のフレーズは、慣例として小文字で始まり、ピリオドでは終わりません(まれに Java のコアライブラリ実装でも、ピリオドが付いているものがありますが)。ただし、後ろに文が続く場合は、その文は大文字で初めてピリオドで終わります。@param タグの説明は、多くの場合 the/a/an などで始められます。@throws タグの説明は、多くの場合 if/when などで始められます。

 * @param ch  the character to be tested
 * @param observer  the image observer to be notified, not null
 * @param x  the x-coordinate, measured in pixels
 * @param x  the x-coordinate. Measured in pixels.

@throws タグでは、メソッドが投げる可能性のあるすべての捕捉例外の説明を記述すべきです。非捕捉例外 (RuntimeException) に関しての説明を記述すべきかどうかはいろいろと議論があります。なぜなら、メソッドが内部で呼び出した外部メソッドの非捕捉例外をすべて把握することは不可能だからです。とはいえ、少なくとも、使用者の判断で捕捉 (catch) するかしないかを選択することを前提に作られた非捕捉例外は @throws で説明しておくべきです。

メソッドの説明の中で、そのクラスのインスタンスに関して言及する場合は、「the ~」のように表現するのではなく、「this ~」と表現するのが慣例です。「the ~」とすると対象が曖昧になることがあるというのが理由でしょう。Java 言語の中の this キーワードの意味にも合致するという面もあります。

 * Gets the toolkit for this component. (preferred)
 * Gets the toolkit for the component. (avoid)

コード断片、HTML メタ文字の記述方法

HTML タグを記述できるようになっているため、小なり記号「<」や、大なり記号「>」をそのまま表示したい場合は、{@literal} インラインタグを使用する必要があります(JDK1.4 までは &lt; のように実体参照を使用していました)。

{@literal 0 < size < 10}

コードの断片を文章内に記述したい場合は、{@code} インラインタグを使用します。{@literal} と同様に HTML メタ文字をエスケープする機能を持ちますが、{@code} を使用することで、等幅フォントで表示されるようになるため、コードの一部であることをより視覚的に表すことができます。Java の予約語やメソッドのパラメータ名など、コード内に登場するキーワードは、通常の英単語と区別しやすいように、{@code} を使って記述すべきです。

... cannot be {@code null}.

サンプルコードなど、複数行にわたる表示を行いたい場合は、<pre> HTML タグと、{@code} インラインタグを組み合わせて使用します。

<pre>{@code
StringBuilder sb = new StringBuilder();
Formatter formatter = new Formatter(sb, Locale.US);
}</pre>

ドキュメント間のリンク

別のメソッドやクラスへのリンクを張るときには、{@link} インラインタグを使用できます。

{@link パッケージ名.クラス名#メソッド名 代替テキスト}

対象パッケージの import を行っている場合は、「パッケージ名」を省略することができます。さらに、クラス内のメソッドやフィールドへのリンクであれば、「クラス名」も省略することができます。「代替テキスト」を指定すると、ドキュメント上はそのテキストが表示されるようになります。下記は、いずれも正しいリンク方法です。

{@link com.example}
{@link com.example.Class}
{@link com.example.Class#Constructor()}
{@link com.example.Class#method()}
{@link com.example.Class#method(int, String)}
{@link com.example.Class#MAX_SIZE}
{@link com.example.Class#MAX_SIZE 最大値}
{@link Class}
{@link Class#Constructor()
{@link Class#method()}
{@link Class#method(int, String)}
{@link Class#MAX_SIZE}
{@link Class#MAX_SIZE 最大値}
{@link #Constructor()
{@link #method()}
{@link #method(int, String)}
{@link #MAX_SIZE}
{@link #MAX_SIZE 最大値}

パラメータの型などで Generics を使用している場合、そのオブジェクトの型は Object としてリンクを記述しておく必要があります。クラスやメソッドにリンクを張るときは、その Javadoc ブロックの中で最初に登場したときに限定するのがよいプラクティスです(リンクだらけになって見にくくなるからです)。2 回目の登場の際には、{@link #something(Foo, Bar)} とするのではなく、シンプルに {@code something} と書くとスッキリします。

等幅フォントでリンクが表示されるがいやな場合は、{@link} の代わりに {@linkplain} を使用します。

/**
 * A ThreadFactory builder, providing any combination of these features:
 * <ul>
 * <li> whether threads should be marked as {@linkplain Thread#setDaemon daemon}
 * threads
 * <li> a {@linkplain ThreadFactoryBuilder#setNameFormat naming format}
 * <li> a {@linkplain Thread#setPriority thread priority}
 * ...

他にも、参照すべきドキュメントを示すためのタグとして、@see ブロックタグが用意されています。{@link} インラインタグと同様に、別のクラスへのリンクを記述することもできますし、下記のように、外部ドキュメントの名前を記述することもできます。外部ドキュメントが、Web サイト上にある場合は、HTML リンクの形で記述しておくと親切です(RFC のサイトのように、URL があまり変化しないことが前提です)。

@see RFC 2045 section 6.8
@see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 6.8</a>

定数値のドキュメントへの埋め込み

{@value} インラインタグを使用すると、定数として定義された値そのものをドキュメント内に表示することができます。

/**
 * Chunk size per RFC 2045 section 6.8.
 *
 * <p>The {@value} character limit does not count the trailing CRLF, but counts
 * all other characters, including any equal signs.</p>
 *
 * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 6.8</a>
 */
static final int CHUNK_SIZE = 76;

ただし、一般的には具体的な定数値はドキュメンテーションコメントとして含めるべきではありません。実装者がその値を前提にコーディングしてしまうと、後から定数の値を変更することができなくなってしまうからです。将来的にも絶対に変わることがない値であることが分かっているのであれば、ドキュメンテーションコメント内に含めてしまっても問題ありません。例えば、以下のような値は、具体的な値を含めてしまうことができます(含める必要性があるかは別の話です)。

  • 具体的なエラーコード
  • TCP/IP の well-known ポート番号
  • アルゴリズム上、変更できないバッファサイズ

段落、リスト、テーブルのための HTML タグを使用する

Javadoc コメントには、HTML タグを含めることができるため、次のように使用することが可能です。

段落を分ける (p)

/**
 * First paragraph.
 * ...
 * <p>
 * Second paragraph.
 * ...
 * <p>
 * Third paragraph.
 * ...
 */

リストを表示する (ul/ol/li)

/**
 * ...
 * <ul>
 * <li>Item 1 ...
 * <li>Item 2 ...
 * <li>Item 3 ...
 * </ul>
 */

表(テーブル)を表示する (table/tr/th/td)

/**
 * ...
 * <table>
 * <tr><th>Header1</th><th>Header 2</th></tr>
 * <tr><td>Description 1</td><td>Description 2</td></tr>
 * <tr><td>Description 3</td><td>Description 4</td></tr>
 * </table>
 */

単語や文章を強調表示する (b/em/strong)

/**
 * Return a list of the services that are currently running.
 *
 * <p><b>Note: this method is only intended for debugging or implementing
 * service management type user interfaces.</b></p>
 * ...
 */

一般的に em はイタリックで表示されてしまい、見た目の強調が若干弱いため、代替手段として b が使われることが多いようです(意味的には strong の方が正しいのですが、無駄に長くなってしまうので避けられているっぽい)。

最低限守っておきたいこと

  • 一行目の概要文は、大文字で初めて、ピリオドで終わる。
  • /** */ だけ書くような CheckStyle 警告抑制のためだけのコメントを書かない。
  • クラス名、関数名、フィールド名と同じことを書かない。
  • メソッドが null を返すことがあるのであれば、必ずその条件を記述する(逆に null を返さないことが分かっているのであれば、呼び出し側では無駄な null チェックを行わない)。
  • javadoc コマンドでドキュメント生成して文法エラーが出ていないかを確認する (ant javadoc で簡単に実行できるようにしておくのがよい)。

『Effective Java 項目 44』にも、これまでの内容とほぼ同様のことが記述されています。

  • メソッドとそのクライアント間の契約を簡潔に記述すべき。
  • どのように処理を行っているかではなく、メソッドが何を行っているかを述べるべき。
  • すべての事前条件 (precondition) と事後条件 (postcondition) を列挙すべき。
  • メソッドではいかなる副作用も文書化すべき。
  • クラスのスレッド安全性 (thread safety) について記述すべき。
  • スローするすべての例外に対する @throws タグが書かれるべき。
  • @param タグあるいは @return タグに続くテキストは名詞句であるべき。
  • @throws タグの説明は if から構成される。
  • @param@return@throws タグの後の名詞句は慣例としてピリオドでは終わらない。
  • 2つのメンバーあるいはコンストラクタが同じ概要説明を持つべきではない。=> オーバライドしたメソッドがあるのであれば、それぞれ異なる概要説明を書くべきということ。
  • クラスのスレッド安全性のレベルを記述すべき(項目 70)。Immutable である、スレッドセーフである、条件付きスレッドセーフ (unconditionally thread-safe) である、などなど。、synchroznized 修飾子は Javadoc の出力には影響しません。
  • クラスがシリアライズ可能であれば、シリアライズ形式を文書化すべき(項目 75)。
  • 外部文書があるときは、関連するクラスあるいはパッケージからのリンクがあるべき。
  • ドキュメンテーションコメントは必須だとみなされるべきです。

(参考)Effective Java の中でのコメントサンプル:

/**
 * Returns the element at the specified position in this list.
 *
 * <p>This method is <i>not</i> guaranteed to run in constant
 * time. In some implementations it may run in time proportional
 * to the element position.
 *
 * @param index index of element to return; must be
 *        non-negative and less than the size of this list
 * @return the element at the specified position in this list
 * @throws IndexOutOfBoundsException if the index is out of range
 *         ({@code index < 0 || index >= this.size()})
 */
 E get(int index)

よくない例

クラス/メソッド説明で、その名前以上の情報を提供していない

/** Frame data. */
public class FrameData {

クラス名から得られる情報以上の情報が含まれていません。「クラスのコメント」のセクションを参照してください。

// ダメな例
/**
 * Sets the tool tip text.
 * @param text  the text of the tool tip
 */
public void setToolTipText(String text) {

// よい例(Oracle サイトより抜粋)
/**
 * Registers the text to display in a tool tip. The text
 * displays when the cursor lingers over the component.
 *
 * @param text  the string to display. If the text is {@code null},
 *              the tool tip is turned off for this compornent.
 */

API ドキュメントは、メソッド名以上の情報を提供するべきです。「メソッドのコメント」のセクションを参照してください。

実装詳細が含まれてしまっている

/**
 * Returns the mData.
 */

このドキュメントはツッコミどころ満載ですが、少なくとも、ドキュメンテーションコメントは、コードを読まないでも API 仕様が分かるように記述されている必要があります。詳細実装に出てくる mData のようなワードを API ドキュメントに含めるべきではありません。詳細実装を修正したとたんに嘘の情報になりますし、そもそもこういった情報は、API の使用者にとって役に立ちません。

パラメータの説明が型情報だけ

@param dateFormat DateFormat

パラメータの型情報はシグネチャから分かるので、あえて記載する必要はありません。何のために、どんな値を渡せばよいのか分かるように書きましょう。

翻訳しただけ

@param errorType エラータイプ

情報量ゼロ。

Unknown の説明が Unknown

enum MessageType {
    /** Unknown type. */
    UNKNOWN,

UNKNOWN という列挙値があるのであれば、そのコメントは "Unknown" ではなく、それが何を意味しているのかを記載すべきです。例えば、UNKNOWN という正しい値が存在していることと、正常なデータが取得できなかったときのために UNKNOWN という値を用意しているのでは、全く意味が異なります。

さいごに

プログラミングに必要な能力の半分は思いやりです。思いやりを持って設計、コーディングすれば、自然に分かりやすいクラス、メソッドができあがっていくはずです。


  • [2004-07-12] 初版発行
  • [2014-09-29] Markdown 化。なぜ書くべきか、サンプル、Effective Javaの言及、リンク切れなどを修正・追記。
  • [2017-03-14] Typo 修正(suy0ng さんご指摘ありがとうございます)