QCon 演讲火热征集中,快来分享技术实践与洞见! 了解详情
写点什么

代码文档的文艺复兴:代码走读

作者:Omer Rosenbaum,Tom Ahi Dror

  • 2021-11-17
  • 本文字数:3153 字

    阅读完需:约 10 分钟

代码文档的文艺复兴:代码走读

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


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


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


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

持续文档化宣言回顾

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


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

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

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

常见的文档类型

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


内联文档


这是一种策略性文档,用于解释代码行或代码块,最常见的是代码注释。例如,有一行代码包含了除数是 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

2021-11-17 09:174650

评论

发布
暂无评论
发现更多内容

解读《深入理解计算机系统(CSAPP)》第8章异常控制流

小明Java问道之路

操作系统 异常 csapp 异常机制 7月月更

读书笔记之数据密集型应用的可靠性

宇宙之一粟

读书笔记 7月月更

「阿里云 RocketMQ 系列公开课」重磅来袭!

阿里巴巴云原生

Apache 阿里云 RocketMQ 云原生 直播

一款代替Typora的软件---MarkText

IT蜗壳-Tango

7月月更

普通二本院校如何从所谓的从寒冬破冰?

KEY.L

7月月更

新星计划Day5【数据结构与算法】 链表

京与旧铺

7月月更

使用 doscify 将文章写成文档一般丝滑

宁在春

GitHub Pages docsify 7月月更

mac 本地hive2 安装

飞哥

大数据 hadoop hive

几种跨平台方案的对比

Geek_99967b

小程序 跨平台

浅入浅出Mybatis(二)

ES_her0

7月月更

编译器优化那些事儿(4):归纳变量

openEuler

openEuler 毕昇 JDK 开源操作系统 编译器 加速器 编程语言

小程序容器对工业互联网平台建设的加速的体现

Geek_99967b

小程序

mac 本地安装spark

飞哥

大数据 spark 数据仓库

设计稳定的微服务系统时不得不考虑的场景

阿里巴巴云原生

阿里云 微服务 云原生 OpenSergo

基于ResNet50实现宫颈细胞病变分类

逝缘~

分类神经网络 7月月更

深度揭秘阿里云函数计算异步任务能力

阿里巴巴云原生

阿里云 Serverless 云原生 异步 函数计算

避坑:@Around与@Transactional混用导致事务不回滚

Paul

Spring Boot 2 事务失效 spring aop Java’

3000字11张图硬核科普:什么是边缘计算?与云计算有什么联系和区别?

wljslmz

云计算 边缘计算 7月月更

机器学习-西瓜书小记

AIWeker

机器学习 7月月更

动态广播发送流程

北洋

android 7月月更

【Docker 那些事儿】关于Namespace隔离机制的奥秘

Albert Edison

Docker Kubernetes 容器 云原生 7月月更

redis安装

想要飞的猪

【刷题记录】8. 字符串转换整数 (atoi)

WangNing

7月月更

微服务与Kubernetes容器云的边界

穿过生命散发芬芳

微服务 7月月更

Istio整体架构解析

阿泽🧸

istio 7月月更

接口测试进阶接口脚本使用—apipost(预/后执行脚本)

Xd

接口测试

斐波拉契数最小步数与合法括号序列判断

未见花闻

7月月更

hive on spark 和 spark on hive

飞哥

大数据 spark hive

小程序遇上Flutter 3.0

Geek_99967b

flutter 小程序

深入浅出 eBPF|你要了解的 7 个核心问题

阿里巴巴云原生

阿里云 云原生 k8s ebpf

分布式——Maven多模块管理

Java学术趴

7月月更

代码文档的文艺复兴:代码走读_语言 & 开发_InfoQ精选文章