# Code Standards

> 代码规范与完整开发工作流，涵盖 Python、PyTorch/深度学习、Shell 脚本、配置文件。贯穿始终的四条设计原则：易读直观、拒绝过度抽象、单一职责、命名自解释。强制三段式流程：（1）写代码前——在已有项目上开发时必须先了解项目全貌避免重复实现，然后先出 plan（思路+实现步骤）并获得用户批准才能开工；（2）写代码中——遵循命名/类型/docstring/张量操作/异常处理等规范，同时时刻守住四条设计原则；（3）写代码后——必须先补齐对应单元测试并本地跑通，再主动调用 simplify 简化新代码，最后调用 review 做审查。触发条件：用户要求写/生成/重构 Python 或 PyTorch 代码；用户要求在已有项目/代码库上开发；用户创建新模块、脚本或训练循环；用户说"按代码规范来"或引用 /code-standards。

- Skill: `tencent-hunyuan/code-standards` (Agent Skill)
- Install (CLI): `npx skillmds@latest add tencent-hunyuan/code-standards`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tencent-hunyuan/code-standards/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tencent-hunyuan (https://skillmd.com/u/tencent-hunyuan)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tencent-hunyuan/code-standards

---


# 代码规范（Code Standards）

本项目通用的编码规范 **+ 完整开发工作流**。不仅规定写出来的代码长什么样，还规定**写之前**要做什么、**写之后**要做什么。

## 使用方式（三段式工作流）

本 skill 强制以下三个阶段，缺一不可：

### 阶段 A ── 写代码前
1. **项目全貌认知**（若在已有项目上开发）：必须先完成"§0 项目探索"中的检查，避免重复造轮子。
2. **规范预热**：快速浏览将要涉及的语言/领域对应章节（§1–§4），心里有数再动手。
3. **先 plan，再实施（强制 · 需用户批准）**：
   - **禁止**未出 plan 直接开写。除非任务是极小的改动（单行修复、typo、显而易见的调参）。
   - 动手前必须先向用户呈交一份 plan，内容见"§0.5 Plan 格式"。
   - **必须等到用户明确批准**（"可以"、"同意"、"开始吧"、"OK" 等）后才开始写代码。用户未回复前一律等待，不要擅自推进。
   - 若实施中发现 plan 与现实不符需要偏离，**暂停**，说明偏差并重新请求批准。

### 阶段 B ── 写代码中
- 遵循 §1–§4 的规范。
- **时刻牢记 §0.9 的四条设计原则**：易读直观、反对过度抽象、单一职责、命名自解释。
- 优先复用阶段 A 中识别到的项目已有工具/函数/基类，不要另起炉灶。
- 按已批准的 plan 推进；若需要临时改动范围，回阶段 A.3 重新请求批准。

### 阶段 C ── 写代码后（强制）
完成代码后**主动执行**，无需用户催促（见 §5 详细流程）：
1. **自检**本次变更，对照审查清单。
2. **补齐单元测试并跑通**（除非命中 §4.5.2 免测清单，且已明说）——没单测不能进下一步。
3. **调用 `simplify` skill** 对本次新增/修改的代码做简化与复用检查。
4. **调用 `review` skill** 做代码审查。
5. 按本文件定义的"**必改 / 建议改 / 可考虑**"三级汇总审查结论：
   - **必改（Must-fix）**——违反硬性规则（如可变默认参数、裸 `except`、硬编码设备、公共 API 缺少类型注解、功能缺失单测）。
   - **建议改（Should-fix）**——违反强推荐（命名、docstring 缺失、魔法数字、测试覆盖不足）。
   - **可考虑（Consider)**——依上下文而定的软建议。
6. **同步项目文档（强制）**——向用户汇报完本次结果后，把开发进展与"下次可 resume 的状态"写回项目文档目录（见 §5.1 第 6 步）。没这一步视为未完成。

审查时**不要静默改写代码**——先暴露问题，再修复用户确认的部分（或用户明确要求一并清理的必改项）。

---

## 0. 项目全貌认知（在已有项目上开发时强制执行）

**目标**：在已有代码库上动手前，先摸清项目结构，确保新代码与现有组件风格一致，并**优先复用已有函数/类**而非重新实现。

### 0.1 何时必须执行
- 在已有仓库中新增模块、函数、脚本。
- 修改涉及多个文件的跨模块功能。
- 用户未明确告知项目结构时。

### 0.2 必做检查清单
动手前按以下顺序完成，并把发现简要汇报给用户：

1. **顶层结构扫一遍**
   - 读 `README*`、`CLAUDE.md`（若存在）、`pyproject.toml` / `setup.py` / `requirements*.txt`。
   - `ls` 或 `Glob` 看顶层目录划分（`src/`、`scripts/`、`configs/`、`tests/` 等）。
   - 明确项目的包名、入口、主要依赖。

