首页
/ LobeHub deep-review:AI 编码坏习惯(ai-coding-bad-habits)审查维度全解

LobeHub deep-review:AI 编码坏习惯(ai-coding-bad-habits)审查维度全解

2026-09-04 20:43:45作者:晏闻田Solitary

LobeHub 的 .agents/skills/deep-review 是一套多维度、多智能体协作的代码评审技能,其中 ai-coding-bad-habits.md 定义了专门识别"机械性未完成代码"的审查维度。本文完整解读该维度的五大坏习惯模式、五条检查流程、判定边界(什么算违规、什么不算)与严重级校准,并结合仓库内 typescript/SKILL.mddata-fetching-architecture/SKILL.md 等规则来源与真实源码示例,说明每一条审查规则在 LobeHub 代码库中如何落地验证。读完后,你可以用同一套标准审视 AI(或人)产出的 diff:哪些是必须修复的机械缺陷,哪些只是风格偏好,避免把"看起来像 AI 写的"当成论据。

1. 定位:这个维度在 deep-review 中的角色

deep-review 技能的 SKILL.md 规定:评审规则集中在 references/dimensions/ 目录,每个维度一个文件,Light 模式只读各维度的 Quick checklist,Deep 模式则读取完整维度文件加上命中的规则来源。ai-coding-bad-habits 是其中 14 个维度之一,元信息如下(摘自文件头部 frontmatter 与维度表):

字段 含义
文件名 ai-coding-bad-habits.md 规则唯一定义处
id_prefix ai 该维度产出的发现项统一以 ai 为前缀
verify true 发现项必须经过独立 verify 子代理复核后才能进入报告
skip_when docs/lockfile-only diff 仅文档/lockfile 变更的 diff 跳过本维度

维度自身的定义是:"检测在局部合理、但在机械意义上不完整的代码——能在孤立状态下编译通过,却忽视了语义作用域、静态类型信息、重构闭包、解释性注释或仓库先例"。文档特别强调两点立场:

  1. "AI coding" 命名的是重复出现的实现习惯,不是作者或出处——只评审可观察的代码,绝不推测代码是谁写的;
  2. 本维度不是报告个人喜好的许可证——每条发现必须指出具体代价:误导阅读流程、策略重复、过期命名、冗余运行时开销、漏掉的兼容边界,或偏离既有仓库模式。

它与相邻维度的分工边界在文档中写得很死:同一根因若已被 code-style(孤立的片段可读性问题)或 reuse-architecture(被证实的重复实现、错误层级、绕过扩展缝)完整覆盖,就在彼处报告,绝不就同一发现重复上报。这一点在 code-style.mdreuse-architecture.md 中互为印证——code-style 声明"跨文件复用与抽象问题归 reuse-architecture",reuse-architecture 声明"只看单个 diff 片段不可判断复用问题"。

