现代软件工程 — 第三部分:文档
文档是一个常年有争议的话题,因为根据我的经验,软件工程非常注重像源代码和运输功能这样的人工制品的商业价值,而不是其他。我经常听到有人说,我们应该只写敏捷实践中需要的文档,或者说写设计文档不是一个好的时间利用。这些说法有一定的道理,而且通常是因为正在开发的文档没有为它所要服务的对象…
文档是一个常年有争议的话题,因为根据我的经验,软件工程非常注重像源代码和运输功能这样的人工制品的商业价值,而不是其他。我经常听到有人说,我们应该只写敏捷实践中需要的文档,或者说写设计文档不是一个好的时间利用。这些说法有一定的道理,而且通常是因为正在开发的文档没有为它所要服务的对象提供持久的价值。
当我听到另一个软件工程师抱怨别人写的文档不好时,我一直在想,为什么我们要费心去写文档。有时候,我遇到了写得非常好的文档,我就会想起为什么它值得做。不幸的是,这指出了一些对很多人来说可能很明显,但对一些人来说可能并不明显的东西:文档是一门艺术,而不是一门科学,大多数软件工程师并不是艺术家。
事实上,有效的写作涉及到很多技巧,对于一个接受过逻辑和精确训练的人来说,在告诉计算机做某事时,这并不是与生俱来的。如果我个人不学习也不尝试在写作方面表现出色,我很容易看到自己处于 “阅读代码 “的阵营,以了解它在做什么,而不去理会注释或任何其他形式的文档。但是,在我20年的软件工程师生涯中,我发现,一个好的注释可以解释为什么要用某种方式来实现,或者一个描述其他方法被拒绝时的权衡的文件,可以为我节省大量的时间和麻烦,让我能够尽快适应并改变代码库的情况。
朋友们,这就是价值所在。
好的文档会让它的听众铭记在心,并提供一个视角来促进理解。
简介 Introduction
讲故事时间到!
当我在Friendster时,我加入了一个软件工程小组,该小组有很多来自生产方面的挑战,以及一个快节奏的环境,旨在提供功能,同时建立他们拥有的关键知识产权。当时,Friendster拥有一项名为 “图谱服务器 “的专利,这是维护用户(或 “朋友”)之间联系的核心技术。
这个想法是,你会维护一个邻接列表,并在一些Memcache服务器池中 “分片” — 你可以配置池子,这样你可以有一个N个Memcache服务器的环,池子大小为3,你会有一个由3个逻辑上相邻的服务器组成的池子,数据的副本将驻留其中。这带来了冗余,以防其中一个Memcache服务器发生故障,但也允许客户从3个服务器中选择一个来请求读取数据,并在正确的路径上写到所有3个服务器。这种设计还有很多优点,但这不是本文的重点。)
这个图形服务器是我致力于改善其弹性、效率、性能和可扩展性的组件。在2007–2008年我们拥有的工具中,我们基本上成功地实现了这个目标。
我们当时也没有很好的文档来开始。
图形服务器被实现为一个处理HTTP 1.0/1.1请求的C++服务,其中的HTTP URIs表示某些操作。这并不完全是一个REST API,因为它不允许突变,而且当时基于资源的API设计还没有流行起来。这个实现没有足够的评论,也没有记录下为什么要这样实现的设计决定。
在第一部分中,我在一定程度上描述了这一点,在第二部分中,我描述了我们是如何启动项目,从头开始编写替代方案的。在这一部分中,我将描述我们如何用不同种类的文档逐步填补文档的空白。
文档都有哪些类型
文档有很多种 — 事实上,你现在读到的就是一种文档(有些人可能把这叫做知识库文章,但我们下次再谈知识库和如何建立知识库)。在这篇文章中,我们将介绍一些与典型的软件工程项目有关的文档。这些是:
- 内联/代码文档 In-line/Code Documentation: 这些文件通常以注释的形式存在于程序源代码中,描述相邻代码的某些方面。有时,这些文件被解析和提取出来,作为外部人工制品,如HTML或PDF文件,与程序结构一起使用自动工具进行查看。
- 系统文档 System Documentation: 这些通常是独立的文件,描述了系统(有或没有图表)和相互作用的组件。在一些地方,这些文件被称为系统设计文件、技术设计文件或软件设计文件,并遵循预定义的模板或至少涵盖系统级属性。
- 用户手册 User Manuals: 这些是面向用户的文件,说明用户如何与系统互动,预期结果是什么,任何错误模式,以及说明典型用法的例子。
- 操作手册 Operations Manuals: 这些是面向操作人员的文件,描述了系统应该如何操作(如何配置、部署、监控等),以及当系统没有按照预期操作时如何进行故障排除。
一些可能不属于上述范围的文件仍然是有用的,但通常是在需要的基础上产生。这些东西包括:架构决策记录(记录所做的权衡和决定)、技术建议(在一些项目中,这些是文档或工程工作跟踪系统中的票据)、用户故事(缺失的功能规格或打算支持的功能),以及缺陷报告或变更请求(在一些地方,正式流程要求记录与先前商定或交付的功能的任何偏差)。我不会涉及其中任何一项,但请放心,这些都是有价值的,原因与我下面要讲的相同。
为什么写文档?
这始终是一个没有答案的问题,很多人都不敢问,或者已经在某种程度上下了决心。我在这里提供一个观点,也许能说服你,写有效的文档是一个值得追求的目标。
- 解释为什么使用某种算法,或者字段的排序如何重要,或者甚至解释为什么某样东西以这种方式命名,以及可以使用哪些替代名称,可以使阅读和理解系统更加容易。
- 在像软件工程这样的团队运动中,取得并保持一致的意见可以让团队共同为同一个目标而努力。没有什么比在一个不了解代码结构的团队中更糟糕的了,他们互相踩着对方的工作,没有保持良好的沟通渠道。当一个时间多于理智的人在不了解原始结构的目标是什么的情况下就去重写了60%的代码,这就会消磨团队的士气,使团队环境变得有毒。或者在这样做的时候,并没有沟通为什么需要做这样的改变。站在同一起跑线上,可以让每个人都被纳入其中,并与你们的工作目标保持一致 — 这能更有效地促进团队工作和协作。
- 将问题、可用的解决方案和提供价值的计划记录下来,为每个人的成功做好准备。至少,这样做可以使你和你的团队清楚地知道你要解决什么问题,有哪些解决方案 — 以及你选择了哪一个 — 使阅读的人(团队、利益相关者、管理层、客户)清楚你打算如何提供价值。如果你不能把这些写下来,并让利益相关者同意,那么如果你只是潜心编码,并希望一切都能顺利进行,那么你的工作就会变得很困难。希望不是一种策略。
这些只是写文档很重要的几个原因。我相信还有更多的原因被我遗漏了,但在下一节中,我将尝试描述在我上面列出的原因下,有效的文档是什么样子的。
有用的文档看上去是什么样子的?
有效的文件有四个关键属性:
- 清晰 Clarity
- 完整 Completeness
- 一致 Consistency
- 相关并保持更新的 Relevance / Being up-to-date
这些都应该从文档的目标受众的角度来衡量。正如上面所暗示的,软件工程文档通常有三个主要受众:
- 用户 Users
- 软件工程师 Software Engineers
- 使用者 Operators
这些受众可能是同一组人(例如,软件工具针对的是软件工程师,他们自己也在运行这个系统),但他们的关注点是明确的,可以做到相互独立。
多年来,我学到的有效写作的一个原则是: 牢记你的听众。
在下面的章节中,我将介绍每一个关键属性以及如何判断你的文档是否有效。
清晰 Clarity
当一件事是清楚的,它是毫不含糊的,没有留下误解的空间。
清楚地写作需要有一个单一的重点,并使用词语来表达这个意思,而不分散注意力。
尽可能避免使用专业术语,当使用专业术语时,在一个规范的位置(如术语表)对其进行一次定义,以澄清其含义。
以下是我所做的几件事,以帮助确保我所写的文档是清晰的:
- 在受众身上测试文件,并征求对清晰度的反馈意见。
- 请我信任的人对文档进行批评,以达到最大的清晰度。如果这个人对问题和/或解决方案的空间不是很熟悉,那就会有帮助。这有助于识别我的偏见和盲点,并帮助我在未来改善我的文档写作技巧。
- 写出同一句子/段落的不同版本,并从听众的角度看哪一个可能更清晰,然后再确定一个。这很耗时,但我发现这在过去导致了更好的质量结果。
写清楚的目的是为了减少理解上的障碍。当人们在阅读文档时,对文档的内容有了很好的理解,并且比开始时有更多的问题时,写得清楚的文档就是有效的。
如果你的文档导致人们在阅读后还在问你问题,那么你的文档还不够清晰。
完整 Completeness
当文档涵盖了解决方案的所有相关部分,从目标受众的角度来看,它是完整的。
只要你与受众有畅通的沟通渠道,了解相关部分并不难。想象一个客户会很难,所以和一个真实的客户交谈会更好。或者,只得到你作为一个软件工程师的观点,并假设其他每个软件工程师都会有和你一样的担忧,也不可能产生完整的文档。
这可能会很耗时,也是通常会有很多人反对的地方 — 软件工程师通常不擅长写文档,因为它不被看作是一种解决问题的工作。然而,如果你真的把它当作一个需要解决的问题,大多数软件工程师会找到一种方法,使这个过程自动化或一劳永逸地解决它。
在谷歌,对公共接口的文档有一个很高的标准。面对成百上千万行的代码,我对功能文档的完善程度感到非常惊讶。更棒的是,如果有些东西不完整,有人会把它作为一个问题来对待,并试图尽快解决它(没有破窗和所有的好处)。
当你的文档从你的听众的角度回答一些关键问题时,你就知道你的文档是完整的:
- 这个解决方案的特点是什么?如果它是代码中的一个函数,那么参数是什么,如何影响结果,如何失败,它能做什么?如果它是一个工具,有哪些选项/命令,它如何接受输入,以什么格式,输出是什么?
- 如果我需要更多的信息,哪里有进一步的文档?比如说,从一个命令行工具,可能有一个`-help`选项,有一些信息,但也可能有一个链接到更全面的文档。如果用户手册没有涵盖每一个可用的选项,那么也许一些从行内注释中生成的选项的在线文档可能是有用的链接。
- 该解决方案是如何工作的?它是否有运行中需要的依赖性?是否有一些功能只适用于某些硬件/软件包?你可能会感到惊讶,即使用户自己不是软件工程师,他们也想知道某款软件是如何工作的,所以为了完整起见,这些信息应该是文档的一部分。
- 该软件是为谁准备的?明确记录这一点也算作是完整性。
- 什么时候应该使用这个软件?另一种说法是,这个软件要解决的问题是什么?
- 为什么这个软件会存在?这涉及到权衡问题 — 这是因为现有的东西不符合要求而建立的吗?这些要求是什么(记录下来)?
这可能是像我这样的软件工程师会看的地方,并说 “太难了;有没有一个80/20的解决方案来解决这个问题?” 我的回答是肯定的 — 但知道哪20%的文件可以产生80%的价值比覆盖100%的关注点更难。
请注意,文件不一定要很长才算完整。
一致 Consistency
有效的文档会自始至终使用相同的词汇、风格和语气。
在Google,我们有一个风格指南,不仅涵盖代码,也包括公共接口的文档。这对阅读和编写文档有很大的帮助,因为有一个一致的准则,每个人都会遵守,并对彼此负责。在那里,我最珍视的就是这种社区和文化方面。
即使有原创性和创造性的空间(例如,你可以有幽默或俏皮的用户手册),文档的方法是一致的 — (例如,每个项目都需要一个README.md)。这种一致性使得每个交叉链接的文档都是可搜索的,因此容易被发现。如果一个项目依赖于另一个项目,那么你可以沿着文档链接甚至是实现来确定关系是什么。
自我一致性对于独立的文档来说是很重要的,比如对于设计文档来说,这样文档的读者就不必为同一事物使用不同的词语而分心。在多个文档中使用相同的词,可以减少对相关领域有了解的读者产生混淆的可能性。
当读者不抱怨你的文档中使用的术语混乱时,你就知道你的文档是一致的。
相关并保持更新的 Relevance / Being Up-to-date
没有什么比过期的文档更糟糕的了。
在最好的情况下,文档是有一点错误的,在最坏的情况下,它是在主动误导读者。在我的职业生涯中,我觉得有必要多次向过期的文档发送一个拉动请求,以直接删除它。值得庆幸的是,有一次我自己这么做了,项目负责人对此表示感谢。
文档的价值来自于它的准确性和相关性。当文档不再描述实施的现实时,它就不再准确,也不再相关。同样地,当文档指向的方向不再是项目的发展方向,那么它也不再准确或相关。在这两种情况下,培养文档以保持其准确性和相关性应该是软件工程的正常过程的一部分。
在我们重写基于C++的图形服务器的努力中,我们确保每一次我们改变了实现,我们就改变了文档。我们用Doxygen来生成文档和UML关系,把这些文档藏在代码旁边,并保持它们是最新的。我们有一个Wiki,在那里我们定义了术语、项目目标,以及我们已经交付的功能。性能数据也在Wiki上。生产测试和结果也被记录在那里。
如果有人要加入这个项目,我们确保这些文件足以让他们进入这个项目,并以他们自己的进度为限制因素,取得成效。当然,我们会帮助他们,但我们在很大程度上依靠文件来沟通运营团队、产品团队、工程团队的其他成员和管理层。
在Google,我得到了一个系统,你可以配置该系统来自动检查代码库中托管的文档的最后更新时间,并将错误提交给拥有该文档的团队,让他们审查并适当更新。这使得文档管理成为系统维护的一部分,这使得它被尊重地对待。
过时的文档就会成为历史上的一个制品(Artifact)。我们应该从文件中得到的是对现实的反映。
开源中的文档
文档及其对软件工程师和用户社区的价值的一个关键成功案例是,开源项目是如何通过提供有效的文档而生存和死亡的。
例如,Rust、Python、OCaml、Zig、C#、Java和LLVM编译器工具链等项目的文档质量,是为用户和从事项目工作的软件工程师提供价值的典范。
例如,云原生计算基金会(CNCF)的项目有一个他们需要满足的文档质量标准,这是有原因的 — 这些为大多数现代软件工程项目提供动力的基础部分需要高质量的文档来减少新系统的维护和运营负担。例如,Kubernetes、gRPC和Istio都有非常好的文档,以使开发人员能够启动并运行现代系统架构。
想象一下,如果不能获得这些系统的高质量文档,而只能从代码中学习它们。我知道,如果我被要求在这样的工作条件下工作,我会转到另一个行业!
尽管代码是开放的和可用的,但这些项目的维护者和他们周围的社区为了他们所服务的受众的利益,对有效的文档进行了足够的投资,这是伟大的远见。
Conclusion
在我20年的职业生涯中,如果我说我不喜欢写文档,那是在撒谎。我个人认为,编写有效的文档是所有软件工程师都应该投资的一项关键技能,不仅因为它带来的价值,还因为这项技能如何转化为软件工程的许多方面。
能够以他人能够理解的形式写出想法,是现代软件工程的关键所在。软件工程的团队运动只会变得更加注重合作、记录和解决问题,而不是真正的手工写代码。事实上,在现代软件工程中,写代码不再是一个重要的部分。让一个想法写出来,被同事们批评,用一个计划来完善,然后以最快的速度执行,并牢记一个共同的目标,这个过程才是价值所在。
当有效的文档被列为优先事项时,我们可以看到开源软件的积极成果。应该没有什么可以阻止你和你的团队通过遵循同样的模式来进行现代软件工程。
如果你想的话,你甚至可以尝试让ChatGPT为你写文档! :)
后记
我希望你一直喜欢这个系列的文章。如果你喜欢这篇文章,但没有读过前两部分,你可以在这里找到它们:
我也写过关于软件工程的文章,你可能也会喜欢:
我也在我的论坛和bbs.market上上发布关于软件工程的想法,行业内发生的事情,我读到的一些有趣的东西,以及与软件工程行业的一般互动。