IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

代码在发臭:一个能"闻"出坏味道的 AI 技能,我拿它扫了最新的开源代码

鸟窝 2026-07-04 16:40:40 累计浏览 14 次
本机暂存
<blockquote><p>「任何傻瓜都能写出计算机能懂的代码。好的程序员写出人能懂的代码。」——Martin Fowler</p></blockquote><p>你有没有过这种感觉:打开一个文件,还没读懂逻辑,先皱起了眉头。说不上哪儿错了,但就是觉得&quot;不对劲&quot;。</p><p>这种&quot;不对劲&quot;,有个专门的名字——<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 技能,把一个真实开源项目的核心代码&quot;闻&quot;了个遍。</p><hr><span id="more"></span><h2 id="一、一个厨房比喻,是怎么变成行业术语的"><a href="#一、一个厨房比喻,是怎么变成行业术语的" class="headerlink" title="一、一个厨房比喻,是怎么变成行业术语的"></a>一、一个厨房比喻,是怎么变成行业术语的</h2><p>&quot;代码坏味道&quot;这个词,比很多人想的要年轻。</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 的&quot;鼻子&quot;——一种对烂代码的直觉嗅觉。</p><p>真正让这个词火遍全球的,是 <strong>1999 年出版的《Refactoring: Improving the Design of Existing Code》</strong>。Fowler 在书里专门用一章(和 Beck 合写)罗列了一份&quot;坏味道目录&quot;,并给每种味道配了对应的&quot;重构手法&quot;。从此,&quot;我觉得这段代码有点臭&quot;这种模糊的工程师直觉,第一次有了<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> 单个坏味道无所谓,一屋子坏味道,就是所谓的&quot;技术债&quot;和&quot;大泥球&quot;。</li></ul><p>二十多年后的今天,<strong>这个概念比 1999 年更重要了</strong>。因为写代码的,多了一位新玩家——AI。AI 生成代码的速度是人的几十倍,坏味道的<strong>产量</strong>也跟着水涨船高。&quot;<strong>闻味道</strong>&quot;这件事,从一门手艺,正在变成一道必须自动化的工序。</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 &#x2F; Extract Class &#x2F; Introduce Parameter Object</td></tr><tr><td><strong>OO 滥用类</strong></td><td>Switch 语句、临时字段、被拒绝的遗赠、异曲同工的类</td><td>Replace Conditional with Polymorphism &#x2F; Replace Inheritance with Delegation</td></tr><tr><td><strong>变更阻碍类</strong></td><td>发散式变化、霰弹式修改、平行继承体系</td><td>Extract Class &#x2F; Move Method</td></tr><tr><td><strong>冗余类</strong>(Dispensables)</td><td>注释(本该自说明)、重复代码、冗赘类、纯数据类、死代码、夸夸其谈未来性</td><td>Extract Method &#x2F; Inline Class &#x2F; 直接删除</td></tr><tr><td><strong>耦合类</strong>(Couplers)</td><td>依恋情节、狎昵关系、消息链、中间人</td><td>Move Method &#x2F; Hide Delegate &#x2F; 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>」:你先嗅到一个症状(比如&quot;过长方法&quot;),目录再告诉你该用哪几种手法去治。这套映射关系明确、能机械执行——<strong>而&quot;能机械执行&quot;这四个字,恰恰是 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 滥用、层边界违反、过度分层、过度抽象、&quot;未来主义&quot;架构</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&#x2F;Helper&#x2F;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>这类&quot;低速炸弹&quot;——小数据量下一切正常,一上量就爆炸,代码评审时一眼还看不出来。</li></ul><p>换句话说,坏味道的家谱一直在生长。从 Kent Beck 的一个厨房比喻,到 Fowler 的 22 种,再到今天覆盖架构与性能的 50+ 种——<strong>每一次扩张,都是软件复杂度提升后,工程界给自己补的一课。</strong></p><hr><h2 id="三、把-嗅觉-装进-Agent:-smell-技能是怎么工作的"><a href="#三、把-嗅觉-装进-Agent:-smell-技能是怎么工作的" class="headerlink" title="三、把&quot;嗅觉&quot;装进 Agent:/smell 技能是怎么工作的"></a>三、把&quot;嗅觉&quot;装进 Agent:<code>/smell</code> 技能是怎么工作的</h2><p>概念讲完了,来看工具。<code>/smell</code> 是一个可以直接在命令行 AI Agent(Claude Code &#x2F; 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 攒出来的几万行代码库,最难的往往不是&quot;怎么改&quot;,而是**&quot;先改哪&quot;**。哪个模块烂得最厉害?是架构错了,还是只有某个函数太长?<code>/smell</code> 回答的就是这个问题。</p><p>它的工作流分四步:</p><p><strong>1. 确定范围。</strong> 全项目扫、还是只扫某个模块、还是只看 <code>git diff</code> 的最近改动。大项目默认只扫改动,小项目全量扫。</p><p><strong>2. 并行取证。</strong> 派出多个 Explore 子 Agent,用 <code>find</code> &#x2F; <code>grep</code> 同时跑七类扫描:项目结构、依赖关系、模块内聚、模式识别、测试覆盖、命名清晰度、复杂度热点。技能内置了一整套<strong>检测启发式规则</strong>,比如:</p><ul><li>单文件 &gt; 500 行、公共方法 &gt; 20 个 → 疑似<strong>上帝对象</strong></li><li>循环体里出现 <code>fetch</code>&#x2F;<code>query</code>&#x2F;<code>exec</code> → 疑似 <strong>N+1 查询</strong></li><li>循环里用 <code>.includes()</code>&#x2F;<code>.indexOf()</code> 做查找 → <strong>重复线性扫描</strong>,本该用 Set&#x2F;Map</li><li>满屏的 <code>Manager</code>&#x2F;<code>Helper</code>&#x2F;<code>Util</code> → <strong>模糊命名</strong></li></ul><p><strong>3. 生成结构化报告。</strong> 不是简单列问题,而是产出一份带优先级的 Markdown:</p><ul><li>执行摘要 + 整体健康评估</li><li>检测到的架构风格,对比它&quot;本该是&quot;的样子</li><li>按 严重 &#x2F; 警告 &#x2F; 建议 三级分类的发现</li><li>依赖图分析 + 模块健康评分卡</li><li>8 大类坏味道的分布统计</li><li><strong>一份分「立即 &#x2F; 短期 &#x2F; 长期」的重构路线图</strong></li></ul><p><strong>4. 落盘 + 汇报。</strong> 报告存到 <code>tasks/smell-report-[时间戳].md</code>,同时给你一份口头摘要。</p><p>最有价值的是那份路线图。它把&quot;这代码是不是该重构了&quot;这种主观焦虑,变成了<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&#x2F;zero</strong></a>——一个用 Go 写的终端 AI 编程 Agent(&quot;A terminal coding agent you own&quot;),支持 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=" 先说好的:这项目&quot;体质&quot;其实不错"></a> 先说好的:这项目&quot;体质&quot;其实不错</h3><p>扫完第一个惊喜是——<strong>测试文件(500)比源码文件(474)还多</strong>。测试-代码比超过 1:1,这在开源项目里相当罕见。对照 <code>/smell</code> 的「测试」类坏味道(零测试覆盖、测试-实现耦合),zero 在这一维度上健康度很高。</p><p>第二个惊喜:<strong>全项目只有 1 处 TODO&#x2F;FIXME 注释</strong>。&quot;注释当除臭剂&quot;(用 TODO 掩盖烂代码)这个坏味道,在这里基本不存在。目录结构也清晰——<code>cmd/</code> 放入口、<code>internal/</code> 按职责分了 60+ 个子包,不是&quot;大泥球&quot;。</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> 技能的启发式(&gt; 500 行、&gt; 20 个公共方法即疑似上帝对象),这个文件超标了 <strong>9 倍</strong>。它是典型的 TUI &quot;全知全能对象&quot;——把整个终端界面的所有状态、所有行为都塞进了一个结构体。</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 行 &#x2F; 84 个函数)是 Agent 的核心循环,长度可以理解,但也值得警惕<strong>发散式变化</strong>——一个文件因为太多不同原因被反复修改。</p><p><strong>3. 潜在复杂度热点 —— 建议(需人工确认)</strong></p><p>启发式扫描发现:</p><ul><li><strong>307 处</strong>&quot;循环内做线性查找&quot;的疑似点(<code>for range</code> 内出现 <code>strings.Contains</code>&#x2F;<code>slices.Contains</code>&#x2F;<code>.Index()</code>)</li><li><strong>63 处</strong>&quot;循环内排序&quot;疑似点</li></ul><p>这两个数字需要辩证看——<code>/smell</code> 技能本身也强调 <strong>&quot;What NOT to Flag&quot;</strong>:如果这些循环跑在冷路径(启动、配置加载)或数据量极小(N &lt; 100),就是可读性优先的合理取舍,不值得改。但如果落在<strong>热路径</strong>(比如每次渲染、每个 token 都跑一遍),那就是实打实的性能坑,该换 Set&#x2F;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 层的&quot;上帝对象&quot;和长文件——这也是 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>&quot;闻代码&quot;这件事,走过了从直觉、到方法论、到自动化的完整二十年。</strong></p><p>AI 让写代码变快了,但也让坏味道产量暴增。这时候,一个能自动&quot;闻味道、排优先级、给路线图&quot;的工具,价值不在于它多聪明,而在于它把&quot;这代码是不是该收拾了&quot;这个含糊的焦虑,变成了一份<strong>你今天下午就能动手的清单</strong>。</p><p>代码必须变干净——而变干净的第一步,是先知道它哪儿臭了。</p><hr><p><strong>Sources &#x2F; 参考资料:</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 &amp; 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>

同分类推荐文章

  1. 我的 Claude Code 常用 SKILLS 和工具 (2026-06-29 08:00:00)
  2. 重构:AI 时代的代码进化 (2026-06-28 17:30:00)
  3. Goal Workflow:目标驱动的研发闭环 (2026-06-28 11:30:00)

查看更多 开发者 文章 →

建议继续学习

  1. 开发与研发 (累计阅读 12,039)
  2. AWStats简介:Apache/Windows IIS的日志分析工具的下载,安装,配置样例和使用(含6.9中文定义补丁) (累计阅读 10,104)
  3. 怎么样才是好的程序员 (累计阅读 7,782)
  4. 抵制代码重写 (累计阅读 5,556)
  5. MogileFS 的介绍(MogileFS 系列1) (累计阅读 5,177)
  6. 我对开源的看法 (累计阅读 4,942)
  7. 五款最好的免费同步软件 (累计阅读 4,824)
  8. 如何避免重构带来的危险 (累计阅读 4,668)
  9. 什么是重构,什么不是重构 (累计阅读 4,646)
  10. 代码里的命名规则:错误的和正确的对比 (累计阅读 4,450)