2. **定位相关领域代码**
   - 按关键词 Grep 搜索将要实现的功能名、相关动词/名词（如要写"采样器"就搜 `sampler`、`sample`、`Sampler`）。
   - 检查是否已有功能相近的实现——可能叫不同的名字。
   - 看 `__init__.py` 暴露了哪些公开 API。

3. **识别现有工具层**
   - 配置加载、日志、checkpoint、设备管理、分布式、数据加载——这些"基础设施"类功能**几乎必然已有实现**。不要重写。
   - 典型目录：`utils/`、`common/`、`core/`、`base/`。

4. **识别现有风格**
   - 抽样读 2–3 个同类型文件，注意：类型注解风格、docstring 风格、命名风格、错误处理方式、日志方式。
   - **你的新代码必须与现有风格一致**，即使现有风格与 §1–§4 默认推荐略有差异（以项目内一致性优先）。

5. **识别扩展点**
   - 是否有基类/抽象接口/插件注册机制需要遵循？（如 `BaseSampler`、`register_model` 装饰器等。）
   - 是否有约定好的配置 schema？

### 0.3 开工前的最小汇报
动手前，用一两段话简要告诉用户：
- 找到的相关现有代码（文件路径 + 函数名）。
- 计划复用什么、新增什么。
- 若发现可能重复实现的风险，**先询问用户**是复用已有实现、扩展它、还是确实需要新实现。

### 0.4 常见反模式（禁止）
- 未检索就新写一个 `load_config`、`set_seed`、`get_logger`、`save_checkpoint`——这些几乎都已存在。
- 新建一个 `utils.py` 或 `helpers.py` 放散装函数——先查现有 utils 目录。
- 在新文件里复制粘贴已有函数改个名字。
- 新增与现有风格不一致的模块（如项目全用 dataclass 你却写 Pydantic；项目全用 `logging` 你却用 `print`）。

### 0.5 Plan 格式（强制 · 动手前呈交，等待批准）

完成 §0.1–§0.3 的项目探索后，**必须**先向用户呈交一份 plan，获得明确批准后才能写代码。plan 用如下结构，保持简洁（通常 < 200 行）：

```
## 任务理解
<用一两句话复述需求，表明你理解的目标。>

## 项目现状（复用机会）
- 已有可复用组件：<文件:符号> —— <用途>
- 可扩展的基类/接口：<...>
- 参考的同类实现：<...>

## 实现思路
<核心设计/算法思路 2–5 句话，必要时画个数据流或调用链。>

## 实现步骤
1. <第一步：改/新增哪个文件，做什么>
2. <第二步：...>
3. ...

## 涉及文件
- 新增：<path/to/new_file.py>
- 修改：<path/to/existing.py>（修改点概述）

## 风险与取舍
- <列出与现有代码可能冲突的点、性能/内存假设、未决问题>
- <若有两种实现路径，给出推荐并说明理由>

## 待确认的问题（如有）
- <若有需要用户裁决的设计分歧，列在这里等回复>
```

**批准前禁止的动作**：
- 禁止调用 Edit / Write / NotebookEdit 类改写文件的工具。
- 禁止 `git add`/`git commit` 等修改仓库状态的操作。
- 只允许只读探索（Read / Glob / Grep / Bash 的只读命令）。

**何时可省略 plan**：
- 单行/几行的 typo 修复、显而易见的 bug 修复、参数微调。
- 用户已在本轮对话中给出具体到文件/函数级的指令（相当于他已经给了 plan）。
- 纯研究/探索类问题（不改代码）。

即使可省略正式 plan，仍应用一两句话简述你要做的事再动手。

---

## 0.9 核心设计原则（高于一切具体规则）

本节的四条是**第一性原则**——当具体规则与它们冲突时，以这四条为准。实施阶段（阶段 B）的每一行代码都要同时满足这四条；写代码后的 simplify/review 也围绕它们展开。

### 0.9.1 易读、直观
代码是写给人读的，顺便能被机器执行。
- **直线胜过曲线**：能用顺序/条件/循环表达的逻辑，不要用回调、装饰器链、元编程。
- **显式胜过隐式**：关键行为写在代码里，不要藏进 `__init_subclass__`、monkey patch、全局注册表里——除非项目已有此模式。
- **局部性**：读一个函数时所需的上下文应尽量都在它附近。参数显式传入比从 `self` 深处挖好；常量在模块顶部比散落各处好。
- **可扫描**：一眼扫过去能看到主流程。提前返回（early return）让嵌套变浅，胜过层层 `if/else`。
- 评判标准：**一个不熟悉这段代码的同事，能否在 30 秒内讲清它做了什么**？不能就重写。

