mcpField
T
typescript-best-practices

typescript-best-practices

github.com/justbecauselabs开发1108 次安装9 周前更新
代码仓库

关于 typescript-best-practices

为 TypeScript 和 TSX 修改提供编码与重构规范,适用于需要一致类型边界和清晰代码的开发者。内容涵盖数据校验、函数设计、复用、注释和重复代码检查。

主要功能

  1. 01用 Zod 校验边界 JSON
  2. 02保持函数小巧且可组合
  3. 03复用共享工具与不变量
  4. 04明确区分边界数据和领域类型
  5. 05通过 jscpd 检查重复代码

适用场景

  1. 01修改 TypeScript 或 TSX 文件
  2. 02审查或修复代码变更
  3. 03重构重复的数据转换逻辑
  4. 04统一团队类型与边界处理规范

安装

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

$npx skills add https://github.com/justbecauselabs/agent-skills --skill typescript-best-practices

安装后在 Claude Code 中输入/typescript-best-practices确认已加载。

附带文件references/review-and-fix-changes.md

详细介绍

TypeScript 最佳实践

对任何 .ts 或 .tsx 修改,默认遵循本指南。

保持本文档实用。记录代码库实际采用的 TypeScript 标准。

参考资料

  • 对工作区、分支、提交或提交范围进行审查或审查并修复时,请阅读 references/review-and-fix-changes.md,并将其作为工作流程,同时将本文档作为 审查标准。

目标

  • 为 TypeScript 的编写和重构提供统一的基准。
  • 让代码类型明确、易于阅读和修改。
  • 减少 AI 式冗余代码,例如防御性清理、含糊的抽象、重复转换和脆弱的回退逻辑。

快速指南

  • 在边界处使用 Zod 验证无类型的 JSON。
  • 保持函数小巧且可组合。
  • 将边界数据结构与内部领域数据结构分开。
  • 只转换一次外部数据,然后让内部代码保持严格、简洁。
  • 除非有实际的边界或产品需求,否则不要规范化值。
  • 保留原始来源值;必要时添加派生的内部键。
  • 对稳定领域数据中有意义的缺失值使用 null。
  • 对可选输入和原始边界载荷字段保留使用 undefined。
  • 添加新辅助函数之前,先复用现有辅助函数。
  • 对字符串、数字、URL、数组、字典、去重、过滤或类似基础逻辑,先在代码库中查找现有的共享辅助函数位置,并在那里添加共享逻辑,而不是将其埋在功能代码中。
  • 提取共享不变量和边界适配器;不要臆造通用清理工具。
  • 重构后、审查前,使用 jscpd 快速检查重复代码。
  • 除非用户明确要求添加代码注释,否则不要添加或扩充代码注释。

优先级顺序

  1. 优先遵循项目特定约定。
  2. 明确标注类型并清晰定义边界。
  3. 添加新抽象之前,先复用现有辅助函数和不变量。
  4. 仅当确实出现重复时,才提取重复的边界适配器或不变量。
  5. 清晰优先于巧妙。
  6. 只有在正确性、边界和复用问题都解决后,才考虑风格偏好。

核心规则

1. 在边界处验证并转换无类型数据一次,然后保持领域类型严格。

错误示例:

type Issue = {
  id: string;
  attemptCount: number;
};

function parseIssue({ body }: { body: string }): Issue {
  return JSON.parse(body) as Issue;
}

正确示例:

import { z } from "zod";

const issueSchema = z.object({
  id: z.string().min(1),
  attemptCount: z.coerce.number().int().nonnegative(),
});

type Issue = z.infer<typeof issueSchema>;

function parseIssue({ body }: { body: string }): Issue {
  return issueSchema.parse(JSON.parse(body) as unknown);
}
2. 保持函数小巧且可组合。函数接受多个由调用方提供的参数时,使用 params 对象。
  • 编写函数时,优先使用 function name() 声明,而不是 const name = () => {}。

错误示例:

function buildWorkspaceLabel(issue: Issue, ownerName?: string): string {
  const normalizedState = issue.state.trim().toLowerCase();
  const normalizedOwner = ownerName ? ownerName.trim() : "unassigned";
  const slug = `${issue.identifier}-${normalizedState}-${normalizedOwner}`
    .replaceAll(/[^\w-]+/g, "-")
    .replaceAll(/-+/g, "-")
    .replace(/^-|-$/g, "");

  if (!slug) {
    return "workspace";
  }

  return slug;
}

