架构整洁之道

罗伯特·C·马丁

推荐序一

  • 而且,这些工程师还渐渐发现,每当引入一个新的技术来解决一个已有的问题时,这个新的技术就会带来更多的问题,问题就像有一个生命体一样,它们会不断地繁殖和进化。渐渐地,他们发现,问题的多少和系统的复杂度呈正比,而且不仅是线性正比,还可能呈级数正比,此时就越来越难做技术决定。
  • 《架构整洁之道》大体分成三个部分:编程范式(结构化编程、面向对象编程和函数式编程),设计原则(主要是SOLID),以及软件架构(其中讲了很多高屋建翎的内容)。
  • 这本书是架构方面的入门级读物,但也并不适合经验不足的人员学习,这本书更适合的读者群是,有3~5年编程经验、需要入门软件设计和架构的工程师或程序员。
  • 、平衡vs.妥协、迭代vs.半成品。如果你不能很

推荐序二

  • 运用SOC(关注点分离)、SRP(单一职责原则)、OCP(开闭原则)
  • 所谓架构就是“用最小的人力成本来满足构建和维护系统需求”的设计行为
  • 如果又要保证操作原子性又要能精确还原各时刻的状态,有个办法是这样的:只提供CR操作,而不提供完整的CRUD操作(就像MySQL的binlog那样)。平时只要追加操作记录即可,各时刻的状态永远通过重放之前的操作记录得出,这样就彻底避免了状态的错乱

序言

  • 软件架构学关注的的一个重点是组织结构(structure)
  • 所以,当讨论软件架构时,要特别注意软件项目是具有递归(recursive)和分形(fractal)特点的,最终都要由一行行的代码组成。
  • 比例模型是深入人心的展示方式,但是不管某个PowerPoint图表中的彩色方块多么好看,多么简单易懂,它也无法完全代表一个软件的架构。它只能是该软件的架构的一个视图,而非全部。
  • CPU速度和网络带宽往往在很大程度上决定了系统的性能,而内存和存储空间的大小也会大幅影响代码的设计野心。
  • 一个好的架构,不仅要在某一特定时刻满足软件用户、开发者和所有者的需求,更要在一段时间内持续满足他们的后续需求。

前言

  • 软件架构的规则是相同的!
  • 软件架构的规则其实就是排列组合代码块的规则

第1部分 概述

  • 采用好的软件架构可以大大节省软件项目构建与维护的人力成本。

第1章 设计与架构究竟是什么

  • 本书的一个重要目标就是要清晰、明确地对二者进行定义。首先我要明确地说,二者没有任何区别。一丁点区别都没有!
  • 软件设计也是如此。底层设计细节和高层架构信息是不可分割的。它们组合在一起,共同定义了整个软件系统,缺一不可。所谓的底层和高层本身就是一系列决策组成的连续体,并没有清晰的分界线。
  • 软件架构的终极目标是,用最小的人力成本来满足构建和维护该系统的需求。
  • 工程师的大部分时间都消耗在对现有系统的修修补补上,而不是真正完成实际的新功能。
  • 最开始的十几万美元工资给公司带来了很多新功能、新收益,而最后的2千万美元几乎全打了水漂。应立刻采取行动解决这个问题,刻不容缓。
  • 1.慢但是稳,是成功的秘诀。2.该比赛并不是拼谁开始跑得快,也不是拼谁更有力气的。3.心态越急,反而跑得越慢。
  • 但是他们真正偷懒的地方在于——持续低估那些好的、良好设计的、整洁的代码的重要性。
  • 这些工程师们普遍用一句话来欺骗自己:“我们可以未来再重构代码,产品上线最重要!”但是结果大家都知道,产品上线以后重构工作就再没人提起了。
  • 无论是从短期还是长期来看,胡乱编写代码的工作速度其实比循规蹈矩更慢。
  • 测试驱动开发(TDD)
  • 要想跑得快,先要跑得稳。
  • 过度自信只会使得重构设计陷入和原项目一样的困局中。
  • 不管怎么看,研发团队最好的选择是清晰地认识并避开工程师们过度自信的特点,开始认真地对待自己的代码架构,对其质量负责。

