我们正在进入一个代码协作的新时代,一个具有实质性的重大变化即将出现。它到底是什么?更重要的是,它为什么会出现?

本文是“持续文档化宣言”的第二部分。我们在宣言中呼吁将创建和维护高质量的文档纳入开发流程。这一次,我们重点关注经常被低估的第三类文档——代码走读文档。

首先,我们一致认为,开发者和开发团队需要好文档。从理论上看,这个很容易做到。我们每个人都写出好文档,一切都会变得更好,不是吗?

事情并没有那么简单。当前的文档并不是为开发者服务的。在大多数情况下,文档不是缺失就是过期,所以开发者不信任文档,或者干脆从一开始就不创建文档。这会影响到整个团队或业务。要想走出这种困局,需要新的方法和工具。

持续文档化宣言回顾

持续文档化方法有助于创建和维护稳定的高质量文档,确保文档化成为开发过程的关键组成部分,就像测试或编码一样。持续文档化依赖三个原则:

  • 与代码耦合——文档应该显式引用部分代码。

  • 保持最新——随着代码的不断演化,持续保证文档的当前状态与代码的当前状态是保持一致的。

  • 即时创建——在知识最“新鲜”的时候创建文档。

常见的文档类型

内联文档(策略性的底层文档,比如代码注释)和高阶文档(可以为你提供更高层的视图)是最为常见的文档类型。但要向希望了解代码逻辑或者要修改代码的人解释清楚,这两种文档并不是很管用。

内联文档

这是一种策略性文档,用于解释代码行或代码块,最常见的是代码注释。例如,有一行代码包含了除数是 24601 的数学运算,注释里解释了为什么是这个除数,那么这个注释就是一种用于解释这行代码的内联文档。它并不会告诉我们更多东西,也不会告诉我们这行代码在一个更复杂的架构中承担了怎样的角色。另外一个例子是位于函数下面的注释,用于解释这个函数做了什么以及它的参数和返回值是什么。这个注释只解释了函数本身,并没有说明它为什么被用在特定的代码流程中。

有些人不写注释,但会尽力提高代码的可读性,我们认为,这也是一种内联文档。

高阶文档

如果说内联文档提供的是细节,那么高阶文档提供的是大视图,可以是整体的代码架构、代码背后的业务逻辑以及与二者有关的重大决策的缘由。有时候,高阶文档用于描述各种代码库或者某个特定代码库的主要模块。在初次了解一个代码库时,这种文档是非常有用的。不过,它的价值也就止于此。因为,它很少会包含与日常任务有关的东西,开发者不会频繁阅读它。

为什么这两类文档不是很管用?内联代码注释描述的是与之相关的代码块,范围狭小。高阶文档可以提供大视图,但缺乏开发者需要知道的细节。例如,在一份有关如何扩展 Git 的文档中,你可以从高阶视图描述创建一个新 Git 命令的过程,但如果缺乏细节,或者没有代码示例,你就不可能实现。这些细节包括文件的路径、函数的签名等。如果你将这些细节加到文档中,那它就不是高阶文档了。

代码走读文档

代码走读文档至少会让阅读代码的人看到代码的两个地方。它们描述了代码流程和交互,可能还会依赖代码片段。换句话说,它们与代码是耦合的(符合持续文档化原则之一)。

这种文档有点类似在有经验的代码贡献者的帮助下熟悉代码库。就像他们会向你解释代码的各个部分一样,代码走读文档做的是同样的事情。

下面列出了三个例子。

反复出现的代码模式

所谓反复出现的代码模式,可以是添加一个配置文件、继承一个类、创建一个新命令或者从数据库读取数据。这些模式都是很小的模式,很容易理解,通常涉及多次调用一个函数(调用日志函数也是一种模式)。更有意思一点的模式是跨多文件模式。因为这些模式在代码中多次出现,很有必要了解它们。阅读代码的人必须对整体有所了解,还需要知道特定的实现细节。

以给 Git 的 CLI 添加新命令为例。假设你是 Git 的贡献者,想要创建一个新命令,比如git new-command。我们来看一下如何添加这样的一个命令。我们不会涉及太多的细节,因为这不是这篇文章的重点,我们也不是要介绍 C 语言。

为了了解如何增加一个新命令,我们先看一下现有的例子——`git

add命令。这个命令的入口点位于builtin/add.c中,因为每一个命令在builtins`目录都有一个与之对应的文件。

