关于 principle-type-system-discipline
面向开发者的类型系统实践技能,帮助构建更安全的类型模型。它强调直接表达有效状态、在边界解析数据,并借助编译器发现遗漏分支。
主要功能
- 01让非法状态无法通过类型表达
- 02从有效值构造类型
- 03为语义不同的原始值添加品牌类型
- 04在边界解析外部数据
- 05穷尽检查所有变体
- 06从权威模式派生类型
适用场景
- 01建模互斥状态
- 02避免不同 ID 类型混用
- 03验证 JSON 等外部输入
- 04变更后发现未处理的变体
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ q-qp-p/ pstack --skill principle-type-system-discipline安装后在 Claude Code 中输入/principle-type-system-discipline确认已加载。
详细介绍
类型系统纪律
让编译器拒绝不可能的状态、不匹配的基本类型和未处理的变体。与其不断增加处理逻辑,不如从定义上消除错误和特殊情况。TypeScript 语法请参阅 typescript-best-practices 技能。
- 让非法状态无法表示。 将变体建模为和类型(判别联合、带载荷的枚举、密封类),而不是一组可选字段,因为相互矛盾的组合也能通过编译。
{ completed: boolean; completedAt?: Date }允许completed: true却没有日期。可以从completedAt推导布尔值,或建模为{ kind: 'open' } | { kind: 'done'; at: Date }。如果你需要写注释来说明字段组合在什么情况下有效,就应该将其拆分为和类型。 - 类型是构造,不是限制。 从你想要的值出发构造类型,而不是先采用宽松类型,再通过检查剔除不需要的值。非空列表由一个头元素和其余元素组成。有效时间范围由开始时间和持续时长组成。选择无法构造非法值的形状,并在其上公开调用方需要的接口。
- 为有语义的基本类型添加品牌。
UserId和OrderId底层都是字符串,但不应互换。为它们分别定义类型,例如在 TypeScript 中使用品牌交叉类型,或在其他语言中使用 newtype。在创建时验证一次,下游代码就可以信任其类型。如果两个参数的基本类型相同,但含义不同,就为它们添加品牌。 - 外部数据在解析前都没有类型。 RPC 载荷、JSON、IPC 消息、CLI 参数、配置文件、环境变量、数据库行,都属于此类。每个边界都应有一个解析函数,将非结构化输入转换为带类型的模型。boundary-discipline 原则技能说明了验证应放在哪里。
- 不要欺骗类型系统。 绕过编译器的类型转换、不安全强制转换和断言函数,都会埋下运行时崩溃的隐患。如果编译器无法证明某个事实,就应该证明它(通过验证、收窄类型或完善模型),否则就要承认类型转换存在风险。沿着每个
any、as或assertNotNull追溯到边界,并在那里进行验证。 - 穷尽匹配是编译器的职责。 对和类型进行匹配时,如果新增变体却未处理,就必须编译失败,让编译器告诉下一个 Agent 应在哪里添加分支。使用语言惯用的方式:TypeScript 中绑定到
never类型的变量、Rust 中未标注类型的match、Haskell 中的-Wincomplete-patterns,或 Kotlin 中对密封类的穷尽匹配检查。 - 从权威架构定义派生类型。 如果协议缓冲区、OpenAPI 规范、GraphQL 架构、数据库迁移、设计令牌文件或其他文件定义了某种结构,就应从中派生类型,而不是手动创建一套平行类型。请参阅 encode-lessons-in-structure 原则技能。
- 只在出现不完整性的地方强化类型。 运行时断言、空值检查或“这种情况绝不应该发生”的抛错,都表明类型过于宽松。将检查提升到类型中,然后就此为止。类型系统的职责是追踪每个使用点必须处理的情况,而不是尽可能精确地描述数据。优先使用全函数。空列表的
sum结果是 0,因此它接受普通列表。空列表的head没有答案,因此它要求非空列表。如果没有其他情况会导致程序崩溃,就保留普通类型。
评论
支持 Markdown