第2章 两个价值维度

  • 对于每个软件系统,我们都可以通过行为和架构两个维度来体现它的实际价值。
  • 软件系统的行为是其最直观的价值维度。程序员的工作就是让机器按照某种指定方式运转,给系统的使用者创造或者提高利润。
  • 软件系统必须保持灵活。软件发明的目的,就是让我们可以以一种灵活的方式来改变机器的工作行为。对机器上那些很难改变的工作行为,我们通常称之为硬件(hardware)。
  • 变更实施的难度应该和变更的范畴(scope)成等比关系,而与变更的具体形状(shape)无关。
  • 需求变更的范畴与形状,是决定对应软件变更实施成本高低的关键
  • 如果系统的架构设计偏向某种特定的“形状”,那么新的变更就会越来越难以实施。所以,好的系统架构设计应该尽可能做到与“形状”无关。
  • 完成现在的功能比实现未来的灵活度更重要。
  • 我有两种难题:紧急的和重要的,而紧急的难题永远是不重要的,重要的难题永远是不紧急的。
  • 软件系统的第一个价值维度:系统行为,是紧急的,但是并不总是特别重要。软件系统的第二个价值维度:系统架构,是重要的,但是并不总是特别紧急。
  • 1.重要且紧急 2.重要不紧急 3.不重要但紧急 4.不重要且不紧急
  • 但研发人员还忘了一点,那就是业务部门原本就是没有能力评估系统架构的重要程度的,这本来就应该是研发人员自己的工作职责!所以,平衡系统架构的重要性与功能的紧急程度这件事,是软件研发人员自己的职责。
  • 软件架构师这一职责本身就应更关注系统的整体结构,而不是具体的功能和系统行为的实现。软件架构师必须创建出一个可以让功能实现起来更容易、修改起来更简单、扩展起来更轻松的软件架构。
  • 请记住:如果忽视软件架构的价值,系统将会变得越来越难以维护,终会有一天,系统将会变得再也无法修改。如果系统变成了这个样子,那么说明软件开发团队没有和需求方做足够的抗争,没有完成自己应尽的职责。

第2部分 从基础构件开始:编程范式

  • 编程范式指的是程序的编写模式,与具体的编程语言关系相对较小。这些范式会告诉你应该在什么时候采用什么样的代码结构。

第3章 编程范式总览

  • 本章将讲述三个编程范式,它们分别是结构化编程(structured programming)、面向对象编程(object-oriented programming)以及函数式编程(functional programming)。
  • 结构化编程对程序控制权的直接转移进行了限制和规范。
  • 面向对象编程对程序控制权的间接转移进行了限制和规范。
  • 函数式编程对程序中的赋值进行了限制和规范。
  • 每个编程范式的目的都是设置限制。这些范式主要是为了告诉我们不能做什么,而不是可以做什么。
  • 这三个编程范式分别限制了goto语句、函数指针和赋值语句的使用。
  • 这和软件架构的三大关注重点不谋而合:功能性、组件独立性以及数据管理。

第4章 结构化编程

  • 程序员可以用代码将一些已证明可用的结构串联起来,只要自行证明这些额外代码是正确的,就可以推导出整个程序的正确性。
  • goto语句的某些用法会导致某个模块无法被递归拆分成更小的、可证明的单元,这会导致无法采用分解法来将大型问题进一步拆分成更小的、可证明的部分。
  • goto语句的其他用法虽然不会导致这种问题,但是Dijkstra意识到它们的实际效果其实和更简单的分支结构if-then-else以及循环结构do-while是一致的。如果代码中只采用了这两类控制结构,则一定可以将程序分解成更小的、可证明的单元。
  • 可以用顺序结构、分支结构、循环结构这三种结构构造出任何程序。
  • 这就是科学理论和科学定律的特点:它们可以被证伪,但是没有办法被证明。
  • 最终,我们可以说数学是要将可证明的结论证明,而与之相反,科学研究则是要将可证明的结论证伪。
  • 测试只能展示Bug的存在,并不能证明不存在Bug
  • 结构化编程范式中最有价值的地方就是,它赋予了我们创造可证伪程序单元的能力。
  • 软件架构师需要定义可以方便地进行证伪(测试)的模块、组件以及服务。为了达到这个目的,他们需要将类似结构化编程的限制方法应用在更高的层面上。

