乐于分享
好东西不私藏

数字图解 | 软件项目中的大泥球等架构灾难

数字图解 | 软件项目中的大泥球等架构灾难

在任何软件项目中,错误都是不可避免的。但是,如果处理得当,错误并不可怕。错误可以被拦截、调整,并最终得到解决。导致软件项目深层次、致命问题的根源,在于你犯错时却浑然不觉。这类错误往往会恶化为大规模的、系统性的项目失败。正因如此,我特别喜欢引用 McConnell 列出的经典错误清单;我觉得每隔一段时间回顾一下,把它当作一种“分类自查”,是很有帮助的。我会问自己——我是否正在犯下这些错误,自己却没有意识到?我想这可能会导致一种项目“疑病症”,让你时常提防着那些看不见、摸不着的项目疾病。我不知道你怎么想,但我宁愿和一个多疑的项目经理共事,也不愿跟着一个毫无察觉的。正如那句话所说:“只有偏执狂才能生存。”

这大概也是我为什么如此喜欢 Brian Foote 和 Joseph Yoder 的那篇论文《大泥球》的原因。这篇论文最初提交于 1997 年的“编程语言模式大会”,它描述了软件开发中经典的架构错误

在实际工作中,真正占据主导地位的架构是“大泥球”

大泥球是一种结构混乱、随意蔓延、粗制滥造、用胶带和铁丝拼凑起来的意大利面条式代码丛林。我们都见过这样的系统。这些系统带有明显的不受约束的增长迹象,以及反复进行的权宜修补。系统中的信息在遥远的各个部分之间随意共享,常常到了几乎所有重要信息都变成全局或重复的地步。系统的整体结构可能从未被明确定义过;即便定义过,也早已面目全非。但凡有一丁点架构意识,程序员都会对这些泥潭避而远之。只有那些不关心架构、并且或许对日复一日修补这些残破大坝上窟窿的惯性工作感到自在的人,才会满足于在这样的系统上工作。

然而,这种方式却依然存在,并且大行其道。为什么这种架构如此流行?它真的像看上去那么糟糕吗?或者它可能只是通往更持久、更优雅作品的路上的一站?是什么力量驱使优秀的程序员去构建丑陋的系统?我们能避免吗?我们应该避免吗?我们又如何让这样的系统变得更好?

这篇论文读来引人入胜。作者列举了七种架构病症:


1. 大泥球(又称棚户区、意大利面条式代码)

棚户区通常由普通、廉价的材料和简单的工具建造而成。建造棚户区可以使用相对不熟练的劳动力。尽管劳动力在通常意义上是“不熟练的”,但这种住房的建造和维护却相当耗费人力。专业化程度很低。每间房屋主要由其居住者建造和维护,每个居住者都必须是所有必要工种的多面手。人们对基础设施关注甚少,因为基础设施需要协调、资金以及专业化的资源、设备和技能。整体规划和增长管控几乎不存在。

当有住房需求、有大量不熟练劳动力、而资金投入匮乏时,棚户区便应运而生。棚户区通过调动现有资源来解决迫切的、局部的住房需求。更高远的建筑目标是一种可以等待的奢侈品。维护棚户区是劳动密集型的,并且需要广泛的技能。你必须能够利用手头的材料即兴进行修补,掌握从屋顶修理到临时卫生处理等各种技能。然而,这里缺少成熟经济体中所见的那种熟练的专业化分工。

我们太多的软件系统,在架构上,不过是棚户区而已。对工具和基础设施的投资往往严重不足。工具通常是原始的,而库和框架等基础设施则资金匮乏。系统的各个部分不受控制地增长,缺乏基础设施和架构使得系统某一部分的问题得以侵蚀和污染相邻部分。最后期限像季风一样迫近,而架构上的优雅似乎遥不可及。

2. 一次性代码(又称快速黑客、面巾纸代码、一次性代码、脚本、杀手级演示、永久性原型、暴发城)

房主可能会搭建一个临时储藏棚或车棚,本意是想很快拆掉它,换上更永久性的东西。但这类结构却常常出人意料地长久留存下来。原本打算用来替换它们的钱可能不到位;或者,一旦新结构建好了,继续使用旧结构“一阵子”的诱惑又很难抗拒。

同样,当你在构建系统原型时,通常并不关心你的代码有多优雅或多高效。你知道你只会用它来证明一个概念。原型一完成,代码就会被扔掉,然后再正确地重写。然而,当展示原型的时刻临近时,用令人印象深刻但极其低效的系统最终预期功能的实现来加载它,这种诱惑很难抵挡。有时,这种策略可能过于成功了。客户可能不会资助项目的下一阶段,而是直接将原型本身定为发布版本。

3. 零碎增长(又称城市蔓延、迭代增量式开发)

