Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

补充资源笔记 - Refactoring & Design Principles

来源:Rust Patterns - Additional Resources

原文版本:main@f279f35 (2026-03-29 查验)


📋 设计原则速查表

#缩写全称一句话介绍
1SRPSingle Responsibility Principle一个模块只有一个引起它变化的原因
2OCPOpen/Closed Principle对扩展开放,对修改关闭
3LSPLiskov Substitution PrincipleRust 中体现为实现同一 trait 的类型应遵守 trait 的语义约定
4ISPInterface Segregation Principle多个小接口优于一个大接口
5DIPDependency Inversion Principle依赖抽象而非具体实现
6CRPComposition Reuse Principle优先使用组合而非继承来复用代码
7DRYDon’t Repeat Yourself每份知识只有一个表示
8KISSKeep It Simple, Stupid简单应该是设计的关键目标
9LoDLaw of Demeter只和直接朋友交谈,不和朋友的朋友交谈
10DbCDesign by Contract用类型系统定义明确的接口规范
11-Encapsulation隐藏内部实现,暴露受控接口
12CQSCommand-Query Separation查询无副作用,命令不返回值
13POLAPrinciple of Least Astonishment行为应符合用户预期
14-Uniform Access统一访问方式,不暴露存储还是计算
15-Single-Choice选项列表只在一处定义
16-Self-Documentation代码即文档,命名表达意图
17-Linguistic-Modular-Units模块对应语言的语法单元
18-Persistence-Closure存储对象时同时存储其依赖对象

Refactoring(重构)

核心要点

重构是将好代码变成优秀代码的关键过程。

关键原则

原则说明
使用设计模式用设计模式来 DRY 代码,泛化抽象
避免反模式反模式虽然诱人,但弊大于利
使用 Idioms用习惯用法组织代码结构

重构的实践原则

原则说明
先设计再重构有整体视角和最终目标,避免盲目重构
无测试不重构测试是重构的安全网
拆分环节把整个重构过程拆分成可验证的小环节
多个检查点每个环节完成后可以验证,确保正确

重构流程

1. 整体设计(目标视角)
       ↓
2. 拆分环节(可验证的小步骤)
       ↓
3. 设置检查点(测试、编译验证)
       ↓
4. 逐个小步重构(Small changes)
       ↓
5. 每个环节验证(Tests)
       ↓
6. 完成重构

关键名言

“Shortcuts make for long days.”(捷径导致长路)


SOLID 原则

SRP - Single Responsibility Principle(单一职责原则)

含义:一个模块应该只有一个引起它变化的原因。

Rust 体现

  • 小模块、小 crate
  • 一个函数只做一件事

例子

#![allow(unused)]
fn main() {
// ❌ 违反 SRP
fn process_user_data(user: &User) {
    validate(user);
    db.save(user);
    send_email(&user.email);
    log.info("user processed");
}

// ✅ 遵循 SRP
fn validate_user(user: &User) -> Result<()> { ... }
fn save_user(user: &User) -> Result<()> { ... }
fn send_welcome_email(email: &str) -> Result<()> { ... }
}

OCP - Open/Closed Principle(开闭原则)

含义:软件实体应该对扩展开放,对修改关闭。

Rust 体现

  • trait 扩展,无需修改现有代码
  • 泛型允许编译时扩展

例子

#![allow(unused)]
fn main() {
trait Shape {
    fn area(&self) -> f64;
    fn draw(&self);
}

// 添加新形状不需要修改现有代码
struct Triangle { base: f64, height: f64 }
impl Shape for Triangle {
    fn area(&self) -> f64 { (self.base * self.height) / 2.0 }
    fn draw(&self) { println!("Drawing triangle"); }
}
}

与 ISP 的关系

  • ISP 是 OCP 的前提:没有小接口,无法优雅扩展
  • OCP 是 ISP 的目标:拆分接口是为了更容易扩展

LSP - Liskov Substitution Principle(里氏替换原则)

Rust 中的正确理解

Rust 中没有传统 OOP 的里氏替换原则(因为没有类继承)。

Rust 中体现为:实现同一 trait 的类型应该在相关行为上遵守 trait 的语义约定

例子

#![allow(unused)]
fn main() {
// Eq trait 隐含的契约
// 实现 Eq 的类型必须满足等价关系的三个性质:
// 1. 自反性:a == a 总是 true
// 2. 对称性:a == b 等价于 b == a
// 3. 传递性:a == b 且 b == c 则 a == c

#[derive(Eq, PartialEq)]
struct UserId(u32);  // ✅ 正确的实现
}

ISP - Interface Segregation Principle(接口隔离原则)

含义:多个专门的接口比一个通用的接口更好。