第5章 面向对象编程

  • 面向对象理论是在1966年提出的,当时Dahl和Nygaard主要是将函数调用栈迁移到了堆区域中。
  • 继承的主要作用是让我们可以在某个作用域内对外部定义的某一组变量与函数进行覆盖。
  • 虽然面向对象编程在继承性方面并没有开创出新,但是的确在数据结构的伪装性上提供了相当程度的便利性。
  • 归根结底,多态其实不过就是函数指针的一种应用。
  • 我们认为面向对象编程其实是对程序间接控制权的转移进行了约束。
  • 系统行为决定了控制流,而控制流则决定了源代码依赖关系。
  • 请注意模块ML1和接口I在源代码上的依赖关系(或者叫继承关系),该关系的方向和控制流正好是相反的,我们称之为依赖反转
  • 通过这种方法,软件架构师可以完全控制采用了面向对象这种编程方式的系统中所有的源代码依赖关系,而不再受到系统控制流的限制。不管哪个模块调用或者被调用,软件架构师都可以随意更改源代码依赖关系。
  • 当某个组件的源代码需要修改时,仅仅需要重新部署该组件,不需要更改其他组件,这就是独立部署能力
  • 面向对象编程就是以多态为手段来对源代码中的依赖关系进行控制的能力,这种能力让软件架构师可以构建出某种插件式架构,让高层策略性组件与底层实现性组件相分离,底层组件可以被编译成插件,实现独立于高层组件的开发和部署。

第6章 函数式编程

  • 函数式编程语言中的变量(Variable)是不可变(Vary)的。
  • 所有的竞争问题、死锁问题、并发更新问题都是由可变变量导致的。如果变量永远不会被更改,那就不可能产生竞争或者并发更新问题。如果锁状态是不可变的,那就永远不会产生死锁问题。
  • 一种常见方式是将应用程序,或者是应用程序的内部服务进行切分,划分为可变的和不可变的两种组件。
  • 一个架构设计良好的应用程序应该将状态修改的部分和不需要修改状态的部分隔离成单独的组件,然后用合适的机制来保护可变量。
  • 而内存越大,处理速度越快,我们对可变状态的依赖就会越少。
  • 这就是事件溯源[10],在这种体系下,我们只存储事务记录,不存储具体状态。当需要具体状态时,我们只要从头开始计算所有的事务即可
  • 更重要的是,这种数据存储模式中不存在删除和更新的情况,我们的应用程序不是CRUD,而是CR。因为更新和删除这两种操作都不存在了,自然也就不存在并发问题。
  • 结构化编程是对程序控制权的直接转移的限制。面向对象编程是对程序控制权的间接转移的限制。函数式编程是对程序中赋值操作的限制。

第3部分 设计原则

  • SOLID原则的主要作用就是告诉我们如何将数据和函数组织成为类,以及如何将这些类链接起来成为程序
  • 使软件可容忍被改动。使软件更容易被理解。构建可在多个软件系统中复用的组件
  • SOLID原则应该直接紧贴于具体的代码逻辑之上,这些原则是用来帮助我们定义软件架构中的组件和模块的。
  • 如果软件系统想要更容易被改变,那么其设计就必须允许新增代码来修改系统行为,而非只能靠修改原来的代码。

第7章 SRP:单一职责原则

  • 任何一个软件模块都应该有且仅有一个被修改的原因。
  • 任何一个软件模块都应该只对一个用户(User)或系统利益相关者(Stakeholder)负责。
  • 任何一个软件模块都应该只对某一类行为者负责。
  • “软件模块”指的就是一组紧密相关的函数和数据结构。
  • 多人为了不同的目的修改了同一份源代码,这很容易造成问题的产生。

第8章 OCP:开闭原则

  • 一个设计良好的计算机系统应该在不需要修改的前提下就可以轻易被扩展。
  • 一个好的软件架构设计师会努力将旧代码的修改需求量降至最小,甚至为0。
  • 如果A组件不想被B组件上发生的修改所影响,那么就应该让B组件依赖于A组件。
  • 软件架构师可以根据相关函数被修改的原因、修改的方式及修改的时间来对其进行分组隔离,并将这些互相隔离的函数分组整理成组件结构,使得高阶组件不会因低阶组件被修改而受到影响。
  • 软件系统不应该依赖其不直接使用的组件
  • OCP是我们进行系统架构设计的主导原则,其主要目标是让系统易于扩展,同时限制其每次被修改所影响的范围