### 0.9.2 有简单方案时，禁止引入复杂抽象（YAGNI）
- **禁止**提前抽象："以后可能要支持 X" 不是理由——**等真的要支持 X 时再抽象**。
- **禁止**为"对称/优雅"新建一堆基类、接口、工厂、策略——除非当下已有 **≥ 2 个真实实现**需要共用。
- **禁止**为唯一一次使用而建配置/插件系统。
- **一个 if/else 的策略选择，不需要策略模式**；一个参数化的函数，不需要工厂类。
- 抽象的代价是实际存在的：多一层跳转、多一份文档、多一个测试表面、多一处未来 bug 的藏身处。
- 判断公式：**抽象带来的收益 > 引入的阅读/维护成本**——不成立就不要抽。
- 如果你发现自己在写 `class AbstractBaseManagerFactory`，**停下**重新想一下。

### 0.9.3 函数/类的职责要单一（SRP）
- **一个函数只做一件事**：输入 → 一个清晰的输出/副作用。动词能描述完整行为就行。
- **一个类只有一个变化维度**：它的字段、它的方法应当围绕同一个概念。一个类既管数据加载又管日志格式化，是两个类。
- **副作用要集中**：读写文件、打日志、改全局状态的代码集中在明显位置，其他函数保持纯粹。训练循环这类天然副作用重的地方除外。
- **描述函数时必须用"and"才能说清 → 拆**。"它加载数据**并**归一化**并**写缓存"——三个函数。
- 判断信号：
  - 函数参数超过 6 个且彼此无关 → 可能在做多件事。
  - 函数里有大段互不引用的代码 → 它们应该是独立函数。
  - 类里的方法明显分成互不共享字段的两组 → 拆成两个类。

### 0.9.4 命名自解释
- **名字即文档**。读到一个名字就应知道它是什么、干什么，不必跳到定义。
- **具体 > 抽象**：`max_batch_size` 胜过 `limit`；`user_email_by_id` 胜过 `data`；`normalize_per_channel` 胜过 `process`。
- **命名要反映用途，不是类型或结构**：`users`（list of User）胜过 `user_list`；`is_ready`（bool）胜过 `ready_flag`。
- **禁止出现的名字**：`data`、`info`、`item`、`obj`、`tmp`、`result`、`helper`、`util`、`handle`、`do_stuff`、`process`、`manager`、`handler`——除非上下文已锁死含义。
- **布尔名用谓词**：`is_ready`、`has_children`、`should_retry`、`can_load` —— 不要用 `flag`、`status`。
- **函数名用动词**：`load_checkpoint`、`compute_loss`、`normalize_image`；**类名用名词**：`TrajectorySampler`、`CheckpointManager`。
- **缩写只用公认的**：`cfg`、`ctx`、`msg`、`df`、`idx`、`lr`、`bs` 可以；`usrmgr`、`calcval`、`procimg` 不行。
- **避免同义词漂移**：一个概念全项目只用一个名字。不要一会儿 `user_id`、一会儿 `uid`、一会儿 `account_id`。
- **测试一下你的名字**：把它念给另一个开发听，如果对方会问"这是什么意思？"，就换个名字。

### 0.9.5 冲突裁决
这四条原则偶尔互相拉扯，顺序如下：
1. **正确性** > 一切。
2. **易读直观（0.9.1）** > 抽象层级。
3. **单一职责（0.9.3）** 与 **YAGNI（0.9.2）** 同级：拆分是为了当下的清晰，不是为了未来的扩展。
4. **命名（0.9.4）** 贯穿所有层级——**再好的结构配上差名字也会变坏**。

---

## 1. Python

### 1.1 命名
- `snake_case`：函数、方法、变量、模块。
- `PascalCase`：类名。
- `UPPER_SNAKE_CASE`：模块级常量。
- `_前导下划线`：内部/私有名称；`__双下划线__`：仅用于协议方法。
- 避免单字母变量，除了循环索引（`i`、`j`）、数学约定（短作用域内的 `x`、`y` 表示张量可以接受）、或公认缩写（`df` 表示 dataframe）。
- 不要使用非通用缩写（`cfg`、`ctx`、`msg` 可以；`usrmgr`、`calcval` 不行）。

### 1.2 布局与格式
- 4 空格缩进，禁止 Tab。
- 行长度：软限制 100 字符；能提升可读性就提前换行。
- 顶层定义间隔两个空行；方法间隔一个空行。
- Import 顺序，每组之间空一行：
  1. 标准库
  2. 第三方库
  3. 本地/第一方模块
