代码简洁性
推理与选择
任何软件系统最重要的特性之一,是能够在无需运行的情况下理解其行为。这一概念通常被称为“系统可推理性”。本质上,你希望在不首先观察系统运行的情况下,就能对系统的结构、行为和结果做出陈述。
为了理解其重要性,假设一个由一百个不同部件组成的系统。为简化理解,我们假设这是一个真实的物理系统而非计算机。假设我们有一个自动化汽车工厂,从原材料到成品车需要100个步骤。每个部件都对输入材料进行某些改变以产生输出产品。
我们可以通过多种方式配置这个系统及其每个部件:可以让每个部件执行多个动作,根据所采取的动作,下一个选择的机器实际上会不同。例如,假设我们将金属转换为圆棒。每辆车所需圆棒数量不同,而我们的圆棒可以由5种不同金属制成。因此机器有一个程序,每次收到钢条时决定制造哪种圆棒——这取决于一天中的时间和当前汽车需求。然后,根据制造的圆棒类型,将其送往五个不同下一台机器中的一台。
现在想象整个系统中的每台机器都如此复杂——接收复杂的输入,产生复杂的可能输出,这些输出又可能前往复杂的下一台机器集合。不仅人类无法在任何给定时间对整个系统的确切行为做出陈述(即推理),甚至对单个部件的行为进行推理都会很困难。
现在考虑另一种设置:每台机器接收一个输入,提供一个输出,且每台机器只与另一台机器“对话”(即其输入始终来自一个特定机器,输出始终前往另一个单一机器)。虽然一次性思考整个系统可能很困难(因为仍有100台机器),但查看每个单独部件很容易,从而可以推理单个部件和整个系统的逻辑行为。
这就是简洁性的核心部分——对此类系统进行推理的能力。当你查看软件系统的任何单独部分时,应该能够在不运行该部分的情况下对其行为、保证、结构和潜在结果做出陈述。应该清楚该部分如何与系统其余部分交互——要么确切知道什么调用它以及它调用什么,要么理解创建使用边界的结构。例如,这就是为什么许多编程语言中“私有”和“公共”函数的概念能增强系统可推理性——它们是告诉我们什么可能发生、什么不可能发生的边界。当查看函数或类的实际实现时,应该能通过阅读代码和注释轻松理解其采取的动作。这就是为什么命名对函数和变量如此重要——好的命名让读者能够推理系统的行为和边界。
选择
然而,使系统具备这种品质还有另一个非常重要的组成部分。为解释这部分,假设我们想象中的汽车工厂中的每台机器不是自动化的,而是由人操作。这更像是一个软件工程师在输入实际代码,“运行”他们的IDE、计算机、编译器、编程语言等机器。
在第一个例子中,复杂机器做出复杂决策,现在想象所有自动化机器之前做出的选择都必须由人类做出。也就是说,每次有金属进入我们的机器时,一个人必须查看它,决定是什么类型的金属,决定制造哪种圆棒,所有这些都基于查看当前汽车需求并记录时间。在真实工厂中,其中一些可能实际上可以接受。至少它为一个人创造了一份有趣的工作。但即使在那里,你也能看到会打开许多错误和不良结果的大门。
与后一个例子比较,我们有简单的机器,具有简单的输入和输出。它们对人来说操作如此简单,以至于一个人可能操作多台机器,并且几乎消除了所有错误或不良结果的可能性。
现在考虑到在编程中,程序员通常操作数十或数百个这样的“机器”(就他们维护的类和函数而言)。因此,对复杂汽车工厂的更好类比是让一个人运行所有一百台机器。如你所见,如果系统的每个部分提供太多操作员必须做出的决策,快速制造我们的“汽车”变得不可能。即使你能做到,制造汽车的速度也会非常慢,并且会让操作机器的人精疲力竭。瞧,这正是必须维护具有那种复杂度的软件系统的团队所发生的情况。
然而,当我们在“工厂”中加入人类时,我们引入了什么关键点?我们引入了决策(人类用头脑做的事情)和选择(呈现给人类的选项)的因素。
有些思想流派认为,所有开发人员应该始终被授权对其软件系统做出所有可能的决策。这听起来很棒,因为它听起来像是为聪明人提供智力自由——这是我们所有人都想要的。然而,如果你把这个原则推得太远,你实际上最终为你的开发人员创造了复杂的汽车工厂——一个有很多选择要做的系统,以至于他们要么变得瘫痪,要么保证做错,要么开发出其他人无法轻易理解的高度不一致的系统。
那么解决方案是什么,是消除所有人的所有选择,让他们成为执行首席架构师意志的无意识自动机吗?嗯,我相信有些软件架构师会喜欢这样,但实际上,这是一个有点极端的解决方案。答案是认识到哪些选择对开发人员来说重要,哪些不重要。
这取决于你在软件团队中的角色以及你在软件生命周期中所处的阶段。例如,如果你刚创办一家新公司并且是第一个开发人员,你能够选择公司运行的基本平台的几乎所有方面——你使用的语言、框架、库等——很重要。但即使那时,你也不希望那些框架和库呈现你不需要做出的决策。想象一下,如果编译器停下来问你它应该如何优化每段代码。这会帮助你或提高你的生产力吗?这对你的公司或你试图实现的目标实际上是一个净收益吗?我不这么认为。
然后,在项目生命周期的不同阶段,一旦你标准化了一种语言和正在使用的特定框架,通常你不会允许随机初级开发人员为他们的代码库部分选择不同的语言或框架。这是一个他们不需要花时间做出的决定——随波逐流对他们来说更高效。即使有更好的语言或框架可以使用,重写整个系统只是为了实现这个初级开发人员的一个功能似乎不是资源的好用途。
总的来说,如果你能消除开发人员不需要的足够选择,你实际上可以在整个公司范围内节省相当多的开发时间。想象一下,如果你公司的每个团队在开始开发系统之前必须花两周时间审查不同的框架。现在想象一下,你标准化了一个好的框架(即它能够满足所有将要使用它的人的业务需求),即使不完美,也没有人必须再做那个决定。你为整个公司节省了多少工程时间?这是巨大的——从长远来看,比几乎任何其他你可以做出的生产力改进都大。
现在,重要的是要记住,有些决定是开发人员需要做出的。他们绝对需要能够决定其系统业务逻辑如何运作——这是他们能够完成工作的核心要求。过去有些框架和库根本不允许人们实际编写他们需要的系统,这种限制水平对生产力有害。例如,想象一下你的公司标准化了一个支持HTTP但基本上无法支持SSL(即没有HTTPS)的框架。当你需要为安全目的加密连接时,这将是灾难性的。所以这将是一个非常糟糕的限制。
有时这是一条非常棘手的路线,但总的来说,我发现从长远来看,错误地删除选择实际上让开发人员更快乐,因为它让他们更高效。当你从人们那里拿走某些选择时,一开始非常困难,因为他们觉得你在影响他们的个人自由。在某种程度上,在短期内,你确实如此。但事实是,你试图提供更多的创造自由——开发人员实际上根本想要的自由。限制选择的目