Rust 体现

  • 小 trait 优于大 trait
  • 不应该强迫实现不需要的方法

例子

#![allow(unused)]
fn main() {
// ❌ 违反 ISP
trait Worker {
    fn work(&self);
    fn eat(&self);
    fn sleep(&self);
}

// ✅ 遵循 ISP
trait Workable { fn work(&self); }
trait Eatable { fn eat(&self); }
trait Sleepable { fn sleep(&self); }
}

DIP - Dependency Inversion Principle(依赖倒置原则)

含义:依赖抽象,而不是具体实现。

“倒置“的含义

  • 从“抽象依赖实现细节“ 倒置为 “实现细节依赖抽象,同时上层业务逻辑也依赖抽象”

Rust 体现

  • 依赖 trait 而非具体类型
  • dyn Trait 或泛型参数

例子

#![allow(unused)]
fn main() {
trait Database {
    fn save_user(&self, user: &User) -> Result<()>;
}

struct UserService {
    db: Box<dyn Database>,  // 依赖抽象
}
}

其他设计原则

CRP - Composition Reuse Principle(组合复用原则)

别名:Composition over Inheritance(组合优于继承)

含义:优先使用对象组合,而不是类继承来复用代码。

Rust 体现

  • Rust 没有类继承,天然支持 CRP
  • struct 组合 + trait

例子

#![allow(unused)]
fn main() {
trait Engine {
    fn start(&self);
    fn drive(&self);
}

struct Car {
    engine: Box<dyn Engine>,  // 组合
    door_count: u32,
}

// 可以动态改变行为
car.engine = Box::new(ElectricEngine);
}

DRY - Don’t Repeat Yourself(不要重复自己)

含义:系统中的每一份知识应该只有一个表示。

实践方法

  • 使用 CCD 工具(如 clippy)检测代码克隆
  • 提取公共函数、公共类型
  • 使用泛型、宏消除重复

Rust 工具

工具用途
函数提取重复逻辑
泛型提取重复的类型模式
trait提取重复的行为
提取重复的代码模式

例子

#![allow(unused)]
fn main() {
// ✅ 用泛型消除重复
fn print_vec<T: std::fmt::Display>(v: &[T]) {
    for item in v {
        println!("{}", item);
    }
}

// ✅ 用宏消除重复
macro_rules! impl_display {
    ($struct:ident, $unit:literal) => {
        impl std::fmt::Display for $struct {
            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                write!(f, "{} {}", self.0, $unit)
            }
        }
    };
}
}

KISS - Keep It Simple, Stupid(保持简单)

含义:简单应该是设计的关键目标,应该避免不必要的复杂性。

与 DRY 的平衡

  • DRY 和 KISS 需要平衡
  • 过度 DRY 会违反 KISS
  • 有时适当重复比过度抽象更好

LoD - Law of Demeter(迪米特法则/最少知识原则)

含义:一个对象应该对其他对象有尽可能少的了解。

通俗理解:只和你的“直接朋友“交谈,不要和“朋友的朋友“交谈。

例子

#![allow(unused)]
fn main() {
// ❌ 违反 LoD:链式调用
let city = user.profile.address.city;

// ✅ 遵循 LoD:委托访问
impl User {
    fn get_city(&self) -> &str {
        &self.profile.address.city
    }
}
let city = user.get_city();
}

DbC - Design by Contract(契约设计)

含义:定义正式的接口规范,包括前置条件、后置条件和不变量。

核心:利用类型系统,通过设计良好的类型提供约束。

完整架构

Design by Contract (Rust)
│
├── 类型系统(最强约束 - 编译时保证)
│   ├── 新类型模式 (Newtype)
│   ├── 类型状态模式 (Type State)
│   └── 生命周期约束
│
├── 返回类型(表达契约 - 编译时强制处理)
│   ├── Result<T, E> - 可能失败
│   ├── Option<T> - 可能为空
│   └── T - 保证成功
│
├── 运行时检查(辅助验证)
│   ├── assert!() - 总是检查
│   ├── debug_assert!() - 仅调试模式
│   └── panic!() - 违反契约
│
└── 测试(验证契约)
    ├── 单元测试 - 单个函数契约
    ├── 集成测试 - 模块间契约
    ├── 属性测试 (proptest) - 验证不变量
    └── 文档测试 - API 示例验证

契约三要素

要素含义责任方
前置条件调用前必须满足的条件调用者负责
后置条件调用后保证的结果实现者负责
不变量始终成立的条件双方共同维护

Encapsulation(封装)

含义:隐藏内部实现,暴露受控接口。

分层封装策略

