代码简洁性:系统推理与选择的关键

本文探讨了软件系统简洁性的核心价值,即让开发者能够在不运行代码的情况下理解系统行为。通过类比汽车工厂的运作模式,阐述了简化系统结构和限制不必要选择如何提升开发效率和系统可维护性。

代码简洁性:推理与选择

作者:Max Kanat-Alexander
发布日期:2020年8月7日

任何软件系统最重要的特性之一,就是能够在无需运行的情况下理解它将做什么。这一概念通常被称为“对系统进行推理”的能力。基本上,你希望能够在不先观察系统运行的情况下,对系统的结构、行为和结果做出陈述。

为了理解这一点的重要性,想象一个包含一百个不同部件的系统。为简化起见,我们假设这是一个实际的物理系统,而不是计算机。假设我们有一个自动化工厂生产汽车,从原材料到成品车需要100个步骤。每个部件都对输入材料进行某种改变,以产生输出产品。

我们可以通过多种方式配置这个系统及其每个部件:我们可以让每个部件执行多个动作,根据所采取的动作,我们选择的下一个机器实际上不同。例如,假设我们将金属转换为圆棒。每辆汽车需要不同数量的圆棒,而我们的圆棒可以由5种不同的金属制成。因此,这台机器有一个程序,每次收到一根钢条时决定制造哪种圆棒。这取决于一天中的时间和当前对我们汽车的需求。然后,根据制造的圆棒类型,该圆棒会前往五个不同下一台机器中的一台。

现在想象整个系统中的每一台机器都是这样的——它接受一组复杂的输入,产生一组复杂的可能输出,这些输出又前往一组复杂的可能下一台机器。不仅人类无法在任何给定时间对整个系统的确切行为做出陈述(即进行推理),甚至对单个部件的行为进行推理也会很困难。

现在想象另一种设置,其中每台机器接受一个输入,提供一个输出,并且每台机器只与另一台机器“对话”(即其输入始终来自一个特定的机器,其输出始终前往另一个单一机器)。尽管一次性思考整个系统可能很困难,因为它仍然有100台机器,但查看每个单独部件很容易,并由此对单个部件和整个系统的逻辑行为进行推理。

这是简洁性的核心部分——对这样的系统进行推理的能力。当你查看软件系统的任何单独部分时,你应该能够对其行为、保证、结构和潜在结果做出陈述,而无需运行该部分。应该清楚该部分如何与系统的其余部分接口——要么我们应该确切地知道什么调用它以及它调用什么,要么我们应该理解创建该部分使用边界的结构。例如,这就是为什么许多编程语言中“私有”和“公共”函数的概念增强了系统推理能力——它们是告诉我们什么可能发生和什么不可能发生的边界。当你查看函数或类的实际实现时,应该能够通过阅读代码和注释轻松理解它采取的操作。例如,这就是为什么命名对函数和变量如此重要——因为良好的命名允许读者对系统的行为和边界进行推理。

选择

然而,使系统具备这种品质还有另一个非常重要的组成部分。为了解释这一部分,想象我们假想汽车工厂中的每台机器不是自动化的,而是由一个人操作。这更像是一个软件工程师在输入实际代码,“运行”他们的IDE、计算机、编译器、编程语言等机器。

在我们的第一个例子中,我们有做出复杂决策的复杂机器,想象所有自动化机器之前做出的选择,现在必须由一个人做出。也就是说,每次一块金属进入我们的机器时,一个人必须查看它,决定它是什么类型的金属,决定制造哪种圆棒,所有这些都基于查找当前汽车需求并记下时间。在真实工厂中,其中一些可能实际上是可以接受的。它至少为一个人创造了一份有趣的工作。但即使在那里,你也可以看到你会为许多错误和不良结果打开大门。

将其与我们后一个例子进行比较,在那里我们有简单机器,具有简单的输入和输出。它们对一个人来说操作起来如此容易,以至于你可能可以让一个人操作多台机器,并且你几乎消除了所有错误或不良结果的可能性。

现在考虑到在编程中,程序员通常操作数十或数百个这样的“机器”,就他们维护的类和函数而言。因此,对复杂汽车工厂的更好类比是让一个人操作所有一百台机器。如你所见,如果系统的每个部分向操作员提供太多必须做出的决策,制造我们的“汽车”很快变得不可能。即使你能做到,你制造汽车的速度也会非常慢,并且会让操作机器的人精疲力尽。你瞧,这正是必须维护具有那种复杂程度的软件系统的团队所发生的情况。

然而,当我们将人类添加到我们的“工厂”时,我们引入的关键点是什么?我们引入了决策(人类用头脑做的事情)和选择(呈现给人类的选项)的因素。

有些思想流派认为,所有开发人员应该始终被授权对其软件系统做出所有可能的决策。这听起来很棒,因为它听起来像是为聪明人提供智力自由——这是我们所有人都想要的。然而,如果你把这个原则推得太远,你实际上会为你的开发人员创造复杂的汽车工厂——一个系统中有如此多的选择,以至于他们要么变得瘫痪,要么保证做错,要么开发出其他人无法轻易理解的高度不一致的系统。

