notes
这个 skill 用于写代码注释。注释默认写给刚接手当前模块、还不熟悉调用链的人。读者不应先研究完整实现和内部术语,才能看懂注释。
只有任务涉及注释治理时才使用它。普通 React、后端或 SQL 开发任务不应仅因为出现这些技术栈就触发;如果用户要求检查当前改动是否缺少必要注释,可以使用。
规范读取要求
通用注释规则已经写在本文件内。执行注释相关任务时,先应用本文件规则,再按场景读取补充文档。
- 涉及前端、React、Hook、组件、事件处理、渲染分支时,再读
references/frontend.md。 - 涉及 Node.js 后台、controller、service、SQL、任务、队列、外部接口时,再读
references/backend.md。
通俗易懂
先用日常动词说明最终效果,再补确实影响理解的原因。不要把代码前提、内部机制、操作对象和最终结果全部塞进一句注释。
- 优先使用“找出、删除、保留、跳过、合并、放到……之前”等具体说法。不要为了显得准确而堆叠只有熟悉代码的人才懂的术语。
- 必须使用技术名词时,只保留代码里确实存在且无法替代的名称,并把它放进完整、简短的句子。
- 不要只写“过滤数据”“遍历列表”这类代码表面动作;说明删除或保留了什么,以及这样做的必要原因。
- 写完按新人视角检查:没有参与实现的人能否看懂数据发生了什么变化、最终留下什么、为什么这样做?不能就重写。
过于复杂:
// 历史状态可能残留同类消息,下游只消费当前阶段追加的最后一项。
过于简单:
// 过滤消息。
推荐写法:
// 删除旧消息,只保留最新一条,避免重复发送。
注释决策流程
看到一个代码位置
│
├─ 是导出函数或 class static 方法?
│ └─ 写简短 JSDoc,说明业务用途、关键入参或返回含义
│
├─ 是 for / if 控制流?
│ └─ 默认在块上方写一行总结,说明业务产出、过滤边界、状态优先级、状态推进或副作用顺序
│
├─ 是同一逻辑阶段里连续定义多个变量,且后面紧跟 for / if 等逻辑处理?
│ └─ 每个变量上方写 // 描述变量在后续逻辑里的职责,for / if 上方写逻辑块目标、分支边界或优先级
│
├─ 是复杂判断、状态流转、SQL 口径、异步副作用或模型协议边界?
│ └─ 写注释说明为什么这样做、避免什么误解
│
├─ 是一组相似字段映射或模板取值?
│ └─ 在这一组上方写一行总结,不逐字段解释
│
├─ 前面有空行,表示进入新的业务小阶段?
│ └─ 在这一段代码上方写一行组注释;如果说不出注释,就去掉空行
│
└─ 是简单赋值、普通取字段、明显空值回退或普通按钮点击?
└─ 不写注释
通用判断表
| 代码场景 | 推荐动作 | 注释重点 |
|---|---|---|
| 导出函数、class 的 static 方法 | 写 JSDoc | 写业务用途、关键入参或返回结果的业务含义 |
| for / if 控制流 | 默认写组注释 | 说明业务产出、过滤边界、状态优先级、状态推进或副作用顺序 |
| 多变量准备后紧跟 for / if 逻辑处理 | 逐变量注释 | 说明变量在后续逻辑里的职责,逻辑块说明过滤、分支边界或优先级 |
| 复杂判断、连续判断、复杂三元、switch | 写行内注释 | 写判断依据、状态优先级或分支边界 |
| 数字计算、聚合统计、字段口径转换 | 写行内注释 | 写计算口径、精度边界、最终展示含义 |
| 数据结构转换、模板拼接、prompt 边界 | 写行内注释 | 写转换后的读取方式、模型可见边界或用户可见效果 |
| 多行相似取值或字段映射 | 写一行组注释 | 解释这一组数据的共同语义 |
| 空行分隔出的业务小块 | 写一行组注释 | 说明这一段代码在当前流程里的业务目标 |
| 简单赋值、普通取字段、明显空值回退 | 不写 | 代码本身已经能表达,不要制造噪音 |
| “获取某某”“设置某某”“返回某某”“处理某某” | 不写 | 这类注释只复述代码表面行为 |
规则细节
注释只写在值得解释的地方。它应该说明为什么这样写、避免什么误解、字段代表什么业务含义。简单赋值、普通取字段、明显的空值回退、完全不承载业务含义的语法胶水,不要单独写注释。
多行相似取值如果需要说明,优先在这一组代码上方写一行总结注释,不要每一行都配一句旁白。所有 for、for...of、for await...of、for...in 循环默认都要在循环上方写一行组注释,说明本轮循环的业务产出、过滤或归一化边界、状态推进、批量副作用顺序;不要写“遍历某某”这种语法描述。if、else if、提前返回、continue 这类控制流默认也是复杂逻辑入口,只要它在筛选数据、决定状态优先级、保护业务边界、切换副作用顺序或改变后续产出,就要在分支上方写注释说明分支意义;只有一眼可见的空值 guard、普通布尔短路、没有业务含义的语法胶水可以不写。空行只用于分隔不同业务阶段、不同控制流或确实需要停顿的说明。只要保留空行,空行后面的代码块上方就要有一行组注释说明这一小块在做什么;如果这一段简单到写不出有意义的组注释,就去掉空行,让代码连续读下去。
当同一逻辑阶段先连续定义多个变量,后面又紧跟 for、if、switch 等逻辑处理时,不能只用一条组注释覆盖整段。每个变量定义上方都要写 // ...,说明这个变量在后续逻辑里的职责,比如基准值、比较对象、累计容器、状态标记、外部协议字段或展示口径;每个 for / if 逻辑块上方都要写 // ...,说明该块负责的过滤、归一化、分支边界、优先级或产出结果。注释仍然不要解释语法本身,也不要写“定义变量”“遍历数组”“判断条件”这类表面描述。
这些要求的重点是补齐复杂流程的阅读入口。agent 不能只给导出函数补 JSDoc 后就跳过内部逻辑,也不能因为代码看起来短就默认没有业务复杂度;只要循环、分支或变量组承载了业务状态、过滤口径、状态优先级或副作用顺序,就要把这层意图写出来。
导出函数、class 的 static 方法,必须在定义上方写简短 JSDoc,说明方法自身职责、关键入参或返回结果的业务含义。不要写只复述函数名的 JSDoc,也不要把通用方法绑定到某个当前调用方场景。
复杂判断、连续判断、复杂三元表达式、switch、数字计算、字段语义、数据结构转换、展示文案拼接、输出模板和 prompt 边界,才是优先补注释的地方。注释要讲判断依据、计算口径、转换后的读取方式、最终用户可见效果或模型可见边界。for / if 控制流默认要写说明块目的或分支意义的总结;例外只留给完全不承载业务含义的语法胶水。
不要写“获取某某”“设置某某”“返回某某”“处理某某”这种表面注释。不要把十几个字段取值逐行解释。不要写长段落,一条注释只讲一个重点。
示例
/** 生成学生在当前课程范围内的课堂表现摘要。 */
export function buildStudentPerformanceSummaries(records: StudentActivityRecord[], activeCourseIds: Set<number>) {
// studentSummaryById 是学生维度的累计容器,后续循环持续合并同一学生的课堂表现。
const studentSummaryById = new Map<number, StudentPerformanceSummary>();
// absentStudentIds 记录已命中缺勤边界的学生,避免后续互动分数把缺勤状态覆盖掉。
const absentStudentIds = new Set<number>();
// 逐条过滤课程范围内的课堂记录,并累计学生维度摘要。
for (const record of records) {
// 历史课程记录不进入本轮统计口径,避免跨课程表现混入当前摘要。
if (!activeCourseIds.has(record.courseId)) continue;
const summary = studentSummaryById.get(record.studentId) ?? createEmptySummary(record.studentId);
// 缺勤是最高优先级状态,先锁定学生,再跳过互动分累计。
if (record.status === StudentActivityStatus.Absent) {
absentStudentIds.add(record.studentId);
summary.status = StudentPerformanceStatus.Absent;
studentSummaryById.set(record.studentId, summary);
continue;
}
// 已缺勤学生只保留缺勤状态,不再用后续互动记录回写为活跃。
if (absentStudentIds.has(record.studentId)) continue;
summary.answerCount += record.answerCount;
studentSummaryById.set(record.studentId, summary);
}
return [...studentSummaryById.values()];
}
下面这种注释不要写,它只翻译语法,没有降低复杂逻辑的理解成本。
// 定义学生 Map。
const studentSummaryById = new Map<number, StudentPerformanceSummary>();
// 遍历课堂记录。
for (const record of records) {
// 判断课程 ID。
if (!activeCourseIds.has(record.courseId)) continue;
}