mcpField
A
awesome-code-standards

awesome-code-standards

github.com/khasky开发2263 次安装1 周前更新
代码仓库

关于 awesome-code-standards

一项用于在项目和模块中统一编码约定的技能。它帮助开发者发现现有做法,并据此规范命名、结构、错误处理、类型、并发和文档。

主要功能

  1. 01发现项目现有约定
  2. 02命名与结构指导
  3. 03不可变性与错误处理规范
  4. 04类型安全与并发指导
  5. 05注释和文档规范

适用场景

  1. 01启动项目或模块
  2. 02让重构符合团队约定
  3. 03设置 Lint、格式化或类型检查规则
  4. 04为新成员整理编码约定

安装

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

$npx skills add https://github.com/khasky/awesome-agent-skills --skill awesome-code-standards

安装后在 Claude Code 中输入/awesome-code-standards确认已加载。

附带文件evals/eval_queries.json

详细介绍

编码规范

遵循统一的命名、结构和模式,让团队中的代码更易读、更易维护。

适用场景

  • 启动新项目或模块
  • 重构以符合团队规范
  • 设置或更新 lint、格式化或类型检查规则
  • 确定代码库应遵循哪些规范(审查特定差异应使用 awesome-code-review)
  • 新人入职:记录或应用编码规范
  • 强制保持命名、格式或结构一致

核心原则

  1. 可读性优先——代码被阅读的次数多于编写次数;清晰的命名和结构胜过巧妙的技巧。
  2. KISS——选择可行的最简单方案;避免过度设计和过早优化。
  3. DRY——将通用逻辑提取到函数或模块中;避免复制粘贴。
  4. YAGNI——不要为推测的未来需求构建功能;有需要时再增加复杂性。
  5. 不可变性——优先使用 const;避免修改参数或共享状态;必要时使用展开或复制。

工作流程(应用规范时)

  1. 了解项目规范——检查现有代码:命名方式(camelCase 还是 snake_case)、文件布局、导入风格、测试模式。查看 CONTRIBUTING、.eslintrc、.prettierrc 或 editorconfig。按维度统计(引号、缩进、命名格式):占多数的变体即为规范,少数变体记为问题;若票数相同,则询问而不是自行决定。
  2. 识别违规项——将已更改或新增的代码与这些规范及下方规则进行比较。
  3. 提出具体修复方案——重命名符号、提取函数、添加类型、修正格式。每条建议尽量只包含一项逻辑编辑。
  4. 记录例外情况——如果项目有例外规定(例如“此处为兼容旧代码而使用 any”),应注明,而不是不加说明地“修复”。

命名规范

变量和函数
// GOOD: Descriptive, verb-noun for functions
const marketSearchQuery = 'election';
const isUserAuthenticated = true;
async function fetchMarketData(marketId: string) {}
function calculateSimilarity(a: number[], b: number[]) {}

// BAD: Unclear or noun-only for actions
const q = 'election';
const flag = true;
async function market(id: string) {}
function similarity(a, b) {}
常量
  • 真正常量使用 UPPER_SNAKE(例如 MAX_RETRIES、API_BASE_URL)。
  • 或遵循项目规范(某些代码库对配置对象使用 camelCase)。
类型和接口
  • 使用 PascalCase:User、OrderItem、ApiResponse<T>。
  • 如有助于说明用途,可添加角色后缀:CreateUserRequest、UserResponse。
文件
  • 组件:PascalCase(Button.tsx、UserProfile.tsx)。
  • 工具函数/钩子:camelCase(formatDate.ts、useAuth.ts)。
  • 类型:根据项目使用 camelCase 搭配 .types 或 .d(market.types.ts)。
其他语言