那么这里的解决方案是什么,是消除所有人的所有选择,让他们成为执行首席架构师意志的无意识自动机吗?嗯,我相信有一些软件架构师会喜欢这样,但实际上,这是一个有点极端的解决方案。答案是转而认识到哪些选择对开发人员能够做出是重要的,哪些是不重要的。

这取决于你在软件团队中的角色以及你在软件生命周期中所处的阶段。例如,如果你刚创办一家新公司,并且你是第一个开发人员,你能够选择公司将在其上运行的基本平台的几乎所有方面——你使用的语言、框架、库等——是很重要的。但即使在那时,你也不希望那些框架和库向你呈现你不需要做出的决策。想象一下,如果一个编译器停下来问你它应该如何优化每一段代码。这会帮助你或提高你的生产力吗?这实际上会对你的公司或你试图实现的目标产生净收益吗?我不这么认为。

然后,在项目生命周期的不同阶段,一旦你标准化了一种语言和你正在使用的特定框架,你通常不会允许随机初级开发人员为你的代码库的他们的部分选择不同的语言或框架。这是一个他们不需要花时间做出的决定——他们随波逐流更有效率。即使有更好的语言或框架他们可以使用,仅仅为了实施这个初级开发人员的一个功能而重写整个系统似乎不是对你资源的良好利用。

总的来说,如果你能消除开发人员不需要拥有的足够多的选择,你实际上可以在整个公司范围内节省相当多的开发人员时间。想象一下,如果你公司中的每个团队在开始开发系统之前必须花两周时间审查不同的框架。现在想象你标准化了一个好的框架(即它能够满足所有将要使用它的人的业务需求),即使不完美,也没有人必须再做那个决定。你为整个公司节省了多少工程时间?这是巨大的——从长远来看,比你几乎任何其他生产力改进都要大。

现在,重要的是要记住,有些决定是开发人员需要做出的。他们绝对需要能够决定他们系统的业务逻辑如何运作——这是他们能够完成工作的核心要求。过去有一些框架和库根本不允许人们实际编写他们需要的系统,这是一种对生产力有害的限制水平。例如,想象你的公司标准化了一个支持HTTP但 somehow 根本上不支持SSL(即没有HTTPS)的框架。当你为了安全目的需要加密连接时,这将是灾难性的。因此,这将是一个非常糟糕的限制。

有时这是一条非常棘手的路线,但总的来说,我发现从长远来看,在删除选择方面犯错实际上会让开发人员更快乐,因为这使他们更有效率。当你从人们那里拿走某些选择时,起初这非常困难,因为他们觉得你在影响他们的个人自由。在某种程度上,在短期内,你确实是在这样做。但事实是,你正在试图提供更多的创作自由——那个开发人员实际上从根本上想要的自由。限制选择的目的应该始终是提高创建系统的能力。你不是在扼杀生产,而是在删除分散注意力、障碍和困惑,这些以某人根本不需要做出的选择的形式存在。

  • Max

评论

Steven Gordon, PhD 说:
2020年8月7日下午2:11

在实践中,几乎总是存在对不透明遗留子系统的某种依赖,这使得推理和选择目标在实践中完全实现变得不切实际。理论上是不错的目标,但你需要足够的容错能力,以便不完全满足这些目标不会威胁导致你陷入欺骗性推理或选择政策过于僵化。

就推理而言,我宁愿有一套组织良好的自动化测试来理解,而不是整个代码库。自动化测试使得实际验证你推理所基于的假设变得微不足道,而不是被可能早已离开或隐藏依赖不透明遗留子系统的过于聪明的开发人员所困扰。

回复

Max Kanat-Alexander 说:
2020年8月8日晚上11:54

我理解你在说什么,但我确实认为,个体开发人员通常有能力实际使他们正在工作的系统部分足够简单以便进行推理。底层平台的复杂性可能使这非常具有挑战性,特别是当它们设计时没有考虑到这些原则时。但在实际应用中,人们可以设计系统,其中这些系统的部分可以在合理准确性下进行推理。

即使是容错点理想情况下也涉及对系统进行推理的能力——其在错误条件下的行为。

通过测试对系统进行推理是可以的,这是一个非常有用的工具,我同意。它们在验证你所拥有的推理方面也做得很好,这是真的。但我希望代码本身,当你查看它时,是预期读者能够进行推理的东西,并且当集成到更大系统中时,不会向用户呈现他们不需要做出的选择。

  • Max

Kursith 说:
2020年10月10日上午12:33

然而现在一切都在自动化。我想知道工程师们那时做什么?这篇博客对我很有帮助,因为技术正在通过人类的创造力增长。感谢分享知识。它确实使具有机械知识的软件开发人员受益。

回复

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计