mcpField
C
code-quality

code-quality

github.com/alexanderstephenthompson测试与质量40 次安装
代码仓库

关于 code-quality

为 AI Agent 提供统一的测试、代码结构、错误处理和命名规范,适用于编码与重构,并以清单和详细规则指导执行。

主要功能

  1. 01四层测试验证框架
  2. 02测试驱动开发循环
  3. 03代码结构与命名规则
  4. 04具体错误处理指导
  5. 05编码与重构清单

适用场景

  1. 01实现前规划测试
  2. 02检查代码质量问题
  3. 03重构不一致的代码

安装

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

$npx skills add https://github.com/alexanderstephenthompson/claude-hub --skill code-quality

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

包含的文件

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

assets/
docstring-templates.md
tdd-checklist.md
code-review-checklist.md
references/
naming-reference.md
error-handling-reference.md
testing-reference.md
scripts/
check_naming.py
check_complexity.py

详细介绍

代码质量标准

不容妥协的代码质量标准。这些不是偏好,而是要求。

问题

AI Agent 会生成能运行但风格不一致的代码。每次会话都会选择略有不同的命名、嵌套模式和错误处理方式——这并非有意为之,而是各自独立的结果。随着时间推移,这种偏差会不断累积:代码库中会出现三种错误处理方式、四套命名约定,以及散落各处、几乎一致却又不完全匹配的魔法数字。这些标准会将偏差降至零,让重构保持小规模且不频繁。

使用方式

  • 构建者: 编写代码前先阅读 ## Builder Checklist。如果检查清单中的条目含义不明确,请参阅叙述性章节。
  • 重构者: 将 ## Enforced Rules 用作问题清单。阅读叙述性章节以了解修复指导。
  • 两者: 叙述性章节是权威标准。检查清单和规则表是对相同内容的精简呈现。

测试驱动开发:4 层验证框架

第 1 层 — 单元测试(逻辑是否正确?)

单独测试函数、计算、状态转换和数据格式化。纯输入/输出——给定此输入,能否得到此输出?对被测代码周围的一切进行模拟。这些测试快速、成本低,数量也应该最多。覆盖:正常路径、当前要实现的特定场景,以及边界情况。

第 2 层 — 集成测试(各部分是否衔接?)

测试多个部分如何协同工作。API 调用是否返回预期的数据结构,组件是否使用正确的 props 渲染,数据库查询是否返回正确数据,钩子和状态管理是否正确连接,各模块之间是否正确传递数据。这些测试能发现“单独运行正常,组合起来就出问题”的情况。

第 3 层 — 行为测试(流程是否正常?)

使用 Playwright 或类似自动化工具,端到端测试完整的面向用户流程。模拟真实交互——点击此处、确认出现预期结果、跳转至此处、表单是否正确提交、数据是否显示在正确位置。同时测试相邻流程,确保没有引入回归。这些测试速度较慢,也更脆弱,但能发现其他层级遗漏的问题。

第 4 层 — 人工验证(实际体验是否合适?)

验证自动化测试无法判断的所有内容:视觉效果是否正确、动画是否流畅、用户体验是否直观、屏幕上的数据是否符合现实预期、在实际设备和屏幕尺寸上是否能正常运行。第 1 至第 3 层通过后,根据变更内容生成具体的人工检查清单,而不是使用通用检查项。如果工作涉及图表组件,请人工检查图表;如果涉及 API 集成,请验证真实数据是否顺利流转;如果涉及布局,请在实际屏幕宽度下检查。

TDD 原则

真正的 TDD 会在编写任何实现代码之前,先编写第 1 至第 3 层的测试。所有测试一开始都应该失败——这正是目的。测试定义了要构建的内容。然后编写代码,直到测试通过。第 4 层是所有其他测试通过后的最终关卡。

每次变更都遵循以下循环:

  1. 红 — 编写一个失败的测试
  2. 绿 — 编写通过该测试所需的最少代码
  3. 重构 — 在不改变行为的前提下清理代码
  4. 在第 1 层重复,直到单元完成;然后进行第 2 层,再进行第 3 层
  5. 所有自动化测试通过后,第 4 层是最终关卡