以上示例使用 TypeScript;规则是“遵循该语言自身发布的规范”,且各语言的规范各不相同。各语言的风格指南优先于此处展示的大小写格式:

  • Python(PEP 8)——函数、变量和模块使用 snake_case;类使用 PascalCase;常量使用 UPPER_SNAKE;前导 _ 表示非公开。
  • Go(Effective Go)——使用 MixedCaps,绝不使用下划线;首字母大写 即为 导出标记。包名是名称的一部分:使用 chi.NewRouter,而不是 chi.NewChiRouter。
  • Rust(RFC 430)——函数、变量和模块使用 snake_case;类型和 trait 使用 UpperCamelCase;const 和静态变量使用 SCREAMING_SNAKE_CASE。
  • Java / Kotlin——方法和字段使用 camelCase,类型使用 PascalCase;每个文件放置一个公开类型,文件名与之相同。
  • C#——方法、属性和类型使用 PascalCase;局部变量和参数使用 camelCase;接口名称以 I 为前缀。

不可变性(关键)

// GOOD: Spread and new references
const updatedUser = { ...user, name: 'New Name' };
const updatedArray = [...items, newItem];

// BAD: Direct mutation
user.name = 'New Name';
items.push(newItem);

其他语言——规则相同,机制不同:

  • Python — 切勿修改调用方仍持有的参数,也切勿使用可变默认值(def f(xs=[]) 会让多次调用共用一个列表;应使用 None 并在函数内创建)。固定集合优先使用元组和 frozenset;修改副本可使用 dataclasses.replace(obj, field=…)。
  • Go — 存储切片时,它仍使用调用方的底层数组,因此后续 append 可能会写入该数组;保留切片前先复制(slices.Clone)。对于不应修改数据的方法,使用值接收者。
  • Rust — 绑定默认不可变,借用检查器会强制执行;mut 是需要说明理由的例外,而不是默认写法。
  • Java — 数据载体使用 record,在边界处使用 List.copyOf/Map.copyOf 创建防御性副本。
  • C# — 使用 record 类型和 with 表达式;共享集合使用 ImmutableArray/ImmutableList。

错误处理

// GOOD: add context, keep the cause; log once at the boundary
async function fetchData(url: string) {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }
    return await response.json();
  } catch (error) {
    throw new Error(`Failed to fetch ${url}`, { cause: error });
  }
}

// BAD: No handling or swallowed errors
async function fetchData(url: string) {
  const response = await fetch(url);
  return response.json();
}

其他语言——形式各不相同,但“补充上下文,绝不吞掉错误”的原则不变:

  • Python — 抛出特定的异常类型,绝不使用裸 except:(它也会捕获 KeyboardInterrupt)。重新抛出时保留异常链:raise ParseError(...) from err。
  • Go — 错误是值:检查每个错误,并使用 %w 添加上下文,以便 errors.Is/errors.As 仍能正常工作(fmt.Errorf("fetch %s: %w", url, err))。使用 _ = 丢弃错误时,必须添加注释说明为何安全。
  • Rust — 返回 Result<T, E> 并使用 ? 传播错误;在库边界使用带类型的错误枚举(或 thiserror),仅在二进制程序中使用 anyhow。在库代码中使用 unwrap()/expect(),就等于发布了一个会触发 panic 的程序。
  • Java — 每种失败模式使用一种异常类型,绝不留下空的 catch;保留原因(throw new X(msg, err))。
  • C# — 捕获特定异常,并使用裸 throw; 重新抛出(不要使用 throw ex;,它会重置堆栈跟踪)。

异步与并发

// GOOD: Parallel when independent
const [users, markets, stats] = await Promise.all([
  fetchUsers(),
  fetchMarkets(),
  fetchStats(),
]);

// BAD: Sequential when unnecessary
const users = await fetchUsers();
const markets = await fetchMarkets();
const stats = await fetchStats();

