0%

通过重视注释和代码来减少技术债务

通过重视注释和代码来减少技术债务

好的代码注释可以帮助维护者理解你的编码决策。

长期以来,注释在开发人员的输出中一直占据着明显的次要位置。注释是如此次要,而且被如此低估,以至于在今天的代码中,它们很少出现,除了作为文件顶部的常规版权和许可标头。这是一个错失的机会,因为积极维护的良好注释是减少技术债务的最有效方法之一,也就是说,它们减轻了未来将处理代码的程序员的工作量。

由于许多开发组织的文化低估了注释的价值,因此团队领导(和经理)允许注释与代码不同步,从而增加而不是减少技术债务。

而且,由于低估是常态,开发人员学会了责怪注释,而不是允许他们与代码不同步的文化。Bob Martin 断言,每条注释都代表着“未能在代码中表达自己”(见图 1),从而表达了这种消极观点。请注意,Martin 在他的《Clean Code》一书中发表了这个奇怪的声明,然后用了 15 页的篇幅列举了注释非常有用的许多例子。

为什么要写注释?

注释以多种方式消除了技术债务。

注释解释了代码的用途。文件开头的注释解释了代码中可以找到的功能,无需通读所有方法来收集对文件的理解。如果您正在寻找特定文件,能够阅读描述其内容的单个句子可以大大加快搜索速度。出于这个原因,大多数编码标准都敦促在每个文件中只包含这样的注释。

在文件级注释中使用关键字可以进一步方便在大型代码库中搜索特定功能。

类开始时的 Javadoc 注释可以替代文件级注释。如果注释描述了该类在更大项目中的角色,它们将进一步帮助读者。描述为什么需要该类(如果不明显)有助于其他人理解该类的作用。这些注释对于在类的 API 中呈现任何异常信息特别有用。

注释对代码进行说明。许多开发人员有一种堂吉诃德式的信念,即如果代码足够清晰,他们就不需要使用注释。这就是 Bob Martin 在图 1 中断言的内容。这是一个可爱的前提,根本不成立。第一个问题是,大多数开发人员都承受着巨大的时间压力,没有时间使代码变得如此清晰,以至于不需要进一步的代码注释。事实上,程序员更常见的经历是看着他们六个月前写的代码,然后想“我简直不敢相信是我写的!不要自欺欺人地认为你可以写得如此清晰,以至于不需要注释就能理解。

明码异议的另一个局限性是,代码只解释了一件事是如何完成的,而不是为什么这样做的,尤其是在有明显的替代方案的情况下;如果原因不明显和明显,那么在没有解释的情况下就会产生技术债务。请注意,如果没有这样的解释性注释,代码可能非常难以维护,因为没有人敢碰它,而这正是技术债务的定义。

代码注释突出显示了潜在的陷阱。例如,Google 的 Java 指南要求每当 switch 中的 case 语句进入下一个 case 时都要添加注释。同样,该指南要求注意和解释一个空的异常捕获块,以及所有其他可能被忽略的、非常规的或不受欢迎但偶尔必要的步骤的项目。

代码注释为未来的工作提供了里程碑。术语“自我承认的技术债务”是指代码库中经常遇到的 TODO 和 FIXME 注释。从表面上看,这些看起来像是好心的程序员留下的碎屑,他们随后被其他任务所占据。然而,一项关于维护良好的开源项目的有趣研究,“关于消除自我承认的技术债务的实证研究”发现,74%的此类条目在180天内被删除,最常见的是将它们放在那里的开发人员。换言之,这些代码注释往往代表了未来工作的有用里程碑,而不是碎屑。

虽然不是专门针对技术债务的对策,但我使用第三个标记 CURR 来表示“当前”。这是一个面包屑,它告诉我我从哪里开始编码。我经常包括几行代码注释,其中包含恢复工作时可能需要的信息。我在一个代码库中只允许一个 CURR,所以如果我在其他地方恢复工作,我会将现有的 CURR 转换为 TODO。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/* Create a string object with EMBSTR encoding if it is smaller than
* OBJ_ENCODING_EMBSTR_SIZE_LIMIT, otherwise =the RAW encoding is used.
*
* The current limit of 44 is chosen so that the biggest string object
* we allocate as EMBSTR will still fit into the 64 byte arena of jemalloc.
*/
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *CreateStringObject(const char *ptr, size_t len) {
if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
return CreateEmbeddedStringObject(ptr, len);
else
return CreateRawStringObject(ptr, len);
}

/* Same as CreateRawStringObject, can return NULL if allocation fails */
robj *tryCreateRawStringObject(const char *ptr, size_t len) {
sds str = sdstrynewlen(ptr, len);
if (!str) return NULL;
return CreateObject(OBJ_STRING, str);
}

/* Same as CreateRawStringObject, can return NULL if allocation fails */
robj *tryCreateRawStringObject(const char *ptr, size_t len) {
if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
return CreateEmbeddedStringObject(ptr, len);
else
return CreateRawStringObject(ptr, len);
}

