架构师必看-架构之美第14章-两个系统的故事:设计之城(一)

   形式永远服从功能。                                                       —Louis Henry Sullivan

                   “设计之城”软件项目表面上与“混乱大都市”非常相似。它也是用C++写的消费音频产品,运行在Linux操作系统上。但是,它的构建方式有很大不同,所以内部结构也非
常不同。

                    我从一开始就参加了“设计之城”项目。我们用有能力的开发者组成了一个全新的团队,从头开始构建这个产品。团队很小(开始有4名程序员),像“大都市”项目一样,团队的结构是扁平的。幸运的是,没有出现“大都市”项目中的个人对抗,在团队中也没有出现任何争权夺利的事。在此之前,团队成员之间并不非常熟悉,不知道我们在一起可以配合得多好,但我们对这个项目都很热心,喜欢这项挑战。

                    这样很好。

                    Linux和C++是项目早期的决定,这项决定确定了团队成员的组成。从一开始,项目就有清晰定义的目标:具体的首个产品和将来功能的路线图,代码集必须能够支持这些功能。这将是一个通用目标的代码集,可以适用于多种产品配置。

                     开发过程采用了极限编程(XP)(Beck和Andres 2004),很多人相信这种开发过程避开了设计:直接开始编码,不要想太远。实际上,一些旁观者对我们的选择感到震惊,并预言项目将以泪收场,就像“大都市”一样。但这是一种常见的误解。XP没有贬低设计,它贬低的是不必要的工作(即YAGNI原则,You Aren’t Going To Need It)。但是,如果需要前端设计,XP就要求你进行设计。它也鼓励使用快速原型(所谓的“spike”),快速展现并验证设计的有效性。这些都非常有用,对最终的软件设计产生了极大的影响。

                     2.2.1 设计之城的第一步

                     在设计过程的早期,我们确定了主要的功能领域(这包括核心的音频通道、内容管理和用户控制/界面)。我们考虑了它们如何在系统中适配,推出了初步的架构,包括了实现性能需求所必需的核心线程模型。

                     系统中各独立部分的相对位置关系体现为传统的分层结构,图2-2展示了简化后的结果。请注意,这并不是庞大的前端设计。它是有意为之的“设计之城”的简单概念模型:图中只有一些大块,这是一个基本的系统设计,可以随着功能模块的添加而轻松地增长。虽然很基本,但这个初始架构为增长提供了坚实的基础。“大都市”没有总体规划,在“方便”的地方嫁接(或修补)功能。

                      我们在系统的核心上花了额外的设计时间:音频通道。它实际上是整个系统的一个内部子架构。为了确定它,我们考虑了穿过一系列组件的数据流,最后得到了一个“过滤器和管道”音频架构,如图2-3所示。根据产品的不同物理配置,它包含了这样一些管道。同样,开始时这些管道只是一个概念,即图中的一些方块。我们当时还没有决定如何将所有模块拼装在一起。


                        我们在早期也选择了项目将采用的支持库(例如,可以从http://www.boost.org获得的Boost C++库和一组支持数据库的库)。关于一些基本关注点的决定是这时候做出的,目的是确保代码能够容易而一致地增长,这些决定包括:

                        • 顶层文件结构。
                        • 我们如何对事物命名。
                        • “内部”展示的风格。
                        • 共用的编码惯例。
                        • 选择单元测试框架。
                        • 支持基础设施(例如版本控制、适合的构建系统和持续集成)。
                         这些“细节”完美的因素非常重要:它们与软件架构密切相当,影响到后来的许多决定。

2.2.2 故事展开

                         在团队完成了初始设计之后,“设计之城”项目按照XP过程推进。设计和编码要么以结对的方式完成,要么经过仔细的复审,确保工作的正确性。

                         随着时间的推移,设计和代码不断发展和成熟;随着“设计之城”的故事逐渐展开,产生了下面的结果。

                        定位功能
                       由于从一开始我们就有系统结构的清晰总体视图,所以新的功能单元可以一致地添加到代码集的正确功能区域。代码应该属于哪一块从来就不是问题。在扩展功能或修复问题时,我们总是很容易找到已有功能的实现代码。

                        现在,把新的代码放到“正确”的位置有时候比简单“嫁接”到方便而不妥的地方而更难一些。所以,架构规划的存在有时候让开发者的工作变得更难一些。这些额外工作的回报就是今后的生活要容易很多,当我们维护或扩展系统时,不愉快的事情会很少。


注意:架构有助于定位功能:添加功能、修改功能或修复缺陷。它为你提供了一个模板,让你将工作纳入到一张系统导航图中。
                       一致性

                       整个系统是一致的。各个层次的所有决定都是在整个设计的背景下做出的。开发者从一开始就有意为之,这样得到的所有代码都完全符合系统设计,并与编写的所有其他代码相匹配。

                       在项目的历史中,尽管有许多变更,涉及代码集的各处(从单行代码到系统结构),但这些变更都符合最初的设计模板。


注意:清晰的架构设计将导致一致的系统。所有决定都应该在架构设计的背景下做出。

                       顶层设计的好风格和优雅很自然会为较低的层带来好处。即使在最低层,代码也是统一而整洁的。清晰定义的软件设计确保了没有重复,熟悉的设计模式到处使用,熟悉的接口惯例普遍采用,没有特殊的对象生命周期或奇怪的资源管理问题。代码是在城市规划的背景之中写成的。


注意:清晰的架构有助于减少功能重复。

                   架构的增长
                  有一些全新的功能领域出现在了设计“全图”中,例如存储管理和外部控制功能。在“大都市”项目中,这是致命的一击,难度超乎想象。但在“设计之城”项目中,事情就不一样了。

                 系统设计就像代码一样,被认为是可扩展、可重构的。开发团队的一项核心原则就是保持敏捷,没有什么是一成不变的,所以在需要时架构也可以修改。这促使我们让设计保持简单并易于修改。这样一来,代码可以快速地增长,同时又保持好的内部结构。添加新的功能块不是问题。


注意:软件架构不是一成不变的。需要时就改变它。要想做到可以修改,架构就必须保持简单。牺牲简单性的修改要抵制。

                  延迟设计决定有一项XP原则确实提高了“设计之城”的架构品质,这就是YAGNI(如果你不是马上需要,就不要去做)。这促使我们在早期只设计了重要的部分,将所有余下的决定推迟,直到我们对实际的需求有了更清晰的理解并知道如何放到系统中最好时,再做出这些决定。这是一种非常强大的设计方法,在很大的程度上解放了我们的思想。

• 当你还不理解问题时就开始设计,这是一件糟糕的事。YAGNI迫使你等待,直到你知道真正的问题是什么,它应该怎样由设计来体现为止。这消除了猜测的工作,确保设计是正确的。

• 当你开始创建软件设计时就加入所有可能需要的东西(包括厨房水槽)是危险的。你的大部分设计工作会变成无用功,得到的只是额外的负担,你不得不在软件的整个变更生命周期中支持这些设计。它一开始就增加了成本,而且在项目的生命周期中不断增加成本。


注意:延迟设计决定,直到你必须做出这些决定为止。不要在你还不知道需求的时候就做出架构决定。不要猜测。

保持品质
从一开始,“设计之城”就准备好了一些品质控制过程:
• 结对编程。
• 对没有结对编程的工作进行代码/设计复审。
• 对每一段代码进行单元测试。
这些过程确保了系统中从未加入不正确的、不合适的变更。所有不符合软件设计的内容都被拒之门外。这可能听起来有点过于严厉,但这些是开发者们坚信的过程。这种信念凸显了一个重要的态度:开发者们相信设计,认为设计对项目相当重要。他们拥有设计,对设计负责。


注意:必须保持架构品质。只有当开发者们相信它并对它负责时,才能做到这一点。

管理技术债务
                  除了这些品质管理方法之外,“设计之城”的开发是相当注重实效的。随着最后期限的临近,一些不太重要的功能被砍掉,让产品能够准时推出。小的代码“瑕疵”或设计问题允许存在于代码集中,要么是为了让功能快一点实现,要么是为了在接近发布时避免高风险的改动。但是,与“混乱大都市”项目不同的是,这些逃避职责的地方被标记为技术债务,并安排在后续的版本发布中修正。这些问题很清楚,开发者对它们不满意,直到将它们处理掉为止。同样,我们看到了开发者对设计的品质负责。

单元测试打造了设计
                 关于代码集的一项核心决定就是所有代码都要有单元测试(这也是在XP开发中强制要求做到的)。单元测试带来了许多好处,其中一点就是能够修改软件的一些部分,而不必担心在修改的过程中破坏其他的东西。我们对“设计之城”内部结构的某些部分进行了相当激进的返工,单元测试给了我们信心,让我们相信系统的其他部分没有被破坏。例如,线程模型和音频管道的内部连接接口都进行了彻底的改变。这是在子系统开发较晚的时候发生的严重设计变更,但与音频通道接口的其他代码仍然执行得很好。单元测试让我们能够改变设计。随着“设计之城”的逐渐成熟,这种类型的“主要”设计变更越来越少了。在经过一些设计返工之后,情况稳定下来,此后只有一些不重要的设计变更。系统开发得很快:以迭代的方式进行,每一次迭代都改进了设计,直到它达到了相对稳定的状态。


注意:你的系统应该有一组不错的自动化测试,它们让你在进行根本的架构变更时风险最小。这为你提供了工作的空间。

设计时间
                 “设计之城”成功的另一个因素是分配的开发时间段,它既不长也不短(就像金发歌蒂的粥,既不热也不冷,刚刚好)。项目需要一个有利的环境才能获得成功。如果时间太多,程序员常常会想创建他们的巨作(那种总是快要好了,但永远不会实现的东西)。有一点压力是好事,紧迫感有助于完成事情。但是,如果时间太少,就不可能得到任何有价值的设计,你只会得到半生不熟的解决方案,就像“大都市”那样。


注意:好的项目计划将带来优质的设计。分配足够的时间来创建架构杰作,它们不会立即出现。

与设计同行
                 尽管代码集很大,但它是一致而易于理解的。新的程序员可以比较容易地拿起代码并开始工作。不需要去理解不必要的复杂内部关系,也不需要面对奇怪的遗留代码。由于代码中产生的问题比较少,工作起来有乐趣,所以团队人员的流失率很低。这是因为开发者们负责设计,并不断希望改进它。看着开发团队动态地遵守架构设计是一件有趣的事情。“设计之城”的项目原则规定没有人“拥有”哪一部分设计,这意味着任何开发者都可以改动系统的所有地方。每个人都应该写出高品质的代码。“大都市”是许多不协作的、互相争斗的程序创造的一团混乱,而“设计之城”则是由密切合作的同事创建的一组干净、一致、密切合作的软件组件。在很大程度上,Conway法则(注)反过来也生效,团队的组织方式就像软件的组织方式一样。


注意:团队的组织方式必然对它产生的代码有影响。随着时间的推移,架构也会影响到团队协作的好坏。当团队瓦解时,代码的交互就很糟糕。当团队协作时,架构就集成得很好。

2.2.3 现状
                在一段时间之后,“设计之城”的架构如图2-4所示。也就是说,它与最初的设计非常相似,同时也包含了一些值得注意的变更。此外,它还包含了大量的经验,证明这个设计是正确的。健康的开发过程,小的、更善于思考的开发团队,适当注意确保一致性,带
来了极为简单、清晰、一致的设计。这种简单性为“设计之城”带来了好处,得到了可扩展的代码和快速开发的产品。
                在编写本书时,“设计之城”项目已走过了3年。代码集仍在使用,而且扩展出了一些成功的产品。它还在开发、成长、扩展,还在每天发生变化。下一个月它的设计可能与这个很不同,但也可能没有不同。

                我要澄清一点:这些代码并不完美。有些地方存在着技术争论,但是它们在整洁的背景下显得特别突出,会在将来得到解决。没有什么是一成不变的,由于适应性强的架构和灵活的代码结构,这些问题都可以解决。几乎所有东西都各就各位,因为架构很好。


注: Conway法则指出,代码结构符合团队的结构。简而言之,“如果你让4个小组开发一个编译器,就会得到一个4阶段编译器。”


2.3 说明什么问题
                     等那完全的来到,这有限的必归于无有了。                                —《哥林多前书》第13章10节
                    这个关于两个软件系统的简单故事当然不是软件架构的全面介绍,但我已展示了架构如何对软件项目产生深远的影响。架构几乎会影响所有与之相关的人和事,它决定了代码集的健康,也决定了相关领域的健康。就像一个繁荣的城市会为当地带来成功和声望,好的软件架构将帮助项目获得发展,为依赖于它的人带来成功。好的架构是很多因素的结果,包括以下方面(但不限于此):
• 确实进行有意为之的前端设计。(许多项目甚至还没开始,就因为这一点而失败了。)
• 设计者的素质和经验。(以前犯过一些错误是有帮助的,这能在下一次为你指出正确方向!“大都市”项目肯定教会了我一些东西。)
• 在开发过程中,保持清晰的设计观点。
• 授权团队负责软件的整体设计,而团队也承担起这一责任。
• 不要害怕改变设计:没有什么是一成不变的。
• 让合适的人加入到团队中,包括设计者、程序员和经理,确保开发团队的规模合适。确保他们具有健康的工作关系,因为这些关系将不可避免地影响代码的结构。
• 在合适的时候做出设计决定,当你知道所有必要信息时再做出决定。延迟那些暂时不能做出的决定。
• 好的项目管理,以及合适的最后期限。
2.4 轮到你了
                   绝不要失去神圣的好奇心。                                 —阿尔伯特·爱因斯坦
你正在读这本书是因为你对软件架构感兴趣,而且你对改进自己的软件感兴趣。所以这里就有一个极好的机会。对于你目前的软件经验,请考虑以下简单的问题:
1. 什么是你看到过的最好的系统架构?
• 你怎么知道它是好的?
• 这个架构在代码集之内和之外带来了什么结果?
• 你从中学到了什么?
2. 什么是你看到过的最差的系统架构?
• 你怎么知道它是差的?
• 这个架构在代码集之内和之外带来了什么结果?
• 你从中学到了什么?

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第一部分 论架构 第1 架构概述  13 1.1 简介  13 1.2 创建软件架构  19 1.3 架构结构  23 1.4 好的架构  27 1.5 美丽的架构  28 致谢  30 参考文献  31 第2 两个系统故事:现代软件神话  33 2.1 混乱大都市  34 2.2 设计之城  40 2.3 说明什么问题  47 2.4 轮到你了  48 参考文献  48 第二部分 企业级应用架构 第3 伸缩性架构设计  51 3.1 简介  51 3.2 背景  52 3.3 架构  56 3.4 关于架构的思考  61 第4 记忆留存  67 4.1 功能和约束  68 4.2 工作流 69 4.3 架构关注点  70 4.4 用户反应  90 4.5 结论  90 参考文献  90 第5 面向资源的架构:在Web中  91 5.1 简介  91 5.2 传统的Web服务  92 5.3 Web  94 5.4 面向资源的架构  99 5.5 数据驱动的应用  102 5.6 应用面向资源的架构  103 5.7 结论  108 第6 数据增长:Facebook平台的架构  109 6.1 简介  109 6.2 创建一个社会关系Web服务  114 6.3 创建社会关系数据查询服务  121 6.4 创建一个社会关系Web门户:FBML  129 6.5 系统的支持功能  142 6.6 总结  147 第三部分 系统架构 第7 Xen和虚拟化之美  151 7.1 简介  151 7.2 Xenoservers  152 7.3 虚拟化的挑战  154 7.4 半虚拟化  155 7.5 Xen的变换形式  158 7.6 改变的硬件,改变的Xen  163 7.7 经验教训  165 7.8 延伸阅读  166 第8 Guardian:一个容错操作系统环境  169 8.1 Tandem/16,将来所有的计算机都会像这样构建 170 8.2 硬件  170 8.3 物理布局  172 8.4 处理器架构  172 8.5 处理器间总线  178 8.6 输入/输出  178 8.7 进程结构  179 8.8 消息系统  179 8.9 文件系统  183 8.10 轶闻趣事  188 8.11 弊端  189 8.12 后继者  190 8.13 延伸阅读  191 第9 JPC:一个纯Java的x86 PC模拟程序  193 9.1 简介  193 9.2 概念验证  195 9.3 PC架构  198 9.4 Java性能技巧  199 9.5 把4GB放入4GB:这不起作用  200 9.6 保护模式的危险  203 9.7 从事一项毫无成功希望的斗争  206 9.8 劫持JVM  210 9.9 终极灵活性  220 9.10 终极安全性  222 9.11 第二次做会更好  223 第10 元循环虚拟机的力量:Jikes RVM  225 10.1 背景  225 10.2 与运行时环境相关的传言  227 10.3 Jikes RVM简史  229 10.4 一个自足执行的运行时自举  230 10.5 运行时组件  234 10.6 经验教训  246 参考文献  247 第四部分 最终用户应用架构 第11 GNU Emacs:滋长的特性是其优势  251 11.1 使用中的Emacs  252 11.2 Emacs的架构  254 11.3 滋长的特性  260 11.4 另外两个架构  262 第12 当集市开始构建教堂  267 12.1 简介  267 12.2 KDE项目的历史和组织结构  269 12.3 Akonadi  274 12.4 ThreadWeaver  289 第五部分 语言与架构 第13 软件架构:面向对象与面向函数  299 13.1 概述  299 13.2 函数式示例  302 13.3 函数式解决方案的模块性评价  305 13.4 面向对象视图  313 13.5 面向对象模块性的评价和改进  319 13.6 代理:将操作封装到对象中  323 致谢 328 参考文献 328 第14 重读经典  331 14.1 所有东西都是对象  335 14.2 类型是隐式定义的  342 14.3 问题  348 14.4 砖块和灰浆建筑架构  352 参考资料  359 跋 漂亮地构建 363

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值