其他语言——让相互独立的工作并发执行,并确保每个并发单元都有取消机制:

  • Python — 对相互独立的 await 使用 asyncio.gather(*coros);当某项失败时应取消其他任务,则使用 asyncio.TaskGroup(3.11+)。阻塞调用应放入 run_in_executor,绝不在协程中内联执行。
  • Go — 对必须作为整体失败的扇出任务使用 errgroup.Group(golang.org/x/sync),对无需作为整体失败的任务使用 sync.WaitGroup。任何可能阻塞的函数都要将 context.Context 作为第一个参数,并将其向下传递,而不是存储在结构体中。
  • Rust — 对相互独立的 future 使用 tokio::join!,当第一个错误应中止其他任务时使用 try_join!;拆除任务时使用 CancellationToken 或丢弃 JoinHandle。
  • Java — 使用 CompletableFuture.allOf(...),或在可用时使用结构化并发(StructuredTaskScope)。
  • C# — 使用 Task.WhenAll(...),并在每个异步签名中传递 CancellationToken。

类型安全

// GOOD: Explicit types, no any
interface Market {
  id: string;
  name: string;
  status: 'active' | 'resolved' | 'closed';
}
function getMarket(id: string): Promise<Market> { /* ... */ }

// BAD: any or untyped
function getMarket(id: any): Promise<any> { /* ... */ }

其他语言——目标相同:让无效值无法表示,并在边界处检查,而不是在每个调用点反复检查。

  • Python — 为所有公共签名添加注解,并在 CI 中以严格模式运行 mypy/pyright;如果没有任何检查器检查类型提示,那它就只是一条注释。在 API 和解析边界使用 TypedDict/dataclass/Pydantic 模型,而不是原始的 dict[str, Any]。
  • Go — 优先使用具体类型而非 any;接受接口,返回结构体。签名中的 any 表示解析边界,必须立即使用类型分支或 errors.As 处理。
  • Rust — 使用新类型而非裸原始类型(struct UserId(u64),而不是 u64),让编译器捕获参数顺序错误;使用枚举而非依赖字符串表示的状态。
  • Java / Kotlin — 使用 Kotlin 的可空类型,或在 Java 中使用 Optional 和可空性注解;公共签名中不要使用 Object。
  • C# — 在整个项目中启用可空引用类型(<Nullable>enable</Nullable>),并将警告视为错误。

对于没有类型检查器的动态类型语言(纯 JS、Ruby、PHP、Lua),运行时也要完成同样的工作:在信任边界使用 schema 验证,内部调用则无需重复防护。

注释和文档

  • 解释原因,而非内容——写“使用指数退避以避免给 API 造成过大压力”,而不是“增加重试计数”。只复述代码的注释是噪声;删除它,或重命名代码,让注释变得多余。
  • 使用纯 ASCII 标点——像开发者平时输入那样写注释:用 -,而不是 —;用 ...,而不是 …;使用直引号,不用装饰性符号或表情符号。注释中出现排版用符号是 AI 生成的迹象,不符合项目风格。(仅限注释文本——绝不适用于字符串字面量、标识符或数据。)
  • 注释文本使用朴素动词——注释中绝不使用一份封闭清单里的借用措辞:有朴素动词可用时,不用那些煞有介事的动词。清单及其替代词见 awesome-humanize-en/references/lexicon.md 的 Borrowed diction 部分,其中还列出了某些词属于产品自有词汇的领域。即使没有该技能,这条规则仍然适用:有更朴素的动词时,注释就用更朴素的动词。(仅限注释文本——在字符串字面量或产品数据中,这些词属于内容。)
  • 注释不要复述代码声明的值——注释应说明某个值的用途,而不是它是什么:不要写 border-radius 简写各长度的值、带引号的 max-height、颜色、超时、与常量相同的裸数字,也不要写源码中已有的选择器、类名或存储键。代码重命名时,注释是唯一不会随之更新的副本。仓库中未声明的值(例如供应商限制或协议固定宽度)可以写明,并说明其固定依据。
  • 不要使用分节分隔线——例如 // --- HTTP、// ==== Helpers ==== 及类似写法:用空行分隔代码块;如果文件需要目录,就应该拆分。
  • 注释中的列表顶格书写——标记后空一格,换行后从标记所在位置开始,不要用 // 制造悬挂缩进。反映所记录内容实际嵌套关系的缩进属于内容,应保留。
  • 不要声称某种选择是有意为之——deliberately、intentionally、by design 只是在断言意图,只有原因才能证明这一点;写明原因,删掉这些词(lexicon.md,Borrowed diction)。
  • 文档注释应说明类型未表达的契约——包括引发的错误、副作用、单位、顺序、特殊值(null、0 或空值的含义),以及调用方式不明显时的示例。对于已发布的库或 SDK 接口,应提供更多文档;对于内部辅助函数或普通数据类型,几乎无需文档。只有当 @param 和 @returns 补充了名称和类型之外的信息时才使用;重复签名的文档块是噪声。使用语言自身的格式,并遵循项目风格:JSDoc/TSDoc、Python 文档字符串(PEP 257,采用项目使用的 Google/NumPy/reST 风格)、以符号名称开头的 Go 文档注释、带 # Examples 部分的 Rust ///、Javadoc、C# XML 文档注释。
  • 不要保留注释掉的代码——删除它,或在工单中说明原因;用版本控制保存历史。

