鉴于最近又写出大量低质量代码, 所以需要重新回顾一下这些有用的编码哲学.
主要参照 <一些软件设计的原则> – 酷壳, 做一下简单理解的笔记.
1. 一句话总结:
- 大段重复代码要提取函数, 方便复用和统一修改
- 高层依赖抽象, 抽象去依赖底层实现. 主要是避免两个易变模块耦合在一起(业务逻辑/底层实现)
- 功能之间的相互依赖越少越好, 逻辑越独立越好, 尤其不要产生循环依赖
- 不要过度优化和过早优化
- 底层的每个函数职责尽量单一, 每个类只做好一件事, 功能粒度越细越好
- 无状态无副作用的纯函数是捷径
- 多用接口少用继承. 由上层抽象来统一组合调配, 进而实现完整功能
2. 设计原则明细
面向对象设计的 S.O.L.I.D 原则
-
Single Responsibility Principle (SRP) – 单一功能原则
- 一个"类"只做好一件事, 一个函数只实现一个功能
- 纯面向过程写代码会导致逻辑不够清晰, 更容易出错
-
Open/Closed Principle (OCP) – 开闭原则
- 依赖抽象,而不是实现
- 对扩展开放,而对修改封闭
- 一方面要保证业务层对底层具体实现相隔离, 避免随意修改导致原有功能遭到破坏
- 另一方面避免修改底层代码而导致未知引用遭到破坏, 产生不期结果
-
Liskov Substitution Principle (LSP) – 里氏替换原则
- 任何基类可以出现的地方,子类一定可以出现
- 类的继承不要割裂, 一般情况下, 多考虑新增功能, 而不是覆盖父类同名方法 (抽象方法除外)
-
Interface Segregation Principle (ISP) – 接口隔离原则
- 把功能实现在接口中,而不是类中
- 对 Python 来说, 可以将多重继承改为 Mixin 类的组合, 使功能更加内聚, 也避免了 MRO(方法解析顺序)混乱
-
Dependency Inversion Principle (DIP) – 依赖倒置原则
- 依赖抽象而不是具体实现
- 依赖一个统一的标准(抽象), 有利于标准化管理
- 高层抽象尽量不变, 迫使底层做各种兼容与修复
- 功能标准化, 设计一致性
- 增加复用的安全性, 减少修改成本
- 依赖抽象而不是具体实现
Don’t Repeat Yourself (DRY)
- 相似代码抽取共性, 构造复用函数
- 避免出现大团的重复代码, 导致每次逻辑改变时遗漏一些修改
- 配合正确的函数名, 可读性优于一段段注释
Keep It Simple, Stupid (KISS)
- 不要把事情搞复杂, 围绕要解决的问题保持简洁
- 不要臆想 “不存在的问题” 而过早优化
Program to an interface, not an implementation
- 多依靠抽象接口解决问题, 少依赖具体实现, 方便后期修改时保持独立
- 先抽象出标准, 做好回归测试, 再考虑具体实现
Command-Query Separation (CQS) – 命令-查询分离原则
- Query 功能和 Command 功能不要合并到一起写. 主要考虑到: 安全, 性能, 可维护性, 功能唯一原则等因素
- 增删改查之中, 查是频率最高的操作, 却又不会影响原始数据
- 所以像数据库做读-写分离一样, 代码结构也相互隔离, 更有利于后期维护
- 而且查询一般是幂等操作, 可以添加缓存来提速, 修改就不一定是幂等行为
You Ain’t Gonna Need It (YAGNI)
- 只实现目前需要的功能,其他功能真正有调用机会时再行添加
- 奥卡姆剃刀, 如无必要, 勿增实体
Law of Demeter – 迪米特法则
- 每个功能对其他功能的了解越少越好, 越独立越好, 不要太多复合依赖
- 也就是常说的解耦合
- “无状态” 是分布式计算模块中常见设计方式
Common Closure Principle(CCP)– 共同封闭原则
- 开闭原则的延伸
- 将相关联的修改控制在一个 “包”, 甚至一个 “类” 中, 不要做跨包/跨类修改操作
- 简单的说法就是, 相关联的修改不要东一块西一块
- 结合单个函数不要过长(超出一屏)的思想, 越分散的修改越容易出错
Common Reuse Principle (CRP) – 共同重用原则
1. 包内的类应该一起被重用 (reuse), 剔除掉不使用或不相关的类
2. CCP 让包尽可能大, 把关联的放到一起; CRP 让包尽可能小, 只放用到的类
3. 举个例子, 把 C 和 D 两个功能分开放到模块 A.C 和 A.D 两个部 分, 这样需要用单独某个功能的时候, from A.C import C 就不会构建 A.D 里的代码.
Hollywood Principle – 好莱坞原则
- 允许低层组件将自己挂钩到系统上,但是由高层组件来决定什么时候使用这些低层组件
- 大致是说, 底层只负责基础功能, 把基础功能如何组合起来被使用这件事, 交给调用方来解决
- 调用方自己抽象/组合多个功能, 合并成一套用法
High Cohesion & Low/Loose coupling – 高内聚, 低耦合
- 高内聚: 重用性高, 功能完整
- 低耦合: 相互独立, 减少相互依赖
- 简单地说, 单一功能高内聚, 多个功能低耦合
Convention over Configuration(CoC)– 惯例优于配置原则
1. 使用惯例(约定)代替详细的配置文件.
1. 比如 get_ 开头的函数代表取值操作, 而 get_obj_ 开头代表取得一个对象的操作
2. 惯例可能是跨部门甚至跨企业的, 配置却只能局限在单个项目里面, 无法形成共享知识
2. 要想清楚什么时候适合惯例, 什么时候适合配置
Separation of Concerns (SoC) – 关注点分离
1. 复杂问题拆分为最小的独立问题, 更易于解决
1. 常见于 MVC 模式
2. 有点像超过 5 行代码都抽离成单个函数的说法
Design by Contract (DbC) – 契约式设计
- 类继承的方法, 参数/返回 要和基类一致
- 既然继承了某个类, 重写了某个同名方法, 就要遵循原方法的设计, 否则很容易导致歧义
Acyclic Dependencies Principle (ADP) – 无环依赖原则
1. 杜绝循环依赖
1. 死锁的产生一般伴随着循环依赖
2. 如果确实产生了互相依赖, 那就加入第三方专门用来拆解
3. 总结
目前看来, 设计原则基本围绕的是函数的功能尽可能单一, 类之间的抽象尽量独立; 业务层面依赖抽象做好标准化约束, 不要直接接触具体实现.
也就是所谓的: 高内聚, 低耦合.