架构设计考古:Bob大叔的整洁之道

本文作者 Robert C. Martin

The Clean Coder、Clean Code等名著作者Bob大叔,从1970年起编程至今。他是cleancoders.com和UncleBob Consulting LLC的创始人,8th Light, Inc.的“首席匠人”,曾任C++ Report主编及敏捷联盟(Agile Aliance)首任主席。

自1964年,12岁的我写下了人生的第一行代码算起,到2016年,我已经编程超过50年。我实际构建过大大小小的软件系统。我写过小型的嵌入式系统,也构造过大型的批处理系统;我构建过实时控制系统,也构建过Web网页系统;我写过命令行程序、图形界面程序、进程管理程序、游戏、计费系统、通信系统、设计工具、画图工具等。

我写过单线程程序,也写过多线程程序;我写过由几个重型进程组成的应用,也写过由大量轻型进程组成的应用;我写过跨多个处理器的应用,还有数据库类、数值计算类和几何计算类应用,以及很多很多其他类型的应用。

回首过去,经历了这么多应用和系统的构建过程,我最意外的领悟是:

软件架构的规则是相同的!

我知道,你也一定会对这个结论感到吃惊。为此,我要回顾一下自己从1970年到现在所经历过的几个比较有特点的项目,现在就让我们一起来看其中的一个项目,故事发生在20世纪70年代。

4-TEL维护中心计算机(SAC)是基于M365迷你计算机开发的。这个系统与所有外地部署的COLT进行通信,通过专线或者拨号线路。该系统会命令这些COLT计算机测量每条电话线的状况,接收原始数据,进行一系列复杂分析从而识别和定位问题。

工单分派

这个系统工作的一个核心原则是保证修理工资源的合理利用。根据工会规则,修理工分为三个类别:中央办公室修理工、线路、客户修理工。中央办公室修理工只能维修中央办公室内部的线路问题,线路修理工负责维修中央办公室与客户之间的线缆问题,而客户修理工负责修理客户现场内部与线缆末端连接的问题。

当收到客户投诉时,我们这套系统可以远程分析问题,以决定派遣哪种类型的修理工。这样可以避免派错修理工而无法解决客户问题,造成浪费和延迟,更节省了电话服务公司大量的金钱。

负责决策派遣规则的这段代码是由一个非常聪明,但是非常不善沟通的人设计和实现的。传言这段代码的构建过程是这样的:“他花了三个星期盯着天花板构思,然后用两天时间拼尽全力写成了这段代码——接下来他就离职了。”

没有人理解这段代码。每次我们尝试加一段新功能,或者修复一个问题的时候,都会引入新的问题。由于这段代码事关整个系统存在的核心意义,每个新的问题都让公司上下蒙羞。

最终,管理层要求我们将这段代码封闭起来,不允许再次修改了。这段代码已经正式固化了。

这段经历让我从此以后对代码的整洁性深感重视。

系统架构

这个系统是在1976年用M365汇编语言编写的。作为一个单体程序,它差不多有6万行代码。操作系统是一个我们自己开发的、非抢占式的、依赖轮询的任务切换器。我们将其称为MPS,即并行处理系统。M365计算机没有预置堆栈,所以任务相关的变量都需要在一个特定内存区域中存储,每次上下文切换时会换入换出。共享变量用锁和信号量来管理。代码重入与竞争问题是很常见的。

当时,系统中没有对设备控制逻辑、UI逻辑与业务逻辑代码之间的隔离。例如,调制解调器控制代码交织在业务逻辑与UI代码中间。没有任何模块化或者接口化的工作,调制解调器都是通过散落在各处的代码在比特层面直接控制的。

终端UI 相关代码也类似。消息和格式化代码也没有隔离,散落在6 万行代码中的各处。

我们当时使用的调制解调器硬件模块是设计为挂载在PC电路板上的。我们从第三方公司购买了这些模块,与其他的电路一起集成到我们自己设计的背板上。这些设备相当昂贵。几年过后,我们决定设计自己的调制解调器模块。我们软件组苦苦哀求硬件设计师,在设计新调制解调器时,一定要与老的组件采用同样的控制接口,一直到位级别。我们反复解释,控制代码散落在整个代码库的各处会导致难以修改,而且我们的系统未来还要同时处理两种不同的调制解调器。所以,我们反复不停地强调和哀求:“请一定要和原来的调制解调器采用同样的软件控制接口。”

