关于 type-coverage
这项 TypeScript 技能介绍如何衡量具有具体类型而非 any 的符号比例,并持续跟踪变化。它适合迁移 JavaScript 或维护类型安全的团队,可设置覆盖率门槛并在 CI 中发现退化。
主要功能
- 01衡量有类型符号的覆盖率
- 02定位类型为 any 的符号
- 03支持设置最低覆盖率门槛
- 04跟踪变化并防止覆盖率退化
适用场景
- 01监控 JavaScript 迁移期间的类型安全
- 02在代码审查中检查覆盖率
- 03覆盖率低于门槛时使 CI 失败
- 04安排 any 类型清理工作
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ marius-townhouse/ effective-typescript-skills --skill type-coverage安装后在 Claude Code 中输入/type-coverage确认已加载。
详细介绍
跟踪类型覆盖率,防止类型安全回退
概述
监控代码中使用真实类型与 any 的比例。
类型覆盖率用于衡量具有真实类型而非 any 的符号所占的百分比。持续跟踪,防止类型安全回退。
何时使用此技能
- 将 JavaScript 迁移到 TypeScript
- 长期维护类型质量
- 在代码审查中检查类型安全
- 衡量消除
any的进度
铁律
What gets measured gets managed.
Track type coverage; set coverage goals.
请记住:
any会在代码中悄然扩散- 覆盖率可能在不知不觉中下降
- 明确跟踪可以防止质量滑坡
- 为团队设定提高覆盖率的目标
了解类型覆盖率
let x: number = 1; // x is covered (has real type)
let y: any = 2; // y is NOT covered (has any)
let z = JSON.parse('{}'); // z is NOT covered (implicit any from JSON.parse)
类型覆盖率 =(已覆盖符号数)/(符号总数)
测量覆盖率的工具
type-coverage 包
npm install -g type-coverage
type-coverage --detail
输出:
23384/24058 97.20%
使用 --detail 可以查看哪些符号使用了 any:
src/api.ts:15:7 - data
src/utils.ts:42:3 - result
项目专属配置
// package.json
{
"scripts": {
"type-coverage": "type-coverage --at-least 95"
}
}
如果覆盖率低于阈值,则让 CI 失败。
any 的来源
1. 显式 any
function process(data: any) { ... } // Developer wrote any
2. 隐式 any(noImplicitAny 关闭时)
function process(data) { ... } // data is implicitly any
3. 来自库的 any
const data = JSON.parse(str); // Returns any
const result = $.ajax(url); // jQuery returns any
4. 传染性 any
function getUser(): any { ... }
const user = getUser();
// ^? any - spreads from function
const name = user.name;
// ^? any - continues spreading
改进策略
替换 JSON.parse
// Before: returns any
const data = JSON.parse(str);
// After: validate with zod
const schema = z.object({ name: z.string() });
const data = schema.parse(JSON.parse(str));
// ^? { name: string }
修复库的类型
// Augment JSON.parse to return unknown
declare global {
interface JSON {
parse(text: string): unknown;
}
}
改用 unknown
// Before
function parse(): any { ... }
// After
function parse(): unknown { ... }
添加类型注解
// Before: inferred as any from library
const result = externalLib.process(data);
// After: explicitly typed
const result: ProcessedData = externalLib.process(data);
长期跟踪
# Record in CI
echo "$(date): $(type-coverage)" >> coverage-history.txt
# Fail if coverage decreased
type-coverage --at-least $(cat .type-coverage-baseline)
设定团队目标
| 里程碑 | 覆盖率目标 |
|---|---|
| 迁移开始 | 50% |
| 阶段 1 | 75% |
| 阶段 2 | 90% |
| 稳定阶段 | 95%+ |
随着代码库日渐成熟,逐步提高目标。
防止回退
提交前钩子
# .husky/pre-commit
type-coverage --at-least 95
CI 检查
# .github/workflows/ci.yml
- name: Type Coverage
run: npx type-coverage --at-least 95
代码审查检查清单
- 未经说明,不添加新的
any类型 - 类型覆盖率没有下降
- 外部数据经过验证
处理必要的 any
有时 any 无法避免:
// Document why any is needed
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const legacy: any = oldSystem.getData(); // TODO: Type when migrating legacy system
通过注释和工单跟踪这些情况。
实际示例
// Before migration: 60% coverage
// Sources of any:
// - JSON.parse: 15%
// - Legacy API: 12%
// - Untyped dependencies: 8%
// - Explicit any: 5%
// Action plan:
// 1. Add zod validation: +10%
// 2. Type legacy API responses: +10%
// 3. Add @types packages: +8%
// 4. Remove explicit any: +5%
// After: 93% coverage
抵抗压力的方案
1. “我们无法达到 100%”
压力:“有些代码无法添加类型”
**回应:**目标是不断改进,而非追求完美。尽可能跟踪。
**行动:**设定切实可行的目标;记录必要的例外情况。
2. “工作量太大了”
压力:“修复所有 any 类型要花太多时间”
**回应:**逐步改进。先阻止新增 any 类型。
**行动:**逐步收紧标准:禁止新增 any,再逐步修复旧有问题。
危险信号——停止并重新考虑
- 覆盖率持续下降
- 未经说明就新增
any类型 any从函数返回值开始扩散- 代码未经测试且大量使用
any
常见的自我安慰(都不成立)
| 借口 | 事实 |
|---|---|
“只是一个 any” | any 会扩散;一个会变成很多个 |
| “以后再修” | 不跟踪,就永远不会有以后 |
| “覆盖率已经够高了” | 随着改进,应设定更高的目标 |
快速参考
# Measure coverage
npx type-coverage
# With details
npx type-coverage --detail
# Fail if below threshold
npx type-coverage --at-least 95
# In package.json
"scripts": {
"type-coverage": "type-coverage --at-least 95"
}
归根结底
跟踪类型覆盖率,防止回退。
any 类型会悄然扩散。如果不进行度量,类型安全就会随着时间推移而逐渐削弱。使用工具跟踪覆盖率、设定目标,并在覆盖率下降时让构建失败。
参考资料
改编自 Dan Vanderkam 的《Effective TypeScript》第 49 条:跟踪类型覆盖率,防止类型安全回退。
评论
支持 Markdown