D 的个人博客

但行好事莫问前程

  menu
417 文章
3446695 浏览
3 当前访客
ღゝ◡╹)ノ❤️

细节见真功之 Javadoc

作为一个 Java 程序员,Javadoc 大家都应该写过吧,是不是觉得写的时候特简单呢?相信看完本文后你会若有所思。另外,本文非常适合处女座程序员阅读。

句号

为什么是句号而不是其他的标点符号呢?因为这涉及到一个 JDK 文档生成的规则:

The first sentence of each doc comment should be a summary sentence, containing a concise but complete description of the API item. This means the first sentence of each member, class, interface or package description. (成员、类、接口或包注释的第一个句子将作为该注释项的总结,这个句子应该是言简意赅的)

既然提到了句子,那就说明应该用标准的方式——句号来进行第一个句子的断句识别。也许是为了简洁,Javadoc 工具并没有“多语言句号识别”这个特性,所以不管我们用什么语言撰写文档注释,断句符号都必须是英文状态下的句号——.

比如在 NetBeans IDE 里(默认编辑器配置)可以很直观的看到用于总结的第一个句子和后面描述的区别:

first-sentence

在生成 Javadoc 后,类概要页面我们可以看到最终的效果:

class-summery

在使用中文撰写文档注释时,为了保持整体风格一致,在所有需要使用句号的地方有两种策略可选择:

  • 都使用英文句号:这样做可以让生成的文档句号统一,但缺点是看上去有点别扭
  • 只有第一个句子使用英文句号,其余地方都是要中文句号:这样做后生成的文档看上去比较顺眼,但别人可能会奇怪为什么第一个句号是 .

当然,最彻底的解决方案是不用中文写文档注释,这样就不存在要统一的问题了!

 

下面我们重点介绍各种你熟悉的或是不熟悉的 Javadoc 文档标记(它们很有内涵)

@author

该标记使用频率是所有文档标记中最高的,我想这是因为:

  • 做好事要留名
  • 使用超简单,就像在填表格(姓名:           )一样自然

来看看大牛怎么写的:

commons-lang-stringutils

(from Commons Lang 2.5 StringUtils)

总结下来有三种写法:

  • 纯文本
  • 带邮箱链接
  • 带 HTTP 链接

(个人建议用 HTTP 链接:打码时可以顺便推广一下自己的博客,哈哈)

另外,在 JDK 代码中我们经常看到 @author unascribed,意思是:“该代码第一原作者不是我,但我实在也不知道是谁,就记作无名氏吧”(这是多么严肃的一种版权意识啊)

@serialXXX

这个系列应该是最不常用的文档标记了,它们到底是干嘛用的呢?请看这里

(我一次也没有使用过这些文档标记,看了官方文档后也还是没有搞懂怎么用,求各位指教)

@value

这个文档标记非常实用(不光好用),可以用于生成被标记的常量字段的值。

直接用于常量字段时:

value-code

value-html

也可以使用引用方式:

value-ref-code

value-ref-html 

{@inheritDoc}

 这个标签体现了 Java 面向对象的精辟所在:不但可以类可以集成,连文档都可以继承(足见 Java 在经典面向对象概念上的完备与圆润)。

比如有个计算面积的接口:

inheritdoc-interface-code

它的实现方法标注了 {@inheritDoc}(处女座阅读提示:无 .)

inheritdoc-impl-code

最后生成的文档:

inheritdoc-html

  • 基类的文档注释被继承到了子类
  • 子类可以再加入自己的注释(特殊化扩展)
  • @return @param @throws 也会被继承

其实在不写 {@inheritDoc} 的情况下也存在文档注释的继承,具体规则请看这里

{@link} {@linkplain}

这两个链接标记大家用/见的应该比较多,但它们有什么区别、在什么场景下该怎么使用很少有人能够区分开(我猜你要用的时候一般也都是用 link 吧)。

看看官网的标准解释:

link


link 和 linkplain 的实参都是 package.class#member label 。唯一的不同就是因为字体不同,如果 label 是个纯文本,那就使用 linkplain 吧。(根据这点,我严重怀疑 Javadoc 文档标记的设计者是处女座,~ ~)

pre

没错,这就是那个 HTML 标签,用于显示“原始样子”的。这个标签在写 Javadoc 的时候非常有用,用或者不使用在打码的时候看上去差别不大:

 pre-code

