读程序员的README笔记16_构建可演进的架构(上)

发布时间 2023-12-20 07:18:43作者: 躺柒

1. 行为准则

2. 需求的不确定性

2.1. 不断变化的客户需求

2.2. 软件项目无法避免的挑战

2.3. 产品需求和环境会随着时间的推移而改变,你的应用程序也必须随之改变

2.4. 不断变化的需求会导致不稳定性,使开发工作偏离轨道

2.5. 通过构建可演进的架构来适应不断变化的需求

2.5.1. 可演进的架构可避免复杂性,复杂性是演进性的敌人

2.5.2. 矛盾的是,在软件中实现简洁性会很困难

3. 复杂性

3.1. 复杂系统的特点

3.1.1. 高依赖性

3.1.1.1. 致软件依赖于其他的API或代码行为

3.1.1.2. 依赖性显然不可避免,甚至是可取的,但必须取得平衡

3.1.1.3. 高依赖性的系统很难修改

3.1.1.3.1. 它们有紧密的耦合性和高度的变更放大效应
3.1.1.3.2. 紧密的耦合性是指那些严重依赖彼此的模块,它们导致了更高的变更放大的倍率,即某项单一的变更也需要修改依赖关系

3.1.1.4. 深思熟虑的API设计和有节制地使用抽象模型将最大限度地降低紧耦合性和变更放大效应

3.1.2. 高隐蔽性

3.1.2.1. 使得程序员很难预测某项变更的副作用、代码的行为方式,以及需要修改的地方

3.1.2.2. 晦涩的代码需要更长的时间来学习,开发人员更有可能在无意中破坏某些东西

3.1.2.3. 症状

3.1.2.3.1. “知道”太多的对象
3.1.2.3.2. 鼓励副作用的全局状态
3.1.2.3.3. 掩盖代码的过度间接寻址
3.1.2.3.4. 影响程序远程行为的远距离操作

3.1.2.4. 采用具有明确的约定和标准模式的API可以减少隐蔽性

3.1.3. 高惯性

3.1.3.1. 指软件保持之前的使用习惯

3.1.3.2. 用于快速实验且很容易被丢弃的代码具有低惯性

3.1.3.3. 一项为十几个关键业务应用提供驱动力的服务具有高惯性

3.1.3.4. 复杂性的成本随着时间的推移而增加,所以高惯性、高变化的系统应该被简化,而低惯性或低变化的系统可以保持复杂(只要你抛弃它们或继续让它们保持不变)

3.2. 复杂性不总能被消除,但你可以选择把它放在哪里

3.2.1. 向后兼容的变更可能使代码使用起来更简单,实现起来却更复杂

3.2.2. 用于解耦子系统的间接层可降低依赖性,却会提高隐蔽性

4. 可演进的设计

4.1. 面对未来未知的需求策略

4.1.1. 试图预判未来的需求

4.1.2. 建立抽象模型作为逃生舱门,使后续的代码修改更容易

4.1.3. 都会导致复杂性提高

4.2. KISS的原则

4.2.1. 要保持事情简单一些

4.2.2. 使用KISS记忆法,记住要以简单性为核心原则构建系统

4.2.3. 简单的代码可以让你在未来增加系统的复杂性

4.3. 保持代码简单的最简单的方法之一是避免什么代码都要写出来

4.3.1. 告诉你自己,你不是真的需要(You ain’t gonna need it,YAGNI)

4.3.2. 要使用最小惊讶原则和封装原则

4.4. 不要构建你不需要的东西

4.4.1. 靠猜测而编写出来的代码会继续使事情陷入困境,它需要被维护,开发人员需要理解它,它必须被构建和测试

4.4.2. 避免过早优化,避免不必要的灵活抽象模型,以及避免最小可行产品(minimum viable product,MVP)所不需要的产品特性

4.4.3. 过早优化是指开发人员在证明需要优化之前就对代码进行性能优化

4.4.4. 灵活的抽象模型——插件式架构、封装接口和通用数据结构(如键值对)是另一种诱惑

4.4.4.1. 抽象自有代价:它把实现的代码框在僵硬的边界里,而开发者最终会与之抗争

4.4.4.2. 灵活性也使代码更难以阅读和理解

4.4.4.2.1. 保持代码灵活性的最佳方法之一是减少代码的总量
4.4.4.2.2. 蒙茨法(muntzing),将使你的软件保持“苗条”和适应性

4.4.5. MVP允许你先测试一个想法,而不必押宝在某项成熟的实施上

4.4.5.1. 在你怀疑可以插入优化的地方放置接口填充程序,但不要真正实现它们

4.5. 最小惊讶原则

4.5.1. 不要让用户感到惊讶,构建特性表现得要像用户最初期望的那样,具有上扬的学习曲线或奇怪表现的特性会使用户感到沮丧

4.5.2. 不要让开发者感到惊讶,令人惊讶的代码通常晦涩难懂,这会导致复杂性

4.5.3. 通过保持代码的针对性、避免隐性知识,以及使用标准类库和模式来消除惊讶

4.5.4. 凡是开发者在调用API时需要知道的但又不属于API本身的不明显的知识,都被视为隐性知识

4.5.5. 排序需求决定了某些动作必须按照某种特定的顺序进行

4.5.5.1. 使用文档来说明某些排序需求是种好办法,但最好是一开始就没有这个排序需求

4.5.6. 当一个方法的签名暗示了比该方法实际可以接受的有效输入范围更广时,就会出现隐藏的参数需求

4.5.7. 切记让参数需求具体化和可视化

4.5.7.1. 使用可准确适配约束的特定类型,当使用灵活的类型如JSON时,考虑使用JSON schema来描述预期的对象

4.5.8. 使用标准类库和开发模式

4.5.8.1. 请使用惯用的代码风格和开发模式

4.6. 封装专业领域知识

4.6.1. 将软件组件映射到业务领域,将使代码的变化保持专注和干净

4.6.1.1. 会计、计费、运输等

4.6.2. 封装的领域自然会倾向于高内聚和低耦合

4.6.2.1. 理想的特征

4.6.2.2. 高内聚和低耦合的软件更容易演进,因为变更的“爆炸半径”往往更小

4.6.2.3. 解耦的代码是自成一体的,对其逻辑的改变不需要对其他软件组件也进行改变

4.6.3. 开发人员经常以“层”为单位来思考软件:前端、中间层和后端

4.6.3.1. 每一层都有独立的团队,会增加协调成本,因为每一项业务逻辑的变化都会影响到所有的软件分层

4.6.4. 识别领域边界和封装领域知识既是一门科学又是一门艺术

4.6.4.1. 领域驱动设计(domain-driven design,DDD),它定义了一套广泛的概念和实践,将商业概念映射到软件上

4.6.4.2. 只有在最复杂的情况下才需要全覆盖的DDD

4.6.4.3. 熟悉DDD将有助于你做出更好的设计决策