《架构整洁之道》
概述
第 1 章 设计与架构究竟是什么
- 软件架构的终极目标:用最小的人力成本来满足构建和维护系统的需求。
- 乱麻系统
- 一般没有经过设计、匆忙构建起来
- 随着每次发布生产力直线下降
- 无论短期还是长期来看,胡乱编写代码的工作速度其实比循规蹈矩更慢
第 2 章 两个价值维度
行为和架构两个维度来体现价值
行为价值
- 系统按照指定方式允许,给使用者创造利润
架构价值
- 软件系统必须保持灵活
艾森豪威尔矩阵
我有两种难题:紧急的和重要的,而紧急的难题永远是不重要的,重要的难题永远是不紧急的 - 行为价值:紧急但不重要
- 架构价值:重要但不紧急
- 业务部门没有能力评估系统架构的重要程度,这本是研发人员的工作职责
编程范式
- 第 3 章 编程范式
- 程序的编写模式
- 结构化编程
- 对程序控制权的直接转移进行了限制和规范
- 面向对象编程
- 对程序控制权的间接转移进行了限制和规范
- 函数式编程
- 对程序重的赋值进行了限制和规范
- 软件架构三大关注点
- 功能性
- 组件独立性
- 数据管理
- 第 4 章 结构化编程
- 可推倒性
- goto 是有害的
- 功能性降解拆分
- 模块可以按功能进行降解拆分
- 科学证明法
- 只能证伪,不能证明
- 第 5 章 面向对象编程
- 封装、继承、多态
- 封装
- 将相关联的数据和函数封装在一起,让外部数据不可见
- 继承
- 多态
- 子类覆盖父类
- 面向对象编程就是以多态为手段对源代码中的依赖关系进行控制的能力,这种能力让架构师可以构建某种插件式架构,让高层决策性组件与底层实现性组件相分离 ,底层组件可以被编译成插件,实现独立于高层组件的开发和部署
- 第 6 章 函数式编程
- 函数式编程语言的变量是不可变的
- 事件溯源
- 只存储事务记录、不存储具体状态,需要状态时从头计算所有事务即可
设计原则
- 设计原则
- SRP 单一职责原则
- 每个模块都有且只有一个需要被改变的理由
- OCP 开闭原则
- 如果软件系统想要更容易被改变,那么设计必须允许新增代码来修改系统行为,而非只能靠修改原来的代码
- LSP 里氏替换原则
- 使用可替换的组件来构建软件系统,组件必须遵守同一个约定,以便可以互相替换
- ISP 接口隔离原则
- 在设计中避免不必要的依赖
- DIP 依赖反转原则
- 高层策略性的代码不应该依赖实现底层细节的代码
- 实现底层细节的代码应该依赖高层策略性的代码
- SRP 单一职责原则
- 第 7 章 SRP 单一职责原则
- 任何一个软件模块都应该只对某一类行为者负责
- 在两个讨论层面以不同形式出现
- 组件层面:共同闭包原则
- 软件架构层面:奠定架构边界的变更轴心
- 第 8 章 OCP 开闭原则
- 设计良好的计算机软件应该易于扩展,同时抗拒修改
- OCP 的目标是为了让系统易于扩展,同时限制起每次修改所影响的范围。
- 实现方式:通过将系统划分为一系列组件,并将组件间的依赖关系按层次结构进行组织,使高阶组件不会因低阶组件被修改而收到影响
- 第 9 章 LSP 里氏替换原则
- 模块、接口是可替换的
- 第 10 章 ISP 接口隔离原则
- 任何层次的软件设计如果依赖了它不需要的东西,就会带来意料之外的麻烦
- 第 11 章 DIP 依赖反转原则
- 想要设计一个灵活的系统,在源代码层次的依赖关系中就应该多引用抽象类型而非具体实现
- 主要应该针对那些经常会变动的具体实现模块
- 接口比实现稳定
- 想要在软件架构设计上追求稳定,就必须多使用稳定的抽象接口,少依赖多变的具体实现
- 几条具体的编码守则
- 应在代码中多使用抽象接口,尽量避免使用那些多变的具体实现类
- 不要再具体实现类上创建衍生类
- 不要覆盖包含具体实现的函数
- 应避免在代码中写入与任何具体实现相关的名字,或是其它容易变动的事物的名字
组件构建原则
第 12 章 组件
- 组件是软件的部署单元,是整个软件系统在部署过程中可以独立完成部署的最小实体
第 13 章 组件聚合
- 三个与构建组件相关的基本原则
- REP 复用/发布等同原则
- CCP 共同闭包原则
- CRP 共同复用原则
- 复用/发布等同原则
- 软件复用的最小粒度应等同于其发布的最小粒度
- 共同闭包原则
- 将那些会同时修改,并且为相同目的而修改的类放到同一个组件中,而将不会同时修改,并且不会为了相同目的而修改的那些类放到不同的组件中
- 与 SRP 原则的共通性
- 将由于相同原因而修改, 并且需要同时修改的东西放在一起。将由于不同原因而修改,并且不同时修改的东西分开。
- 共同复用原则
- 不要强迫一个组件的用户依赖他们不需要的东西
- 与 ISP 原则的共通性
- 不要依赖不需要用到的东西
- 组件聚合张力图
- 三个原则之间彼此存在竞争关系
- REP 和 CCP 原则是粘合性原则,会让组件变得更大,而 CRP 原则是排除性原则,它会尽量让组件变小。软件架构师需要在三个原则中进行取舍
- 项目的组件结构设计重心会随着项目的开发时间和成熟度不断变化,如早期可能会牺牲复用性,提高开发速度
- 三个与构建组件相关的基本原则
第 14 章 组件耦合
- 无依赖环原则
- 组件依赖关系图中不应该出现环
- 自上而下的设计
- 组件结构图不可能自上而下被设计出来。必须随着软件系统的变化而变化和扩张,不可能在系统构建最初就被完美设计出来。
- 稳定依赖原则
- 依赖关系必须要指向更稳定的方向
- 稳定性
- 稳定性指标
- Fan-in 入向依赖,指代组件外部类依赖于组件内部类的数量
- Fan-out 出向依赖,指代组件内部类依赖于外部类的数量
- I 不稳定性,I=Fan-out/(Fan-in + Fan-out),0 为最稳定,1 为最不稳定
- 稳定性指标
- 稳定抽象原则
- 一个组件的抽象化成都应该与其稳定性保持一致
- 抽象化程度
- Nc 组件中类的数量
- Na 组件中抽象类和接口的数量
- A 抽象程度 A=Na/Nc,0 为没有抽象类,1 为只有抽象类
- 主序列
- 痛苦区,难以修改
- 无用区,无限抽象没有被依赖
- 离主序列线的距离
- D 指标: 距离 D|A+I-1|,0 意味着组件是直接位于主序列线伤的,1 组意味距离主序列最远的位置
- 通过指标来量化与设计模式的契合度
- 无依赖环原则
软件架构
- 第 15 章 什么是软件架构
- 软件架构自身需要是程序员,并且必须一直坚持做一线程序员
- 软件架构这项工作的实质就是如何将系统切分成组件,并安排好组件之间的排列关系,以及组件之间互相通信的方式
- 设计软件架构的目的:为了在工作中更好的对这些组件进行研发、部署、运行以及维护
- 要设计一个便于推进各项工作的系统,策略就是在设计中尽可能长时间保留尽可能多的可选项
- 开发
- 软件架构的作用是方便开发团队对它的开发
- 部署
- 一键式轻松部署是软件架构的一个目标
- 运行
- 设计良好的软件架构应该能明确的反映该系统在运行时的需求
- 维护
- 降低维护成本,减少维护风险
- 保持可选性
- 基本所有的软件系统都可以将降解为策略与细节两种主要元素,策略体现的是软件中的所有业务规则与操作过程,细节则是那些让操作该系统的人、其他系统 已经开发者与策略进行交互,但是又不影响到策略本身的行为。
- 开发高层策略时摆脱具体细节的就唱,就可以将具体实现相关的细节决策推迟或延后。越到后期,就拥有越多的信息来做出合理的决策。
- 一个优秀的软件架构师应该致力于最大化可选性的数量
- 第 16 章 独立性
- 重复
- 如果两段同样的代码有着不同的演进路径(不同的变更速率和变更缘由),那么就不算是真正的重复
- 解耦
- 源码层次:控制源代码模块之间的依赖关系
- 部署层次:控制部署单元之间的依赖关系
- 服务层次:可以讲组件间的依赖关系降低到数据结构级别
- 重复
- 第 17 章 划分边界
- 插件式开发
- 第 18 章 边界剖析
- 构造合理的跨边界调用需要我们对源码中的依赖关系进行合理管控
- 部署层次
- 线程
- 本地进程
- 服务
- 第 19 章 策略与层次
- 底层组件应该成为高层组件的插件
- 第 20 章 业务逻辑
- 业务实体:包含了一系列用于操作关键数据的业务逻辑
- 用例:关于如何操作一个自动化系统的描述,定义了用户行为的处理步骤
- 业务逻辑应保持纯净,不掺杂技术相关内容
- 第 21 章 尖叫的软件架构
- 架构设计的主题
- 软件的系统架构应该为该系统的用例提供支持
- 架构设计不是与框架相关的
- 架构设计的核心目标
- 良好的架构设计应该围绕着用例来展开,并能将它们与其它的周边因素隔离
- Web
- Web 属于交付方式,不是架构
- 框架是工具
- 可测试的架构设计
- 架构设计的主题
- 第 22 章 整洁架构
- 第 23 章 展示器和谦卑对象
- 谦卑对象模式
- 设计目的为帮助单元测试编写者区分容易测试的行为和难以测试的行为,并加以隔离。
- 将两类行为拆分为两组模块或累,一组为谦卑组,包含难以测试的行为,行为已经简化到不能再简化。另一组为所有其它行为。
- 展示器与视图
- 视图部分属于难以测试的谦卑对象
- 展示器则是可测试的数据
- 强大的可测试性是一个架构的设计是否优秀的显著衡量标准之一
- 谦卑对象模式
- 第 24 章 不完全边界
- 设计架构边界成本高,预留不完全边界
- 省略多组件管理,降低工作量
- 第 25 章 层次与边界
- 过度的工程设计往往比设计不足还要糟糕
- 第 26 章 Main 组件
- 所有的系统中,都至少有一个组件来负责创建、协调、监督其它组件的运转,称为 Main 组件
- Main 组件可以被视为程序的一个插件
- 第 27 章 服务:宏观与微观
- 虽然服务可能有助于提升系统的可扩展和可研发性,但服务本身却并不能代表整个系统架构。
- 第 28 章 测试边界
- 测试是系统的一个重要组成部分
- 易变的测试是脆弱且难以维护的
- 第 29 章 整洁的嵌入式架构
实现细节
- 第 30 章 数据库只是实现细节
- 第 31 章 Web 是实现细节
- 第 32 章 应用程序框架是实现细节