第9章 LSP:里氏替换原则

  • LSP逐渐演变成了一种更广泛的、指导接口与其实现方式的设计原则。

第10章 ISP:接口隔离原则

  • 在一般情况下,任何层次的软件设计如果依赖于不需要的东西,都会是有害的。
  • 任何层次的软件设计如果依赖了它并不需要的东西,就会带来意料之外的麻烦。

第11章 DIP:依赖反转原则

  • 如果想要设计一个灵活的系统,在源代码层次的依赖关系中就应该多引用抽象类型,而非具体实现。
  • 我们主要应该关注的是软件系统内部那些会经常变动的(volatile)具体实现模块,这些模块是不停开发的,也就会经常出现变更。
  • 应在代码中多使用抽象接口,尽量避免使用那些多变的具体实现类。
  • 应避免在代码中写入与任何具体实现相关的名字,或者是其他容易变动的事物的名字。
  • 抽象接口组件中包含了应用的所有高阶业务规则,而具体实现组件中则包括了所有这些业务规则所需要做的具体操作及其相关的细节信息。
  • 源代码依赖方向永远是控制流方向的反转——这就是DIP被称为依赖反转原则的原因。

第4部分 组件构建原则

  • 如果说SOLID原则是用于指导我们如何将砖块砌成墙与房间的,那么组件构建原则就是用来指导我们如何将这些房间组合成房子的。

第12章 组件

  • 组件是软件的部署单元,是整个软件系统在部署过程中可以独立完成部署的最小实体。
  • 设计良好的组件都应该永远保持可被独立部署的特性,这同时也意味着这些组件应该可以被单独开发。
  • 程序的规模会一直不断地增长下去,直到将有限的编译和链接时间填满为止。
  • 由于计算与存储速度的大幅提高,我们又可以在加载过程中进行实时链接了,链接几个.jar文件或是共享库文件通常只需要几秒钟时间,由此,插件化架构也就随之诞生了。

第13章 组件聚合

  • REP:复用/发布等同原则。CCP:共同闭包原则。CRP:共同复用原则。
  • 软件复用的最小粒度应等同于其发布的最小粒度。
  • REP原则就是指组件中的类与模块必须是彼此紧密相关的
  • 我们应该将那些会同时修改,并且为相同目的而修改的类放到同一个组件中,而将不会同时修改,并且不会为了相同目的而修改的那些类放到不同的组件中。
  • 对大部分应用程序来说,可维护性的重要性要远远高于可复用性
  • CCP的主要作用就是提示我们要将所有可能会被一起修改的类集中在一处
  • 将由于相同原因而修改,并且需要同时修改的东西放在一起。将由于不同原因而修改,并且不同时修改的东西分开。
  • 不要强迫一个组件的用户依赖他们不需要的东西。
  • 该原则建议我们将经常共同复用的类和模块放在同一个组件中。
  • 不是紧密相连的类不应该被放在同一个组件里。
  • 不要依赖不需要用到的东西。
  • REP和CCP原则是黏合性原则,它们会让组件变得更大,而CRP原则是排除性原则,它会尽量让组件变小