但最终生成 apidocs 之后差别一目了然(处女座阅读提示:在源码文档注释中特别需要注意 pre 后 { 的位置,紧跟 *,无空格)

pre-html

@since

这个从字面的意思上很好理解,所以使用的比较多(如同 @author、@version 一样)。但问题是大家写的时候表达的意思五花八门,常见的有:

  1. 想表达日期/时间
    @since 2014-01-01
    @since 2014-01-01 14:00:00
  2. 想表达可运行的 JDK 版本
    @since JDK1.5
  3. 想表达加入这个元素的版本
    @since 1.0.0

根据官方文档解释,@since 表达的是被标记元素是哪个发布版本引入的(3)。比如别人在我们的文档注释中看到

since

那他可以(应该)认为这个类是在该程序对外发布 1.0.0 版本时已经引入的。如果他要做二次开发,那他就可以很清晰的向后兼容了(我们在用 JDK 的时候就是这个场景)。

@version

提到了 @since 就自然会联想到 @version,因为它们的实参都是版本相关的。@version 要表达的是被标记元素自己的版本(注意对比 @since),也就是说这个版本只要代码改过就应该发生变化,而 @since 是不会变的。

官方文档也解释了怎么用好这个文档标记:通过 SCCS 字符 "%I%, %G%",例如 1.39, 02/28/97(文件版本号, 日期)生成。但实际上很少有项目这么做(至少目前 Oracle JDK 没这么做,甚至都没有使用 @version,或者是使用了但最后由于特殊原因总体移除了),大家一般都是 @version 1.0.0 然后就再也不修改了,不管被标记的元素改了多少次(这样的做法还不如不写)。

当然,通过版本控制系统 hook 来做是比较经典的做法,不过这样总感觉没有把这个标记的能力完全发挥出来。在我们的项目里是这样使用的:@version 1.2.3.4, Jun 9, 2014

重点是版本号部分,在这个例子中从左到右(1.2.3.4)分别表示:

  • 兼容性位 1,表示兼容性,如果 +1 了说明这个修改是不兼容的
  • 特性位 2,表示已引入了两个特性,每次 +1 说明引入一个新特性
  • 缺陷修复位 3,表示已经修复了 3 个缺陷,每次 +1 说明修复了一个缺陷
  • 重构位 4,表示已经进行了 4 次重构,每次 +1 说明重构了一次

前面 3 位表达的意义和 Semantic Versioning 建议的一致,重构我觉得非常重要,所以也加了进来。

@exception @throws

这两兄弟的情况比 @link @linkplain 更纠结(人家 link 兄弟最起码可以区分出来使用场景)。按照官方文档解释:它们完全是同义词,没有任何区分。那当年 Sun 在 JDK1.2 的时候为什么要加入 @throws 呢——答案是起名失误了,词性没弄匹配:@throws Exception 比 @exception Exception 更符合语法,代入感更好!(细节:In javadoc, what is the difference between the tags @throws and @exception?

标记总表

来张 Javadoc 文档标记总表:

TagIntroduced in JDK/SDK
@author 1.0
{@code} 1.5
{@docRoot} 1.3
@deprecated 1.0
@exception 1.0
{@inheritDoc} 1.4
{@link} 1.2
{@linkplain} 1.4
{@literal} 1.5
@param 1.0
@return 1.0
@see 1.0
@serial 1.2
@serialData 1.2
@serialField 1.2
@since 1.1
@throws 1.2
{@value} 1.4
@version 1.0

这个表是 JDK7 技术手册里的,从中我们可以看出,自 JDK1.5 以后就没有加过新的文档标记了,目测有两个原因:

  • Oracle:“这些已经足够开发人员使用了,没必要加新的了”
  • *Sun:“看吧,Oracle 严重缺乏折腾精神,当初不应该卖给它的”

文档标记介绍完了,下面我们来聊聊 Javadoc 相关的其他侃点。

getter/setter/isTrue

对于 POJO 来说,这几个方法的注释格式非常固定,一般我们都是用 IDE 自动生成:这样的话别人一看到这样固定格式的注释(或者索性不要添加任何注释)就知道这部分相对于其他部分并不重要,而一旦有的 getter/setter/isTrue 注释不是这样约定的,那就说明了实现上面不只是简单的 get/set/is,还加入了额外的逻辑处理。

对齐

Javadoc 文档注释也有对齐(不是前面 pre 例子那种),这里说的对齐主要指的是以源码视图看到的,最典型的场景就是在给方法添加文档注释的时候,我们经常看到两种风格:

align-para

第一眼看上去是不是风格 2 要顺眼得多?但最好还是使用风格 1,因为:

  • 这和编辑器配置的字体有关,如果不是(适合的)等宽字体,那会非常的参差不齐
  • 浪费空间,特别是当注释内容多了需要换行的时候会很别扭
  • 最后生成的 apidocs 效果是一模一样的(无对齐)

(一些 IDE 默认格式化文档注释的时候也是使用风格 1 进行格式化的,强烈建议使用风格 1)

包注释

和前面几点打码风格相关的细节比起来,包注释是具有一定的实用性的。虽然大家可能用得很少,但看得应该比较多,就是这部分:

package

这里我们使用了两种方式来生成包文档:

  • package.html:这是 JDK1.5 以前的方式,现在已经不推荐使用
  • package-info.java:目前推荐方式,因为这样可以使用注解

在包上面使用注解?这个用法和在其他地方使用注解一样,只是被标注的元素变成了包,在运行时可以获取到包的注解,然后做你想做的事情吧!

中文

一开始我们提到了句号的问题(那的确是一个问题),最后我们来看看中文在写文档注释的时候也非常值得注意的一点(其实不只是 Javadoc 文档注释,该建议也适用于其他一些情况):在中文和英文、数字中间插入一个空格(本文就是这样排版的)。

比如说:

  • 我觉得Java非常cool,特别是JDK8中的lambda,真希望9能带来更多实用特性
  • 我觉得 Java 非常 cool,特别是 JDK8 中的 lambda,真希望 9 能带来更多实用特性

后者看上去就比前者更舒服一些,这样的排版方式适合纯文本编辑器,如果使用的是 Office 之类的工具就不需要手动空格了,因为它们默认已经处理的很好了。

总结

本文介绍了一些 Javadoc 文档注释相关的细节,从这冰山一角相信你对 Java 也有了另一番体验(Java 的进化、工业化)。

总结一下本文内容:

  • 对于文档标记,大家可以尽量尝试使用:把自己的思想通过适合的方式表达给他人是一种好习惯
  • 对于风格相关,大家也可以适当尝试(处女座/强迫症就算了):某大厂在某次改句号问题后出现过生产故障

不过,大家也千万不要太较真,毕竟对于一个好的程序来说,代码应该就是它的文档(之一)。

参考