两个系统的故事

前一段时间,我写了一篇文章“碎片化系统”,表达了自己对参与的两个十分混乱的系统的一些感想。刚好前几天看了“架构之美 Beatiful Architecture”一书,其中的第二章“两个系统的故事:现代软件神话 ”,也表达了同样的想法,而且说的更加清晰明了。这一章的部分内容摘要如下(InfoQ出了一本迷你电子书,可以在这里下载):

 


两个系统的故事:现代软件神话

Pete Goodliffe

 

软件系统就像一座由建筑和后面的路构成的城市—由公路和旅馆构成的错综复杂的网络。在繁忙的城市里发生着许多事情,控制流不断产生,它们的生命在城市中交织在一起,然后死亡。丰富的数据积聚在一起、存储起来,然后销毁。有各式各样的建筑:有的高大美丽,有的低矮实用,还有的坍塌破损。随着数据围绕着它们流动,形成了交通堵塞和追尾、高峰时段和道路维护。软件之城的品质直接与其中包含多少城市规划有关。

 

某些软件系统很幸运,创建时由有经验的架构师进行了深思熟虑的设计,在构建时体现出了优雅和平衡,有很好的地图,便于导航。另一些软件系统就没有这么幸运,基本上是一些通过偶然聚集的代码渐渐形成的,交通基础设施不足,建筑单调而平凡,置身于其中时会完全迷失,找不着路。你的代码愿意待在怎样的“城市”中?你愿意构建哪一种城市?

 

在本章中,我将讲述这样两个软件城市的故事。这是真实的故事,就像所有好的故事一样,这个故事最终是有教育意义的。人们说经验是伟大的老师,但最好是别人的经验,如果你能从这些项目的错误和成功之中学习,你(和你的软件)可能会避免很多的痛苦。本章中的这两个系统特别有趣,因为它们有很大不同,尽管从表面上看非常相似:

      •   它们具有相似的规模(大约500 000行代码)。

      •   它们都是“嵌入式”消费音频设备。

      •   每种软件的生态系统都是成熟的,已经经历了许多的产品版本。

      •   两种解决方案都是基于Linux 的。

      •   编码采用C++语言。

      •   它们都是由“有经验的”程序员开发的(在某些情况下,他们本应知道得更多)。

      •   程序员本身就是架构师。

 

在这个故事中,人名都已改变,目的是保护那些无辜的人(和有罪的人)。

      2.1   混乱大都市

         你们修筑、修筑,预备道路,将绊脚石从我百姓的路中除掉。

                                             —《以赛亚书》第57章14节

 

      我们要看的第一个软件系统名为“混乱大都市”。它是我喜欢回顾的一个系统—既不

      是因为它很好,也不是因为它让参与开发的人感到舒服,而是因为当我第一次参与它的

      开发时,它教给了我有价值的软件开发经验。

      我第一次接触“混乱大都市”,是在我加入了创建它的公司时。初看上去这是一份有前

      途的工作。我将加入一个团队,参与基于Linux 的、“现代”的C++代码集开发,已有的

      代码集已经开发几年了。如果你像我一样拥有特殊的技术崇拜,就会觉得很兴奋。

      工作起初并不顺利,但是你不能指望在加入一个新团队、面对新的代码集时会觉得很轻

      松。然而,日复一日(周复一周),情况却没有任何好转。这些代码要花极长的时间来

      学习,没有显而易见的进入系统中的路径。这是个警告信号。从微观的层面来说,也就

      是从每行程序、每个方法、每个组件来看,代码都是混乱而粗糙地垒在一起的。不存在

      一致性、不存在风格、也没有统一的概念能够将不同的部分组织在一起。这是另一个警

      告信号。系统中的控制流让人觉得不舒服,无法预测。这又是一个警告信号。系统中有

      太多的“坏味道”(Fowler   1999 ),整个代码集散发着腐烂的气味,是在大热天里散发

      着刺激性气体的一个垃圾堆。这是一个清晰的警告信号。数据很少放在使用它的地方。

      经常引入额外的巴罗克式缓存层,目的是试图让数据停留在更方便的地方。这又是一个

      警告信号。

      当我试图在大脑中建立“大都市”的全图时,没有人能解释它的结构;没有人知道它的

      所有层、它的藤蔓,以及那些黑暗、隔离的角落。实际上,没有人知道它究竟有多少部

      分是真正能工作的(它实际上靠的是运气和英雄式的维护程序员)。人们知道他们面对