然而,当我们实际拿到新的调制解调器时,控制结构是和以前完全不同的。不仅仅是有所区别,而是完全、彻底不一样。

谢谢啊,硬件工程师!

我们怎么办?这可不是仅仅将老的调制解调器替换为新的调制解调器,而是要混合部署到系统中。软件必须能够同时处理两种不同的调制解调器。我们是不是要在所有设备的代码中都增加各种特殊情况判断呢?这样的地方有几百个!

最终,我们采用了一个更糟糕的办法。

程序中有一个特定的子程序是用来向串口通信总线写入控制数据的,其中包括与调制解调器的通信。我们修改了这段代码,让它主动识别与老的调制解调器通信时候的字节位特征,同时将其转换为与新的调制解调器通信的代码。

过程相当复杂!向调制解调器发送命令的时候,需要向不同的IO地址发送不同的数据,我们的这段黑科技代码还要解析这些数据,然后按照原始顺序,将它们以完全不同的格式和不同的延时发往不同的IO地址。

最终这段代码还是正常工作了,但是这段程序真的是黑到不能再黑的黑科技。经历了这次事件之后,我深深懂得了将硬件代码与业务逻辑代码隔离——使用抽象层的重要意义。

大型重写

随着20世纪80年代的到来,生产自己的迷你计算机和设计自己的计算机架构逐渐失去了吸引力。市面上有很多迷你计算机可供选择,整合它们要比自己根据20世纪60年代的私有计算机体系架构来设计和实现更符合标准和省钱。另外,基于SAC 软件糟糕的架构设计情况,我们的技术管理部门开始推动SAC系统的一次架构重新设计。

新系统计划采用存储在硬盘上的UNIX操作系统,用C语言编写,运行在8086微型计算机体系上。我们的硬件团队开始设计实现硬件平台,同时一小部分软件开发者组成了“虎之队”,负责重写软件部分。

我就不在这讲中间发生的许多故事了。简单来说,第一个“虎之队”在消耗掉2人年到3人年的成本之后,没有能够交付任何东西。

一年或者两年之后,可能在1982年左右,整个过程又启动了一次。这次的目标是重用C语言和UNIX,在我们自己新设计的、非常强大的80286硬件上,将SAC整体重写。我们将这台计算机称为“深思者”。

这项工程耗费了几年时间,然后又延迟几年。我不知道第一套基于UNIX 的系统是何时部署的,我认为直到我离开公司的1988年,也没有部署成功。可能这套系统最终也没有部署成功。

为什么项目总是延期?简单来说,对一个重新设计的团队来说,想要和一大群积极维护旧系统的团队保持一致是非常困难的。下面是他们遇到的其中一个困难的情况。

欧洲

在用C语言重新设计SAC项目启动的差不多同一时期,公司开始向欧洲进军。当然,不可能等待新的软件系统设计完成,所以很自然,基于老的M365的系统被用来在欧洲部署。

问题是,欧洲电话系统与美国电话系统非常不同,不仅如此,人员分工与管理结构也是完全不一样的。所以我们当中的一个最好的软件开发人员被派遣到了英国,在当地带领一个软件开发团队解决欧洲的问题。

当然,没有人认为能把这些修改集成到美国使用的版本中,别忘了,当时可没有可以跨大洋传递大量代码的网络基础。英国开发团队简单地将US代码复制了一份,根据需要自己修改了。

当然,这样造成了其他困难。大洋两侧的代码发现了同样的Bug,需要在两地各自修复。但是由于模块在两地系统中已经被修改得面目全非,很难保证美国一侧的修改可以在英国系统中正常运行。

经过几年的折磨之后,同时也伴随着一条连接美国和英国办公室的高带宽网络线路的开通,我们开始了第一次整合两个分支的尝试,目标是将两套系统的功能区分变为配置文件的区分。这项工作第一次尝试失败了,第二次尝试又失败了,接下来是第三次尝试。两套代码,虽然十分相似,但是细节部分区别实在太多,无法简单整合——尤其是在当时市场多变,两地都在不停修改的情况下。

同时,“虎之队”在尝试用C语言和UNIX来重写这套系统时,也意识到了这个重新设计需要同时处理欧洲和美国两地的情况,当然,这只会让其进度更缓慢。

