时至今日,软件开发技术中最热闹的领域就是前端开发了,各种 xxxScript 语言,各种前端框架,以至于很长时间都没有再听过“面向对象”这种“古老”的词汇了。作为上了年纪的人,当有人问起什么是软件设计的时候,脑子里本能地就会出现 SRP、OCP、DIP 这样的东西。这些“古董”东西现在还有用吗?函数式编程都没有变量了,面向对象的封装还有学习的必要了吗?现在流行分布式系统,“架构师”们言必称分布式设计,在这种体系中各种抽象和接口设计的原则还用得上吗?
就在 SRP、LSP、OCP、DIP 这些词汇快要从记忆中 fade out 的时候,“Bob 大叔”突然跳出来提了一个“简洁架构”的概念,并写了一本新书《架构整洁之道》来“推销”他的“简洁架构”。这一次,他没有从正面解释什么是软件架构,而是不经意地提到了软件架构就是“用最小的人力成本来满足构建和维护系统的需求”。仔细想想,如今的分布式系统设计方法,无非是为了降低系统维护的成本,而之前面向对象系统的那些设计方法,不也是为了满足这句话吗?用什么瓶子装什么酒,全凭一句话,这“老狐狸”,不是一般的滑头啊。
“我所构建的各种软件系统千差万别,但是软件架构的规则却是相同的”,这是“Bob大叔”对软件设计的理解,也是他写这本书的原因。当然,“老年”读者看完这本书的感觉就是心不慌了,因为你所积累的软件设计的知识和原则,仍然适用,并且在不远的将来,还将继续适用。虽说这本书在内容上没有让人眼前一亮的新观点,但是也基本上涵盖了做软件设计需要考虑的方方面面的内容。这本书给我的感觉就是“睁着眼睛瞎说了很多大实话”,其实想想也容易理解,写这种软件设计内容的书,太深了大多数读者看不懂,太浅了被人质疑水平,一般人还真搞不定这种内容的书。但是头顶“Bob大叔”光环的 Robert C.Martin 能搞定,即使是老生常谈的内容,也能说得读者“心服口服”。
“石器时代的软件开发”衡量软件的价值,主要是从软件实现的功能来考虑的,也就是衡量软件的行为对需求的满足程度(行为价值),再加上健壮性之类的考量来评价软件的价值。但是对于大规模软件开发来说,软件的价值就不仅仅是功能的强弱了,软件的价值还包括软件的架构,也就是软件以何种结构被“设计”出来(架构价值)。很多情况下,软件的架构设计都是重要但是不紧急的事情,在头顶交付倒计时牌子的压力之下,完成功能开发的重要性常常因为紧急而被无限放大,于是架构设计就被“牺牲”了,很多人都明白这个道理,但是在关键时刻却无法站出来与这种“陋习”作斗争。这本书再次对这种行为进行了讽刺:“如果你觉得好的架构成本太高,那你可以试试选择差的架构加上返工重来的成本”。在这本书里,“Bob大叔”明确了“为好的架构而持续 Fight” 就是开发团队的责任,如果团队中有软件架构师职责的人,那就是他的责任。所以软件开发团队不要寄希望于别人大发慈悲允许自己做好的设计,也不要怨天尤人说业务部门的人没有给自己足够的时间做好设计,因为那是你自己的责任。如果软件的架构越来越烂,开发人员再也不要抱怨任何人了,那就是因为你没有 Fight 的结果。听听,多可怕!但是掷地有声,没毛病。开发人员自己都不努力,还有谁会关注软件架构的好坏?不过,“Bob大叔”可不仅仅是说说,在这本书的第 2 部分,他举了几个牺牲设计最终失败的悲惨例子,同时给出了在这些情况下为好的架构而持续 Fight 的方法,具有一定的参考价值。
本书的第 3 部分重温了软件设计的 “SOLID” 原则,看过《重构》这本书,但是还不能完全理解这五个基本原则的同学,在这一部分有机会深化对这几个原则的理解。这部分虽然很短,但是对五个原则有了新的阐述,并且都是具体的例子,结合《重构》这本书一起看,有奇效。
面向对象的软件设计到底是什么,怎么用一句话形容这个行为?很多人只能意会,无法言表,看完这本书,终于知道怎么说了,那就是两个控制:
- 分离系统中变与不变的内容,对变化的部分进行控制
- 分析系统中的各种依赖关系(对象、组件),对这些依赖关系进行控制
我们一直念念不忘的“高内聚,低耦合”原则,其实就是为了对依赖关系进行控制,还有“对接口编程,不要对实现编程”的原则,也是为了对变化的部分进行控制。那么,到底怎么进行控制?“Bob大叔”说了,就是“用多态的手段 ... 进行控制”。就是这么简单,那么多“专家”,“大师”们端着,作着,就是不说清楚,于是,“Bob大叔”说了,语言如此直白,你还没有被感动的热泪盈眶吗?
如果说“SOLID”原则太过于关注设计实现的细节,那么本书的第 4 部分和第 5 部分则从更大的维度上关注软件架构的实现方法。第 4 部分介绍了组件的构建原则,分别对组件之间的聚合和耦合两类问题进行了分析,阐述了针对这两个问题的应对策略和原则。组件技术出现的初衷是为了解决软件复用的问题,要想实现组件级别的复用,也有一些原则需要遵守。代码级别的复用需要考虑的是代码的职责尽量单一(SRP 原则),依赖尽量简单(ISP 原则),类似的,SRP 原则应用到组件复用上就是 CCP 原则(共同闭包原则)。这个原则说的就是应该将那些会同时修改,并且是因为相同的目的才会修改的类放在同一个组件中,言外之意就是不相关的东西不要放在一个组件中。这很容易理解,如果因为某个功能需要对代码进行变更,那么这些变更最好体现在一个组件中,而不是同时变更很多个组件。ISP 原则要求不要依赖不需要的东西,组件也一样,CRP 原则(共同复用原则)同样强调“不要强迫一个组件的用户依赖他们不需要的东西”。很显然,组件如果满足 CRP 原则,使用这个组件的用户自然就可以避免依赖他们不需要的东西。
对于组件之间的耦合关系,最常见的就是依赖循环,打破这种循环,DIP 原则依然适用。“Bob大叔”还提出了“稳定依赖原则(SDP)”和“稳定抽象原则(SAP)”。所谓“稳定依赖原则”,就是组件之间的依赖关系必需指向更稳定的方向,通俗地讲,就是一个组件不能依赖一个比它还容易发生变化的组件。那么问题来了,怎样衡量组件的稳定性?“Bob大叔”居然整了一个计算公式来计算组件容易变化的程度:
Fan_in 是入向依赖,代表组件外部的类依赖组件内部的类的数量,Fan_out 是出向依赖,代表组件内部的类依赖组件外部的类的数量,I 是不稳定性,范围是 [0,1],I 越大标识这个组件越不稳定。简单理解就是组件对外依赖越多,说明这个组件越不稳定,因为外部的变化更容易影响到它。
SDP 原则都用到数学公式了,SAP 原则自然也不是省油的灯。 SAP 原则描述起来有点抽象:“一个组件的抽象化程度应该与其稳定性保持一致”,通俗地讲,一个组件如果对外依赖太多,不稳定,就不要把自己设计的太抽象。稳定的组件应该是抽象的,这样他的稳定性不会影响可扩展性。不稳定的组件如果早早地定出一堆抽象接口,一旦功能扩展要变更,少不了要修改其中的某些抽象接口。而一旦确定要改接口,会影响所有使用这些接口的组件,更不用说会出现今天改一下,明天又要改一下的糟糕局面。那么怎样衡量一个组件的抽象化程度呢?公式来了:
Na 是组件中抽象类和接口的数量之和,Nc 是组件中类的数量(包括抽象类、接口类和普通类),A 是抽象程度。
根据这两个原则得到的 I 和 A 的关系,可以推导出这样一个图,位于 (0, 0) 附近的组件,通常稳定且实现具体(不够抽象),设计不佳,难以扩展。位于(1, 1)附近的组件无限抽象,但是没有被其他组件依赖,常常意味着这些组件包含了大量无用的代码。
位于主序列线附近的组件,一般被认为是在抽象和变化之间取得比较好的平衡,通常一个大型系统中的组件不可能都做得很完美,如果大部分能贴近这条线就非常不错了。
曾几何时,“软件架构师”成了一个高大上的职业,人人都想成为软件架构师。出去参加技术交流,交换回来一堆名片,title 几乎都是“软件架构师”。现如今,很少再看见这样的 title 了,大部分人的名片上印的都是“高级程序员”,或者是“资深软件工程师”。是软件架构不重要了吗?我看不是,这恰恰是狂热之后的理性回归,“软件架构”重新成为程序员的一个基本属性。在本书的第 5 部分,“Bob大叔”对什么是软件架构又“老调重弹”了一把,说什么“... 软件架构师其实应该是能力强的一群程序员...”,还有“... 软件架构的质量和系统能否正常工作关系不大 ... 真正的麻烦在系统的开发、部署和后续的补充开发中 ...”。这些大实话看看就行了,重要的是后面二十几个章节,从软件设计、开发、部署的方方面面阐述“软件架构”需要考虑的内容和一些可实施的方法。比如,《独立性》一章介绍了独立的重要性和软件解耦的各种模式。再比如,《划分边界》一章介绍了“划分边界”的艺术,决定哪些做,哪些不做,哪些在这里做,哪些在那里做,以及边界的通信方式。这些章节的内容都是软件架构中必需要考虑的各种问题和决策,需要读者们自己去体会了。
正如“Bob 大叔”所言,“我过去 50 年学到的东西主要是什么不应该做”,同样,对于软件架构,我们可以从足够多的“坏”的设计中学会什么不应该做。好的架构可能有争论,因为各有各的好处,但是不好的架构是确定的,不会有争论,毕竟还是有普世价值的。
转载自:再读《架构整洁之道》