Java语言按照Javadoc注释约定采用了一种集成的方法来进行API文档编制Javadoc工具可以帮助生成好的API文档然而大多数Java API文档却很糟糕因为它是源代码的一部分所以 API 的文档编制职责最终还是落到了工程师身上在本文中Brian 对 Java 文档编制实践的当前状态进行了严厉的批评同时提供了一些关于如何编写更有用的 Javadoc 的准则
对于大多数 Java 类库来说Javadoc 是唯一的文档而且除了商业软件组件之外许多 Java 类不会用到 Javadoc虽然 Javadoc 作为 API 参考工具很出色但对于了解类库是如何组织的和应该如何使用它来说它却是一种十分差劲的方法并且即便用了 Javadoc它通常只包含有关方法完成了什么的最基本信息而忽略了诸如错误处理参数及返回值的作用域和范围线程安全锁定行为前置条件后置条件不变条件或副作用之类的重要特性
向 Javadoc 学习
对于包括大多数开放源码包和大多数内部开发的组件在内的许多Java工具而言实际情况是包括Javadoc在内几乎所有类库或组件都不具有有效的文档这就意味着开发人员要从Javadoc学习使用工具而且我们应该考虑根据这一现实组织我们的Javadoc我经常开玩笑说现在Java程序员需要具备的最重要的技能之一是熟练地使用Google和Javadoc来对那些文档编制得十分糟糕的 API 进行逆向工程这可能是真的但却并不十分好笑
大多数 Java 包都有某种根对象它是在得到该工具内的任何其它对象之前必须创建的第一个对象在 JNDI中该根对象是Context而在JMS和JDBC中它是Connection如果有人告诉您JDBC中的基础对象是 Connection以及如何获得这一对象那么接着您很可能会从 Javadoc 中通过仔细察看 Javadoc 中可用的方法列表找到如何创建并执行 Statement以及如何迭代生成的 ResultSet但您如何知道获得 Connection 是您的第一步呢?Javadoc 在包内按照字母顺序组织类在类中按照字母顺序组织方法遗憾的是Javadoc 中没有神奇的从这里开始(Start Here)记号把读者带到浏览 API 的逻辑开始位置
包描述
最接近从这里开始记号的是包描述但它却很少得到有效的使用如果将文件 l 与源代码一起放在一个包中那么标准的 doclet 会将已生成的 l 文件中的内容连同类列表一起放在该包内遗憾的是生成我们都很熟悉的 HTML 文档的标准 doclet 却无法使包描述易于找到如果您单击左上窗格中的某个包那么这会在左下窗格中产生方法列表但并不会在主窗格中产生包的摘要 — 必须单击左下窗格中的包名称来查看摘要但不要紧毕竟大多数包并没有包描述
包文档是一个放置从这里开始文档的极好的地方这一文档用来概述包做什么主要摘要是什么以及从何处开始浏览包的 Javadoc
类文档
除包文档之外特定于类的文档对于帮助用户彻底了解新工具也能起到重要的作用类文档当然应该包括此特定类做什么的描述但还应该描述该类与包中的其它类如何关联特别是要标识任何与该类相关的工厂类例如JDBC 中的 Statement 类文档应该说明Statement 是通过 Connection 类的 createStatement() 方法获得的这样如果一个新用户偶然进入 Statement 页面那么他会发现首先他需要获得 Connection对每个类都应用这一约定的包会迅速为用户指出根对象用户因而能够得心应手
因为 Javadoc 是围绕对特定类进行文档编制而设计的因此在 Javadoc 中通常没有明显的位置来放置演示几个相关类一起使用的示例代码但由于一味地侧重于特定类或方法的文档编制我们失去了讨论如何组合包中内容的机会如果对于根对象在包文档或类文档中有一个演示一些基本用法的简单代码示例则对于许多用户来说将是非常有用的例如Connection 类文档可以有一个简单示例该示例获取连接创建预编译语句执行该语句并迭代结果集从技术上说这可能不属于 Connection 页面因为它还描述了包中的其它类然而尤其是当结合了上面那种引用当前类所依赖的类的技术时用户才能非常迅速地找到获取简单的实用示例的途径不管类的组织方式如何
糟糕的文档=糟糕的代码
对于大多数 Java 类库来说除了那些作为打包组件出售的商业产品之外要么没有 Javadoc要么非常糟糕由于存在的事实是对于大多数包来说Javadoc 是我们拥有的唯一文档这基本上意味着使我们自己陷入了这样的困境除了作者之外其他人没法使用我们的大部分代码——如果不付出重大的考古一样的努力至少会这样
由于文档现在是代码的一部分因此我认为是软件工程社区形成一个共识的时候了这就是即使代码很出色如果文档很糟糕也应该被认为是差劲的代码因为不能有效地重用单元测试不久前还声誉不佳只是到了最近它才受到了许多工程师的青睐就和它一样为了改善我们生产的软件的可靠性和可重用性API 文档也必须成为开发过程的一个集成部分
编写Javadoc就是某种形式的代码检查
编写合理的Javadoc也会产生副作用它迫使我们进行某种形式的代码检查来研究类的体系结构和它们之间的关系如果单个包类或方法很难编制文档那么或许可以尝试同时对多个包类或方法进行文档编制这应该是个提示即可能它需要重新设计
文档的自我检查方面使得某些方面更加重要即在开发过程中尽早编写Javadoc然后随着代码的不断开发定期对其进行检查而不是仅仅等待代码完成再编写文档(如果有剩余时间的话)后一种策略十分常见它将编写文档拖到项目最后而那时时间安排十分紧张开发人员的压力也很大结果再常见不过了就是那种一文不值的文档它只提供了文档假象用户真正需要的是了解该类的工作原理而该文档却没有提供任何这样的信息
清单 典型的一文不值的 Javadoc
/**
* Represents a command history
*/
public class CommandHistory {
/**
* Get the command history for a given user
*/
public static CommandHistory getCommandHistory(String user) {
}
}
什么是好的文档
那么好的文档包括哪些内容呢?
上面描述的组织技术(在类描述中引用相关类或工厂类也包括了包概述和代码样本)是形成优秀文档的好开端它有助于新用户使用 Javadoc 了解新工具
但体系结构的概述只完成了任务的一半另一半则是详细地解释方法做什么和不做什么在什么条件下运行以及它们如何处理错误条件大多数 Javadoc 都没有完全提供所需的信息即便是那些充分描述了方法在期望情况下的行为的 Javadoc 也是如此这些缺少的信息包括
· 方法如何处理错误条件或不合要求的输入
· 如何将错误条件传回给调用者
· 可能会抛出哪个特定异常的子类
· 哪些值对于输入是有效的
· 类不变条件方法前置条件或方法后置条件
· 副作用
· 在方法之间是否有重要联接
· 类如何处理多个线程同时访问一个实例的情况
Javadoc 约定提供了 @param 标记它让我们除了能够对参数的名称和类型编制文档之外还可以对其意义编制文档然而并不是所有的方法都能很好地接受参数的任何值例如虽然可以合法地向任何获取对象参数的方法传递空值(null)而不违反类型检查规则但并不是所有的方法都能在传入空值时正常工作Javadoc 应该显式地描述有效的参数范围如果它希望某个参数非 null那么它应该这样描述而如果它期望参数值在某个范围内例如某种长度的字符串或大于 的整数那么它也应该那样描述并非所有方法都仔细检查其参数的有效性不进行有效性检查也没有编制关于可接受的输入范围的文档这二者的结合为灾难埋下了隐患
返回代码
Javadoc 使得描述返回值的意义变得很容易但正如方法参数一样@return 标记应该包括对可能返回的值范围的详细描述对于对象取值的返回类型而言它会返回空值吗?对于整数取值的返回类型而言结果会限制在一个已知值或非负值的集合上吗?任何返回代码都有特殊意义吗例如从 javaioInputStreamread() 返回 表示文件结束符?返回代码会被用来表示例如如果无法找到表项则返回空值那样的错误条件吗?
异常
标准 doclet 复制方法的 throws 子句但 Javadoc @throws 标记应该更为具体例如NoSuchFileException 是 IOException 的子类但 javaio 中的大多数方法却只被声明为抛出 IOException然而方法可能独立于其它 IOException 而抛出 NoSuchFileException这是调用者要了解的很有用的事实 — 它应该被包括在 Javadoc 中还应该指出抛出各种异常类的实际错误条件以便调用者知道在给定异常被抛出时该采取什么纠正措施应该用 @throws 标记对方法可能抛出的每个经检查的或未经检查的异常编制文档并对引发抛出异常的条件编制文档
前置条件后置条件和不变条件
当然您应该对方法对对象状态的影响编制文档但您可能需要编制得更详细一些描述方法的前置条件后置条件和类不变条件前置条件是在调用方法前对对象状态的约束例如调用 Iteratornext() 的前置条件是 hasMore() 为真后置条件是方法调用完成后对对象状态的约束例如在调用 add() 之后 List 不能为空不变条件是对对象状态的一种约束它保证该状态始终