- 一行一个 import。常用名称优先用 `from x import y`。
- 禁止通配导入（`from x import *`）。

### 1.3 类型注解
- 所有**公共**函数签名必须标注（参数 + 返回值）。
- 内部辅助函数可选，但推荐。
- 使用现代语法：`list[int]` 而非 `List[int]`，`int | None` 而非 `Optional[int]`（Python 3.10+）。若项目目标是旧版本，跟随现有代码风格。
- `Any` 是兜底方案——优先用 `object`、协议（Protocol）或联合类型。
- 会导致循环导入的类型，用 `TYPE_CHECKING` 分支导入。

### 1.4 Docstring
- 公共函数、类、模块必须有 docstring。内部辅助函数仅在签名无法体现行为时才需要。
- 使用三双引号。首行用祈使语气的单句概括（"返回……"、"计算……"），不要用陈述式。
- 非平凡函数需包含 Args / Returns / Raises 段落。除非项目另有约定，使用 Google 风格（跟随现有风格）。
- 不要重复类型注解已表达的内容——解释**含义**和**为什么**。

示例：
```python
def compute_advantage(rewards: torch.Tensor, values: torch.Tensor, gamma: float) -> torch.Tensor:
    """计算轨迹的 GAE 优势值。

    Args:
        rewards: 形状 (T,)，每步的奖励。
        values: 形状 (T+1,)，价值估计（含 T 处的 bootstrap）。
        gamma: 折扣因子，范围 [0, 1]。

    Returns:
        形状 (T,) 的优势值，未做归一化。
    """
```

### 1.5 函数与类
- 单一职责：函数做两件不相关的事，就拆开。
- 每个函数软限制约 50 行。逻辑本身线性（如训练步）可以更长；但嵌套控制流一深就要抽辅助函数。
- 位置参数最多 5 个，超过用关键字专属参数（`*,`）。
- **禁止可变默认参数**。用 `None` 占位，在函数内部初始化：
  ```python
  def f(items: list[int] | None = None) -> None:
      if items is None:
          items = []
  ```
- 数据类优先用 `@dataclass` 或 Pydantic，不要用裸 dict。
- 组合优先于继承。继承层级超过 2 层需要理由。

### 1.6 异常处理
- **禁止裸 `except:`**。必须指定异常类型。顶层入口可以 `except Exception:`，但必须重抛或带 traceback 记录。
- 不要用 `try` 静默异常。如确需忽略，加注释说明原因。
- 抛出具体异常类型，信息要能定位问题，包含涉事值：
  ```python
  raise ValueError(f"batch size 必须为正，实际为 {batch_size}")
  ```
- 用 `try`/`finally` 或上下文管理器做清理，避免泄漏文件句柄、CUDA 上下文、锁。

### 1.7 日志与打印
- 库/模块代码用 `logging`，不要用 `print`。脚本和 CLI 可以用 `print`。
- 在模块顶部 `logger = logging.getLogger(__name__)`。
- 选对等级：`debug`（开发细节）、`info`（进度）、`warning`（可恢复问题）、`error`（失败）、`critical`（需中止）。
- 当日志等级可能被关闭时，不要用 f-string（会提前求值）：
  ```python
  logger.debug("loss=%.4f", loss)     # 推荐
  logger.debug(f"loss={loss:.4f}")    # 可以，但无论是否打印都会格式化
  ```

### 1.8 通用风格
- 用 `is None` / `is not None`，不要用 `== None`。
- 判空用真值：`if not items:` 而非 `if len(items) == 0:`。
- 单行能写下时，列表/字典/集合推导式优先于 `map`/`filter`。
- 大序列用生成器；不要构造一个只遍历一次的临时列表。
- 文件路径用 `pathlib.Path`，不要拼字符串。
- 字符串格式化用 f-string。除日志外，避免 `%` 和 `.format()`。
- 禁止魔法数字。要么提到模块顶部命名常量，要么走配置。

---

## 2. PyTorch / 深度学习

### 2.1 设备管理
- **禁止硬编码 `.cuda()` 或 `.to("cuda")`**。device 必须可配置：
  ```python
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
  model = model.to(device)
  ```
- `device` 通过函数签名或配置传递，不要依赖全局变量。
- 能直接在目标设备创建张量就直接创建：
  ```python
  x = torch.zeros(N, device=device)   # 推荐
  x = torch.zeros(N).to(device)        # 浪费一次搬运
  ```

### 2.2 dtype 与精度
- 在关键位置显式指定 dtype（`dtype=torch.float32`、索引用 `torch.long`）。
- 不要依赖跨操作的隐式类型提升——明确 cast。
- 混合精度用 `torch.autocast` + `GradScaler`，不要手动 cast。

