代码内文档在软件设计中起着至关重要的作用。 注释对于帮助开发人员理解系统和有效地工作是必不可少的,但是注释的作用远远不止于此。文档在抽象中也扮演着重要的角色;没有注释,就无法隐藏复杂性。最后,编写注释的过程如果处理正确,实际上将改进系统的设计。相反,如果没有良好的文档记录,好的软件设计就会失去很多价值。
不幸的是,这一观点并没有得到普遍认同。相当一部分产品代码基本上不包含注释。许多开发人员认为注释是浪费时间;另一些人看到了注释的价值,但不知何故却从来没有写过。幸运的是,许多开发团队认识到了文档的价值,并且感觉这些团队的流行程度正在逐渐增加。然而,即使是在鼓励文档化的团队中,注释也常常被看作是苦工的工作,许多开发人员不知道如何编写注释,因此生成的文档通常是平庸的。不充分的文档会对软件开发造成巨大且不必要的拖累。
在本章中,我将讨论开发人员用来避免编写注释的借口,以及注释真正重要的原因。第13章将描述如何写出好的注释,接下来的几章将讨论相关的问题,比如选择变量名以及如何使用文档来改进系统的设计。我希望这些章节能让你 相信三件事:好的注释可以使软件的整体质量有很大的不同;写出好的注释并不难;而且(这可能很难相信)写注释其实很有趣。
当开发人员不写注释时,他们通常会用以下一个或多个理由来解释他们的行为:
- 好的代码是自我解释的。
- 我没时间写注释。
- 注释过时了,会产生误导。
- 我所看到的注释都是毫无价值的,何苦呢?
在下面的章节中,我将依次解释这些借口。
12.1 好代码是自我解释的
有些人认为,如果代码写得好,那么显然不需要注释。这是一个美味的神话,就像冰淇淋有益健康的谣言:我们真的愿意相信它。不幸的是,事实并非如此。当然,在编写代码时可以做一些事情来减少注释的需要,比如选择合适的变量名(参见第14章)。尽管如此,仍然有大量的设计信息不能用代码表示。例如,只能在代码中正式指定类接口的一小部分,如其方法的签名。接口的非正式方面,例如每个方法的高级描述或其结果的含义,只能在注释中进行描述。还有许多其他的例子无法在代码中描述,比如特定设计决策的基本原理,或者调用特定方法的条件。
一些开发人员认为,如果其他人想知道一个方法做什么,他们应该只阅读方法的代码:这将比任何注释更准确。读者可以通过阅读代码来推断方法的抽象接口,但这将是耗时且痛苦的。此外,如果您编写代码时期望用户能够读取方法实现,那么您将尝试使每个方法尽可能短,以便于读取。如果该方法做了任何重要的事情,您将把它分解成几个更小的方法。这将导致大量的浅方法。而且,它并没有真正使代码更容易阅读:为了理解顶级方法的行为,读者可能需要理解嵌套方法的行为。对于大型系统,用户通过阅读代码来学习行为是不现实的。
此外,注释是抽象的基础。回顾第4章,抽象的目标是隐藏复杂性:抽象是实体的简化视图,它保留了基本信息,但忽略了可以安全忽略的细节。如果用户必须阅读方法的代码才能使用它,那么就没有抽象:方法的所有复杂性都暴露出来了。如果没有注释,方法的惟一抽象就是它的声明,它指定了方法的名称、参数和结果的名称和类型。声明缺少太多的基本信息,无法提供一个有用的抽象。例如,提取子字符串的方法可能有两个参数,start和end,表示要提取的字符的范围。仅从声明中无法判断提取的子字符串是否包含end所指示的字符,或者如果开始>结束会发生什么。注释允许我们捕获调用者需要的附加信息,从而在隐藏实现细节的同时完成简化的视图。同样重要的是,注释是用英语等人类语言写的;这使得它们不如代码精确,但它提供了更强的表达能力,因此我们可以创建简单、直观的描述。如果您想使用抽象来隐藏复杂性,注释是必不可少的。
12.2 我没有时间写注释
人们倾向于优先考虑比其他开发任务更低的注释。如果要在添加新特性和记录现有特性之间进行选择,那么选择新特性似乎是合乎逻辑的。然而,软件项目几乎总是处于时间压力之下,而且总是有一些事情看起来比写注释更重要。因此,如果您允许取消文档的优先级,那么最终将没有文档。
这个借口的反面论据是第15页所讨论的投资心态。如果您想要一个干净的软件结构,它将允许您在长期内高效地工作,那么您必须预先花一些额外的时间来创建这个结构。好的注释会对软件的可维护性产生巨大的影响,因此花在注释上的精力很快就会得到回报。此外,写注释不需要花很多时间。问问你自己你花了多少开发时间来打字(相对于设计、编译、测试等等),假设你没有任何注释;我怀疑答案是否超过10%。现在假设您输入注释的时间与输入代码的时间一样多;这应该是一个安全的上限。根据这些假设,写出好的注释不会增加超过10%的开发时间。拥有良好文档的好处将很快抵消这一成本。
此外,许多最重要的注释都与抽象相关,比如类和方法的顶级文档。第15章将讨论这些注释应该作为设计过程的一部分来编写,而编写文档的行为可以作为改进整体设计的重要设计工具。这些注释立竿见影。
12.3 注释会过时并产生误导
注释有时确实会过时,但这在实践中不一定是主要问题。保持文档更新不需要付出巨大的努力。只有在对代码进行了较大更改的情况下,才需要对文档进行较大的更改,并且代码更改将比文档更改花费更多的时间。第16章讨论了如何组织文档,以便在代码修改后尽可能轻松地更新文档(关键思想是避免重复的文档,并使文档与相应的代码保持一致)。代码审查为检测和修复陈旧的注释提供了一种很好的机制。
12.4 我所看到的一切注释都是毫无价值的
在这四个借口中,这可能是最有价值的一个。每个软件开发人员都看到过没有提供任何有用信息的注释,而且大多数现有文档充其量也就是一般。幸运的是,这个问题是可以解决的;编写可靠的文档并不难,只要您知道如何做到这一点。下一章将为如何编写良好的文档并随时间进行维护提供一个框架。
12.5 良好的注释的好处
既然我已经讨论了(希望已经揭穿了)反对写注释的争论,让我们考虑一下从好的注释中可以得到的好处。注释背后的总体思想是捕获设计者头脑中但是不能在代码中表示的信息。这些信息的范围很广,从底层的细节(比如驱动一段特别复杂的代码的硬件怪癖),到高层的概念(比如类的基本原理)。当其他开发人员稍后进行修改时,注释将允许他们更快更准确地工作。没有文档,未来的开发人员将不得不重新推导或猜测开发人员的原始知识;这将花费额外的时间,并且如果新开发人员误解了原始设计人员的意图,就会有出现bug的风险。即使是最初的设计人员做出了更改,注释也是很有价值的:如果距离您最后一次使用代码已经超过几个星期了,那么您就会忘记最初设计的许多细节。
第二章描述了复杂性在软件系统中表现出来的三种方式:
- 变更放大:一个看似简单的变更需要在很多地方修改代码。
- 认知负荷:为了做出改变,开发人员必须积累大量的信息。
- 未知的未知:不清楚需要修改哪些代码,或者为了进行这些修改必须考虑哪些信息。
好的文档有助于解决最后两个问题。通过向开发人员提供他们需要更改的信息,并使开发人员很容易忽略不相关的信息,文档可以减少认知负荷。如果没有足够的文档,开发人员可能不得不阅读大量的代码来重构设计者的想法。文档还可以通过澄清系统的结构来减少未知的未知,这样就可以清楚哪些信息和代码与任何给定的更改相关。
第二章指出了复杂性的主要原因是依赖性和模糊性。好的文档可以澄清依赖关系,并填补空白以消除模糊性。
接下来的几章将向您展示如何编写好的文档。他们还将讨论如何将文档编写集成到设计过程中,从而改进软件的设计。