正确示例:

function buildWorkspaceLabel(params: {
  issue: Issue;
  ownerName?: string;
}): string {
  const stateKey = buildStateKey(params.issue.state);
  const ownerKey = buildOwnerKey(params.ownerName);

  return buildWorkspaceSlug({
    identifier: params.issue.identifier,
    stateKey,
    ownerKey,
  });
}

function buildOwnerKey(ownerName?: string): string {
  return ownerName ?? "unassigned";
}
3. 将字符串、数字、URL、数组、字典、去重和过滤等基础逻辑放入共享辅助函数。

错误示例:

function listUniqueIssueIds(issues: Issue[]): string[] {
  return [...new Set(issues.map((issue) => issue.id))];
}

正确示例:

import { unique } from "../shared/collections";

function listUniqueIssueIds(issues: Issue[]): string[] {
  return unique(issues.map((issue) => issue.id));
}

如果目前没有共享辅助函数,请先查找现有的共享辅助函数位置。对于字符串、数字、URL、数组、字典、去重或过滤等纯基础逻辑,应在该处添加可复用的辅助函数,而不是将其嵌入功能模块。务必查找 utils/helpers/etc 文件夹

4. 除非用户明确要求,否则不要编写代码注释。
  • 这包括行注释、块注释、JSDoc、TODO、注释掉的代码和指令注释。
  • 现有注释、相邻代码的风格、lint 规则和文档都不算用户的明确指示。
  • 使用精确的名称、类型、小函数和清晰的结构来表达意图。
  • 保留准确的现有注释。如果修改使注释过时,除非用户明确要求代码注释,否则应删除,而不是改写。
5. 明确报告边界错误,不要使用防御性默认值。

错误示例:

function parseLimit(value?: string): number {
  return Number(value) || 0;
}

正确示例:

const limitSchema = z.coerce.number().int().nonnegative();

function parseLimit(value: string): number {
  return limitSchema.parse(value);
}

重构规则

  • 添加新辅助函数前,先在仓库中搜索是否已有解决相同问题的辅助函数、转换函数或适配器。
  • 对字符串、数字、URL、数组、字典、去重或筛选等基础逻辑,先搜索共享辅助文件,并优先将逻辑添加到其中,而不是引入仅供某个功能使用的辅助函数。
  • 如果同一不变量、转换或适配器在多处出现,优先将其提取出来,而不是再添加一份。
  • 在确实出现重复后再提取,不要仅因两个代码片段看起来有些相似就提取。
  • 共享辅助函数应有明确的领域用途。
  • 不要引入混合无关关注点的通用 sanitize*、normalize*、clean* 或 format* 工具函数。
  • 如果某个抽象仅用于通用地“清理”值,真正的修复通常应位于类型化边界。
  • 重构时保留现有最强类型。不要仅为简化重构而放宽类型。
  • 优先使用小型边界适配器和不变量辅助函数,而不是抽象的工具层。

重复代码快速检查

重构后、评审前,像运行类型检查一样,使用 jscpd 快速检查重复代码。从当前仓库根目录运行:

cd "$(git rev-parse --show-toplevel)"
bunx --yes jscpd --format typescript --pattern '**/*.ts' --gitignore \
  --min-lines 12 --min-tokens 120 \
  --max-lines 5000 --max-size 1mb --reporters console --exitCode 0 .
  • 一开始先忽略测试,因为测试夹具常会产生噪声;需要时添加适合仓库的忽略规则,或将匹配范围限定为生产代码。
  • 搜索较小的辅助函数重复时,将 --min-tokens 调低至 60-80。
  • 如果可浏览的报告有帮助,请使用 --reporters html,console。
  • 将克隆检测与针对语义重复的 rg 搜索结合起来:重复的函数名、注释、负载字段或错误字符串。精确克隆工具无法发现拼写不同但逻辑相同的代码。

代码注释规则

  • 除非用户明确要求,否则不要添加或扩展代码注释。
  • 在此规则中,JSDoc、TODO、解释性代码块、注释掉的代码和指令注释均视为代码注释。
  • 不要从现有注释、相邻代码的风格、lint 规则、生成的示例或文档中推断出添加注释的许可。
  • 保留准确的现有注释。如果代码更改导致注释过时,应删除,而不是改写,除非用户明确要求添加代码注释。
  • 通过准确的命名、强类型、小型辅助函数和清晰的结构,让代码能够自我说明。