第14章 组件耦合

  • 组件依赖关系图中不应该出现环。
  • 1.应用依赖反转原则(DIP)
  • 组件结构图是不可能自上而下被设计出来的。它必须随着软件系统的变化而变化和扩张,而不可能在系统构建的最初就被完美设计出来。
  • 组件依赖结构图并不是用来描述应用程序功能的,它更像是应用程序在构建性与维护性方面的一张地图
  • 组件结构图中的一个重要目标是指导如何隔离频繁的变更。
  • 依赖关系必须要指向更稳定的方向。
  • 让软件组件难于修改的一个最直接的办法就是让很多其他组件依赖于它
  • Fan-in:入向依赖,这个指标指代了组件外部类依赖于组件内部类的数量。Fan-out:出向依赖,这个指标指代了组件内部类依赖于组件外部类的数量。I:不稳定性,I=Fan-out/(Fan-in+Fan-out)。该指标的范围是[0,1],I=0意味着组件是最稳定的,I=1意味着组件是最不稳定的。
  • 稳定依赖原则(SDP)的要求是让每个组件的I指标都必须大于其所依赖组件的I指标
  • 动态类型语言中的依赖关系是非常简单的,因为其依赖反转的过程并不需要声明和继承接口。
  • 一个组件的抽象化程度应该与其稳定性保持一致。
  • 如何才能让一个无限稳定的组件(I=0)接受变更呢?开闭原则(OCP)为我们提供了答案。这个原则告诉我们:创造一个足够灵活、能够被扩展,而且不需要修改的类是可能的,而这正是我们所需要的。哪一种类符合这个原则呢?答案是抽象类。

第5部分 软件架构

  • 软件架构师其实应该是能力最强的一群程序员,他们通常会在自身承接编程任务的同时,逐渐引导整个团队向一个能够最大化生产力的系统设计方向前进
  • 软件架构这项工作的实质就是规划如何将系统切分成组件,并安排好组件之间的排列关系,以及组件之间互相通信的方式
  • 真正的麻烦往往并不会在我们运行软件的过程中出现,而是会出现在这个软件系统的开发、部署以及后续的补充开发中。
  • 软件架构的终极目标就是最大化程序员的生产力,同时最小化系统的总运营成本。
  • 实现一键式的轻松部署应该是我们设计软件架构的一个目标。
  • 如果软件架构师早先就考虑到这些部署问题,可能就会有意地减少微服务的数量,采用进程内部组件与外部服务混合的架构,以及更加集成式的连接管理方式。
  • 设计良好的系统架构应该可以使开发人员对系统的运行过程一目了然
  • 系统维护的主要成本集中在“探秘”和“风险”这两件事上。其中,“探秘(spelunking)”的成本主要来自我们对于现有软件系统的挖掘,目的是确定新增功能或被修复问题的最佳位置和最佳方式。而“风险(risk)”,则是指当我们进行上述修改时,总是有可能衍生出新的问题,这种可能性就是风险成本。
  • 软件有行为价值与架构价值两种价值。这其中的第二种价值又比第一种更重要,因为它正是软件之所以“软”的原因。
  • 软件架构师的目标是创建一种系统形态,该形态会以策略为最基本的元素,并让细节与策略脱离关系,以允许在具体决策过程中推迟或延迟与细节相关的内容。
  • 如果在开发高层策略时有意地让自己摆脱具体细节的纠缠,我们就可以将与具体实现相关的细节决策推迟或延后,因为越到项目的后期,我们就拥有越多的信息来做出合理的决策。
  • 一个优秀的软件架构师应该致力于最大化可选项数量。
  • 优秀的架构师所设计的策略应该允许系统尽可能地推迟与实现细节相关的决策,越晚做决策越好。

第16章 独立性

  • 系统的用例与正常运行。系统的维护。系统的开发。系统的部署。
  • 一个设计良好的架构在行为上对系统最重要的作用就是明确和显式地反映系统设计意图的行为,使其在架构层面上可见。
  • 任何一个组织在设计系统时,往往都会复制出一个与该组织内沟通结构相同的系统。
  • 一个设计良好的架构应该通过保留可选项的方式,让系统在任何情况下都能方便地做出必要的变更。
  • UI界面、应用独有的业务逻辑、领域普适的业务逻辑、数据库等。
  • 如果我们按照变更原因的不同对系统进行解耦,就可以持续地向系统内添加新的用例,而不会影响旧有的用例
  • 如果有两段看起来重复的代码,它们走的是不同的演进路径,也就是说它们有着不同的变更速率和变更缘由,那么这两段代码就不是真正的重复。
  • 总之,我们一定要小心避免陷入对任何重复都要立即消除的应激反应模式中。一定要确保这些消除动作只针对那些真正意义上的重复
  • 通常,我会倾向于将系统的解耦推行到某种一旦有需要就可以随时转变为服务的程度即可,让整个程序尽量长时间地保持单体结构,以便给未来留下可选项。
  • 一个设计良好的架构应该能允许一个系统从单体结构开始,以单一文件的形式部署,然后逐渐成长为一组相互独立的可部署单元,甚至是独立的服务或者微服务。最后还能随着情况的变化,允许系统逐渐回退到单体结构。

