引言
在 Java 软件部门,我们有若干指导方针,使得我们的文档注释可能与第三方开发者的不同。我们的文档注释定义了官方的 Java 平台 API 规范。因此,我们的目标受众不仅包括开发者,还包括那些编写 Java 兼容性测试、实现或重新实现 Java 平台的人。
我们投入大量时间和精力来明确边界条件、参数范围和边缘情况,而不是定义常见的编程术语、撰写概念性概述或提供面向开发者的示例。
因此,通常有两种不同的方式来编写文档注释:
- 作为 API 规范
- 作为 编程指南文档
这两种目标将在以下章节中详细说明。拥有充足资源的团队可以将两者融合在同一份文档中(合理“分块”);然而,我们的优先级决定了我们必须将主要精力放在编写 API 规范上。这也是为什么开发者常常需要查阅其他文档(如 Java SE 技术文档和《Java 教程》)以获取编程指南的原因。
编写 API 规范
理想情况下,Java API 规范应包含所有用于“一次编写,到处运行”(Write Once, Run Anywhere)干净实现 Java 平台所需的断言——即任何 Java 小程序或应用程序在任何实现上都能一致运行。这可能包括文档注释中的断言,以及任何架构和功能规范(通常用 FrameMaker 编写)或其他文档中的断言。
这是一个崇高的目标,但在实践中存在一些限制。以下是我们在努力遵循的一些指导原则:
- Java 平台 API 规范由源代码中的文档注释及从这些注释可访问的标记为规范的文档共同定义。规范不一定完全包含在文档注释中。特别是,冗长的规范有时最好单独格式化并从文档注释中链接。
- API 规范是调用者与实现者之间的契约。它描述了调用者可以依赖的方法行为的所有方面,但不描述实现细节(例如方法是否为 native 或 synchronized)。
- 规范应以文本形式描述给定对象提供的线程安全保证。除非另有明确说明,否则所有对象都被视为“线程安全”(即允许多个线程并发访问)。
- 当前规范并不总能达到这一理想状态。
- 除非另有说明,Java API 规范的断言必须与实现无关。例外情况必须被明确标出并显著注明。
- API 规范应包含足够断言,使软件质量保证(SQA)团队能够编写完整的 Java 兼容性套件(JCK)测试。这意味着文档注释必须满足 SQA 的一致性测试需求。
- 注释不应记录 bug,也不应描述当前不符合规范的实现的实际行为。
编写编程指南文档
API 规范与编程指南的区别在于:后者包含示例、常见编程术语的定义、某些概念性概述(如隐喻),以及对实现 bug 和变通方案的描述。虽然这些内容有助于开发者更快地理解和编写可靠的应用程序,但由于它们不包含 API “断言”,因此在 API 规范中并非必需。
你可以在文档注释中包含上述任何信息(也可以使用自定义标签,通过自定义 doclet 处理)。在 Java 软件部门,我们有意不在文档注释中包含这一层级的文档,而是提供指向这些信息的链接(如《Java 教程》和变更列表),或将这些信息包含在与 API 规范相同的文档下载包中(JDK 文档包同时包含 API 规范、演示、示例和编程指南)。
关于记录 bug 和变通方案
代码应有的行为与实际行为之间有时存在差异,这表现为两种形式:
API 规范 bug:存在于方法声明或文档注释中,影响语法或语义。
例如:一个方法被指定在传入 null 时抛出NullPointerException,但实际上 null 是一个有用且被实现接受的参数。
如果决定修正 API 规范,应在规范本身或变更列表中说明。在文档注释中记录此类差异及其变通方案,能最有效地提醒开发者。代码 bug:存在于实现而非 API 规范中。
这类 bug 及其变通方案通常单独发布在 bug 报告中。但如果 Javadoc 工具用于生成特定实现的文档,则在文档注释中包含此信息会非常有用(可通过@bug等自定义标签或“注意”段落加以区分)。
谁拥有和编辑文档注释?
Java 平台 API 规范的文档注释由程序员拥有,但由程序员和文档工程师共同编辑。基本前提是:文档工程师和程序员相互尊重对方的专业能力,共同贡献出最佳的文档注释。
通常需要协商决定谁编写哪部分内容,依据包括知识、时间、资源、兴趣、API 复杂度以及实现状态。但最终注释必须由负责的工程师批准。
理想情况下,API 设计者应先在骨架源文件中编写 API 规范(仅包含声明和文档注释),再填充实现以满足已写的 API 契约。API 文档工程师的作用是减轻设计者的这部分工作。此时,API 设计者先用简略语言编写初始注释,文档工程师再审查、润色内容并添加标签。
如果文档注释是为重新实现者编写的 API 规范(而不仅仅是开发者的指南),则应由设计并实现了该 API 的程序员,或已成为该领域专家的 API 文档工程师编写。
如果实现是按规范编写的但文档注释未完成,文档工程师可通过检查源代码或编写测试程序来完成注释(例如检查抛出的异常、参数边界条件、是否接受 null 参数等)。
但如果实现未按规范编写,文档工程师只有在了解设计者意图(通过设计会议或单独的设计规范)或能随时向设计者提问的情况下,才能编写 API 规范。因此,为没有实现者的接口和抽象类编写文档会更加困难。
鉴于此,本指南旨在描述最终的文档注释应是什么样子。它们是建议而非必须盲目遵循的硬性规定。如果某些建议显得过于繁重,或能找到更有创意的替代方案,则可以灵活处理。
在开发像 Java 这样包含约 60 个包的复杂系统时,不同工程师小组(如负责 javax.swing 的团队)可能会制定不同于其他小组的指南,这可能是由于包的不同需求或资源限制所致。
术语
- API 文档(API docs)或 API 规范(API specs):面向 Java 程序员的在线或纸质 API 描述。可通过 Javadoc 工具生成,也可通过其他方式创建。API 规范是特定类型的 API 文档(如上所述)。例如:在线版《Java Platform, Standard Edition 7 API Specification》。
- 文档注释(doc comments):Java 源代码中以
/** ... */分隔的特殊注释。Javadoc 工具处理这些注释以生成 API 文档。 - javadoc:JDK 工具,用于从文档注释生成 API 文档。
源文件
Javadoc 工具可从四种不同类型的“源”文件生成输出:
- Java 类的源代码文件(
.java)— 包含类、接口、字段、构造函数和方法注释。 - 包注释文件 — 包含包注释。
- 概述注释文件 — 包含关于包集合的注释。
- 其他未处理文件 — 包括图像、示例源码、类文件、小程序、HTML 文件等。
更多详情请参阅:Source Files。
编写文档注释
文档注释的格式
文档注释使用 HTML 编写,必须位于类、字段、构造函数或方法声明之前。它由两部分组成:描述 + 块标签(block tags)。
示例:
/**
* Returns an Image object that can then be painted on the screen.
* The url argument must specify an absolute {@link URL}.
* The name argument is a specifier that is relative to the url argument.
* <p>
* This method always returns immediately, whether or not the
* image exists. When this applet attempts to draw the image on
* the screen, the data will be loaded. The graphics primitives
* that draw the image will incrementally paint on the screen.
*
* @param url an absolute URL giving the base location of the image
* @param name the location of the image, relative to the url argument
* @return the image at the specified URL
* @see Image
*/
public Image getImage(URL url, String name) {
try {
return getImage(new URL(url, name));
} catch (MalformedURLException e) {
return null;
}
}
注意事项:
- 每行注释应对齐下方代码。
- 第一行以
/**开始。 - 从 Javadoc 1.4 起,前导星号(
*)可选。 - 第一句应为方法的简短摘要,因为 Javadoc 会自动将其放入方法摘要表(和索引)中。
- 使用内联标签
{@link URL}可生成指向URL类文档的超链接。 - 多段落之间用
<p>分隔。 - 在描述和标签之间插入一个空注释行。
- 第一个以
@开头的行结束描述部分。每个文档注释只有一个描述块。 - 最后一行以
*/结束(只有一个星号)。 - 为避免换行,每行注释不超过 80 个字符。
运行 Javadoc 后的输出效果如下:
getImage
public Image getImage(URL url, String name)
返回一个可在屏幕上绘制的 Image 对象……(略)
文档注释检查工具
Oracle 开发了一个名为 Oracle Doc Check Doclet(简称 DocCheck)的工具,用于检查文档注释的风格和标签错误,并提出修改建议。其规则尽量符合本文档的规定。
DocCheck 是 Javadoc 的一个 doclet(插件),需安装 Javadoc 工具(作为 Java SE SDK 的一部分)。
描述
首句
每个文档注释的第一句应为摘要句,简洁而完整地描述 API 项。Javadoc 工具会将此句复制到相应的成员、类/接口或包摘要中。
句子在以下位置结束:
- 第一个后跟空格、制表符或换行符的句点(
.) - 或第一个 Javadoc 标签
绕过句点截断的技巧:
/** This is a simulation of Prof. Knuth's MIX computer. */
// 或
/** This is a simulation of Prof.<!-- --> Knuth's MIX computer. */
重载方法的首句应相互区分:
/** Class constructor. */
foo() { ... }
/** Class constructor specifying number of objects to create. */
foo(int n) { ... }
实现无关性
描述应尽可能与实现无关,必要时才注明依赖。例如:
On Windows systems, the path search behavior of the loadLibrary method is identical to that of the Windows API's LoadLibrary procedure.
开头的 “On Windows” 明确表明这是实现说明。
方法注释的自动复用
Javadoc 工具会在以下三种情况下自动继承注释:
- 子类方法覆盖父类方法
- 接口方法覆盖超接口方法
- 类方法实现接口方法
若子方法无注释,Javadoc 会复制父方法/接口方法的注释,并生成 “Overrides” 或 “Specified by” 子标题。
风格指南
使用第三人称陈述句,而非第二人称祈使句:
- ✅ Gets the label.
- ❌ Get the label.
方法描述以动词短语开头:
- ✅ Gets the label of this button.
类/接口/字段描述可省略主语:
- ✅ A button label.
- ❌ This field is a button label.
使用 “this” 而非 “the” 指代当前类实例:
- ✅ Gets the toolkit for this component.
- ❌ Gets the toolkit for the component.
避免重复 API 名称。好的注释应提供名称之外的信息:
差:
/** Sets the tool tip text. */好:
/** * 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 null, tool tip is turned off. */注意 “field” 有双重含义:静态字段(类变量) vs 文本字段(如
TextField)。避免拉丁缩写:
- 用 “also known as” 代替 “aka”
- 用 “that is” 代替 “i.e.”
- 用 “for example” 代替 “e.g.”
@tag 标签约定
标签顺序
按以下顺序排列标签:
@author(仅类/接口,可选)@version(仅类/接口)@param(方法/构造函数)@return(方法)@throws(或@exception)@see@since@serial/@serialField/@serialData@deprecated
多个 @see 的排序
从近到远、从简到全,方法按参数数量“望远镜式”排序:
@see #field
@see #method()
@see #method(Type)
@see #method(Type, Type)
@see Class
@see Class#method()
@see package.Class
@see package
必需标签
- 每个参数都必须有
@param(即使显而易见) - 所有非 void 方法都必须有
@return
各标签使用指南
@author:可多人,或写 “unascribed”。通常不包含在 API 规范中。@version:常用 SCCS 字符串%I%, %G%(如1.39, 02/28/97)@param:格式为@param name 描述。描述首词通常是类型(int除外)。不要用<code>包裹参数名。@return:即使与描述重复也必须包含。@deprecated:首句应说明何时弃用及替代方案。使用{@link}指向新方法。@since:标明加入 API 的版本(如@since 1.2),不要写 “JDK1.2”。@throws:记录所有受检异常(checked exceptions)和调用者可能想捕获的非受检异常(如IndexOutOfBoundsException,但不要记录NullPointerException)。
默认构造函数的文档化
根据 Java 语言规范,若类未声明构造函数,编译器会生成一个默认构造函数。
但 Javadoc 无法为其添加注释。
最佳实践:避免在公共 API 中使用默认构造函数。应显式声明构造函数(即使无参),以便添加文档。
若无意中发布了可实例化的类,后续版本中应:
- 显式声明该构造函数
- 使用
@deprecated标记
示例:
/**
* Sole constructor. (For invocation by subclass
* constructors, typically implicit.)
*/
protected AbstractMap() { }
使用 @throws 标签记录异常
@throws与@exception是同义词。- 记录所有受检异常(必须在
throws子句中声明) - 可选择记录非受检异常(如
IndexOutOfBoundsException),但不要记录与具体实现绑定的异常(如ArrayIndexOutOfBoundsException) - 不要记录
Error
注意:即使规范中在
throws子句列出非受检异常(如IndexOutOfBoundsException),实际代码中不应在throws子句中声明非受检异常,而应在@throws注释中说明。
包级注释
从 Javadoc 1.2 起,支持包级注释。每个包可包含一个 package.html 文件(与 .java 文件同目录)。
Javadoc 会:
- 将
<body>内容复制到package-summary.html - 处理其中的 Javadoc 标签
- 将首句复制到概览页
包注释应包含:
- 包的用途摘要
- 包含的内容说明
- 包规范:链接到外部规范文档(如 FrameMaker 文件)
- 相关文档:教程、示例等(不含规范断言的内容)
匿名内部类的文档化
Javadoc 不会直接文档化匿名内部类。
正确做法:在其外层类或关联类的文档注释中描述。
示例:
/**
* Creates the tree. Structural modifications should override this method.
* <p>
* This method adds an anonymous TreeSelectionListener to the returned JTree.
* Upon receiving TreeSelectionEvents, this listener calls refresh with the
* selected node as parameter.
*/
public JTree makeTree(AreaInfo ai) { ... }
包含图像
从 Javadoc 1.2 起,支持在源码树中创建 doc-files 目录存放图像。
约定:
- 图像命名:
<类名>-1.gif、<类名>-2.gif…… - 位置:与源文件同包目录下的
doc-files/子目录- 例:
java/awt/Button.java - 图像:
java/awt/doc-files/Button-1.gif
- 例:
Javadoc 会自动将 doc-files 复制到输出目录。
文档注释示例
/**
* Graphics is the abstract base class for all graphics contexts
* which allow an application to draw onto components realized on
* various devices or onto off-screen images.
* A Graphics object encapsulates the state information needed
* for the various rendering operations that Java supports. This
* state information includes:
* <ul>
* <li>The Component to draw on
* <li>A translation origin for rendering and clipping coordinates
* <li>The current clip
* <li>The current color
* <li>The current font
* <li>The current logical pixel operation function (XOR or Paint)
* <li>The current XOR alternation color
* (see <a href="#setXORMode">setXORMode</a>)
* </ul>
* <p>
* Coordinates are infinitely thin and lie between the pixels of the
* output device.
* Operations which draw the outline of a figure operate by traversing
* along the infinitely thin path with a pixel-sized pen that hangs
* down and to the right of the anchor point on the path.
* Operations which fill a figure operate by filling the interior
* of the infinitely thin path.
* Operations which render horizontal text render the ascending
* portion of the characters entirely above the baseline coordinate.
* <p>
* Some important points to consider are that drawing a figure that
* covers a given rectangle will occupy one extra row of pixels on
* the right and bottom edges compared to filling a figure that is
* bounded by that same rectangle.
* Also, drawing a horizontal line along the same y coordinate as
* the baseline of a line of text will draw the line entirely below
* the text except for any descenders.
* Both of these properties are due to the pen hanging down and to
* the right from the path that it traverses.
* <p>
* All coordinates which appear as arguments to the methods of this
* Graphics object are considered relative to the translation origin
* of this Graphics object prior to the invocation of the method.
* All rendering operations modify only pixels which lie within the
* area bounded by both the current clip of the graphics context
* and the extents of the Component used to create the Graphics object.
*
* @author Sami Shaio
* @author Arthur van Hoff
* @version %I%, %G%
* @since 1.0
*/
public abstract class Graphics {
/**
* Draws as much of the specified image as is currently available
* with its northwest corner at the specified coordinate (x, y).
* This method will return immediately in all cases, even if the
* entire image has not yet been scaled, dithered and converted
* for the current output device.
* <p>
* If the current output representation is not yet complete then
* the method will return false and the indicated
* {@link ImageObserver} object will be notified as the
* conversion process progresses.
*
* @param img the image to be drawn
* @param x the x-coordinate of the northwest corner
* of the destination rectangle in pixels
* @param y the y-coordinate of the northwest corner
* of the destination rectangle in pixels
* @param observer the image observer to be notified as more
* of the image is converted. May be
* <code>null</code>
* @return <code>true</code> if the image is completely
* loaded and was painted successfully;
* <code>false</code> otherwise.
* @see Image
* @see ImageObserver
* @since 1.0
*/
public abstract boolean drawImage(Image img, int x, int y,
ImageObserver observer);
/**
* Dispose of the system resources used by this graphics context.
* The Graphics context cannot be used after being disposed of.
* While the finalization process of the garbage collector will
* also dispose of the same system resources, due to the number
* of Graphics objects that can be created in short time frames
* it is preferable to manually free the associated resources
* using this method rather than to rely on a finalization
* process which may not happen for a long period of time.
* <p>
* Graphics objects which are provided as arguments to the paint
* and update methods of Components are automatically disposed
* by the system when those methods return. Programmers should,
* for efficiency, call the dispose method when finished using
* a Graphics object only if it was created directly from a
* Component or another Graphics object.
*
* @see #create(int, int, int, int)
* @see #finalize()
* @see Component#getGraphics()
* @see Component#paint(Graphics)
* @see Component#update(Graphics)
* @since 1.0
*/
public abstract void dispose();
/**
* Disposes of this graphics context once it is no longer
* referenced.
*
* @see #dispose()
* @since 1.0
*/
public void finalize() {
dispose();
}
}