拿不准时: 放慢脚步,编写测试,并做出尽可能小的变更。

什么是好的测试

每个测试都按 Arrange-Act-Assert 结构编写:

def test_apply_discount_reduces_total():
    # Arrange — set up the scenario
    cart = Cart(items=[Item(price=100)])
    discount = Discount(percent=20)

    # Act — perform the action under test
    cart.apply_discount(discount)

    # Assert — verify the outcome
    assert cart.total == 80

每个测试只验证一个概念。 如果测试名称中有“和”,就把它拆分成两个测试。

测试名称应描述行为,而非实现:

不佳良好
test_calculatetest_calculate_total_sums_item_prices
test_errortest_negative_quantity_raises_validation_error
test_user_servicetest_deactivated_user_cannot_place_order

测试必须:

  • 相互隔离 — 测试之间不能依赖彼此的状态或执行顺序
  • 结果确定 — 相同输入得到相同结果。不能依赖随机性、时钟或网络调用。
  • 运行快速 — 单元测试应在几毫秒内完成。如果运行缓慢,它就不是单元测试。
  • 易于阅读 — 无需阅读测试主体,仅凭失败的测试名称就应能知道哪里出了问题

代码结构

优先使用提前返回,避免嵌套

先写保护子句,简化控制流。

不佳 — 嵌套过多,难以理解:

def process(order):
    if order:
        if order.items:
            if order.is_valid:
                return calculate_total(order)
    return None

良好 — 结构平坦,清晰明了:

def process(order):
    if not order:
        return None
    if not order.items:
        return None
    if not order.is_valid:
        return None

    return calculate_total(order)

嵌套深度最多为 3 层。 如果更深,就提取为函数。

函数长度

函数应只做一件事。如果需要用注释在函数内划分“部分”,就应该把这些部分拆成独立函数。

参考指南:

  • 如果函数超过约 30 行,考虑是否可以提取其中的逻辑
  • 如果函数的参数超过 3-4 个,它很可能承担了太多职责
  • 如果无法为函数起一个清晰的名称,它承担的职责就太多了
单一职责

每个函数、类和模块都应该只有一个变更原因。

异味:“这个函数同时处理验证、格式化和保存。” **修正:**拆分为三个函数——validate、format、save。

导入规范
  • 不保留未使用的导入 — 删除它们,不要注释掉
  • 不使用通配符导入 — from x import * 会掩盖名称的来源
  • 对导入分组 — 先是标准库,再是外部依赖,最后是内部模块,组间用空行分隔
# Bad — unordered, wildcard, unused
from utils import *
import os
import requests
from collections import OrderedDict  # unused

# Good — grouped, explicit, no dead weight
import os

import requests

from app.models import User
from app.services import send_welcome_email
死代码

没有被调用的代码就不存在。删除它——版本控制会保留记录。

  • 不保留被注释掉的代码 — // old version、# was: ...、/* removed */ 都是杂乱内容
  • 不保留无法到达的分支 — 无条件返回后的无用 else、未使用的变量
  • 不保留占位注释 — // TODO: removed 或 // no longer needed,直接删除整行即可
明确胜过巧妙

可读性胜过简洁。把操作拆分成清晰的步骤。

不佳 — 写法巧妙,但难以调试:

names = [u.name for u in users if u.is_active and u.role in allowed]

良好 — 意图清晰,便于调试:

active_users = filter_active(users)
authorized_users = filter_by_role(active_users, allowed)
names = extract_names(authorized_users)

如果单行代码需要费心理解,就把它拆开。优化代码时要优先考虑读者,而非编写者。


错误处理

尽早失败

在边界处验证输入。不要让错误数据深入系统。

def create_user(email, name):
    if not email:
        raise ValidationError("Email is required")
    if not is_valid_email(email):
        raise ValidationError("Invalid email format")

    return save_user(email, name)
使用具体错误,而非通用错误

捕获预期的错误。对未预期的错误重新抛出。绝不要写 except Exception — 先识别实际的失败类型。

根据操作选择匹配的异常类型:

