其他
需要让非法状态不可表示时:用类型驱动设计把校验前移到编译期
在希望编译器阻止非法状态、减少运行时校验时,用 newtype、type state、PhantomData、marker trait、Builder、sealed trait 等把不变量编码进类型。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 46/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 42/100 ×0.15
适用场景
需要让非法状态不可表示时:用类型驱动设计通用
解决思路
- 在写运行时检查前,先问:类型系统能否阻止非法状态?依次尝试:让编译器捕获该错误;让非法状态不可表示;让类型编码不变量。
- 遇到「primitive obsession」时,不要只说「它只是一个 string」,而要问「这个值代表什么?」并据此引入语义类型。
- 遇到「boolean flags」时,不要只说「加一个 is_valid 标志」,而要问「这些状态能否变成类型?」并用类型状态表达。
- 遇到「Optional everywhere」时,不要只说「检查 None」,而要问「缺失真的可能吗?」并用更精确的类型或构造流程表达。
- 遇到「Validation at runtime」时,不要只说「无效就返回 Err」,而要问「能否在构造时验证?」并尽量在构造时验证。
- 添加运行时验证前,第 1 步:判断类型能否编码约束:数值范围 → bounded types 或 newtypes;有效状态 → type state pattern;语义含义 → newtype。
- 添加运行时验证前,第 2 步:判断验证可发生的位置:构造时 → validated newtype;状态转换时 → type state;只能在运行时 → Result 并给出清晰错误。
- 添加运行时验证前,第 3 步:判断谁需要知道不变量:编译器 → 类型级编码;API 使用者 → 清晰的类型签名;仅运行时 → 文档说明。
- 类型设计不清晰时,向上追溯:从「Need to validate email format」出发,问「Is this a domain value object?」;检查 m09-domain(Email as Value Object);检查 domain-*(validation requirements)。
- 追溯表:需要决定创建哪些类型 → 查 m09-domain,问「领域模型是什么?」。
- 追溯表:需要状态机设计 → 查 m09-domain,问「有效转换是什么?」。
- 追溯表:需要使用 marker trait → 查 m04-zero-cost,问「静态分发还是动态分发?」。
- 从设计落到实现:需要原始类型的类型安全包装 → 用 Newtype:`struct UserId(u64);`。
- 从设计落到实现:需要编译期状态校验 → 用 Type State:`Connection<Connected>`。
- 从设计落到实现:需要跟踪幻影类型参数 → 用 PhantomData:`PhantomData<T>`。
- 从设计落到实现:需要能力标记 → 用 Marker Trait:`trait Validated {}`。
- 从设计落到实现:需要渐进式构造 → 用 Builder:`Builder::new().field(x).build()`。
- 快速参考:Newtype 用于类型安全,示例 `struct UserId(u64);`。
- 快速参考:Type State 用于状态机,示例 `Connection<Connected>`。
- 快速参考:PhantomData 用于 variance/lifetime,示例 `PhantomData<&'a T>`。
- 快速参考:Marker Trait 用于能力标志,示例 `trait Validated {}`。
- 快速参考:Builder 用于渐进式构造,示例 `Builder::new().name("x").build()`。
- 快速参考:Sealed Trait 用于阻止外部实现,示例 `mod private { pub trait Sealed {} }`。
- Newtype 示例:定义 `struct Email(String);`,表示不是任意 string;实现 `impl Email { pub fn new(s: &str) -> Result<Self, ValidationError> { validate_email(s)?; Ok(Self(s.to_string())) } }`,做到验证一次,之后信任。
- Type State 示例:定义 `struct Connection<State>(TcpStream, PhantomData<State>);`,以及 `struct Disconnected; struct Connected; struct Authenticated;`;为 `Connection<Disconnected>` 实现 `fn connect(self) -> Connection<Connected> { ... }`;为 `Connection<Connected>` 实现 `fn authenticate(self) -> Connection<Authenticated> { ... }`。
- 决策指南:原始类型需要类型安全 → Newtype。
- 决策指南:需要编译期状态校验 → Type State。
- 决策指南:需要 lifetime/variance 标记 → PhantomData。
- 决策指南:需要能力标志 → Marker Trait。
- 决策指南:需要渐进式构造 → Builder。
- 决策指南:需要封闭实现集合 → Sealed Trait。
- 决策指南:需要零大小类型标记 → ZST struct。
- 避免反模式:不要用 boolean flags 表示状态,否则运行时错误;改用 type state。
- 避免反模式:不要用 string 表示语义类型,否则无类型安全;改用 newtype。
- 避免反模式:不要用 Option 表示未初始化,否则不变量不清;改用 Builder。
- 避免反模式:不要让带不变量的字段公开,否则可被破坏;改为私有字段 + 经校验的 new()。
- 相关技能:领域建模看 m09-domain;trait 设计看 m04-zero-cost;构造函数中的错误处理看 m06-error-handling;反模式看 m15-anti-pattern。
适用边界 · 注意事项
- 约束必须依赖运行时或外部数据、无法在编译期表达时,不要强行类型化,仍需 Result 和清晰错误。
- 一次性脚本、快速原型或类型建模成本明显高于收益时,不要过度设计。
- 跨信任边界的数据不能只靠类型体操,仍需必要运行时校验。