这就是现在编写或修改代码时应达到的标准。若要全面检查仓库中的现有注释——决定删除、精简或修正哪些注释,并处理误报边界及保持行为不变的验证关卡——请使用负责该流程的 awesome-code-cleanup。

文件和项目结构

  • 遵循现有布局(例如 src/app/、src/components/、src/lib/)。
  • 每个文件最多导出一个主要对象,除非项目使用 barrel 文件或 index 再导出。
  • 对导入分组:标准库 → 第三方 → 本地;按字母顺序或项目规定的路径顺序排列。

后端分层与边界(适用时)

  • 三模型拆分——将 DTO/API 模型、领域模型和持久化/ORM 模型分开。让同一个 User 对象流经传输层、业务逻辑和存储层,会导致 API 结构与表结构耦合,任何重构都要改动每一层。
  • 层级放置启发法——需要 HTTP 状态码 → 边界/控制器;需要业务规则 → 服务;需要表/索引/ORM → 仓库。流程保持单向:controller -> service -> repository -> gateway。
  • 在边界处统一处理横切关注点——身份验证、校验、速率限制、请求 ID、日志都应放在 HTTP 流水线中(全局中间件/钩子或路由级设置),不要在每个处理器中手动复制。原则:不要在每个端点中手动重复策略。
  • 错误不应了解传输层——服务和仓库抛出领域错误;由一个全局处理器将其映射为状态码。
  • 契约优先——OpenAPI(或同等规范)是请求/响应结构的唯一真实来源;据此生成类型化客户端,并在 CI 中检测规范偏差,发现偏差即失败。
// GOOD: service throws a domain error; the global handler maps NotFoundError -> 404
throw new NotFoundError('market', id);

// BAD: business logic reaches into HTTP transport
return res.status(404).json({ error: 'not found' });

本节只负责放置各层。错误封装格式、HTTP 状态码映射和重试策略的深入设计属于 awesome-error-standards 的工作——如果任务是设计错误契约本身,请前往那里。

需要修复的代码异味

异味操作
函数超过约 50 行拆分为名称清晰的较小函数
嵌套过深(5 层以上)使用提前返回或提取函数
魔法数字提取为命名常量(例如 MAX_RETRIES、DEBOUNCE_MS)
参数列表过长使用选项对象或拆分为较小类型
两处存在重复逻辑提取为共享函数或模块

上述代码异味适用于任何语言。数值阈值只是起点,并非铁律;始终以仓库中有文档记录的规范为准,也不要重复标记 linter 或类型检查器已强制执行的问题。在引入新模式前,先搜索代码库——问题往往已在某处解决;应复用现有方案,而不是再添加一种解决同一问题的方式。

框架特有的正确性(适用时)

这与上述通用代码异味不同:每个框架都有一小份容易踩的陷阱清单,这些写法看似正确,运行时却会失败。应了解当前框架的清单,不要假设其他框架的清单也适用——权威来源是该框架自己的文档(React 的 Hooks 规则和“You Might Not Need an Effect”、Vue 的响应性注意事项、Angular 的变更检测指南、Svelte 的 store 和响应性说明)。

以 React 为例:

异味操作
JSX 中的 {count && <X/>}值为假时会渲染字面量 0/NaN——请使用显式三元表达式 count > 0 ? <X/> : null
在另一个组件内部定义组件将其提取到外部——嵌套定义每次渲染都会创建新类型并重新挂载,导致状态丢失
将可从 props/state 推导的状态保存在 useState+useEffect 中在渲染期间推导(或使用带 key 的重置)——无需使用 effect

可迁移的部分是这类问题的形态,而不是表格中的这三行:假值渲染为可见输出、标识每次渲染都会变化并悄然丢弃状态,以及状态被重复保存而非推导。无论项目使用什么框架,都要留意这类问题。

前端渲染与动效(适用时)

  • 只对合成器属性进行动画处理——transform 和 opacity。绝不要对布局属性(width、height、top、margin)做动画处理——它们会在每一帧触发回流;位置变化请使用 FLIP 技术。
  • 不要在同一帧中交替读取和写入布局——先批量读取(getBoundingClientRect、offsetWidth),再进行写入,避免布局抖动。
  • 对于与滚动关联的动画,优先使用 animation-timeline: view()/scroll(),而不是 JS 滚动事件监听器;谨慎使用 will-change,并在之后移除。
  • 合理的默认设置:标题使用 text-balance,正文使用 text-pretty,数字列使用 tabular-nums,优先使用 h-dvh 而非 h-screen,交互响应时间控制在约 200ms 以内,每个视图使用一种强调色,并采用固定的 z-index 层级。

前端架构(适用时)

  • 按功能划分文件夹——产品代码(页面、功能组件、状态、功能范围内的 API 适配器、测试)应放在同一个功能/模块文件夹中。共享文件夹只放跨应用的基础组件:设计系统、应用外壳、路由引导、全局配置、国际化。反模式:没有边界的 shared/common/utils 大杂烩;庞大的全局 components/;为一个一次性文件单独建文件夹。
  • 就近放置——重要组件或 hook 应将其测试、故事、样式和 index.ts 再导出文件放在旁边。
  • 路由级代码拆分是默认的性能优化——延迟加载路由代码块;如果落地页不需要仪表盘级依赖,就不要将它们放进初始包中。手动优化组件前先进行性能分析。(这与上文的合成器/动画规则不同——这里关注的是包结构,而非帧预算。)
  • 使用 API 契约,不要重新手写类型——从后端维护的同一份 OpenAPI 规范生成 TypeScript 类型,而不是手动重复定义请求/响应结构。使用服务端状态库(例如 TanStack Query),统一处理加载、错误和重试。集中映射错误(使用单个 parseApiError),并在错误界面中显示服务端的 request_id,以便用户报告能与服务端日志对应起来。

检查清单(强制执行时)

  • 命名符合项目规范(camelCase/PascalCase/snake_case)
  • 不直接修改参数或共享状态
  • 处理错误并附带上下文传递
  • 避免不必要的 any;在边界处显式声明类型
  • 为公共 API 编写文档(使用该语言的文档注释格式或项目标准)
  • 文件和结构符合现有布局
  • 不使用魔法数字;为常量命名
  • 通过 lint 和格式化规则检查(如果项目有相关规则)

反模式

反模式更好的做法
“这只是个小脚本”使用同样的命名和结构;未来的维护者会感谢你
将代码注释掉“留待以后”删除;使用 git 历史或工单
只修复范围内的文件如果涉及某种模式,应建议在整个项目中统一规范,或安排后续跟进
添加没有工具支持的样式规则将规则放入语言的格式化工具和 linter 中,确保格式自动统一(gofmt、ruff/black、rustfmt+clippy、ktlint、dotnet format、ESLint/Prettier),并使用 .editorconfig 处理跨语言的设置

集成

  • 如果项目有风格指南、CONTRIBUTING 或 lint/格式化配置,请优先遵循。只有在用户明确要求时才覆盖。
  • 提出具体的修改建议(重命名、提取函数、添加类型),不要只罗列规则。

评论

支持 Markdown