操作捕获原因
打开、读取或写入文件OSError涵盖 FileNotFoundError、PermissionError、IsADirectoryError
读取文件并解析内容(OSError, UnicodeDecodeError)文件可能存在,但包含无效编码
解析 JSON/YAML(json.JSONDecodeError, ValueError)内容格式错误
将字符串转换为数字ValueError格式无效
访问字典或列表(KeyError, IndexError)缺少键或索引越界
网络请求(ConnectionError, TimeoutError)网络相关故障
执行子进程(subprocess.SubprocessError, OSError)进程启动或执行失败
正则表达式操作re.error模式无效

不佳 — 吞掉所有错误:

try:
    do_risky_thing()
except Exception:
    pass

良好 — 处理能够理解的错误:

try:
    do_risky_thing()
except NetworkError:
    return retry()
except ValidationError as e:
    return error_response(e.message)
# Unexpected errors propagate up

可以使用宽泛捕获的情况: 仅限应用程序的顶层边界(CLI main()、API 请求处理程序),此时替代做法是让程序因未处理的错误而崩溃。即便如此,也要在继续执行前记录完整异常。

绝不吞掉错误

如果捕获了错误,必须执行以下操作之一:

  1. 处理错误——采取有意义的恢复措施
  2. 记录并重新抛出——让失败清晰可见
  3. 转换错误——为调用方包装成更具体的错误

空的 catch / except 块就是 bug。


命名约定

名称必须清楚地表达:

  1. 谁在执行操作——执行操作的主体
  2. 正在执行什么操作——描述行为的动词
  3. 数据或所有权的流向——数据流向何处或来自何处
明确流向

使用介词(to、from、into、onto)或具名参数。

不佳——含糊不清:

shop.buy_item(item_id, buyer)      # Who is buying?
transfer(amount, account)           # Transfer to or from?

良好——清晰明确:

shop.sell_item_to(item_id, buyer)  # Shop sells TO buyer
shop.sell(item_id, to=buyer)       # Named parameter clarifies
transfer_from(account, amount)      # Direction explicit
account.transfer_to(other, amount)  # Direction in method name
大声朗读测试

如果一个方法调用读起来不自然,那么它的名称就不合适。

# "shop buy item buyer" — confusing
shop.buy_item(item_id, buyer)

# "shop sell item to buyer" — clear
shop.sell_item_to(item_id, buyer)
布尔值命名

布尔值名称始终以 is、has、should、can、will 或 did 开头:

# Bad — ambiguous (is it a noun? a verb? a state?)
active = True
permission = True
refresh = True

# Good — clearly a yes/no question
is_active = True
has_permission = True
should_refresh = True
命名模式
模式适用场景示例
verb_noun_to(target)操作流向目标send_message_to(user)
verb_noun_from(source)操作来自来源receive_payment_from(customer)
noun.verb_to(target)对象向目标执行操作cart.transfer_to(order)
verb(noun, to=target)具名参数让含义更清晰assign(task, to=developer)
绝不使用缩写

每次都写出完整单词。唯一可接受的缩写是广泛认可的技术术语:id、url、api、db、io。

**禁止使用单字符变量。**循环计数器也不例外。i 和 j 会掩盖你正在遍历的内容:

# Bad — what is i? what is j?
for i in range(len(rows)):
    for j in range(len(columns)):
        grid[i][j] = calculate(i, j)

# Good — names describe the iteration
for row_index in range(len(rows)):
    for column_index in range(len(columns)):
        grid[row_index][column_index] = calculate(row_index, column_index)

常见违规——这些问题经常出现,必须始终展开:

写这个不要写这个
dependencydep
index / positionidx
sourcesrc
destinationdst
descriptiondesc
thresholdthresh
config / configurationcfg
messagemsg
requestreq
responseres
contextctx
errorerr
valueval
countcnt
buttonbtn
userusr
callbackcb
functionfn
managermgr
servicesvc
repositoryrepo
implementationimpl
passwordpwd
temporarytmp
numbernum

完整列表:references/naming-reference.md

避免
不要这样应改为
单字符名称(i、x、e)描述性名称(row_index、coordinate、error)
通用名称(data、list、temp)具体名词(user_data、order_list)
否定式布尔值(is_not_disabled)肯定形式(is_enabled)

常量与清晰度

不要使用魔法值

每个数字和字符串字面量都应该有名称。编写任何字面量前,先进行提取测试:

**提取测试:**如果字面量不是 0、1、-1、True、False、None 或 "",就需要为其定义具名常量。

包括:

  • 阈值和限制——MAX_RETRIES = 3、HIGH_COUPLING_THRESHOLD = 10
  • 大小和度量值——MIN_FONT_SIZE_PX = 12、MASK_VISIBLE_CHARACTERS = 4
  • 字符串模式——DEFAULT_ENCODING = "utf-8"、CSV_DELIMITER = ","
  • 配置值——TOP_RESULTS_DISPLAY_LIMIT = 20、SCAN_DEPTH = 3

根据常量的含义命名,而不是根据它的值命名。THREE = 3 毫无意义。MAX_RETRIES = 3 则表达了意图。

不佳:

if retry_count > 3:
    sleep(60)
if len(results) > 20:
    results = results[:20]

良好:

MAX_RETRIES = 3
RETRY_DELAY_SECONDS = 60
TOP_RESULTS_DISPLAY_LIMIT = 20

if retry_count > MAX_RETRIES:
    sleep(RETRY_DELAY_SECONDS)
if len(results) > TOP_RESULTS_DISPLAY_LIMIT:
    results = results[:TOP_RESULTS_DISPLAY_LIMIT]

将常量放在模块顶部,按用途分组,并置于所有函数定义之前。

布尔参数

布尔参数会让调用处的含义变得模糊。

不佳——True 表示什么?

create_user(data, True, False)

良好——使用具名参数或选项:

create_user(data, send_welcome=True, require_verification=False)

如果语言不支持具名参数,请使用选项对象/结构体。

默认保持不可变

