代码在发臭:一个能"闻"出坏味道的 AI 技能,我拿它扫了最新的开源代码
本机暂存
<blockquote><p>「任何傻瓜都能写出计算机能懂的代码。好的程序员写出人能懂的代码。」——Martin Fowler</p></blockquote><p>你有没有过这种感觉:打开一个文件,还没读懂逻辑,先皱起了眉头。说不上哪儿错了,但就是觉得"不对劲"。</p><p>这种"不对劲",有个专门的名字——<strong>代码坏味道(Code Smell)</strong>。</p><img src="/2026/07/04/ai-skill-sniffs-out-code-smells/image-20260703065726678.png" class=""><p>它不是 bug,代码跑得好好的;它也不是编译错误,测试全绿。它只是一种<strong>表面的征兆</strong>,暗示底下可能藏着更深的问题。就像厨房里飘来一丝馊味,东西还没坏透,但你知道该去冰箱里翻一翻了。</p><p>这篇文章讲三件事:这个词是怎么来的、坏味道到底有哪几类、以及——我怎么用一个 AI 技能,把一个真实开源项目的核心代码"闻"了个遍。</p><hr><span id="more"></span><h2 id="一、一个厨房比喻,是怎么变成行业术语的"><a href="#一、一个厨房比喻,是怎么变成行业术语的" class="headerlink" title="一、一个厨房比喻,是怎么变成行业术语的"></a>一、一个厨房比喻,是怎么变成行业术语的</h2><p>"代码坏味道"这个词,比很多人想的要年轻。</p><p>它的源头是 1990 年代末的 <strong>WardsWiki</strong>(c2.com,程序员圈最早的维基之一)。<strong>Kent Beck</strong>——极限编程和 JUnit 的作者——在帮 <strong>Martin Fowler</strong> 写《重构》这本书时,随口造了这个词。Fowler 自己在博客里写得很直白:</p><blockquote><p>「A code smell is a surface indication that usually corresponds to a deeper problem in the system. The term was first coined by Kent Beck while helping me with my Refactoring book.」<br>(坏味道是系统表面的一个信号,往往对应着更深层的问题。这个词是 Kent Beck 帮我写《重构》时造的。)</p></blockquote><p>有意思的是,据 c2 wiki 的记载,Beck 造这个词时的灵感,部分来自同事 Massimo Arnoldi 的"鼻子"——一种对烂代码的直觉嗅觉。</p><p>真正让这个词火遍全球的,是 <strong>1999 年出版的《Refactoring: Improving the Design of Existing Code》</strong>。Fowler 在书里专门用一章(和 Beck 合写)罗列了一份"坏味道目录",并给每种味道配了对应的"重构手法"。从此,"我觉得这段代码有点臭"这种模糊的工程师直觉,第一次有了<strong>可以命名、可以分类、可以按方子治疗</strong>的框架。</p><img src="/2026/07/04/ai-skill-sniffs-out-code-smells/image-20260703065559107.png" class=""><p>为什么这个比喻能活到今天?因为它精准地抓住了一个工程真相:</p><ul><li><strong>坏味道 ≠ 错误。</strong> 有味道的代码照样能上线、能赚钱。</li><li><strong>坏味道是概率信号,不是判决。</strong> 闻到味道,你该去查,但查完可能发现没事。</li><li><strong>味道会累积。</strong> 单个坏味道无所谓,一屋子坏味道,就是所谓的"技术债"和"大泥球"。</li></ul><p>二十多年后的今天,<strong>这个概念比 1999 年更重要了</strong>。因为写代码的,多了一位新玩家——AI。AI 生成代码的速度是人的几十倍,坏味道的<strong>产量</strong>也跟着水涨船高。"<strong>闻味道</strong>"这件事,从一门手艺,正在变成一道必须自动化的工序。</p><hr><h2 id="二、坏味道的家谱:从-Fowler-的-22-种,到今天的-8-大类-50"><a href="#二、坏味道的家谱:从-Fowler-的-22-种,到今天的-8-大类-50" class="headerlink" title="二、坏味道的家谱:从 Fowler 的 22 种,到今天的 8 大类 50+"></a>二、坏味道的家谱:从 Fowler 的 22 种,到今天的 8 大类 50+</h2><p>Fowler 的原始目录里有 <strong>22 种坏味道</strong>,refactoring.guru 后来把它们整理成经典的<strong>五大类</strong>。这套分类是所有后来者的地基,先看它:</p><table><thead><tr><th>类别</th><th>坏味道</th><th>主要重构手法</th></tr></thead><tbody><tr><td><strong>臃肿类</strong>(Bloaters)</td><td>过长方法、过大的类、基本类型偏执、过长参数列表、数据泥团</td><td>Extract Method / Extract Class / Introduce Parameter Object</td></tr><tr><td><strong>OO 滥用类</strong></td><td>Switch 语句、临时字段、被拒绝的遗赠、异曲同工的类</td><td>Replace Conditional with Polymorphism / Replace Inheritance with Delegation</td></tr><tr><td><strong>变更阻碍类</strong></td><td>发散式变化、霰弹式修改、平行继承体系</td><td>Extract Class / Move Method</td></tr><tr><td><strong>冗余类</strong>(Dispensables)</td><td>注释(本该自说明)、重复代码、冗赘类、纯数据类、死代码、夸夸其谈未来性</td><td>Extract Method / Inline Class / 直接删除</td></tr><tr><td><strong>耦合类</strong>(Couplers)</td><td>依恋情节、狎昵关系、消息链、中间人</td><td>Move Method / Hide Delegate / Remove Middle Man</td></tr></tbody></table><img src="/2026/07/04/ai-skill-sniffs-out-code-smells/image-20260703071026940.png" class=""><p>这五类的核心逻辑是「<strong>症状 → 处方</strong>」:你先嗅到一个症状(比如"过长方法"),目录再告诉你该用哪几种手法去治。这套映射关系明确、能机械执行——<strong>而"能机械执行"这四个字,恰恰是 AI Agent 能接得住的地方。</strong></p><p>但 Fowler 的目录写于 2000 年,它盯的都是<strong>函数和类这个尺度</strong>的问题。今天的软件更复杂,坏味道也长出了新的分支。我用的这个 <code>smell</code> 技能,把视野往上抬了一层,也往下探了一层,整理成 <strong>8 大类、50+ 种</strong>:</p><table><thead><tr><th>类别</th><th>代表坏味道</th></tr></thead><tbody><tr><td><strong>架构</strong></td><td>大泥球、分布式单体、贫血模型、CQRS 滥用、层边界违反、过度分层、过度抽象、"未来主义"架构</td></tr><tr><td><strong>耦合</strong></td><td>循环依赖、内容耦合、公共耦合(全局状态)、印记耦合</td></tr><tr><td><strong>内聚</strong></td><td>上帝对象、霰弹式修改、依恋情结、数据泥团、发散式变化</td></tr><tr><td><strong>设计</strong></td><td>抽象泄露、静态粘连、服务定位器滥用、SOLID 违反、Switch 类型分支</td></tr><tr><td><strong>代码</strong></td><td>重复代码、长方法、基本类型偏执、魔数魔串、死代码、深层嵌套(箭头反模式)</td></tr><tr><td><strong>测试</strong></td><td>零测试覆盖、测试-实现耦合、不稳定测试</td></tr><tr><td><strong>命名</strong></td><td>模糊命名(Manager/Helper/Util 满天飞)、命名不一致</td></tr><tr><td><strong>复杂度</strong></td><td>嵌套循环 O(n²)、N+1 查询、重复线性扫描、循环内排序、渲染重复计算、数据结构选错</td></tr><tr><td>注意最后两类——<strong>「测试」和「复杂度」是 Fowler 原目录里没有的</strong>。它们为什么被加进来?因为这正是 <strong>AI 时代新暴露的坑</strong>:</td><td></td></tr></tbody></table><ul><li>AI 生成的代码,经常<strong>零测试覆盖</strong>——它写得又快又像模像样,但一行测试没有。</li><li>AI 也经常埋下 <strong>N+1 查询、循环内排序</strong>这类"低速炸弹"——小数据量下一切正常,一上量就爆炸,代码评审时一眼还看不出来。</li></ul><p>换句话说,坏味道的家谱一直在生长。从 Kent Beck 的一个厨房比喻,到 Fowler 的 22 种,再到今天覆盖架构与性能的 50+ 种——<strong>每一次扩张,都是软件复杂度提升后,工程界给自己补的一课。</strong></p><hr><h2 id="三、把-嗅觉-装进-Agent:-smell-技能是怎么工作的"><a href="#三、把-嗅觉-装进-Agent:-smell-技能是怎么工作的" class="headerlink" title="三、把"嗅觉"装进 Agent:/smell 技能是怎么工作的"></a>三、把"嗅觉"装进 Agent:<code>/smell</code> 技能是怎么工作的</h2><p>概念讲完了,来看工具。<code>/smell</code> 是一个可以直接在命令行 AI Agent(Claude Code / Ducc 这类)里调用的技能,一句话概括它的定位:</p><blockquote><p><strong><code>/smell</code> 负责诊断,<code>/refactor</code> 负责治疗。一个出报告排优先级,一个照 Fowler 手法一处处动手术。</strong></p></blockquote><img src="/2026/07/04/ai-skill-sniffs-out-code-smells/image-20260703071315310.png" class=""><p>面对一个 AI 攒出来的几万行代码库,最难的往往不是"怎么改",而是**"先改哪"**。哪个模块烂得最厉害?是架构错了,还是只有某个函数太长?<code>/smell</code> 回答的就是这个问题。</p><p>它的工作流分四步:</p><p><strong>1. 确定范围。</strong> 全项目扫、还是只扫某个模块、还是只看 <code>git diff</code> 的最近改动。大项目默认只扫改动,小项目全量扫。</p><p><strong>2. 并行取证。</strong> 派出多个 Explore 子 Agent,用 <code>find</code> / <code>grep</code> 同时跑七类扫描:项目结构、依赖关系、模块内聚、模式识别、测试覆盖、命名清晰度、复杂度热点。技能内置了一整套<strong>检测启发式规则</strong>,比如:</p><ul><li>单文件 > 500 行、公共方法 > 20 个 → 疑似<strong>上帝对象</strong></li><li>循环体里出现 <code>fetch</code>/<code>query</code>/<code>exec</code> → 疑似 <strong>N+1 查询</strong></li><li>循环里用 <code>.includes()</code>/<code>.indexOf()</code> 做查找 → <strong>重复线性扫描</strong>,本该用 Set/Map</li><li>满屏的 <code>Manager</code>/<code>Helper</code>/<code>Util</code> → <strong>模糊命名</strong></li></ul><p><strong>3. 生成结构化报告。</strong> 不是简单列问题,而是产出一份带优先级的 Markdown:</p><ul><li>执行摘要 + 整体健康评估</li><li>检测到的架构风格,对比它"本该是"的样子</li><li>按 严重 / 警告 / 建议 三级分类的发现</li><li>依赖图分析 + 模块健康评分卡</li><li>8 大类坏味道的分布统计</li><li><strong>一份分「立即 / 短期 / 长期」的重构路线图</strong></li></ul><p><strong>4. 落盘 + 汇报。</strong> 报告存到 <code>tasks/smell-report-[时间戳].md</code>,同时给你一份口头摘要。</p><p>最有价值的是那份路线图。它把"这代码是不是该重构了"这种主观焦虑,变成了<strong>可量化的行动清单</strong>:先动哪、后动哪,一目了然。改完再跑一遍,健康评分涨了没有,前后一对比就知道这次重构有没有白干。</p><p>安装也简单:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npx skills add smallnest/goal-workflow --skill smell</span><br></pre></td></tr></table></figure><p>然后直接打 <code>/smell</code>,或者用自然语言触发:「找出代码坏味道」「检测架构反模式」「分析代码复杂度」。</p><hr><h2 id="四、实战:我用-smell-扫了-Gitlawb-zero-这个开源项目"><a href="#四、实战:我用-smell-扫了-Gitlawb-zero-这个开源项目" class="headerlink" title="四、实战:我用 /smell 扫了 Gitlawb/zero 这个开源项目"></a>四、实战:我用 <code>/smell</code> 扫了 <code>Gitlawb/zero</code> 这个开源项目</h2><p>光说不练假把式。我挑了一个真实的开源项目下手:<a href="https://github.com/Gitlawb/zero"><strong>Gitlawb/zero</strong></a>——一个用 Go 写的终端 AI 编程 Agent("A terminal coding agent you own"),支持 25+ 家模型提供商,代码量不小。它昨天刚发布,比较适合用来学习。</p><p>先上体检数据:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">项目:Gitlawb/zero</span><br><span class="line">语言:Go 1.25(toolchain 1.26)</span><br><span class="line">Go 文件总数:974</span><br><span class="line">代码总行数:~25 万行</span><br><span class="line">测试文件:500 个 | 非测试文件:474 个</span><br></pre></td></tr></table></figure><img src="/2026/07/04/ai-skill-sniffs-out-code-smells/image-20260703062722697.png" class=""><p>代码量非常的大,我先用它分析核心的agent的代码,下面是分析结果的总结:</p><h3 id="-先说好的:这项目-体质-其实不错"><a href="#-先说好的:这项目-体质-其实不错" class="headerlink" title=" 先说好的:这项目"体质"其实不错"></a> 先说好的:这项目"体质"其实不错</h3><p>扫完第一个惊喜是——<strong>测试文件(500)比源码文件(474)还多</strong>。测试-代码比超过 1:1,这在开源项目里相当罕见。对照 <code>/smell</code> 的「测试」类坏味道(零测试覆盖、测试-实现耦合),zero 在这一维度上健康度很高。</p><p>第二个惊喜:<strong>全项目只有 1 处 TODO/FIXME 注释</strong>。"注释当除臭剂"(用 TODO 掩盖烂代码)这个坏味道,在这里基本不存在。目录结构也清晰——<code>cmd/</code> 放入口、<code>internal/</code> 按职责分了 60+ 个子包,不是"大泥球"。</p><h3 id="-但也闻到了明确的坏味道"><a href="#-但也闻到了明确的坏味道" class="headerlink" title=" 但也闻到了明确的坏味道"></a> 但也闻到了明确的坏味道</h3><p><strong>1. 上帝对象(God Object)—— 严重</strong></p><p>最刺鼻的一处:<code>internal/tui/model.go</code>,<strong>4657 行</strong>,单个 <code>Model</code> 结构体扛着 <strong>173 个字段</strong>、<strong>111 个方法</strong>。</p><img src="/2026/07/04/ai-skill-sniffs-out-code-smells/image-20260703071243737.png" class=""><p>对照 <code>smell</code> 技能的启发式(> 500 行、> 20 个公共方法即疑似上帝对象),这个文件超标了 <strong>9 倍</strong>。它是典型的 TUI "全知全能对象"——把整个终端界面的所有状态、所有行为都塞进了一个结构体。</p><ul><li><strong>违反原则</strong>:单一职责(SRP)</li><li><strong>处方</strong>:<code>Extract Class</code>——把 173 个字段按关注点分组(会话状态、渲染状态、输入状态、模型选择……),拆成若干个内聚的小结构体,<code>Model</code> 只做组合。</li></ul><p><strong>2. 长文件扎堆 —— 警告</strong></p><p>除了那个 4657 行的巨无霸,超过 800 行的源文件还有 <strong>23 个</strong>,重灾区集中在 <code>internal/tui/</code> 和 <code>internal/agent/</code>:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">4657 internal/tui/model.go ← 上帝对象</span><br><span class="line">2736 internal/agent/loop.go ← Agent 主循环,84 个函数</span><br><span class="line">2493 internal/tui/rendering.go</span><br><span class="line">1709 internal/tui/onboarding.go</span><br><span class="line">1693 internal/tui/provider_wizard.go</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p><code>internal/agent/loop.go</code>(2736 行 / 84 个函数)是 Agent 的核心循环,长度可以理解,但也值得警惕<strong>发散式变化</strong>——一个文件因为太多不同原因被反复修改。</p><p><strong>3. 潜在复杂度热点 —— 建议(需人工确认)</strong></p><p>启发式扫描发现:</p><ul><li><strong>307 处</strong>"循环内做线性查找"的疑似点(<code>for range</code> 内出现 <code>strings.Contains</code>/<code>slices.Contains</code>/<code>.Index()</code>)</li><li><strong>63 处</strong>"循环内排序"疑似点</li></ul><p>这两个数字需要辩证看——<code>/smell</code> 技能本身也强调 <strong>"What NOT to Flag"</strong>:如果这些循环跑在冷路径(启动、配置加载)或数据量极小(N < 100),就是可读性优先的合理取舍,不值得改。但如果落在<strong>热路径</strong>(比如每次渲染、每个 token 都跑一遍),那就是实打实的性能坑,该换 Set/Map 或把排序提到循环外。<strong>这一步机器只能标记,真正的判断得人来做。</strong></p><h3 id="-给-zero-的重构路线图"><a href="#-给-zero-的重构路线图" class="headerlink" title=" 给 zero 的重构路线图"></a> 给 zero 的重构路线图</h3><p><code>/smell</code> 的收尾,是把发现整理成一份可执行的路线图:</p><ul><li><strong>立即可做</strong>:拆分 <code>tui/model.go</code> 这个 4657 行的上帝对象——收益最高、风险可控(有 500 个测试兜底)。</li><li><strong>短期(1-3 月)</strong>:审查 23 个超长文件里的 <code>tui/</code> 一组,按关注点做 Extract Class。</li><li><strong>长期</strong>:给 307 处线性扫描 + 63 处循环内排序做一次热路径分析,确认哪些真在性能关键路径上,针对性优化数据结构。</li></ul><p><strong>整体健康评估: 良好偏上。</strong> 架构清晰、测试充分、几乎无死代码,这是它的底子;主要问题集中在 TUI 层的"上帝对象"和长文件——这也是 TUI 类项目的通病,状态多、交互密,天然容易膨胀。</p><p><code>/smell</code>技能除了生成总结外,还会生成一个详细的分析报告:</p><img src="/2026/07/04/ai-skill-sniffs-out-code-smells/image-20260703063103024.png" class=""><img src="/2026/07/04/ai-skill-sniffs-out-code-smells/image-20260703063126184.png" class=""><hr><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><p>从 Kent Beck 1990 年代末的一个厨房比喻,到 Fowler 的 22 种坏味道目录,再到今天覆盖架构与性能的 50+ 种、并且能被 AI Agent 自动扫描的技能——<strong>"闻代码"这件事,走过了从直觉、到方法论、到自动化的完整二十年。</strong></p><p>AI 让写代码变快了,但也让坏味道产量暴增。这时候,一个能自动"闻味道、排优先级、给路线图"的工具,价值不在于它多聪明,而在于它把"这代码是不是该收拾了"这个含糊的焦虑,变成了一份<strong>你今天下午就能动手的清单</strong>。</p><p>代码必须变干净——而变干净的第一步,是先知道它哪儿臭了。</p><hr><p><strong>Sources / 参考资料:</strong></p><ul><li><a href="https://martinfowler.com/bliki/CodeSmell.html">Code Smell — Martin Fowler</a></li><li><a href="https://en.wikipedia.org/wiki/Code_smell">Code smell — Wikipedia</a></li><li><a href="http://c2.com/ppr/wiki/WikiPagesAboutRefactoring/CodeSmell.html">CodeSmell — c2 WardsWiki</a></li><li>《Refactoring: Improving the Design of Existing Code》, Martin Fowler & Kent Beck, 1999</li><li>refactoring.guru 坏味道与重构手法目录</li><li>《AI时代的软件工程》第 23 章「重构:AI 时代的代码进化」</li><li><code>smell</code> 技能:<a href="https://github.com/smallnest/goal-workflow">https://github.com/smallnest/goal-workflow</a></li><li>分析对象:<a href="https://github.com/Gitlawb/zero">https://github.com/Gitlawb/zero</a></li></ul>
同分类推荐文章
- 我的 Claude Code 常用 SKILLS 和工具 (2026-06-29 08:00:00)
- 重构:AI 时代的代码进化 (2026-06-28 17:30:00)
- Goal Workflow:目标驱动的研发闭环 (2026-06-28 11:30:00)
建议继续学习
- 开发与研发 (累计阅读 12,039)
- AWStats简介:Apache/Windows IIS的日志分析工具的下载,安装,配置样例和使用(含6.9中文定义补丁) (累计阅读 10,104)
- 怎么样才是好的程序员 (累计阅读 7,782)
- 抵制代码重写 (累计阅读 5,556)
- MogileFS 的介绍(MogileFS 系列1) (累计阅读 5,177)
- 我对开源的看法 (累计阅读 4,942)
- 五款最好的免费同步软件 (累计阅读 4,824)
- 如何避免重构带来的危险 (累计阅读 4,668)
- 什么是重构,什么不是重构 (累计阅读 4,646)
- 代码里的命名规则:错误的和正确的对比 (累计阅读 4,450)