重新思考软件工程:为何在可维护性上失败
Source: Dev.to
我对当前软件工程在构建可维护应用方面的现状有强烈的看法:它已经失败,必须改变。并不是要把一切都抛弃重新开始,而是要逐步演进。今天的软件工程中有许多值得保留的好东西。然而,在我们能够辨别良莠之前,必须先弄清楚为什么现有方法不起作用。
现代软件工程的问题
自 2000 年代以来,几部作品在该领域产生了极大影响:Refactoring¹、Design Patterns²、Domain‑Driven Design³、Clean Code⁴、Clean Architecture⁵、SOLID 原则⁶,以及 Hexagonal⁷、Microservices⁸ 等架构风格。这并不代表所有关于可维护性的软件工程实践,但它涵盖了一些最具影响力的著作,足以让我们对当前状态有一个大致的认识。尽管有如此丰厚的理论基础,实际操作中仍然出现了问题。
前段时间,我观看了 Adam Tornhill 的演讲《Prioritizing Technical Debt as if Time & Money Matters》⁹。他描述了一个案例:他所咨询的公司团队抱怨某个服务难以维护。当他作为外部顾问分析代码库并与团队正在进行的其他项目进行对比时,未能发现代码质量的客观差异。但该团队抱怨的项目并非由他们自己开发——它是原团队解散后被继承的。
这个案例揭示了一个关键点:问题不在代码本身,而是代码反映了更深层的社会组织问题。专业的软件开发本质上是一种社会行为。我们与他人共同构建软件,代码不可避免地体现出这些关系的功能失调或健康程度,正如 Conway 定律¹⁰ 自六十年代以来所指出的那样。这一认识促使我重新审视即便是出于最佳初衷的实践。
原则与实践之间的鸿沟
以领域驱动设计(DDD)为例。它在引入“通用语言”(Ubiquitous Language)¹¹——认识到领域语言源自社会互动,正如我们日常使用的语言——方面做得很好,但它并未深入解决实际落地的挑战。我们把通用语言当作技术与业务之间沟通问题的概念性解决方案,却缺乏实施层面的实用指导:
- 如何说服业务利益相关者参与语言的构建?
- 在一个中等规模的组织里,普通的个人贡献者是否有能力改变整个团队甚至多个团队的构建方式?
- 有哪些局限性?
- 对于已经写入当前代码库的非通用语言遗留物,我们该怎么办?有哪些风险?
这些并非修辞性的抱怨,而是实践工程师每天都会遇到的真实阻碍。然而,我们的影响力著作很少涉及这些问题。我们拥有的知识把原则与其前提、以及影响其应用的现实约束分离开来。
同样的问题也出现在最基础的原则中。如果我们采用 clean‑code 原则¹²——函数只做“一件事”,这与 SOLID 中的单一职责原则¹³ 十分相似——同样会碰到可适用性的难题。什么是职责?什么是“一件事”?什么是“改变的理由”?甚至哲学家都在争论这些问题;哲学中有一个专门的分支——本体论¹⁴——已经争论了千年。
下面用两个不同的对象来说明为何实现“单一”职责、单一事物或单一改变理由并不简单。以维护角度比较台式机和笔记本:
- 台式机 – 键盘坏了,只需拔掉坏的键盘并更换。相较于笔记本,台式机的职责更少。
- 笔记本 – 键盘是内嵌的,修理需要打开整台设备,难度大得多。
即便是台式机的例子也会引出问题:键盘算是一件“事”吗?如果只有 “A” 键坏了,是更换整把键盘还是只换那个键?而一个键本身是单一的“事”,还是由开关等部件组成?继续放大到亚原子层面。软件中,我们的对象和模块也面临类似的两难。我们可以把抽象层次放大到位级别,但单一职责原则并未告诉我们需要什么抽象层次,也未说明在社会组织约束下我们能处理到哪一层。
在试图定义微服务应该有多“微”时也会出现同样的问题。目标并不是“单一”或“微”,而是调节职责数量和“事物”规模以实现特定目标。把达到单一“事物”本身当作原则或目标是不合理的。许多有经验的从业者已经明白这一点,但理论缺乏形式化,容易误导缺乏经验的工程师。
缺失的基石:实证证据
与其他知识领域相比,软件工程是一个相对新且快速发展的学科。我们仍未拥有许多所需的答案,作为专业人士只能在经验的指引下尽可能做出最佳决策。但我们可以更加有条理、更加严谨。
关于可维护性的软件工程原则和实践的有效性与挑战缺乏数据,是问题的核心之一。我们需要更多关于 clean code、SOLID、DDD、重构以及其他主流技术的研究。发表“对我有效”的新技术固然有价值,但必须认识到它们只在特定前提和条件下奏效。没有关于大规模采纳的客观、实证数据,这类主张仍然是假设。对一个团队有效的技术并不保证对另一个团队同样有效;我们需要理性地理解为什么它在特定情境下会或不会起作用。
我的亲身经历也说明了这一鸿沟。十年间,我从事企业级 Web 应用(涵盖 ERP、农业、物流)的开发,从未遇到因“Shotgun Surgery”¹⁵ 代码变更导致的交付延迟。虽然把改动散布到多个位置令人烦恼、慢且易出错,但它并不是阻断因素。相反,我曾因公司里没有人记得如何使用某个代码库而卡住好几天——原始贡献者已经离职,想要做一点小贡献就需要进行深度探索,阻力相当大。
我也见过项目因项目负责人在团队尚未准备好的情况下强行推行 DDD 而延误。我的看法是,错误在于负责人没有将技术与团队的实际情况匹配。这进一步凸显了实证依据的必要性:在推荐方法论之前,我们必须评估准备度、组织健康度以及其他约束。
脚注
- Martin Fowler, Refactoring (1999)
- Erich Gamma et al., Design Patterns (1994)
- Eric Evans, Domain‑Driven Design (2003)
- Robert C. Martin, Clean Code (2008)
- Robert C. Martin, Clean Architecture (2017)
- SOLID Principles (Robert C. Martin)
- Alistair Cockburn, Hexagonal Architecture (2005)
- Microservices Architecture (various sources)
- Adam Tornhill, “Prioritizing Technical Debt as if Time & Money Matters” (talk)
- Melvin Conway, Conway’s Law (1968)
- Eric Evans, Ubiquitous Language (DDD)
- Robert C. Martin, Clean Code (2008) – “one thing per function”
- Robert C. Martin, SOLID – Single Responsibility Principle
- Ontology (philosophical discipline)
- Martin Fowler, “Shotgun Surgery” (code smell)