默认使用不可变数据。只有确实需要重新赋值时,才使用可变绑定。

  • 使用 const(JS/TS)、final(Java/Dart)、readonly(C#)或等效方式
  • 仅当值必须变化时才使用 let / var(循环计数器、累加器)
  • 不要修改函数参数——改为返回新值
# Bad — mutates the input
def apply_discount(cart):
    cart["total"] *= 0.8
    return cart

# Good — returns a new value
def apply_discount(cart):
    return {**cart, "total": cart["total"] * 0.8}

文档

文档字符串

文档字符串是持续更新的文档。 公共 API 必须能够自我说明,无需阅读实现。

必需元素

每个公共函数、方法和类都必须包含:

  1. 用途 — 它的功能(一行)
  2. 参数 — 每个参数的类型和含义
  3. 返回值 — 返回什么,以及在什么情况下返回
  4. 副作用 — 任何状态变化、I/O 或修改
  5. 错误 — 可能出现的异常/错误
  6. 示例 — 展示常见场景的实际用法
文档字符串示例
def sell_item_to(self, item_id: str, buyer: Customer) -> Receipt:
    """Sell an item from shop inventory to a customer.

    Transfers ownership of the item from the shop to the buyer,
    processes payment, and updates inventory.

    Args:
        item_id: Unique identifier of the item to sell.
        buyer: Customer purchasing the item. Must have sufficient balance.

    Returns:
        Receipt containing transaction details and timestamp.

    Raises:
        ItemNotFoundError: If item_id doesn't exist in inventory.
        InsufficientBalanceError: If buyer can't afford the item.
        ItemAlreadySoldError: If item was sold between check and purchase.

    Examples:
        Basic sale:
        >>> shop = Shop(inventory=[item])
        >>> buyer = Customer(balance=100)
        >>> receipt = shop.sell_item_to(item.id, buyer)
        >>> assert receipt.amount == item.price
        >>> assert item.id not in shop.inventory

        Handling insufficient balance:
        >>> poor_buyer = Customer(balance=0)
        >>> shop.sell_item_to(item.id, poor_buyer)
        Raises InsufficientBalanceError
    """
文档字符串规则
  • 示例应与实际测试场景一致
  • 行为变化时更新文档字符串
  • 将文档字符串视为一等代码,而非装饰
注释

写任何注释之前,先进行删除测试: 在脑海中删除这条注释。是否会遗漏任何信息?如果代码通过命名和结构已经传达了相同信息,就不要写注释。

应该注释不要注释
原因 — 意图、业务原因、不明显的背景信息内容 — 代码已经说明了这一点
不明显的注意事项或边缘情况显而易见的操作
复杂算法概要用来解释糟糕代码的注释(应改进代码)
带有工单/问题引用的 TODO缺少背景信息的 TODO
正则表达式模式说明(该模式匹配什么)重述函数调用(# Send the email)

不好 — 重述代码:

# Get the users
users = get_users()
# Filter active users
active_users = filter_active(users)
# Count the results
count = len(active_users)

好 — 无需注释(代码本身已说明):

users = get_users()
active_users = filter_active(users)
count = len(active_users)

好 — 解释原因:

# Offset by 1 because CSS cascade position is 1-indexed but array is 0-indexed
cascade_position = file_index + 1

如果需要注释来解释代码的作用,就说明代码应该更清晰。 重命名变量、提取函数、简化逻辑——这样注释就不再必要。


构建者检查清单

在编写受本规范约束的代码之前,请根据这些约束检查你的计划。这是上文详细说明的相同指导原则,经过精简以便在构建前核对。构建者应在编写代码前阅读本节;重构者则应使用“强制规则”表和完整说明。

  • 在实现之前编写第 1–3 层(先让测试失败,再编写代码使其通过)
  • 第 1 层:单元测试覆盖逻辑、正常路径和边缘情况
  • 第 2 层:集成测试验证各部分正确连接
  • 第 3 层:行为测试端到端模拟真实用户流程
  • 第 4 层:根据变更内容生成由人工验证的检查清单
  • 每个测试只验证一个概念,并采用 Arrange-Act-Assert 结构
  • 测试彼此隔离、结果确定且运行快速
  • 函数简短且职责单一
  • 嵌套最多 3 层,并使用提前返回
  • 在边界处快速失败,并使用具体类型
  • 异常类型与操作相匹配(不得使用裸 except Exception)
  • 不得有空的 catch/except 块
  • 名称通过朗读测试
  • 不得使用单字符变量——应使用描述性名称
  • 不得使用缩写名称——写出完整单词(使用 dependency,而非 dep)
  • 方法名称应清楚表达方向(to/from)
  • 布尔值名称以 is/has/should/can 开头
  • 每个字面值都通过提取测试——若不是 0/1/True/False/None/"",就定义具名常量
  • 常量放在模块顶部,并按用途分组
  • 布尔参数使用具名参数或选项
  • 变量默认不可变(const/final/readonly)
  • 不得有未使用或通配符导入
  • 不得有注释掉的代码或死代码
  • 所有公共 API 都有完整的文档字符串
  • 注释通过删除测试——只解释原因,绝不解释内容

强制规则

这些规则由 check.js(clean-team)确定性地检查。更新这些标准时,请同步更新对应的 check.js 规则,反之亦然。

规则 ID严重级别检查内容
no-debugger错误代码中残留的 debugger 语句
no-var错误var 声明(请使用 const/let)
no-empty-catch错误没有处理逻辑的空 catch 块
no-console警告console.log/warn/error 语句
no-double-equals警告使用 ==/!= 而非严格相等运算符(允许 == null)

参考资料

  • references/testing-reference.md — 深入了解测试金字塔、模拟指南和反模式
  • references/naming-reference.md — 完整的命名约定、缩写规则和领域命名
  • references/error-handling-reference.md — 错误层级、重试/回退模式和错误边界

资源文件

  • assets/tdd-checklist.md — 逐步 TDD 工作流检查清单
  • assets/docstring-templates.md — 可直接复制粘贴的文档字符串模板(Python、JS/TS、C#、Rust、Go)
  • assets/code-review-checklist.md — 全面的代码审查检查清单

模板

特定领域的代码质量扩展。每个文件都针对一种项目类型,提供相同类别(结构、命名、测试、错误处理、性能)的脚手架:

  • templates/web-development.md — Web 专属质量规则
  • templates/data.md — 数据管道和分析质量规则
  • templates/world-building.md — Unity 和 VRChat 质量规则

脚本

  • scripts/check_naming.py — 验证任何代码库中的命名约定
  • scripts/check_complexity.py — 检查函数长度、嵌套深度和参数数量

评论

支持 Markdown