mcpField
S
strong-types

strong-types

github.com/jpcaparas开发5648 次安装1 天前更新
代码仓库

关于 strong-types

strong-types 技能为使用具备类型系统语言的开发者提供编码与审查准则,重点是消除类型歧义、明确边界数据结构和空值策略。它按语言提供针对性参考,并强调遵循现有检查器配置。

主要功能

  1. 01为函数参数和返回值明确类型
  2. 02用命名类型表达跨边界数据
  3. 03显式处理空值与非法状态
  4. 04避免类型逃逸、强制转换和抑制
  5. 05按语言查阅针对性参考
  6. 06通过项目配置的检查器验证

适用场景

  1. 01实现或重构类型化代码
  2. 02审查变更中的空值与类型歧义
  3. 03将外部输入解析为明确的数据结构
  4. 04在既有检查器设置下验证改动

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jpcaparas/skills --skill strong-types

安装后在 Claude Code 中输入/strong-types确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

AGENTS.md
metadata.json
README.md
skill-card.prompt.md
agents/
openai.yaml
assets/
.gitkeep
evals/
evals.json
references/
go-rust-swift.md
gotchas.md
gradual-languages.md
jvm-and-dotnet.md
php.md
principles.md
python.md
review-rubric.md
source-notes.md
typescript.md
scripts/
analyze_type_strictness.py
test_skill.py
validate.py
templates/
type-review.md

详细介绍

强类型

编写、编辑和审查代码,确保程序中的每个值在任何时刻都有一个明确可知的类型。类型歧义就是缺陷:如果作者不运行代码就无法说出某个表达式的类型,那么代码就尚未完成。

被动触发