的那一小部分区域,但没人了解整个系统。很自然,没有任何文档。这也是一个警告信

        号。我需要的是一份地图。

        这是一个悲伤的故事,我曾是其中的一部分:“大都市”是城市规划的恶梦。在你开始

        整治混乱之前,先要理解混乱,所以我们花了很大的精力和毅力,得到了一份“架构图”。

        我们标出了每一条公路、每一条主干道、每一条很少人了解的小路、所有灯光昏暗的辅

        路,并将它们画在一张主图上。我们第一次看到了这个软件的样子,并不令人赏心悦目。

        它是一些混乱的区块和线条。为了让它更好理解一些,我们用颜色标出了控制路径,突

        出了它们的类型。然后我们后退一步看着它。

        它令人吃惊。它令人目眩神迷。它就像一只喝醉了的蜘蛛,跌进了一些海报颜料罐里,

        然后在一张纸上织成了一张彩色的网。它看起来就像图2-1那样(这是一个简化后的版

        本,细节已经修改了,为了保护那些有罪的人)。事情变得很清楚了。我们画出了伦敦

        地铁图。它甚至有环线。

        图2-1:“混乱大都市”的“架构”

        这就是那种让跑遍各地的销售员恼怒的系统。实际上,它与伦敦地铁的相似性让人印象

        深刻:从系统的一端到另一端有很多条路线,哪条路最好通常是不明显的。地理位置很

        近的目的地常常很难到达,你希望能在两点之间再挖掘一条隧道。有时候,走出地铁换

        乘巴士实际上是更好的选择。或者干脆步行。

        无论从哪个角度来看,这都不是一个“好的”架构。“大都市”的问题超出了设计的范

        畴,它涉及开发过程和企业文化。这些问题实际上导致了许多架构腐烂。代码经过几年

        的“有机”生长,没有人进行过任何架构设计,而且各个部分是随着时间推移,没有经

        过太多思考就栓在一起的。我们这么说真的算是客气的了。没有人停下来为代码建立一

个理智的结构。它增长、膨胀,成为绝对没有任何架构设计的系统的一个典型。代码集

      从来不会没有架构。这个系统只是拥有一个很糟糕的架构。

      如果我们回顾创建“大都市”的公司的历史,它所处的状态是可以理解的(但是不可宽

      恕):这是一个初创的公司,快速提供许多新版本的压力很大。延期是不可容忍的—

      这会带来财务灾难。软件工程师被迫尽其极限,快速交付。所以代码是以一系列疯狂冲

      刺的方式垒在一起的。

      注意:不好的公司结构和不健康的开发过程将在糟糕的软件架构中得到反映。

      2.1.1  后果

      “大都市”缺少城市规划,这带来了许多后果,我们将在这里进行分析。这些后果的影响是

      很严重的,远远超出了你对不良设计的天真想象。地铁变成了云宵飞车,飞速地朝下猛冲。

      不可理解

      正如你已经看到的,“大都市”的架构以及缺乏强制的结构,导致了一个很难理解的软

      件系统,实际上几乎不可能修改。新加入项目的团队成员(譬如我)会被复杂性惊呆,

      不能够搞清楚状况。

      坏的设计确实会招致在它上面叠加坏的设计(实际上它简直就是迫使你那样做),因为没有

      一种明智的方式可以扩展该设计。在所有能解决手上工作的方法之中,阻力最小的总会被

      采用,没有明显的办法来修复这些结构问题,所以只要能减少麻烦,就会扔进去新的功能。

      注意:重要的是要保持软件设计的品质。坏的架构设计会招致更坏的架构设计。

      缺乏内聚

      系统的组件完全没有内聚性。每个组件本来都应该有一个定义良好的角色,但是它们却

      包含了一堆杂乱的、不一定相关的功能。这使我们很难确定组件存在的原因,也很难弄

      明白系统中已经实现了哪项具体的功能。

      很自然,这让缺陷修复成为了一场噩梦,严重地影响了软件的品质和可靠性。

      功能和数据都放在了系统中错误的地方。许多你认为是“核心服务”的部分却没有在系

      统的核心部分实现,而是由边远的模块来模拟实现(非常痛苦并且代价很大)。

      进一步的软件历史考察揭示了原因:原来的团队中存在个人斗争,所以一些关键程序员

      开始创建他们自己的软件小帝国。他们把自己认为酷的功能放到他们的模块中,即使它

      不应该属于那里。为了做到这一点,他们于是又实现了更为巴罗克式的通信机制,把控