另外注意 SKILL.md 中对 "docs-only" 的限定:.agents/skills/**AGENTS.md/CLAUDE.md、prompt 模板等属于"给代理执行的可执行指令",按代码对待。也就是说,修改本维度文件本身永远不算 docs-only,评审时不会因此被裁剪。

2. 五大坏习惯模式与仓库佐证

维度文件给出一份 Quick checklist,共五条。以下逐条展开,并附原文档示例与 LobeHub 仓库内的实现证据。

2.1 通用代码里藏着的窄特例(Narrow special case inside generic code)

模式:某个 provider / 模型 / 调用点特有的条件、辅助函数或命名,被嵌进了共享抽象,使通用契约更难读、更难扩展。重点警惕那种"把单一窄分支藏在 typestatusmode 这类通用属性后面"的一次性 helper;变体策略应留在拥有它的层,或在决策点把异常条件显式写出来。

文档示例:

const getBlockErrorType = (reason: string) =>
  reason === 'IMAGE_PROHIBITED_CONTENT' ? ErrorType.ContentPolicyViolation : undefined;

// Inside a shared stream parser:
type: getBlockErrorType(finishReason);

typegetBlockErrorType 读起来像错误分类的通用所有者,但该 helper 实际只藏了一条图像生成的判断条件。文档给出的两条出路:在决策点保持条件显式(当这更清晰时),或把策略移入拥有它的图像/provider 适配器。

仓库佐证reuse-architecture.md 的 "Fix placement" 一节给出了同一思想的可操作判据——先判断根因是"单一变体特有"还是"整类共有":变体特有则修复必须落在变体自己的层(例如"某个 provider 的参数怪癖应进 packages/model-runtime/src/providers/<provider>/,而不是共享工厂里为每个 provider 加分支");共享层若已提供扩展缝(factory options、hooks、per-variant config),往共享代码里加 if (variant === ...) 才是违规。

2.2 停在中途的重构(Partial refactor)

模式:一个概念被重命名后,遗留了过期的文件名、测试、导出、文档或相邻标识符;或者明明没有任何调用者需要,却留着兼容别名、转发包装、const newName = oldName。正确做法是按仓库的文件命名惯例,在受影响的单元内完成语义重命名。

文档示例:

// register-file-work.ts
export const registerWork = async () => {
  // ...
};

export const registerFileWork = registerWork;

若没有已发布的调用者依赖旧名,就应当重命名文件、测试、import 与相邻概念,然后删除别名。"只改导出名"会让一个概念有两个名字,读者会去寻找一场并不存在的迁移。

仓库佐证typescript/SKILL.md 的"Code Structure"一节要求优先使用 named exports 而非 export default,理由正是"让重构重命名与 IDE 自动导入保持同步,避免 import Foo from './foo' 的 default 重命名漂移"。这与本条规则是同一枚硬币的两面:named export 让 2.2 的检查变成一次全仓搜索即可完成——旧名若有调用者,搜索必然命中;别名若无调用者,即是待删的死代码。

2.3 类型已知却堆防御性噪音(Defensive type noise despite known types)

模式:对静态类型与调用方已经保证形状的值,反复使用 typeofinisRecord、防御性可选链或"先转再查"逻辑。审查前先用 LSP / 类型定义确认真实类型;对真正不可信的结构化输入,在边界上只校验一次——本仓库优先使用既有 schema(通常是 Zod),而不是散落各处的临时 guard。

文档示例:

interface Work {
  id: string;
}

const getWorkId = (work: Work) =>
  isRecord(work) && typeof work.id === 'string' ? work.id : undefined;

work.id 已被进程内类型保证,直接使用即可;若值来自 JSON、SDK 或其他信任边界,就用既有 WorkSchema 在边界解析一次,把校验后的 Work 传递下去,而不是在每个使用点重复守卫。

仓库佐证isRecord 本身就是仓库工具库的一等公民,定义在 packages/utils/src/object.ts 第 20 行:

export const isRecord = (value: unknown): value is UnknownRecord => ...

typescript/SKILL.md 的"Reusability"一节同时要求:不要手写 typeof value === 'object' && value !== null 这类 guard,应从 @lobechat/utils/object 导入 isRecordisPlainRecordpickString 等现成 helper。两者合起来的审查口径是——guard 用错位置(进程内可信值)是 2.3 违规;guard 放在真实边界且用的是仓库现成工具,则是合规的,两条边界都要有源码可查。

2.4 叙述编码过程而非解释代码的注释(Commentary that narrates coding)

模式:注释保留的是作者思维过程、编辑历史、带编号的实现步骤,或与下一行代码同义复述;而真正的 workaround、不变量、取舍或非显然约束反而没写。原则:保留持久的"为什么",删掉瞬时的"我在干什么"。

文档示例:

// First check whether the cache has a value.
// If it does, return it.
if (cached) return cached;

这类只复述控制流的叙述应当删除。应当保留的注释是解释"为什么代码不能更简单"的持久理由,文档给出的正面示例:

// Ignore v1 cache entries because they lack the workspace isolation key.

typescript/SKILL.md 的"Logging"一节还有一条同源要求值得注意:.catch() 回调必须记录错误,silent .catch(() => fallback) 会吞掉失败、让排障不可能——这与 2.4 是同一类问题:代码留白处缺少"为什么",读者无法区分"有意为之"与"忘了写"。

2.5 无视仓库先例的实现(Precedent-blind implementation)

模式:命名、文件布局、错误处理、数据流或抽象形状在本地"自创",没有先查最近同类实现或相关仓库 skill。判定前提很严格:必须找到具体的仓库先例,才能称某个偏离是错的;有说明理由的有意偏离是有效的。

文档示例:

useEffect(() => {
  lambdaClient.work.list.query().then(setWorks);
}, []);

若仓库同类功能走的是 store SWR hook → service → lambdaClient 流水线,那么这个组件直连调用就是先例盲——尽管它在局部能跑。修复方式二选一:遵循既定路线,或文档化说明"这个功能为什么不同"的约束。

仓库佐证:这条规则在 LobeHub 里有完整、可直接引用的先例体系——data-fetching-architecture/SKILL.md 把标准数据流画成了四层:Component → Zustand Store(useFetchXxx / useClientDataSWR)→ Service(xxxService)→ lambdaClient(TRPC Client),并列出硬禁令:"Never use useEffect for data fetching / Never call lambdaClient directly in components or stores / Never use useState for server data"。该 skill 甚至给出了与 2.5 反例几乎同形的"Anti-pattern"对照代码(useEffect + lambdaClient.agentEval.listBenchmarks.query().then(setData)),以及 src/services/agentEval.tssrc/store/eval/slices/benchmark/action.ts 中的 canonical Benchmark 实现。审查者做 2.5 判定时,这份 skill 就是现成的"先例标尺":先例存在且结构一致(对比结构而非名字:同层?同命名法 useFetchXxx/refreshXxx?同失败处理形状?),而 diff 无说明地偏离——即违规。

3. 五遍检查流程(How to check)

维度文件要求审查者按顺序跑五遍聚焦检查。每一遍的问题与判定要点如下表(内容完整继承自原文档):

# 检查遍 核心问题 判定要点
1 Scope locality(作用域局部性) 给每个新条件/新 helper 命名后问:它的词汇是否比所在文件或抽象更窄? 先追踪流水线和既有扩展缝再下结论;"报告写得很窄"不等于"根因就窄"
2 Refactor closure(重构闭包) 被重命名的概念,旧名新名在符号、文件名、导出、测试、文档、import 路径中是否全部闭环? 对每个 alias/wrapper 搜全部调用者;只有已发布调用者或分阶段迁移才保留兼容 shim,且必须有弃用/移除计划
3 Type confidence(类型置信度) 这个值在静态层面是否已被保证? 有 LSP 就用 hover/跳转;否则读声明类型与代表性调用方。区分进程内可信值与外部 JSON、数据库 JSON、SDK 载荷等不可信边界;不要仅因 TypeScript 能编译就删守卫
4 Comment intent(注释意图) 不带 diff 叙事地重读新增注释:它解释的是持久理由还是编辑流水账? 保留解释"为何不能更简单"、不变量、兼容约束、外部引用的注释;标记下次编辑就会过期的叙述,尤其是真正的 workaround 仍无文档时
5 Repository precedent(仓库先例) 最近的同类实现与相关 skill 是什么? 对比命名、分层、校验、错误处理、测试;没有具体先例,就没有 precedent-blind 发现

第 5 遍的规则来源在文档中显式列出(deep 模式审查前必读):

  • 仓库根 AGENTS.md / CLAUDE.md——全仓命名、注释、工作流惯例;
  • typescript/SKILL.md——静态类型、运行时校验与类型守卫惯例;
  • project-overview/SKILL.md——所有权与层级边界;
  • 与变更代码同类的类别 skill(testingdrizzletrpc-routerzustandbuiltin-tool 等,见 .agents/skills/)——以它已确立的模式为第一先例;
  • 最近同类兄弟实现,以及被改符号的调用方。

这五遍的设计逻辑与 SKILL.md 的核心原则呼应:"规则胜过模型"——评审质量来自细粒度、可执行的维度规则,而非更强的模型;"按代码库现状与生命周期校准"——用代码库已满足的标准而非理想标准衡量 diff,且安全维度豁免校准。

4. 违规与非违规的完整边界

判定边界是这个维度最有引用价值的部分,因为它把"值得上报"与"风格偏好"硬性切开。

判定为违规(Violations)

  • 窄变体关注点污染了通用层,或被藏在一用即弃的间接层后面,使共享决策更难理解或扩展;
  • 重命名/重构留下互相矛盾的命名,或留下没有兼容调用者的 no-op 别名/包装;
  • 运行时守卫重复了声明类型与可信调用方已确立的保证,反而遮蔽真实业务逻辑;
  • 新增注释记录的是实现叙述,却遗漏了代码存在的非显然理由;
  • diff 在存在明确适用的仓库先例或 skill 时自创本地模式。

明确不算违规(Not violations)

  • 变体专属规则实现在该变体自己的 adapter/provider/module 内;
  • 以更大模块名而非单个导出函数命名的文件名;
  • 为保护已发布公共 API、插件契约、序列化名称或多 PR 迁移而保留的别名/包装,且有调用者仍依赖它的证据;
  • 真实信任边界上的运行时校验——解析后的 JSON、数据库 JSON、网络/SDK 载荷、IPC、插件/工具输入、遗留持久化数据。静态类型不会让外部数据变得可信
  • 比 schema 更清晰地收窄真实 union 的小型内联类型守卫;
  • 解释 workaround、不变量、取舍、兼容约束或来源引用的注释;
  • 有意偏离先例,且约束已在代码或 PR 中文档化。

这组边界与 SKILL.md 的"校准"原则一致:广泛存在于既有代码中的模式,若本次 diff 没有使其更糟,就不是发现项(code-style 的 "Not violations" 用同样口径:未触碰行的既有债务不报)。

5. 严重级校准与上报纪律

维度文件对严重级(severity)给出硬性校准:

  • P1 仅当该习惯造成具体的正确性、兼容性或共享抽象风险时使用;
  • P2 用于可维护性/可读性债务——这类问题很可能误导下一次的修改;
  • 禁止上报"看起来是 AI 生成的"——习惯只是线索,发现项必须点名代码级后果与证据。

再结合 frontmatter 的 verify: true,一条完整的 ai 前缀发现要经历 SKILL.md 规定的反幻觉流程:候选发现由独立 verify 子代理读取完整上下文后给出三向判定(confirmed / false_positive / need_more_context),通过后才进入报告。这套机制正是"机械缺陷维度"可信度的来源:模式匹配产生候选,全量上下文复核才产生结论。

6. 实践速查:拿到一个 diff 怎么用本维度

把上述内容压缩成一份可执行的检查表:

  1. 先裁剪:仅文档/lockfile 的 diff 直接跳过本维度(skip_when);注意 agent 指令文件(.agents/skills/**AGENTS.md 等)按代码处理。
  2. 过五遍:按第 3 节的顺序跑 Scope locality → Refactor closure → Type confidence → Comment intent → Repository precedent;每遍只产出该遍的证据。
  3. 查先例:对每个"像自创"的实现,按 reuse-architecture.md 的 Outward 搜法全仓检索(packages/utils/src/utils/src/hooks/src/lib/、变更文件同级目录),并核对 data-fetching-architecture/SKILL.md 等类别 skill 是否给出标尺;无具体先例即放弃该发现。
  4. 定边界:对照第 4 节逐条排除 Not violations,确认不是 code-style / reuse-architecture 已覆盖的同一根因。
  5. 写发现:点名代码级后果(误导阅读、重复策略、过期命名、冗余运行时工作、漏掉的兼容边界、偏离既定模式),给出文件级证据,按第 5 节标 P1/P2——绝不出现"看起来像 AI 写的"。

这套维度的价值不在"识别 AI 代码",而在于把"局部合理、全局未完成"这类最常见的机械缺陷变成可复现、可复核、可去重的检查流程。规则源文件(typescript/SKILL.mddata-fetching-architecture/SKILL.mdproject-overview/SKILL.md)与 AGENTS.mdCLAUDE.md 中的仓库惯例互为引用,构成完整的证据链;而 SKILL.md 末尾的 "Keeping this skill sharp" 机制(skill-freshness 维度与 workflow_feedback 通道)则保证这类维度规则会随着评审反馈持续校准,而不是一次写定、永久过期。

登录后查看全文
热门项目推荐
相关项目推荐