城市规划的成功史并不均衡。例如,华盛顿特区是根据法国建筑师朗方的总体规划布局的。巴西首都巴西利亚和尼日利亚首都阿布贾最初也是纸上规划的城市。而其他城市,如休斯顿,则是在没有任何总体规划指导下成长起来的。每种方式都有其问题。

例如,在朗方的总体规划中,辐射状的街道布局在离中心一定距离后就会变得笨拙。而另一方面,完全没有任何规划,则会导致由土地所有权、资本和分区等局部力量任性互动所决定的住宅、商业和工业区的杂乱拼凑。由于娱乐、就近购物、远离噪音和污染等问题没有被直接纳入考量,它们便得不到妥善解决。

大多数城市更像休斯顿,而非阿布贾。它们可能始于定居点、分区、码头或火车站。也许是黄金、木材、交通便利或空地吸引了人们。随着时间的推移,某些定居点达到了临界质量,并引发了正反馈循环。城市的成功吸引了工匠、商人、医生和牧师。不断增长的人口能够支撑基础设施、政府机构和警察保护。而这些反过来又吸引了更多的人。城市的不同区域发展出各自独特的身份。除了少数例外,这些定居点的创始人们从未停下来想过他们正在创建大城市。他们的雄心通常更为朴素和着眼于眼前。

4. 保持其运行(又称生命力、小步前进、每日构建、首先,不要伤害)

一旦一座城市建立了基础设施,就必须让它保持运转。例如,如果下水道坏了而没有迅速修复,后果可能会从仅仅令人不快升级到真正危及生命。人们期望他们能够依赖公用事业 24 小时不间断的服务。他们(理所当然地)期望能够要求将服务中断作为紧急情况来处理。

软件也可能如此。通常,企业会变得依赖驱动它的数据。企业已经变得严重依赖它们的软件和计算基础设施。有许多关键任务系统必须一天 24 小时、一周 7 天不间断运行。如果这些系统宕机,库存无法检查,员工无法拿到工资,飞机无法调度,等等。也许有时候有理由让系统停机进行大修,但这样做通常充满风险。

然而,一旦系统重新上线,很难判断一大群修改中哪一个可能导致了一个新问题。每一次变更都有嫌疑。推迟这种整合是自寻烦恼。

5. 剪切层

斯图尔特·布兰德的《建筑如何学习》一书的核心思想之一便是“剪切层”的概念。布兰德则综合了来自不同来源的思想,包括英国设计师弗兰克·达菲和生态学家 R·V·奥尼尔。布兰德引用达菲的话说:“我们的基本论点是,并不存在什么‘建筑’这种东西。一个建筑如果被恰当构思,它是多个具有不同寿命的建筑构件的层。”

布兰德将达菲提出的层提炼为以下六层:场地、结构、表皮、服务、空间规划和陈设

  • 场地:地理环境。

  • 结构:承重元素,如地基和骨架。可能持续 30 到 300 年。

  • 表皮:外表面,如墙板和窗户。大约持续 20 年。

  • 服务:建筑的循环和神经系统,如供暖设备、布线和管道。在 7 到 15 年内更迅速地因磨损和技术过时而失效。

  • 空间规划:包括墙壁、地板和天花板。商业空间规划可能每 3 年就翻新一次。

  • 陈设:包括灯具、椅子、电器、公告板和绘画。就像软件一样,处于不停的变化之中。

这些层以不同的速率变化。

6. 把问题扫到地毯下(又称波将金村、打扫房屋、漂亮面孔、隔离、藏到床底下、修复)

把问题掩盖起来最壮观的例子之一,是苏联工程师建造的混凝土石棺,用来给如今位于乌克兰的、臭名昭著的切尔诺贝利四号反应堆盖上一个持续一万年的盖子。

如果你无法让一团糟消失,至少可以把它藏起来。城市更新可以从用壁画覆盖涂鸦、在废弃地产周围建起围栏开始。孩子们常常学到,在壁橱里堆成一堆,比在房间中央散落一地要好。

7. 重建(又称完全重写、拆除、打算扔掉一个、从头开始)

亚特兰大的富尔顿县体育场建于 1966 年,是棒球队亚特兰大勇士队和橄榄球队亚特兰大猎鹰队的主场。1997 年 8 月,该体育场被拆除。两个因素导致它相对快速地过时。

一是原始体育场的架构无法容纳 90 年代体育经济电子表格所要求的“空中包厢”套间的增加。任何可行的改造都无法满足这一要求。解决它意味着从头开始重建。

二是该体育场试图为棒球和橄榄球观众提供一个廉价、通用的场地解决方案,结果却损害了双方的需求。仅仅三十一年后,这些力量之间的平衡发生了决定性的转变。该设施正在被两个新的单一用途体育场所取代。

这里是否有关于意外需求和设计通用组件的经验教训可供我们借鉴?

处理问题的第一步是承认自己有问题。如果你在你当前的软件项目中瞥见了上述任何主题的影子,我鼓励你去阅读论文中的相关章节,那里有更详细的阐述,并提供了修复策略的思路。