制连回到正确的地方。

        注意:开发团队中健康的工作关系将直接有益于软件设计。不健康的关系和个性膨胀会导致不健

            康的软件。

                                    内聚和耦合

               软件设计的关键品质是内聚和耦合。这不是什么新奇的“面向对象”概

               念;自从20世纪70年代出现结构化设计开始,开发者对这一概念已经谈论

               了许多年。我们的目标是通过设计使系统的组件具备下列品质:

               •    高内聚(Strong cohesion)

               内聚是一个测量指标,说明相关的功能如何聚集在一起,模块内的各部分

               作为一个整体工作得如何。内聚性是将模块粘成一个整体的胶水。

               弱内聚的模块是不良分解的信号。每个模块都必须具有清晰定义的角色,

               而不只是一堆不相关的功能。

               •    低耦合(Low coupling )

               耦合是模块之间独立性的测量指标—它们之间进出“电线”的数量。在

               最简单的设计中,模块几乎没有什么耦合,所以彼此间的依赖关系较少。

               显然,模块不能够完全解耦,否则它们将根本不能够一起工作!

               模块之间的联系有多种方式,有的是直接的,有的是间接的。模块可以调

               用其他模块中的函数,或被其他模块所调用。它可能使用其他模块提供的

               Web服务或设施,可能使用其他模块的数据类型,或提供某些数据让其他模

               块使用(可能是变量或文件)。

               好的软件设计会限制通信的线路,只提供那些绝对需要的。这种通信线路

               是确定架构的一部分因素。

        不必要的耦合

        “大都市”没有清晰的分层。模块之间的依赖关系不是单向的,耦合常常是双向的。组

        件A会到达组件B 的内部,目的是完成它的一项任务。在其他的地方,组件B又通过硬编

        码调用了组件A 。系统没有最底层,也没有控制中心。它是整体式的一大块软件。

        这意味着系统的各部分之间耦合非常紧密,你想启动系统骨架就不得不创建所有的组件。

        单个组件的任何改变都会波及其他组件,需要修改许多依赖它的组件。孤立地看代码组

        件没有任何意义。

这使得低层次的测试不能够进行。不仅是代码层次的测试不可能进行,而且组件层次的

      集成测试也不能够创建,因为每个组件都依赖于几乎所有其他组件。当然,在公司中,

      测试从来也不具有很高的优先级(我们根本没有时间来做这种测试),所以这“不成为

      问题”。不必说,这个软件不太可靠。

      注意:好的设计考虑到内部组件连接的连接机制和连接数(以及连接性质)。系统的单个部分应该

           能够独立存在。紧耦合将导致不可测试的代码。

      代码问题

      不良的顶层设计所带来的问题也影响到了代码层面。问题会引起其他问题(参见Hunt

      和Davis[1999] 中关于破窗理论的讨论)。因为没有通用的设计,也没有整体项目“风格”,

      所以也没有人关心共同的编码标准、使用共同的库,或采用共同的惯例。组件、类和文

      件都没有命名惯例。甚至都没有共同的构建系统。胶带、Shell脚本、Perl 胶水与

      makefile和Visual Studio项目文件混在一起。编译这个怪物被视为一场复杂的成人仪式!

      “大都市”最微妙而又最严重的问题是重复。由于没有清晰的设计,也不清楚功能应该

      处于的位置,所以轮子在整个代码集中不断重新发明。一些简单的东西,如通用算法和

      数据结构,在许多模块中重复出现,每种实现都带有自己的一些未知的缺陷和怪异的行

      为特征。更大范围的关注点,如外部通信和数据缓存,也实现了许多次。

      更多的软件历史考察揭示了原因:“大都市”开始是从一系列独立的原型拼起来的,这

      些原型本该抛弃。“大都市”实际上是偶然形成的城市群。当代码组件缝合在一起时,

      组件之间匹配得不好。随着时间的推移,这种随意的缝合开始破裂,所以组件互相拉扯,

      导致代码集破碎,而不是和谐地协作。

      注意:松弛而模糊的架构将导致每个代码组件编写得不好,并且相互之间匹配得不好。它也会导

           致重复的代码和工作。

      代码以外的问题

      “大都市”内部的问题已经超越了代码集,在公司中其他的地方导致了混乱。不仅开发

      团队中有问题,而且架构的腐烂也影响到了支持和使用该产品的人。

      开发团队

         项目的新成员(例如我)被复杂性惊呆了,不能够搞清楚状况。这很好地解释了为

         什么很少有新人能在公司里待下来—员工流失率非常高。