Ousterhout 的注释方法

在前面的方案中,注释的好处是不可否认的。它们减少了技术债务,维护良好的注释也使新团队成员更容易理解代码库,而无需花费数小时在死胡同中摸索或向现有员工提出数百个问题。

然而,重视代码注释的文化可以鼓励它们用于远远超出技术债务的补救。

John Ousterhout 的优秀著作《软件设计哲学》(A Philosophy of Software Design)描述了大型代码库的最佳实践;它接续了马丁离开的地方,更倾向于现实世界的情况。Ousterhout 是 Tcl 语言的设计者和实现者;后来,他与他人共同创立了Electric Cloud,这是一家专门从事大规模代码库(即代码行超过100万行的代码库)的DevOps工具的公司。

虽然 Ousterhout 的书只有 175 页,但它用了整整两章来讲代码注释。第一章回答了对撰写和重视代码注释的最常见反对意见;第二个重点是提高代码注释的质量。

Ousterhout 的一般格言是,代码中的注释应该传达程序员脑海中无法准确反映在代码本身中的任何内容。这是一个很好的指导方针,它直接反驳了“每条代码注释都是失败的”的论点。真正的失败是省略了后续编码人员可能需要的澄清和上下文信息。

代码注释还可以帮助您理清自己的想法。不止一次,当我为一段代码编写注释时,我意识到我的实现存在缺陷。这与向同事描述问题并在解释问题时出现解决方案的感觉大致相同——这是一种常见的体验。在之前的文章中,我描述了这种优势在混合方法中的扩展,该方法从Ousterhout的书中获取了元素。

创建类时,请考虑使用以下有用的步骤,这些步骤是对注释使用的重大扩展:

  • 首先编写类接口注释。
  • 编写最重要的公共方法的接口注释和签名,但将方法主体留空。
  • 为最重要的实例变量编写注释和声明。
  • 填写方法的正文,并在进行时添加实现注释。
  • 当您发现需要更多方法时,请在正文之前写下注释。
  • 根据 Ousterhout 的经验,这些步骤的好处是三方面的。
  • 代码完成后,会正确注释,并且注释完全是最新的。
  • 注释优先的方法使您能够专注于抽象,而不是被实现分散注意力。
  • 注释揭示了代码的复杂性 - 如果方法或变量需要冗长而复杂的注释,则可能需要重新考虑和简化。

这有很多好处!

在Ousterhout看来,最重要的内容是抽象(很难通过阅读实现代码来梳理)和对代码存在原因的解释。总之,第一次处理代码的开发人员应该能够扫描类的注释,并很好地了解类的作用并理解最重要的实现方面。

如果这种方法对你有吸引力——就像它对我一样——Ousterhout 建议你使用它,直到你习惯了以这种方式编写代码。他争辩说,我同意,这样做会通过提供更干净、更清晰的代码来改变你。

Javadoc 的奢华

Java 开发人员有一个很好的工具来追求这种更深入的方法:Javadoc 注解。如果你用一种以上的语言编写代码,你很快就会意识到注释选项在其他语言中是多么微不足道。大多数语言没有任何内置的 Javadoc 对应物。或者,就像 Go 和其他一些语言一样,如果它们有对应的语言,相比之下,它就很无聊了。

Javadoc 允许您将注释转换为优雅、格式良好且可用的文档。从JDK发行版的优秀文档中可以看出,Java团队在深思熟虑、认真的纪律中充分利用了该工具。(顺便说一句,JVM 本身存在广泛的代码注释。

开源项目中的注释

最后一点:如果你正在为一个开源项目做贡献,你应该非常重视代码注释。潜在合作者在考虑加入项目时遇到的最大障碍之一是学习代码库的困难——最常见的原因是它不包含任何注释(在许可证标头之外)。

优秀的开源项目负责人会努力提供易于理解的文档和注释良好的代码,以便潜在的贡献者可以轻松快速地摸索代码,而无需付出太多努力来修复一个小错误。那些以良好的文档和大量代码注释为荣的项目,如Redis,会得到良好的参与。

在我参与的一个项目 Jacobin 中,这是一个用 Go 编写的 JVM,贡献者广泛使用注释。还为潜在的贡献者提供了额外的资源:代码库的概述,使人们能够轻松理解项目的整体架构以及它如何映射到代码布局。

这些步骤需要时间,但它们对项目的贡献者和项目的目标受众一样有帮助,因为它们迫使雅各宾的开发人员明确我们的假设,清晰地思考我们的思维,并在工作中遵守纪律。

结论

维护不善的代码注释是技术债务的明显来源。若要避免此问题,请像重视代码一样重视注释。例如,在进行代码审查时,不仅要特别注意围绕新代码的注释,还要特别注意文件中的更高级别的注释是否仍然正确。

由于 JDK 捆绑了 Javadoc,因此您有独特的机会从对注释的严格赞赏中彻底受益,从而消除了技术债务。

宇宙山河浪漫,赞赏动力无限

Welcome to my other publishing channels