第17章 划分边界

  • 软件架构设计本身就是一门划分边界的艺术。边界的作用是将软件分割成各种元素,以便约束边界两侧之间的依赖关系。
  • 那么我们就需要了解一个系统最消耗人力资源的是什么?答案是系统中存在的耦合——尤其是那些过早做出的、不成熟的决策所导致的耦合。
  • 数据库应该是业务逻辑间接使用的一个工具。业务逻辑并不需要了解数据库的表结构、查询语言或其他任何数据库内部的实现细节。业务逻辑唯一需要知道的,就是有一组可以用来查询和保存数据的函数。这样一来,我们才可以将数据库隐藏在接口后面。
  • 系统的核心业务逻辑必须和其他组件隔离,保持独立,而这些其他组件要么是可以去掉的,要么是有多种实现的
  • 为了在软件架构中画边界线,我们需要先将系统分割成组件,其中一部分是系统的核心业务逻辑组件,而另一部分则是与核心业务逻辑无关但负责提供必要功能的插件。然后通过对源代码的修改,让这些非核心组件依赖于系统的核心业务逻辑组件。

第19章 策略与层次

  • 本质上,所有的软件系统都是一组策略语句的集合。
  • 一条策略距离系统的输入/输出越远,它所属的层次就越高。而直接管理输入/输出的策略在系统中的层次是最低的。
  • 我们希望源码中的依赖关系与其数据流向脱钩,而与组件所在的层次挂钩。
  • 离输入/输出最远的策略——高层策略——一般变更没有那么频繁。即使发生变更,其原因也比低层策略所在的组件更重大。反之,低层策略则很有可能会频繁地进行一些小变更。

第20章 业务逻辑

  • 业务逻辑就是程序中那些真正用于赚钱或省钱的业务逻辑与过程。
  • 我们通常称这些逻辑为“关键业务逻辑”,因为它们是一项业务的关键部分,不管有没有自动化系统来执行这项业务,这一点是不会改变的。
  • 业务实体这个概念只要求我们将关键业务数据和关键业务逻辑绑定在一个独立的软件模块内。
  • 用例本质上就是关于如何操作一个自动化系统的描述,它定义了用户需要提供的输入数据、用户应该得到的输出信息以及产生输出所应该采取的处理步骤。
  • 用例并不描述系统与用户之间的接口,它只描述该应用在某些特定情景下的业务逻辑,这些业务逻辑所规范的是用户与业务实体之间的交互方式,它与数据流入/流出系统的方式无关。
  • 这些业务逻辑应该保持纯净,不要掺杂用户界面或者所使用的数据库相关的东西。在理想情况下,这部分代表业务逻辑的代码应该是整个系统的核心,其他低层概念的实现应该以插件形式接入系统中。业务逻辑应该是系统中最独立、复用性最高的代码。

第21章 尖叫的软件架构

  • 软件的系统架构应该为该系统的用例提供支持。这就像住宅和图书馆的建筑计划满篇都在非常明显地凸显这些建筑的用例一样,软件系统的架构设计图也应该非常明确地凸显该应用程序会有哪些用例。
  • 一个良好的架构设计应该围绕着用例来展开,这样的架构设计可以在脱离框架、工具以及使用环境的情况下完整地描述用例。
  • 良好的架构设计应该只关注用例,并能将它们与其他的周边因素隔离。
  • 一个系统的架构应该着重于展示系统本身的设计,而并非该系统所使用的框架

第22章 整洁架构

  • 它们都具有同一个设计目标:按照不同关注点对软件进行切割。也就是说,这些架构都会将软件切割成不同的层,至少有一层是只包含该软件的业务逻辑的,而用户接口、系统接口则属于其他层。
  • 基本上,外层圆代表的是机制,内层圆代表的是策略。
  • 源码中的依赖关系必须只指向同心圆的内层,即由低层机制指向高层策略。
  • 软件的接口适配器层中通常是一组数据转换器,它们负责将数据从对用例和业务实体而言最方便操作的格式,转化成外部系统(譬如数据库以及Web)最方便操作的格式。
  • 最内层的圆中包含的是最通用、最高层的策略,最外层的圆包含的是最具体的实现细节。