### 2.3 张量形状纪律
- 在 docstring 里用 `(B, C, H, W)` 风格标注预期形状，复杂 reshape 加行内注释。
- 非平凡的 reshape 用 `einops`（`rearrange` / `repeat` / `reduce`），自解释。
- 优先 `.reshape()`，除非明确需要 view（依赖连续性）。
- 在函数边界对依赖的形状加断言：
  ```python
  assert x.ndim == 4 and x.shape[1] == 3, f"期望 (B,3,H,W)，实际 {tuple(x.shape)}"
  ```
- 避免不带 `dim=` 参数的 `squeeze()` / `unsqueeze()`——行为依赖形状，容易踩坑。

### 2.4 梯度与 autograd
- 推理/评估路径用 `with torch.no_grad():` 或 `@torch.inference_mode()`——后者略快。
- 评估前 `model.eval()`，训练前 `model.train()`，两边都要做。
- 转 CPU/NumPy 前先 detach：`.detach().cpu().numpy()`。
- 不要对非预期张量调用 `.backward()`——检查 `requires_grad`。
- `optimizer.zero_grad(set_to_none=True)` 优于默认行为，更快更合理。

### 2.5 训练循环结构
- 关注点分离：数据加载、前向、损失、反向、优化器 step、日志、checkpoint 各自分明（至少注释分段，或抽成方法）。
- 按固定节奏打日志（每 N 步），不要每步都打。用 step 计数器。
- Checkpoint 按计划 + 最佳指标 + 退出时保存。先写到临时路径再原子 rename，避免崩溃时损坏。
- 进度用 `tqdm` 之类，**包裹 dataloader**，不要包裹 `range`。
- 入口处做可复现种子初始化：
  ```python
  torch.manual_seed(seed)
  torch.cuda.manual_seed_all(seed)
  np.random.seed(seed)
  random.seed(seed)
  ```
  完全确定性还需要 `torch.use_deterministic_algorithms(True)` 和设置 `CUBLAS_WORKSPACE_CONFIG`。

### 2.6 数据
- `Dataset` 子类只实现 `__len__` 和 `__getitem__`；batch 整合放到 `collate_fn`。
- `__getitem__` 里不要做能向量化的重 CPU 工作——但也别为 1000 条数据过度优化。
- `DataLoader` 配好：`num_workers`、GPU 场景下 `pin_memory=True`、长训练加 `persistent_workers=True`。
- 不要把完整数据集常驻 GPU，除非确实很小。

### 2.7 模型
- `nn.Module` 子类：`__init__` 注册子模块，`forward` 做计算。**不要**在 `forward` 里定义层。
- 非平凡层显式初始化权重，不要盲目相信库默认。
- 模型结构通过 `__init__` 参数暴露，不要靠全局变量或环境变量。
- 多输出模型的 `forward` 返回结构化结果（dict 或 dataclass），比裸 tuple 更易演进。

### 2.8 可复现性与配置
- 每次实验能被配置（YAML / JSON / dataclass）完整描述，不要把可调参数埋成硬编码字面量。
- 启动时把配置 + git SHA + seed 记录到运行目录。
- Checkpoint 旁边存一份配置，让 checkpoint 自包含。

---

## 3. Shell 脚本与配置文件

### 3.1 Shell（bash）
- 每个脚本开头：
  ```bash
  #!/usr/bin/env bash
  set -euo pipefail
  ```
  `-e` 遇错即停、`-u` 未定义变量即停、`-o pipefail` 传递管道失败。
- 所有变量展开加引号：`"$var"`、`"$@"`、`"${array[@]}"`。不加引号就是等 bug。
- 命令替换用 `$(...)`，不要用反引号。
- 条件用 `[[ ... ]]`，不用 `[ ... ]`（bash 专属，更安全）。
- 脚本里优先用长选项（`--verbose` 而非 `-v`），自解释。
- 函数：`name() { ... }`，函数内变量都加 `local`。
- 开头检查必需工具：`command -v foo >/dev/null || { echo "需要 foo" >&2; exit 1; }`。
- 头部加简短注释：用途、用法、依赖的环境变量。

### 3.2 JSON 配置
- 不支持注释。需要说明就配一个 README 或改用 JSON5/YAML。
- 2 空格缩进。
- 文件末尾留换行。
- 键按逻辑顺序组织——相关设置放一起；顺序有意义就别按字母排。
- 不要留尾逗号（非法 JSON）。