int cmd_add(int argc, const char **argv, const char *prefix)
    {

这个函数的实现细节并不会影响对这个模式的理解,关键的是这个函数的签名——因为所有命令都有相同的参数。

函数签名声明需要被包含在builtin.h中:

int cmd_add(int argc, const char **argv, const char *prefix);

但模式并未结束。要让 Git 知道add命令的存在,需要在git.c文件的commands[]数组中加入一个cmd_struct

static struct cmd_struct commands[] = {
    { "add", cmd_add, RUN_SETUP | NEED_WORK_TREE },
    …
    …
};

为了能够构建项目,我们必须在 Makefile 文件的BUILTIN_OBJS中添加这个命令:

BUILTIN_OBJS += builtin/add.o

注意,这里有很多与代码库耦合的东西,并带有很多特定的细节:cmd_前缀、文件目录结构(特别是builtin目录)、命令函数的签名、文件名称(builtin.hgit.c等)、变量名称(BUILTIN_OBJS),等等。阅读代码的人还能看到一个跨四个文件的模式,尽管从逻辑上讲它们都与创建一个新的 CLI 命令相关。

直接在一个单独的文档中写明这些有助于理解这个模式:


当然,这是一个简单的例子。对于更为复杂的模式和跨更多文件的情况,代码走读文档的作用就愈加凸显。

代码不同部分之间的交互

代码的某些部分(甚至是关键部分)可能难以理解,因为涉及各个部分之间的交互,而且交互细节不是那么显而易见。以一个简单的 Web 应用为例,为了了解它的流程,我们需要在前端和后端之间跳来跳去。特别是对于一个项目的新人来说,一份解释代码不同部分之间交互的文档可以显著降低他们上手的门槛。

多代码库或服务之间的流程

如果代码跨多个代码库,那么上述的例子就会变得更加复杂。假设前端代码位于一个代码库中,后端位于另一个代码库中。随着涉及的代码库数量的增加,情况会变得越来越糟糕。如果有一份文档对各个相关代码库的代码做了说明,理解整个流程就会容易得多。

那么这些东西有哪些共同点?

  1. 它们对代码做了解释,不仅仅是代码的某个方面。

  2. 它们包含了可用于理解代码的信息。

  3. 当相关的代码发生变化,它们就过时了。

第三点也是为什么如果没有持续文档化就很少会有这类文档,因为开发者倾向于一开始就不创建它们。

不管怎样,创建代码走读文档是一种善意的行为,不管是对你的团队还是对未来的你来说都是好事。如果没有代码走读文档,代码膨胀会成为一个需要你花费数月时间去解决的繁琐流程,还会导致知识孤岛在企业中蔓延。

结论

为什么很难找到详细的描述代码流程和交互的文档?这个与任何一段长期、稳定的关系一样,如果你不持续投入,它就会分崩离析。对于大多数开发者来说,当代码发生变化,用于更新文档的时间、注意力和工作量是很大的开销,他们宁愿选择将时间花在开发新功能和修复 bug 上……

遵循持续文档化实践可以发挥代码走读文档的无限价值。在知识还“新鲜”的时候创建文档,并保持最新,将其作为开发流程的一部分,其价值就会彰显出来。

Omer Rosenbaum 和 Tom Ahi Dror 是Swimm的联合创始人。这家公司专注于在团队之间同步代码。

作者简介:

Omer Rosenbaum 是 Swimm 公司的首席技术官和联合创始人。该公司将持续文档化作为开发生命周期的组成部分。Omer 创立了 Check Point 安全学院,并担任 ITC 的网络安全主管。ITC 是一个教育组织,培训技术方面的人才。

Tom Ahi Dror 是国会预算办公室和 Swimm 公司的联合创始人。该公司将持续文档化作为开发生命周期的组成部分。Tom 是一位在技术、培训、战略和业务发展方面有独特经验的领导者。作为以色列最负盛名的军事学院塔尔皮奥特的毕业生,Tom 后来成为该项目的指挥官。在联合创立 Swimm 之前,Tom 是 ITC (以色列科技挑战)的业务发展副总裁。

原文链接

The Renaissance of Code Documentation: Introducing Code Walkthrough

本文文字及图片出自 InfoQ

余下全文(1/3)
分享这篇文章:

请关注我们:

发表评论

邮箱地址不会被公开。 必填项已用*标注