关于这套系统我还有很多故事可以讲,但是这实在是太让人郁闷了。简单来说,我软件研发生涯中的大部分教训都来自维护这套可怕的SAC汇编代码的经历。

————

本文节选自经典著作《架构整洁之道》附录部分。Clean Architecture问世以来好评如潮,抢鲜拜读英文版本的读者无不将其列入年度最佳书单。其穿越架构变迁的洞见,让几经浮沉的资深架构师深深折服,也让奋战一线的开发工程师提前警醒,更让临阵备战的架构初学者得窥门道。阅读原文已经开启智慧之门,点击方知不虚此行!

  • 内容简介:《架构整洁之道》是创造“Clean神话”的Bob大叔在架构领域的登峰之作,围绕“架构整洁”这一重要导向,系统地剖析其缘起、内涵及应用场景,涵盖软件研发完整过程及所有核心架构模式。本书分为6部分,第1部分纲领性地提出软件架构设计的终极目标,描述软件架构设计的重点与模式;第2~4部分从软件开发中三个基础编程范式的定义和特征出发,进一步描述函数、组件、服务设计与实现的定律,以及它们是如何有效构建软件系统的整体架构的;第5部分从整洁架构的定义开始,详细阐述软件架构设计过程中涉及的方方面面,包括划分内部组件边界、应用常见设计模式、避开错误、降低成本、处理特殊情况等,并以实战案例将内容有机整合起来;第6部分讲述具体实现细节;附录则透过作者数十年的软件从业经历再次印证本书的观点。对于每一位软件研发从业人员——无论从事的是具体编码实现、架构设计,还是软件研发管理,本书都是不可或缺的。

原文发布于微信公众号 - 前沿技墅(Edge-Book)

原文发表时间:2018-09-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏编程一生

一款低延迟的分布式数据库同步系统--databus

3926
来自专栏程序人生

浅谈unix之美

昨天写作写得膀子疼,看来花费同样的时间,写作比写代码累多了。今天是个伟大的节日,祝老婆,妈妈及家人节日快乐!祝所有女性读者节日快乐! 今天早上收获一封意外的惊喜...

3389
来自专栏数据和云

DBA入门之路:学习与进阶之经验谈

初入数据库之门的朋友们,总是关心如何能够快速提高,不断进步,事实上任何一个技术方向,都没有太多的捷径可走,勤奋与坚持必不可少,但是有一些方法和他人的经验可做借鉴...

3066
来自专栏ThoughtWorks

谁动了我的Token | TW洞见

今日洞见 文章作者/配图来自ThoughtWorks:禚娴静。 本文所有内容,包括文字、图片和音视频资料,版权均属ThoughtWorks公司所有,任何媒体、网...

3619
来自专栏二进制文集

我的电子学习之路

本科专业是测控技术与仪器,研究生专业是微电子学与固体电子学。回顾整个学生生涯,觉得有必要整理一下我的「电子学习之路」,算是对学生时代的总结吧!

3254
来自专栏EAWorld

DevOps与合规性:鱼和熊掌兼得指南

编者按:很多行业身处强力监管领域,因而格外强调合规性。反映在IT上就是开发、部署和运维等规范(比如开发团队不能碰生产日志)的不可或缺。本文中提到的一些方法(如自...

3224
来自专栏知晓程序

开发到上线仅 16 天,海外党微信小程序全攻略

3414
来自专栏FreeBuf

研究人员演示:用USB设备能够秘密窃取临近USB接口的数据

只需要用一个稍作伪装过的USB设备,插到电脑的USB口中,它就能监听临近USB接口泄露出出来的电信号,如果临近USB口接了键盘的话,那么通过对其进行分析就能获取...

37911
来自专栏极限编程

一枚程序员眼中的单元测试

如今程序员群体赶上了中国最庞大的农民群体,大街上随便抓一把,十有八九是程序员,还一个刚从某国企离职报名参加软件培训班。我想码农的称号或许就是这么来的吧。

2083
来自专栏AI星球

MacBook Pro 为什么值得我写一篇博文——程序猿使用感悟

研究生生涯伊始,撑过大学四年的 Dell 灵越 N4050 笔记本电脑就再次罢工了,一直想换电脑的冲动终于要付诸行动了,本来准备再换一个性价比比较高的 win ...

9.2K4

扫码关注云+社区

领取腾讯云代金券