SuperAPI AI 工具集

type-driven-design

编程与开发

查看项目

更新于 10-28 16:14

技能介绍

用途

type-driven-design 是一个 Rust 技能,目标是把领域模型中「非法状态」从类型层面消除,让错误在编译期而不是运行期暴露。它重构的是领域模型本身,而不是代码风格。该技能带有一条停止规则:只有当违反某不变量在本代码库中构成真实缺陷类别时,才应把不变量编码进类型;仅仅因为「可以表达」并不构成理由。

主要能力

核心原则

一个类型是对「哪些状态存在」的承诺。对每个结构体应追问:有多少字段组合可以被构造出来,其中多少是合法的?两者之差就是缺陷藏身的表面。类型若无法表示坏状态,就消除了对应的运行时检查、针对该检查的测试以及相关缺陷报告。

枚举替代标志组合

两个布尔字段产生四种状态;若只有三种合法,第四种就是潜伏缺陷。资料示例中,is_draft 与 is_published 同时为真没有意义,改为枚举后 Post 只有 Draft、Scheduled、Published 三个变体,且 published_at 不会缺失。同样适用于「两个 Option 字段恰好一个为 Some」的情形:应改为两变体的枚举,使「两者都是 None」不可构造。

解析而非校验

接受 &str 并检查是否为合法邮箱的函数,在每个调用点都要重复检查,因为类型不携带证明。改为只通过 Email::parse(&str) -> Result<Email, EmailError> 构造的 Email 类型后,检查只在边界发生一次,类型本身就是检查已运行的证明。关键是字段私有:公开字段会让构造函数沦为建议,不变量会在有人用字面量直接构造的那天失效。

newtype 承载不变量

除防止两个同类型原始参数在调用点被交换外,newtype 还可承载不变量,如 NonEmptyVec<T>、Percentage、Sanitized<String>,构造函数是唯一入口、字段私有。标准库的 NonZeroU32 等已采用此模式,并带来 niche 优化收益:Option<NonZeroU32> 与 u32 同大小。数值上同理:类型系统否则会允许字节数与毫秒数相加;对不能为零的用 NonZero,边界处用 TryFrom 而非 as。

typestate 表达协议顺序

当操作必须按序发生(先连接后发送、先 build 后 finalize),可把阶段编码进类型参数,使乱序调用成为编译错误而非运行时 Err。资料指出完整示例、builder 变体与代价记录在 TYPESTATE.md 中;简言之,typestate 适用于小型、固定状态机且乱序使用是真实缺陷类别的情况。

使用方式与限制

类型级建模有代价:错误信息更差、泛型更重、领域变化时产生重构税。为只在 main 中构造一次的结构体写五参数 typestate builder 属于过度工程,该技能明确提倡拒绝这种做法而非把判断留给评审者。

技能还给出职责让渡:构造函数如何报告拒绝由 /rust-errors 负责;结果类型对调用方是否易用、收紧类型是否构成 semver 破坏由 /rust-api-design 负责;若模型改动是被借用检查器痛点所迫,应先运行 /ownership-not-clone,答案可能是所有权而非建模。

验证

cargo clippy --all-targets   # 仅在仓库无 lint 配置时追加 -- -D warnings
cargo test

clippy 应按仓库已配置的 lint 级别运行,不得叠加在仓库已设级别之上。该技能要求的特定证据是:改动后,因类型而变得不可能的运行时检查应被删除而非保留;用 grep 确认已不可能的代码分支消失,并确认断言旧运行时拒绝的测试已被删除或改写为编译失败期望。若仓库已使用 trybuild,可增加证明非法构造不再编译的用例;若未使用,不应在本次改动中引入该依赖,改为在文档注释中写明编译失败期望。

适用场景

适用于 Rust 项目中对领域模型进行类型级重构:状态组合含非法组合、同一校验在多个调用点重复、原始类型参数易混淆、操作须严格按序执行的场景。不适用于乱序或非法状态并非真实缺陷类别、或类型建模成本明显超过收益的场景。