软件工程师,如何写好设计文档

作为软件工程师,我们的工作不是产生代码本身,而是解决问题。
很多人问过我这个问题:“怎么写好设计文档?”才想到,几年前看到过一篇总结,我认为写得很好。今天找了一下原文(https://www.industrialempathy.com/posts/design-docs-at-google/#anatomy-of-a-design-doc),才发现是来自谷歌。
以下是正文,文档比较长。
非结构化文本(例如设计文档)的形式,可能是在项目生命周期早期解决问题的更好工具,因为它可能更简洁、更容易理解,与代码相比,它在更高的级别上传达问题和解决方案。
除了软件设计的原始文档外,设计文档在软件开发生命周期中还实现以下功能:
-
在需要更改时,尽早识别设计问题,让变更成本很低。
-
就组织中的设计达成共识。
-
确保考虑横切类问题(cross-cutting concerns)。
-
将高级工程师的知识扩展到组织中。
-
是围绕设计决策形成组织记忆的基础。
-
在软件设计师的技术集中充当摘要。
1
设计文档是一种非正式文档,因此其内容不遵循严格的指导原则。
第一原则(Rule #1) is: 以对具体项目最有意义的任何形式编写它们。
所以,文档结构可以有很多种。话虽如此,但一定有某些结构被证明是真正有用的。例如下面的内容可能就是有效文档所需要的内容。
1.1 上下文和范围(Context and scope)
本节向读者提供了一个非常粗略的概述,介绍了新系统正在构建的环境以及实际正在构建的内容。这不是需求文档。请保持简洁!目标是让读者了解最新情况,但可以假设一些以前的知识,并将详细信息给出链接。本节应完全侧重于客观背景事实。
1.2 目标和非目标(Goals and non-goals)
https://www.industrialempathy.com/posts/design-docs-at-google/#goals-and-non-goals
系统目标是什么,有时更重要的是,非目标是什么。注意,非目标不是否定目标,比如“系统不应该崩溃”,而是可以合理地成为目标,但明确选择不作为目标的东西。一个很好的例子是“ACID compliance”;在设计数据库时,您肯定想知道这是目标还是非目标。如果这是一个非目标,你仍然可以选择一个提供它的解决方案,如果它没有引入阻碍实现目标的权衡。
1.3 正式的设计(The actual design)
https://www.industrialempathy.com/posts/design-docs-at-google/#the-actual-design
本节应从概述开始,然后详细介绍。

设计文档是记录您在设计软件时所做权衡的地方。关注这些权衡,以生成具有长期价值的有用文档。也就是说,给定上下文(事实)、目标和非目标(需求),设计文档是建议解决方案,并显示特定解决方案最能满足这些目标的地方。
在更正式的媒体上编写文档的目的是提供灵活性,以适当的方式表达手头的问题集。
因此,对于如何实际描述设计,没有明确的指导。
话虽如此,一些最佳实践和重复主题已经出现,对大部分设计文档都有意义:
1)系统上下文图System-context-diagram
在许多文档中,系统上下文图(https://en.wikipedia.org/wiki/System_context_diagram) 可能非常有用。这样的图将系统显示为更大技术景观的一部分,并允许读者根据他们已经熟悉的环境对新设计进行语境化。

(Example of a system-context-diagram)
2)APIs
如果正在设计的系统公开了一个API,那么绘制该API通常是一个好主意。然而,在大多数情况下,人们应该能够承受将正式接口或数据定义复制到文档中的诱惑,因为这些定义通常很冗长,包含不必要的细节,并且很快就会过时。相反,关注与设计及其权衡相关的部分。
3)数字存储(Data storage)
存储数据的系统可能应该讨论如何以及以何种粗略形式发生这种情况。与API建议类似,出于同样的原因,应避免复制粘贴完整的模式定义。相反,关注与设计及其权衡相关的部分。
-
代码与伪代码
除非在描述了新算法的情况下,否则设计文档应该很少包含代码或伪代码。在适当的情况下,链接到显示设计可实现性的原型。
4)约束程度
影响软件设计形状和设计文档的主要因素之一是解空间的约束程度。
极端情况的一端是“绿地软件项目”,我们只知道目标,解决方案可以是最有意义的。这样的文档可能涉及面很广,但它还需要快速定义一组规则,以便放大可管理的解决方案集。
另一方面,系统中可能的解决方案定义得很好,但如何将它们结合起来以实现目标却一点也不明显。这可能是一个难以更改的遗留系统,其设计不是为了实现您想要的功能,也可能是一个库设计,需要在宿主编程语言的约束范围内运行。
在这种情况下,你可能能够相对容易地列举所有可以做的事情,但你需要创造性地将这些事情放在一起以实现目标。可能有多种解决方案,其中没有一种是真正伟大的,因此,在所有确定的权衡中,这样的文档应该侧重于选择最佳方式。
1.4 替代方案与考量
本节列出了可以合理实现类似结果的替代设计。重点应放在每个设计所做的权衡,以及这些权衡如何导致选择作为文档主要主题的设计的决定。
虽然可以简明扼要地介绍最终未被选择的解决方案,但本节是最重要的部分之一,因为它非常明确地说明了为什么在给定项目目标的情况下选择的解决方案是最好的,以及读者可能想知道的其他解决方案如何在给定目标的情况下引入不太可取的权衡。
1.5 Cross-cutting concerns
在这里,您的组织可以确保始终考虑某些交叉问题,例如安全性、隐私和可观察性。这些通常是相对较短的部分,解释设计如何影响问题以及如何解决问题。团队应将这些问题标准化。
由于其重要性,谷歌项目需要有一个专门的隐私设计文档,并有专门的隐私和安全审查。虽然审查只要求在项目启动时完成,但最佳做法是尽早与隐私和安全团队接触,以确保设计从一开始就考虑到这些审查。对于这些主题的专用文档,中央设计文档当然可以引用它们,而不是详细讨论。
1.6 设计文档的长度
设计文件应足够详细,但足够短,以便繁忙的人能够阅读。对于一个很大的项目来说,最合适的长度似乎是10-20页左右。如果你超越了这一点,那么将问题分解成更易于管理的子问题可能是有意义的。
还应注意的是,完全可以编写1-3页的“迷你设计文档”。这对于敏捷项目中的增量改进或子任务尤其有用——你仍然可以像处理更长的文档一样执行所有步骤,只是让事情更加简洁,并专注于有限的问题集。
2
编写设计文档是一项开销。是否编写设计文档的决定归结为核心权衡,即决定组织在设计、文档、高级审查等方面达成共识的好处是否超过创建文档的额外工作。该决策的中心在于设计问题的解决方案是否不明确——因为问题复杂性或解决方案复杂性,或两者兼而有之。如果不是这样,那么完成编写文档的过程就没有什么价值。
有一个明显的迹象表明,可能不需要文档,那就是实际上是实施手册的设计文档。如果一个doc基本上说“这就是我们要实现它的方式”,而不涉及权衡、替代方案和解释决策(或者如果解决方案显而易见,意味着没有权衡),那么立即编写实际的程序可能是一个更好的主意。
最后,创建和审查设计文档的开销可能与原型和快速迭代不兼容。然而,大多数软件项目确实存在一系列实际已知的问题。订阅敏捷方法并不是不花时间正确解决实际已知问题的借口。此外,原型设计本身可能是设计文档创建的一部分。“我试过了,效果很好”是选择设计的最佳论据之一。
3
设计文档生命周期中的步骤包括:
1、创建和快速迭代
2、评审(可多轮)
3、实施和迭代
4、维护和学习
3.1 创建与快速迭代期
你写文档,有时会与一组合著者一起。
该阶段迅速演变为快速迭代阶段,在该阶段,文档与对问题空间最了解的同事(通常属于同一团队)共享,并通过他们澄清的问题和建议将文档驱动到第一个相对稳定的版本。
虽然你肯定会发现工程师甚至团队更喜欢使用版本控制和代码审查工具来创建文档,但谷歌的绝大多数设计文档都是在谷歌文档中创建的,并大量使用其协作功能。
3.2 评审期
在审查阶段,设计文档比原始作者和密切合作者更广泛地与受众共享。评论可以增加很多价值,但它们也是一个危险的开销陷阱,所以要明智地对待它们。
回顾可以有多种形式:更轻量级的版本只是将文档发送到(更广泛的)团队列表中,让人们有机会看一看。然后,讨论主要发生在文档中的注释线程中。在审查的重要方面,是正式的设计审查会议,在会议上,作者通常向非常资深的工程观众介绍文档(通常通过专门的演示)。谷歌的许多团队为此定期举行会议,工程师可以报名参加审查。自然,等待此类会议的召开会大大减缓开发过程。工程师可以通过直接寻求最关键的反馈,而不是阻碍更广泛审查的进展来缓解这一问题。
当谷歌还是一家较小的公司时,通常会将设计发送到一个单一的中央邮件列表,高级工程师会在空闲时对其进行审查。这很可能是为公司处理事情的好方法。一个好处是,它确实在整个公司建立了相对统一的软件设计文化。但随着公司规模扩大到更大的工程团队,维持集中式方法变得不可行。此类审查增加的主要价值是,它们为将组织的综合经验纳入设计提供了机会。最一致的是,在审查阶段可以确保设计考虑到跨领域的问题,如可观察性、安全性和隐私。审查的主要价值不是发现问题本身,而是在开发生命周期的相对早期发现问题,此时进行更改的成本仍然相对较低。
3.3 实现与迭代期
当事情进展到足以确信进一步的审查不太可能需要对设计进行重大更改时,是时候开始实施了。当计划与现实发生冲突时,不可避免地会出现缺陷、未解决的需求或有根据的猜测,这些结果是错误的,需要更改设计。在这种情况下,强烈建议更新设计文件。
根据经验:如果设计的系统还没有发货,那么一定要更新文档。实际上,我们人类不善于更新文档,并且由于其他实际原因,更改常常被隔离到新文档中。这最终导致了一个更类似于美国宪法的州,它有一系列修正案,而不是一份一致的文件。从原始文档到这些修改的链接可以极大地帮助未来的维护程序员通过设计文档考古来理解他们的目标系统。
3.4 维护与学习期
当谷歌工程师面对一个他们以前从未接触过的系统时,他们的第一个问题通常是“设计文档在哪里?”。虽然设计文档和所有文档一样,随着时间的推移往往与现实不同步,但它们仍然是了解指导系统创建的思想的最容易进入的入口点。
作为作者,帮自己一个忙,一两年后重新阅读自己的设计文档。你做对了什么?你做错了什么?今天你会做什么来做出不同的决定?回答这些问题是提高工程师水平和提高软件设计技能的好方法。
4
在解决软件项目中最难的问题方面,设计文档是获得清晰性和达成共识的好方法。它节省了资金,因为它避免了无法实现项目目标的编码漏洞,并且可以避免使用预先调查;它需要钱,因为创建和审查需要时间。所以,为你的项目明智地选择!
在考虑编写设计文件时,请考虑以下几点:
-
您是否不确定正确的软件设计,是否需要花费前期时间来获得确定性?
-
与此相关的是,让高级工程师参与设计会有帮助吗?他们可能无法审查每一个代码更改?
-
软件设计是否模棱两可,甚至有争议,以至于围绕它达成组织共识是有价值的?
-
我的团队有时会忘记在设计中考虑隐私、安全、日志或其他交叉问题吗?
-
是否强烈需要提供对组织中遗留系统设计的高级见解的文档?
如果您对其中3个或更多问题的答案是肯定的,那么设计文档可能是启动下一个软件项目的好方法。
夜雨聆风
