代码简洁性
推理与选择
任何软件系统最重要的特性之一,是在无需运行的情况下理解其行为的能力。这一概念通常被称为“系统可推理性”。本质上,你希望在不预先观察系统运行的情况下,对系统的结构、行为和结果做出判断。
为了理解其重要性,假设一个包含上百个组件的系统。为简化理解,我们将其视为实体系统而非计算机系统。假设有一个自动化汽车工厂,从原材料到成品车需要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
然而如今一切都在自动化。我想知道工程师那时做什么?博客对我非常有用,技术通过人类的创造力增长。感谢分享知识。它真正使具有机械知识的软件开发人员受益。