ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

减少AI Slop的关键:写更少、更清晰的代码,提升AI生成质量

减少AI Slop的关键:写更少、更清晰的代码,提升AI生成质量 AI 辅助编程流行到现在一个让人不太舒服的现象越来越明显很多团队代码产出速度是上去了但代码库的“信息密度”却下来了。大量的函数、类、组件是模型生成的看起来能跑改起来却像猜谜。更麻烦的是这些代码还常常携带着重复的逻辑、隐晦的命名、无序的依赖关系导致 AI 工具在后续维护中越来越容易犯错生成的内容也越来越像一种“技术债的印刷机”。最近技术社区里有个判断引起了我的注意Dex Horthy 回应 Matt Pocock 的观点核心落点不是“模型能力不够”也不是“提示词写得不好”而是——减少 AI slop 的办法是写更少的代码。这个说法听起来反直觉。偏偏它是当前 AI 辅助编程走向工程化时最值得认真思考的一个判断。这篇文章就围绕这个观点展开我会先解释 AI slop 到底指什么再拆解为什么“代码量本身”会成为影响 AI 生成质量的关键变量最后给出几条能在实际项目中落地的设计思路和检查方法。1. 先理解AI slop 是内容问题更是代码库结构问题“AI slop”最初用来形容 AI 批量生成的、看似合理但缺乏信息量的内容。放到开发者语境里它指的是一类特殊代码语法正确、结构完整、能通过 review但本质上是大量统计上“合理”的模板堆叠。这类代码有几个典型特征同一个业务逻辑在多个文件里以不同形式重复出现。函数命名抽象不看调用链根本猜不到真实语义。为了“灵活”引入多层封装但这些封装从未被复用。对输入缺少年份、时区、权限、边界等隐式约束的描述。依赖关系混乱删一个看起来没用的工具函数生产环境立刻报错。传统开发里这些问题叫“坏味道”靠代码评审和重构慢慢清理。AI 辅助编程时代问题被加速放大了模型会在已有坏味道的基础上继续生成“和谐一致”的坏味道。代码库越复杂模型越容易生成看似正确但实际绕远路的实现。这不是模型变笨了而是上下文里的噪声变多了模型理解的置信度下降了。Matt Pocock 说的“write less code”表面是写作建议实质是给 AI 减负代码越少模型需要理解的逻辑空间越小生成内容的混乱概率越低。Dex Horthy 的呼应用更工程化的方式验证了这个判断干净的、边界清晰的、尽量精简的代码库本身就是最好的 AI 提示词。2. 为什么“写更少的代码”能直接降低 AI slop如果你只在提示词层面努力比如告诉模型“请生成高质量代码”效果极其有限。模型无法从抽象指令里判断你项目的真实约束。真正影响生成质量的是代码库里可被观察到的一致性。这里必须区分两个概念全部代码量仓库里所有代码的行数。有效决策量代码里真正承载业务约束的、不可继续简化的那部分。AI slop 高发区通常是有效决策量很低、但全部代码量很高的模块。比如一段把“用户状态”和“订单状态”耦合在一起的复杂判断表面是几十行 if-else实际决策只有两个状态本身。模型在看到这种实现时不会意识到可以抽象它只会觉得“这个项目风格就是这样的”于是继续生成类似的长函数进一步加固坏味道。减少代码量本质上是做三件事消除不必要的分支用状态映射表代替重复 if-else。压缩表达空间把多个参数封装成值对象减小函数签名面积。减少跨模块隐式依赖让一个职责只出现在一个地方。当代码结构接近“一个决策只出现一次”时AI 模型读上下文时信息噪声会明显降低。它生成的代码会更倾向于复用现有函数而不是新造一个相似实现。2.1 小函数、单一职责与模型置信度你可能会说函数拆得越细代码量不是越大吗这里的“代码量”指的从来不是“文件数”或“函数个数”而是有效逻辑的重复度。比如有两个 30 行的函数内部有一段 20 行逻辑完全一样合并后变成 40 行代码量是下降的。对模型来说它只需要理解一次这段逻辑生成新代码时也更倾向于调用这个公共逻辑。真正能提升模型置信度的设计原则是每个函数只做一件事且这件事能在 5 秒内讲清楚。模块之间的依赖方向明确最好能画出单向依赖图。业务语义通过类型或常量表达而不是靠魔法值。2.2 显式配置代替流程分支很多情况下减少流程分支比减少函数数量更有效。比如一段根据业务类型执行不同策略的代码新手会用 if-else 堆策略老手会用一个枚举和一张映射表。显式配置让决策点和执行逻辑分离模型只需要读懂策略集合和分发规则生成的代码更容易遵循既有模式。// 反例if-else 堆策略模型很容易在此基础上继续加分支 public String handle(String type) { if (A.equals(type)) { return doA(); } else if (B.equals(type)) { return doB(); } else if (C.equals(type)) { return doC(); } else { return defaultHandle(); } }// 正例把策略放进显式映射新增类型只新增键值 public enum BizType { A(A, HandlerA.class), B(B, HandlerB.class), C(C, HandlerC.class); private final String code; private final Class? extends Handler handlerClass; }后者没有“更短”但把“决策”和“执行”分开了。AI 看到这样的结构时会更容易推断新增业务类型的接入方式而不是自动生成一个又一个 else-if。3. 从提示词工程转向代码工程核心判断依据现在流行很多“AI 编程提示词技巧”比如告诉模型“你是资深架构师”“请仔细思考”云云。这些技巧不是没用但它解决问题的方式是提高单次生成的成功概率没有改变代码库逐步腐化的趋势。Dex Horthy 的回应提供了一个相反的思路不要把希望全压在模型的推理能力上而是通过代码本身的结构把模型每次生成任务的复杂度降下来。这个判断的依据是什么模型生成代码时不是凭空想象而是依赖对话窗口里的上下文。上下文里有什么现有代码、类型定义、错误日志、用户需求。如果现有代码是一个依赖不清、职责混杂的泥球模型就很难判断“哪个函数是真正可以复用的”“哪条路径是正确的”。反过来如果上下文里类型清晰、边界明确、函数短小模型生成新代码时就更有可能找到已存在的工具函数来复用。遵循一致的命名风格。不额外引入不必要的抽象层。不隐藏复杂的副作用。所以有一句话可以概括这个观点好的 AI 编程实践不是学会如何提出更好的问题而是让代码库本身成为更好的“问题描述”。3.1 代码量是硬指标吗很多人会拿“代码行数减少”作为重构目标。这里要泼一盆冷水行数不是目标决策熵才是目标。有些重构虽然增加了行数但减少了模型的决策难度。比如把散落在三个类里的常量收敛到一个枚举里行数不一定减少但模型在生成代码时能更快理解可选值是哪些。所以衡量的指标应该是模型生成代码后需要人工做结构性修改的比例。单次生成中出现“新造一个已有功能函数”的次数。模型在同一个任务上连续生成的方差。代码行数只做参考不必对它做硬性规定。4. 工程化落地用模块边界和类型约束给 AI “降噪”现在把问题落到实际工程操作上。如果你希望减少 AI slop不是去修改提示词模板而是先重构现有的代码结构。这里给出三个可以立刻执行的改造方向。4.1 拆分模块控制单次生成的信息面AI 模型的工作记忆有限上下文窗口再大也不代表它能同时处理好所有信息。当一个文件有 3000 行、耦合了 20 个业务概念时模型读到后几行时前几行的关键约束已经被“稀释”了。拆模块的本质是帮模型建立“这个任务只需要关心这一小片代码”的明确信号。比如一个电商订单服务里不要把支付回调、库存扣减、通知发送、对账逻辑全部揉进同一个类。拆成OrderPaymentService、InventoryService、NotificationService然后在聚合根里按流程调用。这样当你让 AI 修改退款逻辑时它只需要集中在支付相关模块不会因为看到库存扣减的代码而把不相关逻辑带进来。4.2 用类型表达约束把领域规则写进签名另一种减少 AI 生成歧义的方式是让约束进入代码本身。如果你的参数是一个String userId模型无法判断它是用户 ID 还是订单 ID。如果你的返回值是ListMapString, Object模型只能“猜”结构。定义明确的值对象能让 AI 在生成代码时遵循合法的传入传出结构。// 不推荐类型表达的信息量太少 public ListMapString, Object queryUserOrder(String start, String end) { ... } // 推荐签名已经说明大部分规则 public ListOrderSummary queryUserOrders(UserId userId, TimeRange range) { ... }模型看到后一种签名时生成测试、补全逻辑、接入调用的成功率都会更高。因为类型系统已经把很多规则“告诉”模型了它不需要自己推断。4.3 收敛工具函数减少重复生成很多时候AI 生成重复代码的根源是它不知道已有一个公共函数。解决方法是让公共函数集中、命名清晰、列表可发现。比如把所有日期时间处理类方法放在TimeKit里所有订单号生成规则放在OrderNoGenerator里。模型在上下文里看到这些类时调用概率会显著高于在多个工具文件里寻找。5. 判断“代码库是否在变短”的可操作脚本前面说了很多概念没有验证方法就容易变成空谈。这里提供一个 Python 脚本用来统计仓库里高重复度函数、超长函数、魔法值出现次数。这个脚本不是权威工具只是给你一个量化的观察窗口适合在重构前后对比。# 文件路径tools/code_complexity_check.py # 依赖python3不需要第三方库 import os import re import sys from collections import defaultdict # 可自行扩充后缀 CODE_EXTS {.py, .java, .ts, .js, .go, .rs} # 一些常见的高风险魔法值 MAGIC_PATTERN re.compile(r\b([1-9][0-9]{2}|0x[0-9a-fA-F]{2,})\b) # 统计每行超过120字符的长函数简单近似 LONG_LINE_THRESHOLD 120 def file_iter(root_dir): for root, dirs, files in os.walk(root_dir): # 跳过常规构建目录 dirs[:] [d for d in dirs if d not in {node_modules, target, dist, build, .git, venv, __pycache__}] for f in files: if f.endswith(tuple(CODE_EXTS)): yield os.path.join(root, f) def analyze_file(path): long_lines 0 magic_nums 0 func_lines [] current_func None current_start 0 indent_stack [] total_lines 0 with open(path, r, encodingutf-8, errorsignore) as fh: lines fh.readlines() total_lines len(lines) for idx, line in enumerate(lines): if len(line.rstrip(\n)) LONG_LINE_THRESHOLD: long_lines 1 magic_nums len(MAGIC_PATTERN.findall(line)) stripped line.strip() # 近似识别函数定义行Python defJava/TS 方法签名等 if re.match(r^(def|public|private|protected|export function|export const), stripped): current_func stripped[:80] current_start idx # 近似统计函数体行数遇到下一个函数定义则结算 if current_func and idx 0 and idx - current_start 1: # 简单起见读到明显缩进减少或空行超过2次就结算 current_indent len(line) - len(line.lstrip()) if indent_stack and current_indent indent_stack[-1] and idx - current_start 3: func_lines.append((current_func, idx - current_start)) current_func None indent_stack [] if current_func and stripped and not stripped.startswith(#) and not stripped.startswith(//): indent_stack.append(len(line) - len(line.lstrip())) return { total_lines: total_lines, long_lines: long_lines, magic_nums: magic_nums, func_lines: func_lines, } def main(): root sys.argv[1] if len(sys.argv) 1 else . summary defaultdict(int) overlong_funcs [] for path in file_iter(root): info analyze_file(path) summary[total_lines] info[total_lines] summary[long_lines] info[long_lines] summary[magic_nums] info[magic_nums] for name, length in info[func_lines]: if length 60: overlong_funcs.append((path, name, length)) print( 简化结果 ) print(f扫描代码总行数: {summary[total_lines]}) print(f超长行数: {summary[long_lines]}) print(f魔法数字/常量出现次数: {summary[magic_nums]}) print(f超长函数(60行)数量: {len(overlong_funcs)}) for path, name, length in overlong_funcs[:20]: print(f {path}: {name} ({length}行)) # 阈值由读者按项目实际情况设定 if summary[long_lines] 0.01 * max(1, summary[total_lines]): print(提示长行比例偏高模型生成结果容易被噪声干扰。) else: print(提示长行比例尚可但需结合函数长度继续判断。) if __name__ __main__: main()# 运行方式在项目根目录执行 python3 tools/code_complexity_check.py .这个脚本的核心价值不是给出一个“标准答案”而是让“代码库复杂度”这件事变得可观测。你在重构前后各跑一次如果超长函数数量下降、魔法值密度下降说明代码的信息噪声在减少AI 生成质量大概率也会提升。5.1 如何验证代码简化确实改善了 AI 生成效果量化“AI slop”本身是个难题。比较实用的做法是选择一个高频任务在重构前后分别让模型完成同样需求对比生成的代码做三类统计是否需要人工删掉多余分支或无用导入。是否直接调用了已存在的工具函数。是否在评审中被要求重写结构。建议把这类对比记录成简单的 issue 模板让团队成员在遇到明显 AI slop 时能按固定格式上报。数据积累一段时间后你会更清楚代码库的哪个区域最需要重构。6. 这个观点容易引发哪些误解“少写代码”这个口号太容易被人简化为“代码行数越少越好”“用更短的代码实现同样功能”。如果只停留在这种理解上会适得其反。6.1 误解一用技巧把代码压缩到一行为了减少行数把可读性牺牲掉是典型的本末倒置。AI 模型和人类阅读者一样需要的是语义清晰而不是物理行数最少。一个 20 行的明确函数好过一个塞满三元表达式和逗号操作符的 3 行函数。真正的“少”是少在重复、少在隐晦、少在绕路而不是少在换行。6.2 误解二所有代码都该由 AI 生成人类只做删减Dex Horthy 的观点不是让 AI 生成大量代码然后人工删减而是要你先把代码库结构整理到一种“不需要生成大量代码”的状态。功能开发不是做题不是代码越多越有价值。新增一个小功能时如果现有抽象足够好有时只需要改一行配置。6.3 误解三写更少的代码意味着降低业务能力完全不是。业务复杂度是客观存在的减少代码量不是为了消灭业务复杂度而是避免让实现层面产生“第二份复杂度”。业务规则必须存在但实现这些规则的代码不应该重复出现。这个观点在当前编程语言、框架、AI 工具背景下依然成立。7. 具体场景中“减少代码量”的几种常见做法下面结合几个常见编程场景看“少写代码”如何实际影响 AI 生成质量。7.1 场景一状态机逻辑订单状态流转是典型的 AI slop 高发区。如果不用状态机模型很容易生成一堆 if 判断而且状态一多就乱。引入状态机描述后代码量没有显著下降但分支数量少了AI 能更准确理解状态间合法迁移。# 文件路径order/state_machine.py from enum import Enum class OrderState(Enum): CREATED CREATED PAID PAID SHIPPED SHIPPED COMPLETED COMPLETED CANCELLED CANCELLED # 显式定义合法迁移路径 ALLOWED_TRANSITIONS { OrderState.CREATED: {OrderState.PAID, OrderState.CANCELLED}, OrderState.PAID: {OrderState.SHIPPED, OrderState.CANCELLED}, OrderState.SHIPPED: {OrderState.COMPLETED}, OrderState.COMPLETED: set(), OrderState.CANCELLED: set(), } def can_transition(current: OrderState, target: OrderState) - bool: return target in ALLOWED_TRANSITIONS[current]模型看到这个结构时只需新增合法迁移集中的目标状态不需要发明新的判断流。状态合法性清晰slop 自然不容易混进来。7.2 场景二配置切换而不是代码分支经常会遇到按渠道、按用户等级走不同逻辑的场景。如果把这些差异沉到配置中心而不是代码里堆 if-elseAI 的新增功能开发通常是新增一条配置而不是扩展一段业务逻辑。配置化的结果是生成代码时的“规则面积”缩小了。比如一个平台同时支持支付宝和微信支付可以把支付渠道的签约参数放配置中心业务代码只处理“支付结果回调”这一个统一事件。无论新接入什么渠道都不需要在核心代码上新增分支。7.3 场景三封装的粒度和 AI 补全如果你的类是“上帝类”字段多到几十个方法几十个模型在生成调用时往往会猜错字段或方法。把一个通用类拆成多个小类效果是模型在上下文窗口中看到的每个类更聚焦补全代码时选错方法名的概率下降。8. 从代码评审看 AI slop 的预警信号实际开发中评审时下面几种情况出现往往说明代码库已经给了 AI 太多“坏示范”新提交的代码与某个已有工具函数高度相似只是名字不同。一个函数中超过三个if分支处理同一个概念的不同子类型。新代码引入了一个新的日期格式化工具而不是复用已有工具类。错误处理逻辑在多个调用点反复复制而不是收敛到一个统一错误包装器里。小型改动触发了大量无关文件的编译或类型报错。这些现象和“代码量太大”直接相关。它们意味着模块边界已经模糊模型只能按照近期上下文里的坏模式继续生成。当评审出现这些信号时与其抱怨“AI 生成质量差”不如回头审视代码结构是不是模块之间依赖太乱、是不是职责重叠太严重、是不是外部依赖过多导致模型无法判断推荐路径。9. 团队实践建议把“减少 AI slop”变成可执行的协作约定落到团队层面单靠个人写代码时“少写一点”不够。要把这件事变成协作约定才能让整个代码库长期保持对 AI 友好。9.1 约定 1新功能优先找现有抽象不急着新增给团队一个做法需求评审阶段先检索代码库中是否已有近似的函数、枚举、状态机或配置项。如果已有 70% 能覆盖优先扩展而不是另起炉灶。这样模型在后续补全时上下文里会用现成符号而不是见一个造一个。9.2 约定 2代码评审增加一项“AI slop 检查”评审清单除了性能、安全、可读性之外增加问句这段代码里是否有 70% 以上内容可从现成函数或类型推导出来是否存在模型很容易在别处再生成一遍的重复逻辑这段代码新增后相邻模块的复杂度是上升了还是下降了这些问题能帮助团队在早期阻止 AI slop 累积。9.3 约定 3定期重构“上下文杀手”什么是上下文杀手就是那些一打开就占掉大量上下文空间、却提供极少有用约束的文件。典型例子是超大的 DTO、工具类、常量类。定期把大文件按业务维度拆分能让 AI 读取上下文时不被无关符号干扰。这个过程本身不是“删除功能”而是给未来代码生成腾出干净的推理空间。10. 理性看待这不是否定 AI 编程而是换一种使用姿势这篇文章不是在唱反调说“AI 编程不行”而是在探讨一个更现实的问题同一个模型为什么有的团队用得效率倍增有的团队越用代码越乱差别常在于高效率团队往往有一个先决条件——代码库对 AI 来说更容易理解。他们不是用了更神奇的提示词而是把代码本身整理得更短、更直接、更不容易被误解。这个观点的重要性在于它把“AI 生成质量”这个看似归模型管的问题拉回到了工程师自己的掌控范围。不需要等下一代模型解决所有问题今天你就可以通过精简代码边界、收敛模块依赖、把业务决策显式化来大幅减少 AI slop 出现的概率。回到 Matt Pocock 和 Dex Horthy 的呼应减少 AI slop 的办法确实是写更少的代码但这里的“更少”不是简单减法而是把每一行都放在它能承载最大决策价值的位置上。如果代码库里的每一行代码都在表达一个真实的、不可省略的业务约束那么 AI 生成的下一段代码自然会收敛到这个代码库既有的正确模式里。对于正在吃 AI slop 苦头的团队我的建议是不要急着换更大的上下文窗口也不要频繁调提示词模板先把一个最常让 AI 改动的模块拆小、收敛依赖、显式表达状态再让模型重新生成一次同样的功能你大概率会看到明显的质量差异。
返回列表