构建可推理的软件系统:代码简洁性的核心原则

本文探讨了软件系统设计中的核心概念——“可推理性”,即在不运行代码的情况下理解其行为的能力。文章通过类比汽车工厂,阐述了如何通过简化组件交互、减少不必要的选择来构建更易于理解和维护的系统,从而提高开发效率与团队生产力。

构建可推理的软件系统:代码简洁性的核心原则

任何软件系统最重要的属性之一,是无需运行它就能理解它将做什么的能力。这个概念通常被称为“关于系统的推理能力”。从根本上说,你希望能够对系统的结构、动作和结果做出陈述,而无需先看到它们运行。

为了理解为什么这很重要,想象一个由一百个不同部分组成的系统。为了简化,我们假设这是一个真实的物理系统,而不是计算机。假设我们有一个生产汽车的自动化工厂,从原材料到成品汽车有100个步骤。每个部分都对输入材料进行一些更改以产出输出产品。我们配置这个系统及其各个部分有多种方式:我们可以让每个部分执行多个动作,并且根据采取的动作不同,我们选择的下一个机器实际上是不同的。例如,假设我们正在将金属转换为圆形杆。每辆汽车需要的圆形杆数量不同,我们的杆可以由5种不同的金属制成。因此,这台机器有一个程序来决定,每次收到一根钢条时,它将制造哪种杆。这会根据一天中的时间和我们汽车的当前需求而有所不同。然后,根据制造了哪种杆,该杆会送到五台不同后续机器中的一台。

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

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

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

选择

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

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

将其与我们的后一个例子进行比较,我们拥有简单机器,具有简单的输入和输出。一个人操作它们会非常容易,以至于一个人可能可以操作多台机器,并且你将消除几乎所有出错或产生不良结果的可能性。

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

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

有些思想流派认为,所有开发人员都应该始终被赋予对其软件系统做出每一个可能决策的权力。这听起来很棒,因为它听起来像是为聪明人提供了智力自由——这是我们所有人都想要的。然而,如果你将这个原则执行得太过分,你实际上最终会为你的开发者创造一个复杂的汽车工厂——一个存在如此多选择以至于他们要么变得瘫痪,要么注定会做错,要么开发出其他人难以理解的、极不一致的系统。

那么,这里的解决方案是什么?是消除所有人的所有选择,把他们变成执行你首席架构师意志的、没有思想的自动机吗?嗯,我相信有些软件架构师会喜欢那样,但实际上,那是一种有点极端的解决方案。答案是转而认识到哪些选择对开发人员来说是重要的,哪些是不重要的。

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

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

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

现在,重要的是要记住,有些决定是开发人员需要做的。他们绝对需要能够决定其系统的业务逻辑如何运作——这是他们能够完成工作的核心要求。过去有些框架和库根本不允许人们编写他们需要的系统,那种程度的限制对生产力是有害的。例如,想象一下你的公司标准化了一个支持HTTP但本质上无法支持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 设计