### 3.3 YAML 配置
- 2 空格缩进，绝不混用 Tab。
- 可能被误解析的字符串加引号：版本号（`"1.0"`）、布尔样字符串（`"yes"`、`"no"`）、以特殊字符开头的值。
- 长配置用锚点（`&`）和别名（`*`）去重。
- 行长度控制在 120 字符内，长列表换行。

### 3.4 配置卫生（通用）
- **禁止在提交的配置里放 secret**。用环境变量或密钥管理器，只引用名字。
- 任何需要本地创建的配置，都提供带注释的 `*.example` 样例文件。
- 必填字段缺失/格式错误要**大声报错**，不要用默认值悄悄兜底。

---

## 4. 通用约定

### 4.1 文件与目录组织
- 一个模块只做一件清晰的事。文件超过约 500 行就考虑拆。
- 测试文件对应源码结构：`src/foo/bar.py` ↔ `tests/foo/test_bar.py`。
- 入口脚本放 `scripts/` 目录或做成命令行 entry point，不要一堆 `run_*.py` 散落顶层。
- `__init__.py` 定义包的公开 API。**不要**把所有东西都 re-export，选择性暴露。

### 4.2 注释
- 注释写**为什么**，不写**是什么**。代码本身说明"是什么"。
- 刻意绕行的地方标 `# HACK:` 或 `# FIXME:` 加原因。
- `# TODO:` 带条件/时机（如 `# TODO(v2 迁移后): 移除此分支`）。
- 删掉注释掉的代码。版本控制会记住它。

### 4.3 依赖
- 生产 requirements 锁版本（`torch==2.3.1`）；库的依赖上界放宽。
- 开发依赖（lint、测试工具）与运行时依赖分开。
- 标准库能一行解决的事，不要引依赖。

### 4.4 Git 提交
- 一次逻辑变更一个 commit。不要把重构和功能混在一起。
- Commit 信息格式：
  ```
  祈使式概括（≤72 字符）

  需要时写正文：改了什么、为什么。每行 72 字符内折行。
  关联 issue 如 #123。
  ```
- 使用祈使语气："Add X"、"Fix Y"、"Refactor Z"——而非 "Added"、"Fixes"、"Refactoring"。
- 小而可审查的 commit 优于一个巨无霸。

### 4.5 测试（功能完成即补单测 · 强制）

**硬性规则**：每完成一项功能开发（新函数、新类、新模块、新 endpoint、新配置逻辑），**必须**在同一个交付内补齐对应的单元测试。没有单测的功能视为未完成——不能进入阶段 C 的 simplify/review，也不能向用户汇报"完成"。

#### 4.5.1 什么算"一项功能开发"（必须补单测）
- 新增的公共函数 / 方法 / 类。
- 新增的公共模块（至少覆盖其主 API）。
- Bug 修复（加**回归测试**，锁住不再复现）。
- 有纯函数逻辑的数据变换（tokenizer、归一化、advantage 计算、reward shaping 等）。
- 配置解析 / 校验逻辑。

#### 4.5.2 什么可以**不**写单测
- 一次性脚本（`scripts/` 下的临时任务脚本）。
- 纯 I/O 粘合代码（文件读写、简单的 CLI argparse），且无分支逻辑。
- 明显的打印语句、日志消息调整。
- 训练主循环这类难以在 CI 里跑的代码——但其中**可抽出的纯函数必须抽出并测试**（例如学习率调度、损失计算、advantage 估计）。
- 这些例外要在 plan 或汇报里明说，别默默跳过。

#### 4.5.3 测什么（一个功能对应的单测应覆盖）
- **正常路径（happy path）**：1 条主要用例，典型输入 → 期望输出。
- **边界条件**：空输入、0、1、极大值、单元素、重复元素、NaN/Inf（若相关）。
- **错误路径**：非法输入是否抛出预期异常（用 `pytest.raises` 验证异常类型 + 消息关键词）。
- **形状 & dtype（PyTorch 场景）**：显式断言输出 tensor 的 `.shape` 和 `.dtype`，避免"看似跑通"。
- **不变量**：概率之和为 1、归一化后均值 0 方差 1、梯度可回传等。

不需要把每种组合穷举——用 `pytest.mark.parametrize` 覆盖**关键维度**即可。

#### 4.5.4 写法规范
- 框架：优先 `pytest`；若项目已用 `unittest`，跟随项目。
- 测试文件位置镜像源码：`src/foo/bar.py` ↔ `tests/foo/test_bar.py`（没有 `tests/` 时就在源文件旁建 `test_*.py`，跟随项目约定）。
- 测试名描述场景：`test_compute_advantage_handles_zero_gamma`，**不是** `test_1` / `test_compute_advantage`。
- **一个测试一个行为**。名字里出现"and"就该拆。
- 用 `fixture` / `parametrize` 复用 setup，不要复制粘贴。
- 测试本身要快：单个用例 < 1s；需要 GPU / 网络 / 大模型的，标记 `@pytest.mark.slow` 或 `@pytest.mark.gpu` 并说明如何跳过。
- Mock 外部依赖（网络、文件系统、时间），避免测试环境耦合。
- 不要在测试里测私有实现细节——**测行为、不测实现**，否则重构必改测试。

