LobeHub deep-review:AI 编码坏习惯(ai-coding-bad-habits)审查维度全解
LobeHub 的 .agents/skills/deep-review 是一套多维度、多智能体协作的代码评审技能,其中 ai-coding-bad-habits.md 定义了专门识别"机械性未完成代码"的审查维度。本文完整解读该维度的五大坏习惯模式、五条检查流程、判定边界(什么算违规、什么不算)与严重级校准,并结合仓库内 typescript/SKILL.md、data-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 跳过本维度 |
维度自身的定义是:"检测在局部合理、但在机械意义上不完整的代码——能在孤立状态下编译通过,却忽视了语义作用域、静态类型信息、重构闭包、解释性注释或仓库先例"。文档特别强调两点立场:
- "AI coding" 命名的是重复出现的实现习惯,不是作者或出处——只评审可观察的代码,绝不推测代码是谁写的;
- 本维度不是报告个人喜好的许可证——每条发现必须指出具体代价:误导阅读流程、策略重复、过期命名、冗余运行时开销、漏掉的兼容边界,或偏离既有仓库模式。
它与相邻维度的分工边界在文档中写得很死:同一根因若已被 code-style(孤立的片段可读性问题)或 reuse-architecture(被证实的重复实现、错误层级、绕过扩展缝)完整覆盖,就在彼处报告,绝不就同一发现重复上报。这一点在 code-style.md 与 reuse-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 / 模型 / 调用点特有的条件、辅助函数或命名,被嵌进了共享抽象,使通用契约更难读、更难扩展。重点警惕那种"把单一窄分支藏在 type、status、mode 这类通用属性后面"的一次性 helper;变体策略应留在拥有它的层,或在决策点把异常条件显式写出来。
文档示例:
const getBlockErrorType = (reason: string) =>
reason === 'IMAGE_PROHIBITED_CONTENT' ? ErrorType.ContentPolicyViolation : undefined;
// Inside a shared stream parser:
type: getBlockErrorType(finishReason);
type 和 getBlockErrorType 读起来像错误分类的通用所有者,但该 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)
模式:对静态类型与调用方已经保证形状的值,反复使用 typeof、in、isRecord、防御性可选链或"先转再查"逻辑。审查前先用 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 导入 isRecord、isPlainRecord、pickString 等现成 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.ts、src/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(
testing、drizzle、trpc-router、zustand、builtin-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 怎么用本维度
把上述内容压缩成一份可执行的检查表:
- 先裁剪:仅文档/lockfile 的 diff 直接跳过本维度(
skip_when);注意 agent 指令文件(.agents/skills/**、AGENTS.md等)按代码处理。 - 过五遍:按第 3 节的顺序跑 Scope locality → Refactor closure → Type confidence → Comment intent → Repository precedent;每遍只产出该遍的证据。
- 查先例:对每个"像自创"的实现,按 reuse-architecture.md 的 Outward 搜法全仓检索(
packages/utils/、src/utils/、src/hooks/、src/lib/、变更文件同级目录),并核对 data-fetching-architecture/SKILL.md 等类别 skill 是否给出标尺;无具体先例即放弃该发现。 - 定边界:对照第 4 节逐条排除 Not violations,确认不是 code-style / reuse-architecture 已覆盖的同一根因。
- 写发现:点名代码级后果(误导阅读、重复策略、过期命名、冗余运行时工作、漏掉的兼容边界、偏离既定模式),给出文件级证据,按第 5 节标 P1/P2——绝不出现"看起来像 AI 写的"。
这套维度的价值不在"识别 AI 代码",而在于把"局部合理、全局未完成"这类最常见的机械缺陷变成可复现、可复核、可去重的检查流程。规则源文件(typescript/SKILL.md、data-fetching-architecture/SKILL.md、project-overview/SKILL.md)与 AGENTS.md、CLAUDE.md 中的仓库惯例互为引用,构成完整的证据链;而 SKILL.md 末尾的 "Keeping this skill sharp" 机制(skill-freshness 维度与 workflow_feedback 通道)则保证这类维度规则会随着评审反馈持续校准,而不是一次写定、永久过期。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00