鉴于最近又写出大量低质量代码, 所以需要重新回顾一下这些有用的编码哲学.

主要参照 <一些软件设计的原则> – 酷壳, 做一下简单理解的笔记.

1. 一句话总结:

  1. 大段重复代码要提取函数, 方便复用和统一修改
  2. 高层依赖抽象, 抽象去依赖底层实现. 主要是避免两个易变模块耦合在一起(业务逻辑/底层实现)
  3. 功能之间的相互依赖越少越好, 逻辑越独立越好, 尤其不要产生循环依赖
  4. 不要过度优化和过早优化
  5. 底层的每个函数职责尽量单一, 每个类只做好一件事, 功能粒度越细越好
    1. 无状态无副作用的纯函数是捷径
  6. 多用接口少用继承. 由上层抽象来统一组合调配, 进而实现完整功能

2. 设计原则明细

面向对象设计的 S.O.L.I.D 原则

  1. Single Responsibility Principle (SRP) – 单一功能原则

    1. 一个"类"只做好一件事, 一个函数只实现一个功能
    2. 纯面向过程写代码会导致逻辑不够清晰, 更容易出错
  2. Open/Closed Principle (OCP) – 开闭原则

    1. 依赖抽象,而不是实现
    2. 对扩展开放,而对修改封闭
      1. 一方面要保证业务层对底层具体实现相隔离, 避免随意修改导致原有功能遭到破坏
      2. 另一方面避免修改底层代码而导致未知引用遭到破坏, 产生不期结果
  3. Liskov Substitution Principle (LSP) – 里氏替换原则

    1. 任何基类可以出现的地方,子类一定可以出现
    2. 类的继承不要割裂, 一般情况下, 多考虑新增功能, 而不是覆盖父类同名方法 (抽象方法除外)
  4. Interface Segregation Principle (ISP) – 接口隔离原则

    1. 把功能实现在接口中,而不是类中
    2. 对 Python 来说, 可以将多重继承改为 Mixin 类的组合, 使功能更加内聚, 也避免了 MRO(方法解析顺序)混乱
  5. Dependency Inversion Principle (DIP) – 依赖倒置原则

    1. 依赖抽象而不是具体实现
      1. 依赖一个统一的标准(抽象), 有利于标准化管理
    2. 高层抽象尽量不变, 迫使底层做各种兼容与修复
    3. 功能标准化, 设计一致性
      1. 增加复用的安全性, 减少修改成本

Don’t Repeat Yourself (DRY)

  1. 相似代码抽取共性, 构造复用函数
  2. 避免出现大团的重复代码, 导致每次逻辑改变时遗漏一些修改
  3. 配合正确的函数名, 可读性优于一段段注释

Keep It Simple, Stupid (KISS)

  1. 不要把事情搞复杂, 围绕要解决的问题保持简洁
  2. 不要臆想 “不存在的问题” 而过早优化

Program to an interface, not an implementation

  1. 多依靠抽象接口解决问题, 少依赖具体实现, 方便后期修改时保持独立
  2. 先抽象出标准, 做好回归测试, 再考虑具体实现

Command-Query Separation (CQS)  – 命令-查询分离原则

  1. Query 功能和 Command 功能不要合并到一起写. 主要考虑到: 安全, 性能, 可维护性, 功能唯一原则等因素
  2. 增删改查之中, 查是频率最高的操作, 却又不会影响原始数据
    1. 所以像数据库做读-写分离一样, 代码结构也相互隔离, 更有利于后期维护
    2. 而且查询一般是幂等操作, 可以添加缓存来提速, 修改就不一定是幂等行为

You Ain’t Gonna Need It (YAGNI)

  1. 只实现目前需要的功能,其他功能真正有调用机会时再行添加
  2. 奥卡姆剃刀, 如无必要, 勿增实体

Law of Demeter – 迪米特法则

  1. 每个功能对其他功能的了解越少越好, 越独立越好, 不要太多复合依赖
    1. 也就是常说的解耦合
    2. “无状态” 是分布式计算模块中常见设计方式

Common Closure Principle(CCP)– 共同封闭原则

  1. 开闭原则的延伸
  2. 将相关联的修改控制在一个 “包”, 甚至一个 “类” 中, 不要做跨包/跨类修改操作
  3. 简单的说法就是, 相关联的修改不要东一块西一块
    1. 结合单个函数不要过长(超出一屏)的思想, 越分散的修改越容易出错

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 – 好莱坞原则

  1. 允许低层组件将自己挂钩到系统上,但是由高层组件来决定什么时候使用这些低层组件
  2. 大致是说, 底层只负责基础功能, 把基础功能如何组合起来被使用这件事, 交给调用方来解决
    1. 调用方自己抽象/组合多个功能, 合并成一套用法

High Cohesion & Low/Loose coupling – 高内聚, 低耦合

  1. 高内聚: 重用性高, 功能完整
  2. 低耦合: 相互独立, 减少相互依赖
  3. 简单地说, 单一功能高内聚, 多个功能低耦合

Convention over Configuration(CoC)– 惯例优于配置原则

1. 使用惯例(约定)代替详细的配置文件.
   1. 比如 get_ 开头的函数代表取值操作, 而 get_obj_ 开头代表取得一个对象的操作
   2. 惯例可能是跨部门甚至跨企业的, 配置却只能局限在单个项目里面, 无法形成共享知识
2. 要想清楚什么时候适合惯例, 什么时候适合配置

Separation of Concerns (SoC) – 关注点分离

1. 复杂问题拆分为最小的独立问题, 更易于解决
   1. 常见于 MVC 模式
   2. 有点像超过 5 行代码都抽离成单个函数的说法

Design by Contract (DbC) – 契约式设计

  1. 类继承的方法, 参数/返回 要和基类一致
    1. 既然继承了某个类, 重写了某个同名方法, 就要遵循原方法的设计, 否则很容易导致歧义

Acyclic Dependencies Principle (ADP) – 无环依赖原则

1. 杜绝循环依赖
   1. 死锁的产生一般伴随着循环依赖
2. 如果确实产生了互相依赖, 那就加入第三方专门用来拆解

3. 总结

目前看来, 设计原则基本围绕的是函数的功能尽可能单一, 类之间的抽象尽量独立; 业务层面依赖抽象做好标准化约束, 不要直接接触具体实现.

也就是所谓的: 高内聚, 低耦合.