#### 4.5.5 测试的结构（AAA）
每个测试用 Arrange-Act-Assert 三段式组织，可用空行分隔：
```python
def test_compute_advantage_handles_zero_gamma():
    # Arrange
    rewards = torch.tensor([1.0, 2.0, 3.0])
    values = torch.tensor([0.5, 0.5, 0.5, 0.0])

    # Act
    adv = compute_advantage(rewards, values, gamma=0.0)

    # Assert
    assert adv.shape == (3,)
    torch.testing.assert_close(adv, rewards - values[:-1])
```

#### 4.5.6 运行与验证（交付前必做）
- 完成功能 + 单测后，**本地跑一次**目标测试，确认通过：
  ```bash
  pytest tests/foo/test_bar.py -v
  ```
- 如果新测试引入了跨模块依赖，也跑一次更大范围（至少该模块目录），确认没打破别的测试。
- **测试未跑通不能进入阶段 C 的 simplify/review**。
- 汇报时要附上测试文件路径 + 用例数量 + 通过状态（见 §5.1 第 2 步）。

### 4.6 文档
- 项目根目录的 README 回答四件事：这是什么、怎么装、怎么跑、下一步去哪。
- 非显然的算法加注释指向论文/参考。
- 文档与代码就近放，长篇设计文档放 `docs/`。

---

## 5. 完成后流程（强制 · 主动执行）

写完代码后**不要等用户催**，按顺序执行以下步骤，并把结果以结构化方式汇报。

### 5.1 步骤

1. **自检清单（先自己扫一遍）**
   - 本次变更的文件 & 新增/修改的符号清单。
   - 对照"审查清单（快速参考）"自查一轮——明显的必改项先自己修掉。

2. **补齐单元测试并跑通（强制 · 在 simplify/review 之前完成）**
   - 对本次新增/修改的每项功能，按 §4.5 补齐对应单元测试。
   - 测试位置镜像源码目录（`src/foo/bar.py` ↔ `tests/foo/test_bar.py`）。
   - 覆盖：正常路径 + 边界条件 + 错误路径；PyTorch 场景还需断言 shape/dtype。
   - **本地跑通测试**：
     ```bash
     pytest <新增测试文件> -v
     ```
   - 测试未通过不得进入步骤 3。若遇到无法在本地跑通的情况（需 GPU / 大数据等），明确标注 `@pytest.mark.slow` 或 `@pytest.mark.gpu`，并在汇报里说明未执行的原因和跳过方式。
   - 按 §4.5.2 的例外条款可省略单测时，**必须在此步骤明说**哪些文件、为什么省略。

3. **调用 `simplify` skill**
   - 目的：让 simplify 针对本次新代码做复用性、质量、效率检查并修复。
   - 明确告知 simplify 审查的范围（只看本次变更，不要扩散到全库）。**测试文件一并纳入范围**。
   - simplify 提出的改动若属于"必改/建议改"级别，直接采纳；属于"可考虑"级别，汇报给用户决定。
   - 若 simplify 修改了被测代码，**重新跑一次相关测试**确认没破坏行为。

4. **调用 `review` skill**
   - 目的：完整 review 本次新增/修改的代码 **+ 新增测试**。
   - 若 simplify 阶段已做修改，review 的是**修改后**的版本。

5. **汇总报告**
   按以下格式给用户最终报告：
   ```
   ## 完成情况
   - 新增：<文件清单>
   - 修改：<文件清单>

   ## 单元测试
   - 测试文件：<路径>
   - 用例数：<N>
   - 运行结果：<passed / failed / skipped>
   - 省略单测的部分（若有）：<文件 + 理由>

   ## simplify 结果
   - 已自动修复：<...>
   - 待确认：<...>

   ## review 结果
   ### 必改（Must-fix）
   - <文件:行号> <问题> → <建议>
   ### 建议改（Should-fix）
   - ...
   ### 可考虑（Consider）
   - ...

   ## 下一步
   <等待用户确认 / 继续修复 / 已全部完成>
   ```