那些留下来的人非常努力地工作,项目的压力非常大。规划新的功能会导致极大的

           恐惧。

        缓慢的开发周期

            由于维护“大都市”是一项恐怖的任务,所以即使是最简单的变更或“很小的”缺

           陷修复都不知道要花多少时间。管理软件开发周期非常难。客户只好等着实现重要

           的特征,管理层对开发团队不能满足业务目标感到越来越沮丧。

        支持工程师

           在支持这个极不寻常的产品时,产品支持工程师度过了可怕的时光,他们要设法弄

           明白很小的软件版本差异之间错综复杂的行为差异。

        第三方支持

           项目开发了一个外部支持协议,支持其他设备远程控制“大都市”。由于它只是软

           件内部结构上面薄薄的一层,所以它反映了“大都市”的架构,这意味着它也是巴

           罗克式的、难以理解的、容易偶尔出错的、不可能使用的。第三方工程师的生活也

           被“大都市”的可怕结构搞得一团糟。

        公司内部政治

           开发问题导致了公司内部不同“种族”的分裂。开发团队与营销销售团队之间关系

           紧张,每次新版本要推出时,制造部门总是要承受巨大的压力。经理们已经绝望了。

        注意:不良架构的影响不仅限于代码。它会进一步影响到人、团队、过程和时间表。

        清晰的需求

        软件历史考察凸显了“混乱大都市”之所以混乱的一个重要原因:在项目开始之初,团

        队并不知道要构建的是什么。

        本来的初创公司知道它要占领哪个市场,但不知道哪种产品能占领这个市场。所以他们两

        面下注,要求一个可以做许多事情的软件平台。噢,我们昨天就想得到它了。所以程序员

        们急急忙忙创建了一个毫无希望的总体基础设施,它具有做任何事情的潜力(但做得不好),

        而不是创建一个把一件事情做好的架构,并能够在将来进行扩展,做更多的事情。

        注意:重要的是要在开始设计系统之前知道你打算设计什么。如果你不知道它是什么,也不知道

            它将做什么,暂时不要开始设计它。只设计你知道需要的东西。

        在规划“大都市”的早期阶段,有太多的架构师。面对糊涂的需求,他们都拿着一块拼 不起来的拼图,

      试图独自工作。他们在工作时没有考虑到整个项目,所以当他们试图将

      这些拼图拼在一起时,就拼不起来了。没有时间进一步思考架构,软件设计的各个部分

      有一些重叠,于是开始了“大都市”的城市规划灾难。

      2.1.2  现状

      “大都市”的设计几乎完全是无可救药的—相信我,随着时间的推移,我们也尝试过

      修复它。修复工作需要返工、重构、修改代码结构中的问题,这些已经成为不可能的任

      务。重写也不是省事的方案,因为支持老的、巴罗克式的控制协议是需求的一部分。

      你可以看到,“大都市”的“设计”产生的后果是残酷的,并且会无情地变得更糟。很难

      加入新的特性,所以人们只是忙着添加更多不完善的功能、救急补丁和编造的谎言。没

      有人在面对代码时感到愉快,项目正盘旋着向下栽。缺乏设计导致了不良的代码,从而

      又导致了不良的团队精神和不断变长的开发周期。这最终导致了公司严重的财务问题。

      最后,管理层宣布“混乱大都市”已经不盈利了,它被抛弃了。对于任何组织机构来说,

      这都是勇敢的一步,特别是这个公司一直都眼高手低,同时又试图避免沉沦。带着团队

      从以前版本中得到的所有C++和Linux经验,他们在Windows上用C#重写了系统。猜猜

      看会怎么样。

      2.1.3  来自“大都市”的名信片

      那么我们学到了什么?不良的架构会产生深远的影响和严重的反弹。在“混乱大都市”

      中缺少预见性和架构设计,导致了下面的问题:

      •   低品质的软件和漫长的版本发布周期。

      •   系统没有弹性,不能够适应变更或添加新的功能。

      •   无处不在的代码问题。

      •   员工问题(压力大、士气低、跳槽等)。

      •   大量混乱的公司内部政治。

      •   公司不能成功。

      •   许多痛苦和面对代码深夜加班。

Advertisements