1. 软件设计中的坏味道(对应原书第7章)
核心理解:
- 抽象不足:业务代码中掺杂过多细节,导致代码僵化、脆弱、难以理解和维护。
- 业务逻辑应调用功能,而非实现功能:业务逻辑应通过调用功能接口来完成,而不是直接实现功能细节。
- 功能抽象为接口,业务逻辑组合接口:功能应抽象为接口,业务逻辑通过组合和调度这些接口来实现。
- 接口可扩展但不可修改:接口应保持稳定,允许扩展但不允许修改。
- 功能实现可变,接口不可变:功能的实现可以灵活变化,但接口应保持稳定。
- 优先调用库而非原始实现:尽量使用通用库(如STL、Boost),避免重复造轮子。
坏味道分类:
- 僵化性:设计难以修改,单一改动可能引发依赖模块的连锁反应。
- 表现:实际工作量超出预期,未预料到的关联改动。
- 脆弱性:设计易于破坏,一个改动可能导致多个地方出现问题。
- 顽固性:设计难以重用,重用部分需要巨大努力和风险。
- 粘滞性:难以做正确的事情,保持设计的方法比临时拼凑更难。
- 表现:编译时间长、源代码控制系统效率低。
- 不必要的复杂性:过度设计,包含当前无用的组成部分。
- 不必要的重复:滥用复制粘贴,忽视抽象。
- 晦涩性:模块难以理解,表达混乱。
2. 软件设计中的原则(对应原书第8、9、10、11、12、28章)
2.1 设计原则概览
序号 | 原则名称 | 缩写 | 英文全称 | 核心描述 | 对应设计模式 |
---|---|---|---|---|---|
1 | 单一职责原则 | SRP | The Single Responsibility Principle | 一个类(或接口)应只有一个发生变化的原因。 | 适配器模式(Adapter Pattern)、代理模式(Proxy Pattern) |
2 | 开放-封闭原则 | OCP | The Open-Close Principle | 软件实体(类、模块、函数等)应可扩展但不可修改。 | 策略模式(Strategy Pattern)、装饰者模式(Decorator Pattern)、观察者模式(Observer Pattern) |
3 | Liskov替换原则 | LSP | The Liskov Substitution Principle | 子类型必须能够替换其基类型。 | 工厂模式(Factory Pattern)、模板方法模式(Template Method Pattern) |
4 | 依赖倒置原则 | DIP | The Dependency-Inversion Principle | 高层模块不应依赖低层模块,二者都应依赖抽象。 | 依赖注入(Dependency Injection)、服务定位器模式(Service Locator Pattern) |
5 | 接口隔离原则 | ISP | The Interface Segregation Principle | 为用户提供专用接口,避免强迫其依赖不需要的方法。 | 适配器模式(Adapter Pattern)、代理模式(Proxy Pattern)、装饰者模式(Decorator Pattern) |
2.2 设计原则的价值与代价
序号 | 原则 | 违反的坏处 | 遵循的好处 | 代价 |
---|---|---|---|---|
1 | 单一职责原则 | 代码复杂性增加、难以复用、高耦合性、测试困难 | 提高可维护性、增强可复用性、降低耦合性、简化测试 | 增加类的数量、初始设计复杂性、管理和组织难度、潜在的性能开销 |
2 | 开放-封闭原则 | 高风险修改、难以维护、影响其他功能 | 提高稳定性、增强灵活性、提高可维护性 | 设计复杂性增加、初始开发成本增加、可能的性能开销 |
3 | Liskov替换原则 | 程序不稳定、难以维护、降低复用性 | 提高代码的灵活性和复用性、增强系统的稳定性、简化代码维护 | 设计复杂性增加、可能需要更多的抽象、初始开发成本增加 |
4 | 依赖倒置原则 | 高耦合、难以测试、降低灵活性 | 降低耦合度、提高可测试性、增强可扩展性 | 设计复杂性增加、初始开发成本增加、可能的性能开销 |
5 | 接口隔离原则 | 臃肿的接口、难以维护、降低灵活性 | 提高灵活性、增强可维护性、提高代码的可理解性 | 增加设计复杂性、初始开发成本增加、可能的代码冗余 |
个人理解:
- 抽象带来自由:通过抽象隐藏实现细节,编写者和调用者都能获得更大的灵活性。
- 抽象不足的代价:抽象不足会导致编写者和调用者都失去自由,代码难以维护和扩展。
- 隐藏实现细节:将实现隐藏在接口后,编写者可以自由修改实现,调用者无需引入不必要的细节。
2.3 包和组件的设计原则
序号 | 分离 | 概述 | 原则名称 | 缩写 | 英文全称 | 描述 |
---|---|---|---|---|---|---|
1 | 内聚性原则 | 帮助开发者决定如何将类划分到组件 | 重用-发布等价原则 | REP | Reuse-Release Equivalence Principle | 重用的粒度就是发布的粒度 |
2 | 共同重用原则 | CRP | Common-Reuse Principle | 一个组件中的所有类应共同重用 | ||
3 | 共同封闭原则 | CCP | Common-Closure Principle | 组件中的所有类应对同一种变化共同封闭 | ||
4 | 组件耦合性原则 | 处理组件之间的关系,平衡可开发性和逻辑设计 | 无环依赖原则 | ADP | Acyclic-Dependencies Principle | 组件之间的依赖关系不应形成循环 |
5 | 稳定依赖原则 | SIP | Stable-Dependencies Principle | 依赖应朝向稳定的方向 | ||
6 | 稳定抽象原则 | SAP | Stable-Abstractions Principle | 组件的抽象程度应与其稳定程度一致 |
3. 开发中的经验与教训
- 避免重复犯错:将正确的代码封装为通用函数,避免重复错误。例如,
StrTrim
函数可以封装为通用工具函数。 - 优先使用通用库:尽量使用通用库(如STL、Boost),避免重复造轮子。如果通用库中没有所需功能,可以自行开发通用库。
4. 总结
- 抽象与接口的重要性:通过抽象和接口设计,可以显著提高代码的灵活性、可维护性和可复用性。
- 设计原则的权衡:虽然遵循设计原则会增加初始设计的复杂性,但长远来看,能够显著提升代码质量和开发效率。
- 识别与避免坏味道:通过识别和避免软件设计中的坏味道,可以有效提升代码的可读性和可维护性,降低后期维护成本。