6. **同步项目文档（强制 · 在汇报之后执行）**
   目的：让任何一个新 session 读完 `docs/` 就能 resume 到当前状态，不用靠对话历史。**汇报完不同步文档等于未完成交付**。

   - **落点**：项目内的开发文档目录（本项目是 `docs/`）。新项目无此目录时，先在项目根建 `docs/` 再写。
   - **必更的两类文件**：
     1. **开发日志**（本项目 `docs/DEVELOPMENT_LOG.md`）——在末尾追加本次迭代章节，至少包含：
        - 时间线与本次任务目标（用户原话 + 关键决策理由）
        - plan 演进（如有多轮修订，记录原因）
        - 文件改动清单（新增 / 修改）
        - 测试状态（数量 + passed/failed/skipped）
        - simplify / review 结论摘要（必改/建议改/可考虑）
        - 踩到的坑与未完成事项（留给下次）
     2. **Resume 入口**（本项目在 `DEVELOPMENT_LOG.md` 最后一节"Resume 入口"小节 + 可选的独立 `RESUME_PROMPT.md`）——更新为本次结束后的最新状态：
        - 当前功能/测试/环境的简短快照
        - "下次进来先做的事"优先级列表
        - "绝对不要做的事"若有新增项也要补
   - **按需更新的文件**：
     - `docs/ARCHITECTURE.md`：只有本次改动影响架构/扩展点/数据流时才改；否则不动。
     - `CHANGELOG.md`（若项目维护）：在 `[Unreleased]` 段追加条目。
     - `README.md`：只有对外接口/安装/使用方式变化时才改。
   - **风格要求**：跟随现有文档风格（本项目用中文 + 编号章节 + 代码块示例），不要新造格式。
   - **防止静默废弃**：如果本次把某个旧做法替换了，旧章节保留历史记录，但在 Resume 入口明确标注"以新做法为准"。

   汇报用户"已完成"之前，**最后一步**是贴出同步的文档段落（或 diff 摘要），让用户知道文档已经跟上。

### 5.2 例外
- 用户明确说"只写不审"时，跳过步骤 3–4，但仍需完成 §5.1 步骤 1（自检）、步骤 2（单测）与步骤 6（同步文档）。
- 单次改动极小（< 5 行、纯 typo 或参数调整）时，可合并步骤 3–4 为一次轻量 review，不强制调用 skill；单测按 §4.5.2 判断是否仍需补；**文档同步仍要做**——至少在 CHANGELOG 或日志末尾留一行记录。
- **例外不适用于"跳过单测"与"跳过文档同步"**：功能开发必须有对应单测（除非命中 §4.5.2 的免测清单），同时必须把本次进展同步到 `docs/`。

### 5.3 反模式（禁止）
- 写完代码直接交差，不做 simplify/review。
- **功能完成却没补单测**——除非命中 §4.5.2 且已明示。
- 单测没跑通就上 simplify/review。
- 静默地在 simplify/review 阶段改写代码而不汇报。
- 跳过 simplify 直接上 review，导致 review 在未清理的代码上反馈。
- 为应付流程写空壳测试（只 assert True、不覆盖任何真实行为）——等于没写。
- **汇报完就收尾，不同步 `docs/`**——新 session 无法 resume，视为未交付。
- **Resume 入口陈旧**：只追加开发日志但没更新"下次进来先做的事"，等于文档在撒谎。

---

## 审查清单（快速参考）

审查代码时按以下顺序扫描：

0. **核心设计原则（§0.9，先扫这一层）**
   - 易读直观：主流程能 30 秒读懂吗？嵌套是否过深？
   - 是否存在不必要的抽象（基类/工厂/策略但只有一个实现）？
   - 函数/类是否单一职责？描述时要不要用"and"？
   - 命名是否自解释？有没有 `data`/`info`/`tmp`/`helper`/`process` 这类空壳名？

0.5. **单元测试（§4.5）**
   - 本次每个新增/修改的功能都有对应测试吗？
   - 测试覆盖了正常 + 边界 + 错误路径吗？
   - PyTorch 场景是否断言了 shape/dtype？
   - 测试都本地跑通了吗？

1. **正确性隐患**
   - 可变默认参数
   - 裸 `except` 或吞异常
   - 硬编码设备/dtype
   - 无断言的形状假设
   - 评估路径缺 `model.eval()` / `no_grad`
   - Shell 变量未加引号
   - 配置里有 secret

2. **公共 API 卫生**
   - 签名有类型注解
   - 公共函数/类有 docstring
   - 命名合理（不是 `data`、`foo`、`helper`）

3. **结构**
   - 函数长度与单一职责
   - Import 有序且干净
   - 无死代码/注释代码
   - 无魔法数字

4. **风格打磨**
   - 行长度
   - f-string 该用就用
   - `pathlib` 替代 `os.path`
   - 真值判断

审查结论按**必改 / 建议改 / 可考虑**分组，格式见本文件开头。