封装严格程度
│
├── 对外 API(最严格)
│   └── 私有字段 + 公共方法
│
├── 库内可见 pub(crate)(中等)
│   └── 模块间自由访问,对外隐藏
│
└── 模块内部(最宽松)
    └── 非 pub struct + pub 字段(纯数据载体)

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

含义:查询不应该有副作用,命令不应该返回值。

Rust 体现

  • &self 方法 = 查询(无副作用)
  • &mut self 方法 = 命令(有副作用)

例子

#![allow(unused)]
fn main() {
impl Stack<T> {
    // 查询
    fn len(&self) -> usize { self.items.len() }
    fn is_empty(&self) -> bool { self.items.is_empty() }
    
    // 命令
    fn push(&mut self, item: T) { self.items.push(item); }
    fn pop(&mut self) -> Option<T> { self.items.pop() }
}
}

POLA - Principle of Least Astonishment(最少惊讶原则)

含义:组件的行为应该符合大多数用户的预期。

Rust 体现

  • 遵循命名约定(new() 创建新实例)
  • 运算符符合数学直觉
  • 参考标准库的设计

Uniform Access(统一访问原则)

含义:所有服务应该通过统一的符号访问,不暴露是存储还是计算。

Rust 体现

  • 私有字段 + 公共 getter 方法
  • 无参 getter 方法名与字段同名

例子

#![allow(unused)]
fn main() {
struct Circle {
    radius: f64,  // 私有
}

impl Circle {
    pub fn radius(&self) -> f64 { self.radius }
    pub fn area(&self) -> f64 { std::f64::consts::PI * self.radius * self.radius }
}
// 调用者不关心哪些是字段,哪些是计算的
}

Single-Choice(单一选择原则)

含义:当系统需要支持一组替代方案时,应该只有一个模块知道它们的完整列表。

Rust 体现

  • enum 集中定义所有变体
  • match 穷尽检查

例子

#![allow(unused)]
fn main() {
pub enum PaymentMethod {
    CreditCard,
    PayPal,
    BankTransfer,
    // 添加新选项只需改这里
}
}

Self-Documentation(自文档化)

含义:代码即文档,命名表达意图。

实现方法

  • 清晰的命名
  • 类型即文档
  • 函数签名清晰
  • 文档注释(rustdoc)

Linguistic-Modular-Units(语言模块单元)

含义:模块应该对应于所用语言的语法单元。

Rust 体现

  • mod 定义模块
  • 目录/文件对应模块
  • pub 控制可见性

Persistence-Closure(持久化闭包)

含义:存储对象时同时存储其依赖对象,检索时也必须检索其依赖对象。

通俗理解:完整存储对象以及对象依赖的数据。

例子

#![allow(unused)]
fn main() {
// ✅ 遵循:完整存储
struct User {
    name: String,
    address: Address,  // 嵌套对象
}

// 序列化时自动包含 address
let json = serde_json::to_string(&user)?;
}

例外场景(何时不遵循)

场景原因替代方案
缓存行敏感避免超过 64B 导致伪共享用 ID/句柄引用冷数据
延迟加载需求避免初始化慢、内存占用大按需加载
循环依赖无法完整存储用 ID 打破循环
大数据集无法一次性加载流式处理
跨服务边界数据所有权不清晰用 ID 引用
共享数据多个对象共享同一数据独立存储

决策框架

Persistence-Closure
       │
       ├── 遵循:数据完整性优先
       │    └── 小数据、强一致性、简单对象图
       │
       └── 不遵循:性能/灵活性优先
            └── 缓存敏感、延迟加载、循环依赖、大数据

Additional Resources(额外资源)

Talks(演讲)

演讲演讲者场合
Design Patterns in RustNicholas CameronPDRust 2016
Writing Idiomatic Libraries in RustPascal HertleifRustFest 2017
Rust Programming TechniquesNicholas CameronLinuxConfAu 2018

Books (Online)

资源链接
The Rust API Guidelineshttps://rust-lang.github.io/api-guidelines

📝 学习总结

重构原则

  • 先设计再重构(整体视角)
  • 无测试不重构
  • 拆分环节,设置检查点
  • 小步迭代

SOLID 原则

原则Rust 体现
SRP小模块、小 crate
OCPtrait 扩展
LSPtrait 契约一致性
ISP小 trait
DIP依赖 trait

其他核心原则

原则关键点
CRPRust 无类继承,天然支持组合
DRY用泛型/宏/trait 减少重复
KISS简单优先,与 DRY 平衡
LoD委托访问,避免链式调用
DbC类型系统表达契约
封装分层策略(对外严格,对内宽松)
CQS&self vs &mut self
POLA符合预期,遵循约定
Uniform Accessgetter 方法统一访问
Single-Choiceenum 集中定义选项
Self-Documentation代码即文档
Persistence-Closure完整存储对象图(有例外)