工作流程

  1. 在修改 TypeScript 前,先阅读此技能指南。
  2. 对于审查或审查并修复任务,在界定工作范围前,先阅读 references/review-and-fix-changes.md。
  3. 如果项目特定约定与通用指南冲突,优先遵循项目约定。
  4. 当数据以 JSON 形式进入系统时,在该边界定义 Zod schema。
  5. 对于已有可信类型的值,避免不必要的清理或强制转换。
  6. 保持函数小巧且可组合。
  7. 对于有多个自定义参数的函数,通常优先使用 fn(params: { ... })。
  8. 对于只有一个参数的函数,直接传递该值,而不是将其包装在 params 对象中,除非有充分的本地理由。
  9. 除非用户明确要求,否则不要新增或扩展代码注释。
  10. 如果修改纯粹涉及字符串、数字、URL、数组、字典、去重、筛选或类似基础类型,先在代码库中查找已有的共享辅助函数位置,再将共享逻辑添加到那里。
  11. 重构后,如果代码库和工具链支持,运行一次快速的 jscpd 重复代码冒烟测试。
  12. 如果代码看起来需要使用 as,请先停下来,寻找更安全的类型处理或验证方式。
  13. 在新增辅助函数或抽象之前,先在代码库中查找可复用的现有模式。
  14. 如果多个模块中存在相似逻辑,优先提取共享的不变量或边界适配器,而不是再添加一份副本。
  15. 如果有子 Agent 可用,在引入新抽象前,使用子 Agent 检查代码库中的现有辅助函数、重复转换和重构机会。
  16. 对于写入范围明确的多文件重构,使用子 Agent;但不要创建范围过广、可重复使用的工具,除非确实存在重复逻辑。
  17. 如果某个抽象只是为了通用地“清理”值,请停下来检查真正的修复是否应放在有类型的边界处。

警示信号

  • JSON.parse(...) as Foo
  • Number(value) || 0
  • 没有产品层面理由的 input.trim().toLowerCase()
  • 在业务逻辑中反复使用 foo ?? default
  • 范围含糊的通用 sanitize* 或 normalize* 辅助函数
  • 应拆分为小型可组合函数的大型功能局部辅助函数
  • 应放在共享辅助文件中的新功能局部字符串、数字、URL、数组或字典辅助函数
  • 多个文件中重复的边界转换
  • 没有充分理由却同时包含 null 和 undefined 的领域类型
  • 在各层之间传递宽泛的接口或类,而这些层只需要其中一两个方法
  • 未经用户明确要求而新增或扩展的代码注释
  • 所描述的代码变更后仍未更新的过时注释

审查清单

  • 是否使用 Zod 验证原始 JSON,而不是进行类型断言?
  • 函数是否足够小且可组合?
  • 是否在边界处尽早且只解析一次?
  • 是否将边界 DTO 或视图形状一次性转换为内部领域类型?
  • 是否在边界处处理外部载荷的差异,而不是让这种差异渗入内部逻辑?
  • 代码是否有意区分使用 null 和 undefined,而不是在领域代码中混用二者?
  • 代码是否在缺乏明确边界或产品理由的情况下规范化值?
  • 如果需要规范化,代码是否保留原始值并添加派生键?
  • 仓库中是否已有可复用的辅助函数、转换器或适配器?
  • 对于基础字符串、数字、URL、数组、字典、去重或筛选逻辑,代码是否查找并使用了共享辅助文件,而不是在功能模块中重复实现?
  • 是否应提取重复的不变量或边界适配器,而不是再次复制?
  • jscpd 冒烟测试或针对性的 rg 搜索是否发现了值得重构的重复代码?
  • 是否在本应采用更严格检查或更完善类型的地方使用了 as?
  • 是否使用联合类型、Map、Set 或 as const 值等精确类型来建模领域不变量?
  • 代码是否仅依赖所需的最小能力范围?
  • 除非用户明确要求,变更是否避免添加或扩充代码注释?
  • 未要求注释时,是否删除了过时的现有注释,而不是重写?
  • 代码是否用宽松的默认值或静默回退掩盖了错误的假设?

评论

支持 Markdown