第23章 展示器和谦卑对象

  • 谦卑对象模式[11]最初的设计目的是帮助单元测试的编写者区分容易测试的行为与难以测试的行为,并将它们隔离
  • 强大的可测试性是一个架构的设计是否优秀的显著衡量标准之一
  • 因为跨边界的通信肯定需要用到某种简单的数据结构,而边界会自然而然地将系统分割成难以测试的部分与容易测试的部分,所以通过在系统的边界处运用谦卑对象模式,我们可以大幅地提高整个系统的可测试性。

第24章 不完全边界

  • 构建不完全边界的一种方式就是在将系统分割成一系列可以独立编译、独立部署的组件之后,再把它们构建成一个组件

第26章 Main组件

  • 在所有的系统中,都至少要有一个组件来负责创建、协调、监督其他组件的运转。我们将其称为Main组件。
  • Main组件的任务是创建所有的工厂类、策略类以及其他的全局设施,并最终将系统的控制权转交给最高抽象层的代码来处理。
  • 我们在这里的重点是要说明Main组件是整个系统中的一个底层模块,它处于整洁架构的最外圈,主要负责为系统加载所有必要的信息,然后再将控制权转交回系统的高层组件。

第27章 服务:宏观与微观

  • 所谓的服务本身只是一种比函数调用方式成本稍高的,分割应用程序行为的一种形式,与系统架构无关。
  • 任何形式的共享数据行为都会导致强耦合。
  • 系统的架构边界事实上并不落在服务之间,而是穿透所有服务,在服务内部以组件的形式存在。
  • 服务边界并不能代表系统的架构边界,服务内部的组件边界才是。
  • 系统的架构是由系统内部的架构边界,以及边界之间的依赖关系所定义的,与系统中各组件之间的调用和通信方式无关。

第28章 测试边界

  • 软件设计的第一条原则——不管是为了可测试性还是其他什么东西——是不变的,就是不要依赖于多变的东西。

第29章 整洁的嵌入式架构

  • 虽然软件质量本身并不会随时间推移而损耗,但是未妥善管理的硬件依赖和固件依赖却是软件的头号杀手。
  • 1.“先让代码工作起来”——如果代码不能工作,就不能产生价值。 2.“然后再试图将它变好”——通过对代码进行重构,让我们自己和其他人更好地理解代码,并能按照需求不断地修改代码。 3.“最后再试着让它运行得更快”——按照性能提升的“需求”来重构代码。
  • 在实践中学习正确的工作方法,然后再重写一个更好的版本
  • 软件与固件之间的边界被称为硬件抽象层(HAL)
  • 分层架构的理念是基于接口编程的理念来设计的。当模块之间能以接口形式交互时,我们就可以将一个服务替换成另外一个服务。

第32章 应用程序框架是实现细节

  • 框架作者想让我们与框架订终身——这相当于我们要对他们的框架做一个巨大而长期的承诺,而在任何情况下框架作者都不会对我们做出同样的承诺。这种婚姻是单向的。我们要承担所有的风险,而框架作者则没有任何风险。
  • 如果框架要求我们根据它们的基类来创建派生类,就请不要这样做!我们可以创造一些代理类,同时把这些代理类当作业务逻辑的插件来管理。

第33章 案例分析:视频销售网站

  • 系统架构设计中的第一步,是识别系统中的各种角色和用例。

第34章 拾遗

  • 组件是部署单元。组件是系统中能够部署的最小单位,对应在Java里就是jar文件。
  • 如果不考虑具体实现细节,再好的设计也无法长久。必须要将设计映射到对应的代码结构上,考虑如何组织代码树,以及在编译期和运行期采用哪种解耦合的模式。保持开放,但是一定要务实,同时要考虑到团队的大小、技术水平,以及对应的时间和预算限制。最好能利用编译器来维护所选的系统架构设计风格,小心防范来自其他地方的耦合模式,例如数据结构。所有的实现细节都是关键的!