每当编码任务涉及具有真实类型系统的语言时,都在后台加载此技能,包括静态类型语言(C#、Java、Kotlin、Swift、Go、Rust、TypeScript)以及配备成熟工具的渐进式类型语言(PHP、Python、搭配 Sorbet 的 Ruby、搭配 typespecs 的 Elixir)。将此技能应用于新代码、编辑、重构、审查和实现计划。

保持适度:对于小型编辑,默默检查类型歧义,只提及会改变实现方式的决策。条件允许时,也加载 {{ skill:maintainable-code }},用于提升通用代码质量。如果语言或代码库没有可用的类型系统,不要强行套用;请查阅 references/gradual-languages.md,了解合适的替代方案。

典型问题

$image = $location->preview ?? $location->banner ?? $location->thumbnail;

这一行留下了四个未解答的问题:每个属性是什么类型,其中哪些可为空以及原因是什么,之后的 $image 是什么类型,以及整个链式表达式是否仍可能为 null。强类型代码会在签名中回答这四个问题:

final class Location
{
    public function primaryImage(): Image
    {
        return $this->preview ?? $this->banner ?? $this->thumbnail
            ?? throw new MissingImageException($this->id);
    }
}

兜底逻辑仍然存在,但它被放在一个有名称、有类型的方法中,具有显式返回类型、明确的全空情况处理策略,以及领域名称(primaryImage),而不是在每个调用点内联重新推导。这种转换正是此技能的核心做法。

决策树

你正在处理什么?仅当任务需要特定语言的详细信息时,才将这些参考资料用作针对性辅助,而不要把它们当作每次编辑都必须学习的教程。保留框架和接口的精确类型契约;如需使用逃逸机制,应将其限制在必要范围内并说明理由,不要以不兼容的方式缩窄签名。

  • PHP(Laravel、Symfony、原生 PHP): 详情请查阅 references/php.md。要求使用 declare(strict_types=1)、类型化属性、类型化签名、枚举、用 DTO 替代关联数组,以及 PHPStan/Psalm 级别的泛型注释。

  • TypeScript 或可编译 TypeScript 的 JS 代码库: 详情请查阅 references/typescript.md。要求使用 strict: true,禁止 any 和 @ts-ignore,在边界处使用 unknown 并进行解析,使用可辨识联合类型和穷尽式 switch。

  • Python: 详情请查阅 references/python.md。要求为所有签名添加注解,使用 mypy/pyright 严格模式,用 dataclasses/TypedDict/pydantic 替代原始字典,用 Enum/Literal 替代字符串。

  • C#、Java 或 Kotlin: 详情请查阅 references/jvm-and-dotnet.md。要求使用可空引用类型(C#)、封闭层次结构和记录、穷尽式模式匹配,并避免未经检查的类型转换和 !!。

  • Go、Rust 或 Swift: 详情请查阅 references/go-rust-swift.md。禁止使用 interface{}/any 逃逸机制、在生产路径中使用 unwrap(),以及强制解包;优先使用 newtype 和携带关联数据的枚举。

  • 原生 JavaScript、Ruby、Elixir、Lua、shell 或其他动态类型环境: 详情请查阅 references/gradual-languages.md,了解替代方案。仅在有帮助时建议使用类型工具,不要重写项目或违背其惯例。

  • 审查差异或计划: 适用时使用 references/review-rubric.md。优先指出可能导致运行时故障的歧义:可空值泄漏、通过 any 混淆类型、字符串化状态以及静默类型转换。

  • 不确定从哪里开始: 先阅读 references/principles.md,然后运行下方的类型歧义检查。

快速参考

歧义迹象默认操作
对象属性上的 ?? / `
any、mixed、interface{}、Object、未类型化的 dict/array替换为具体类型、泛型参数、联合类型,或使用 unknown 并进行解析
未类型化的函数签名先添加参数和返回类型,再处理函数体;void/None 也算
跨边界传递的关联数组 / 原始字典 / 匿名对象定义具有命名类型化字段的 DTO、dataclass、record、struct 或 TypedDict
魔法字符串状态("pending"、"active")替换为枚举、密封类型或 Literal 联合类型,并进行穷尽处理
“以防万一”而到处传递的可空值在边界处一次性收窄类型;向内传递时保持非空类型
外部输入(HTTP、JSON、环境变量、数据库行、CLI 参数)在边界处解析为类型化结构;绝不让原始载荷形状继续传递
抑制类型错误(@ts-ignore、# type: ignore、@phpstan-ignore)修正类型,或在行内注明确切错误代码和原因
用于让检查器不再报错的强制转换(as any、(array)、!!、未经检查的强制转换)替换为类型收窄、类型守卫,或能够明确失败的解析步骤
用布尔参数对编码状态机替换为一个枚举 / 密封类型,从而杜绝非法组合
同一变量被重新赋值为不同类型拆分为多个各自只有一种类型的变量
编译器 / 检查器设置宽松新代码保持作用域内严格;旧代码迁移范围明确时使用基线

核心规则

  1. 每个函数签名都必须完整注明类型:每个参数、返回类型,以及语言支持跟踪的抛出/引发错误类型。未注明类型的签名就是未完成的签名。
  2. any、mixed、interface{}、Object 以及未注明元素类型的 dict/array 都是逃生口,而不是类型。每次使用都需要书面说明理由;默认应使用具体类型、泛型,或使用 unknown 后进行解析。
  3. 让非法状态无法表示。优先使用枚举、封闭层次结构、可辨识联合和非空字段,而不是布尔值、魔法字符串和所有字段都可选的记录。
  4. 解析,而非验证。只在边界处将不可信输入转换为类型化结构一次,之后只传递该类型化结构。只检查结构形状却不改变类型,是在做无用功。
  5. 跨越函数边界的数据必须使用具名类型。关联数组、原始 dict 和匿名结构仅可用于私有的本地数据传递;能不用就不用。
  6. 可空性是设计决策,而非默认设置。只有当“缺失”是实际领域状态时,字段才可为空,并且每个可空值读取都必须有且仅有一个负责处理它的所有者。调用点上的备用值链意味着缺少负责处理的所有者。
  7. 一个变量只对应一种类型。将变量重新赋值为其他类型会破坏类型推断和读者的信任。
  8. 类型转换会转移风险,而不会消除风险。优先使用检查器能够验证的类型收窄(类型守卫、模式匹配、instanceof、穷尽式 switch)。不会明确失败的类型转换,只是一个等待别人相信的谎言。
  9. 提高检查器的严格程度,而不是关闭它。对范围明确的新代码,严格的编译器标志和严格的分析器级别是基准;在旧代码中采用时,使用基线/逐步收紧文件,绝不使用一刀切的抑制。普通编辑不需要在整个项目范围内推广工具。
  10. 泛型用于在边界之间保留类型。会擦除类型的容器、存储库或辅助程序(List<Object>、Collection<mixed>)会迫使每个调用方进行类型转换;应改用参数化类型。
  11. 穷尽性由编译器强制执行,而不是靠注释。对封闭类型执行的每个 switch/match 都必须处理所有情况;新增情况时,否则编译必须失败。
  12. 不要强求语言无法实现的要求。在纯 JS、未配备工具的 Ruby、Lua 或 shell 中,应采取防御性写法、记录结构形状,并在适用时建议而非强制使用类型工具。如需了解替代方案,请查阅 references/gradual-languages.md。

类型歧义检查门槛

在类型化语言中完成更改前,请检查适用的门槛。这是辅助推理的工具,并非要求在报告中逐行列出:

门槛通过条件
签名每个新增或修改的函数都显式注明参数和返回类型
逃生口没有新增 any/mixed/interface{}/未注明元素类型的集合,除非附有行内说明
边界外部输入在边界处解析为具名类型;原始负载结构不会向内传递
可空性每个可空值都只有一个负责处理的所有者;调用点没有重复的 ??/`
状态封闭状态集合使用枚举/联合类型,并由编译器检查是否穷尽处理
类型转换没有仅为绕过检查器而进行的类型转换;改用类型收窄或解析
抑制没有新增一刀切的错误抑制;任何针对性抑制都注明错误和原因
严格性新文件达到项目支持的最严格检查器级别;若推广范围涵盖旧代码,则配置逐步收紧机制
数据结构跨边界数据使用 DTO/记录/数据类,而非关联数组或原始 dict
合比例性未将非类型化语言的代码强行转换为类型化代码;相关指导仅供参考

操作工作流

  1. 确定类型体系。 在编写代码前,识别语言、检查器(tsc、PHPStan、Psalm、mypy、pyright、编译器标志)以及配置的严格程度。达到或超过该标准;绝不要低于它。

  2. 先为边界添加类型。 在实现逻辑前,先定义进入和离开此次改动的数据传输对象(DTO)、枚举和签名。形状确定后,顺利路径就很容易实现。

  3. 排查差异中的歧义。 扫描回退链、逃生舱类型、未声明类型的签名、魔法字符串和类型转换。每一项都必须补上具体类型,或用一行说明理由。

  4. 在源头解决可空性问题。 遇到 ?? $b ?? $c 链时,查明每一环为何可空,确定由谁负责(访问器、工厂、数据库默认值、解析步骤),并将这条链收敛为一个命名明确且有类型的成员。

  5. 使用检查器验证。 在配置的级别上,使用项目支持的项目级或包级调用方式运行检查器;如果直接传入任意修改过的文件会绕过配置或不受支持,就不要这么做。确保新增代码在限定范围内保持严格;如果存在既有故障或检查不可用,应如实报告,切勿放宽设置。

  6. 如实报告。 报告重要的类型或可空性决策、验证结果以及未解决的歧义。微小改动无需列出每种类型或每道检查关卡。

可选辅助工具

将辅助工具用作快速歧义扫描器,而非裁决依据:

python3 scripts/analyze_type_strictness.py /path/to/project
python3 scripts/analyze_type_strictness.py /path/to/project --json

它会标记疑似回退链、逃生舱类型、未声明类型的签名、被抑制的类型错误、缺少 strict_types 声明以及非严格的 TypeScript 配置。扫描结果干净,并不能证明代码具有强类型;扫描结果嘈杂,也不能证明代码有误。

指南不再适用时

如果捆绑的类型模式失效或与惯用写法冲突,请检查项目的编译器/检查器版本以及官方语言或框架文档。使用不会放宽项目设置的最小可类型检查示例。提出对规范技能进行修正或删除的建议,并提供来源/版本及反例;如证据不可用,应如实报告,而非编造语法。不要默默更新已安装的副本,也不要将本地修复扩大为类型迁移。

阅读指南

需求阅读
通用强类型原则和黄金参考references/principles.md
PHP:strict_types、类型化属性、枚举、DTO、PHPStan/Psalm 泛型references/php.md
TypeScript:严格标志、禁止 any、联合类型、在边界处解析references/typescript.md
Python:注解、严格模式下的 mypy/pyright、数据类、Protocolreferences/python.md
C#、Java、Kotlin:可空引用、记录类型、密封类型、穷尽匹配references/jvm-and-dotnet.md
Go、Rust、Swift:逃生舱、新类型、枚举、可选值references/go-rust-swift.md
何时以及如何避免强行添加类型references/gradual-languages.md
按严重程度优先审查类型差异references/review-rubric.md
常见陷阱和反模式references/gotchas.md
研究来源和改编说明references/source-notes.md

注意事项

  1. 添加类型注解不等于强类型。function f(mixed $x): mixed 虽然有注解,但仍然完全含糊不清。
  2. 靠强制类型转换让检查器变绿,比没有检查器还糟:类型系统现在会主动断言错误的内容。
  3. 每个边界对应一个 DTO,不代表每个函数都要一个 DTO。过度拆分类型会造成大量映射工作;在确实含义相同的层之间复用领域类型。
  4. 一次性大规模改造无法顺利补上严格性。循序渐进:新文件启用严格检查,旧文件维持基线,再逐步清除基线。
  5. 回退链有时掩盖的是数据问题,而不是代码问题。如果存在三个图像列只是因为没人规范化架构,那么类型化访问器只是权宜之计——记下架构修复事项。
  6. unknown(TS)和 object 是处理不可信数据的安全默认类型;any 和 mixed 则不是。前者会强制进行解析;后者会悄无声息地污染所有下游内容。
  7. 类型体操本身也是一种异味。如果需要一大段条件泛型才能表达某个类型,就简化设计,而不是炫耀类型系统。

评论

支持 Markdown