尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

手动重打LLM代码:消除认知债务,真正理解每一行

手动重打LLM代码:消除认知债务,真正理解每一行 LLM 生成的代码看起来很完整跑起来也能通过测试但如果你自己其实说不清每一行在做什么那么这些代码就会在项目里慢慢堆成一种隐性负债。最近我越来越觉得手动重打 LLM 生成的代码不是浪费时间也不是对工具的不信任而是一种把“看起来能用”变成“我真的懂”的最直接方法。这篇文章就围绕“预防认知债务”这个主题聊聊什么时候该手动重打、怎么重打、怎么验证自己真的理解了以及批量使用 AI 写代码时怎么避免翻车。先说结论手动重打不是让你把代码抄一遍而是让你在抄写过程中完成一次强制理解。只复制粘贴代码是进了仓库但没进脑子。等后面要修 bug、加功能、改逻辑的时候你会发现每一行都像别人写的。那种状态就是认知债务最典型的症状。1. 认知债务是怎么被“能跑的代码”堆出来的1.1 先分清跑得通不代表你真的理解了很多 LLM 生成的代码第一次跑就能出结果。尤其是一些常见的 CRUD 接口、数据处理脚本、前端组件生成的正确率相当高。问题恰恰出在这里越顺利越容易跳过理解。我见过不少团队开发速度看起来很快一个人一天能“写”几百行代码。但到了联调阶段问题开始密集出现。改一个字段名连带报错调一个参数不知道对应哪段逻辑想让程序支持批量输入结果发现原来的代码把所有输入都当成了单条数据。这些代码从语法上没有错但写代码的人并不清楚它背后的假设。比如有些生成代码默认输入是非空字符串有些默认列表长度大于 0有些默认外部接口不会超时。这些假设在第一次跑通时不会暴露等真实数据进来问题才集中爆发。可以把“能跑通”当成最低标准而不是最终标准。真正的最低标准应该是你能在不出意外的情况下把这段代码的逻辑讲给别人听并且能回答“为什么这里要判断空值”“为什么这里要用异步”“为什么这个参数要放在配置里”。1.2 手动重打到底在消除哪一类债务认知债务和普通代码债务不一样。普通代码债务是设计不合理、缺少抽象、没有测试这些问题可以通过重构来偿还。认知债务是你根本不知道代码在做什么连从哪里开始重构都不知道。手动重打消除的是“知道结果不知道过程”的债务。当你把 LLM 生成的代码放到一边自己重新写一遍时你会被迫面对每个函数名、每个参数、每个循环、每个条件分支。你写不出来说明某个地方没理解你写出来但和原版不一样说明你找到了更自然或更适合自己的表达方式。这个过程本质上是在做一件语言学的事把机器生成的表达翻译成你自己能复述的表达。认知科学家管这叫“生成效应”也就是主动回忆和重新产出比被动阅读更容易留下长期记忆。手动重打就是利用这个原理把短期工作记忆变成长期理解。所以别把重打当成一种仪式感。它是在给未来的自己留线索让你在几个月后回头看代码时不需要重新花半天时间推断当初为什么这么写。2. 哪些代码值得手动重打哪些可以直接用2.1 值得重打的代码核心逻辑、算法、关键配置不是所有 LLM 生成的代码都值得手动重打。如果一段代码是项目里最核心的业务规则、算法、状态流转、权限校验那你必须能完全掌控它。这些代码一旦出错影响面很大而且排查的时候需要快速定位。我一般会把这些代码抽出来单独看核心业务规则比如订单金额计算、库存扣减、优惠叠加、风控判断。算法实现比如排序、搜索、匹配、推荐、图表布局、字符串解析。关键配置比如权限模型、路由规则、数据同步逻辑、任务队列的并发策略。所有被多个模块共享的工具函数或基础类。这类代码有一个共同特征它们决定了系统的主要行为而且通常会被频繁修改。你如果不理解它们后面每次改动都是在盲改。2.2 可以直接用的代码样板代码、文档片段、一次性脚本有些代码不需要手动重打直接复制反而更合理。比如脚手架配置Dockerfile、CI 配置文件、ESLint 配置、TypeScript 配置。通用样板代码登录页面、列表页、分页组件、文件上传组件。一次性脚本数据迁移、批量重命名、临时统计。文档示例官方文档里的 API 调用片段、SDK 示例。这些代码的价值在于“标准”和“一致”而不在于“原创”。你手写一份 Dockerfile也不会比 LLM 生成的更好理解因为问题通常出在环境变量和版本上而不是你认不认得每行配置。不过直接复制也有前提你要能看懂每一行配置是什么意思至少要能判断它是否适用于当前项目。如果连“为什么这里要挂载卷”“为什么这个镜像 tag 和项目版本绑定”都说不清那这段配置以后也会变成认知债务。2.3 时间成本怎么控制手动重打确实费时间。一段 200 行的核心逻辑重打一遍可能需要 20 到 40 分钟而复制粘贴只需要几秒。这里要算的不是单次时间而是未来排错时间。如果这段代码之后三个月都不会被触碰直接复制完全没问题。如果这段代码明天就要改那就值得花时间重打。所以我的判断标准很简单这段代码的修改频率高不高出错后的影响大不大是不是项目里的核心链路三个问题里有两个为“是”就重打。不要一上来就给自己立规矩说所有 LLM 代码都要重打。那是形式主义。真正健康的工作方式是分清轻重只对高价值代码做深度理解。3. 正确的手动重打流程不是抄是重构3.1 第一步先读再盖住最后写很多人的重打是看着屏幕上 LLM 生成的代码一行一行敲进编辑器。这没有任何意义那叫打字练习不叫理解。正确顺序是先读再盖住最后写。先把整段代码完整读一遍标注出你不理解的部分。然后关掉代码窗口或把代码滚动到看不见的位置凭记忆和理解重新写出来。写的时候不要追求和原文一字不差而是追求逻辑一致。你用了不同的变量名、重新组织了循环结构都可以。只要核心行为和原版一致就说明你真懂了。读的时候建议留意几个问题这段代码的输入是什么输出是什么有哪些中间状态哪些分支是错误处理哪些分支是主流程如果读一遍回答不了说明这段代码的复杂度已经超过你的即时理解范围更适合拆成小块再重打。3.2 第二步写下你对每一行的“为什么”重打之后在代码旁边用注释或单独文档写下关键决策。不需要每一行都写但至少要写清楚为什么这里要判空为什么这个函数用递归而不是循环为什么把超时时间设成 5 秒而不是 10 秒为什么先更新数据库再删除缓存为什么用异步而不是同步这些问题看起来简单但 LLM 生成的代码经常不会有这些注释。它给你的是结果不是决策过程。你补上“为什么”等于把缺失的上下文找回来。这步也是我区分新手和老手的地方。新手拿到代码只会跑跑通了就完事。老手会花十分钟把关键决策写成注释因为知道半年后的自己一定会感谢现在的自己。3.3 第三步重打之后立刻做三件事重打完成不代表结束。我建议立刻做三件事重新跑一遍测试确认重打后的代码行为和原版一致。故意改一个边界条件比如把空列表换成只有一个元素的列表看看结果是否符合预期。把代码讲给旁边的人听或者写一段 50 字以内的总结。这三件事里最容易被忽略的是第三件。说出来或写下来和“心里觉得理解了”完全是两码事。很多认知债务就是在“心里觉得理解”这个状态下埋下的。当你发现自己说不清楚某个函数存在的价值时大概率理解还不到位。3.4 如果重打过程中卡住怎么办卡住是正常的反而是重打最有价值的时刻。说明这段代码里有一些你没有掌握的知识点。这时候不要硬记要回到原文看一遍搞清楚为什么写不出来然后再次盖上继续写。如果同一个位置反复卡住可能是前置知识缺失。比如你不懂柯里化那不管 LLM 生成的代码多漂亮你都写不出来。这时候应该先去查柯里化而不是死磕这段代码。硬背代码不如补齐概念。还有一种情况卡住是因为 LLM 的写法太绕。比如用了一个很复杂的 reduce 来统计分组你更习惯用 map 和对象计数。这时候完全可以按自己的方式重写没必要忠实还原。手动重打的目的不是复刻而是掌握行为。4. 验证是否真的理解从“能跑”升级到“能改”4.1 用“改一行”代替“重新跑一遍”验证理解最有效的方法不是把代码复制回去再跑一遍而是尝试改动它。改动方向可以很简单把字符串匹配改成正则匹配。把列表遍历改成生成器。把同步请求改成带超时的异步请求。把一个工具函数改成支持多个参数版本。把硬编码的阈值改成从配置读取。如果你能顺利改完并解释为什么这样改那这段代码基本就消化了。如果改一行就报错或者报错后找不到原因说明你还停留在“会运行、不会维护”的状态。我也见过一种反模式为了验证理解把 LLM 生成的代码全部删掉然后要求自己凭记忆重新写一遍。这个方法太极端不适合日常开发。更现实的做法是只删掉核心函数保留接口定义和测试用例然后重新实现。4.2 给代码写测试不如给代码讲一个故事写单元测试是验证逻辑的好办法但对个人理解来说最直接的验证是给代码讲一个故事。比如“用户上传了一个 CSV 文件系统先检查文件大小超过 10MB 就报错。然后读取前 100 行做格式预览发现某列缺少值就标记为脏数据再进入清洗流程。清洗结果写入临时表最后通过异步任务生成报告。”如果你能用这样的故事完整描述一段代码而且故事里的每个环节都能对应到代码里的函数或分支那你就是真理解了。如果讲故事时频繁卡壳说明代码在结构上还不够清晰或者你还没掌握它的行为链。这比写完测试用例更能暴露理解漏洞。4.3 六小时后的复述测试短期记忆会骗人。刚重打完成时你可能觉得全都懂了但第二天再问自己“这个函数为什么接收三个参数”可能就答不上来。一个简单有效的办法是隔一段时间再做一次复述测试。比如上午重打一段代码下午或者第二天在完全不看代码的情况下试图写出这段代码的调用关系图或关键判断条件。能写出来说明进入了长期记忆写不出来说明还欠一遍。这个测试不需要真的把代码完整写下来只需要写出主流程和关键参数。写完后再对照原文看看哪里遗漏了。遗漏的地方往往就是最容易被忽略的边界条件也是未来 bug 最容易出现的位置。5. 批量使用 LLM 生成代码时如何防止认知债务失控5.1 生成之前先写需求清单很多人让 LLM 生成代码时只给一句话描述比如“写一个用户注册接口”。这种输入方式会产生一个隐患AI 会替你做大量假设而这些假设你根本不知道。更稳妥的做法是在生成之前先写一个需求清单包括输入参数、输出格式、错误处理、边界条件、性能要求。比如用户注册接口手机号唯一校验验证码密码加密存储连续失败 5 次锁定账号。返回格式成功时返回用户 ID失败时返回错误码和错误消息。数据库使用现有 user 表不要新建表。清单越明确LLM 生成的代码就越贴近你的意图也更容易理解。更重要的是当 LLM 输出和清单不一致时你能立刻发现问题而不是被一段“包装得很好”的代码带偏。5.2 每次生成只看一个改动点批量使用 LLM 时最容易犯的错误是让它一次性生成一大段包含多个功能的代码比如同时完成数据校验、权限判断、日志记录和业务处理。这种代码往往看起来很完整但你已经很难判断每一部分是否真的符合要求。我建议每次让 LLM 只解决一个改动点。先让它生成数据校验部分看完理解后再让它生成权限判断部分然后才是业务处理。这样虽然交互次数变多但每一段代码都能真正进入你的工作记忆。长期看这种方式积累的理解深度远高于一次性生成。有人会觉得这样效率低。但效率不等于生成速度而是“交付后不需要返工的速度”。一次生成一大段你花 10 分钟阅读30 分钟调试最后可能还要大量改动。每次只生成一个点你花 5 分钟理解10 分钟调试却基本不会返工。算总账后者更划算。5.3 失败重试和日志顺序怎么处理批量任务另一个容易产生认知债务的地方是失败处理和日志记录。LLM 生成的代码往往只覆盖“正常路径”对失败路径考虑得很简单。比如请求第三方接口失败后直接抛出异常没有重试或者日志只打了一条笼统的错误消息没有带上请求 ID 和耗时。如果你准备把 LLM 生成的任务接入队列或批量处理一定要额外补上这些信息失败重试次数第一次失败是直接跳过还是重试 3 次重试间隔用固定间隔还是指数退避日志内容错误消息里是否包含输入文件的路径、批次号、单条记录 ID失败后的输出是写入单独的错误目录还是标记原文件这些内容不需要 LLM 帮你从零设计但你需要能读懂它生成的实现。如果连重试策略和日志顺序都看不明白批量任务跑挂时你连从哪里开始查都不知道。5.4 团队协作时用代码评审兜底个人手动重打解决的是个人理解问题但代码是团队共有的。如果团队里有人直接用 LLM 生成的代码又不做任何解释其他同事在维护这段代码时同样会积累认知债务。这时候代码评审就显得很重要。评审时不要只问“功能实现了吗”还要问几个问题这段代码的输入输出边界是什么为什么选择这个方案而不是另一种哪些情况会报错报错后怎么处理如果需求变化需要改哪些地方这些问题会迫使写代码的人把隐藏的假设说出来。即使他没有手动重打代码只要能清楚回答这些认知债务也会大幅降低。反过来如果回答不了那不管代码跑得多流畅都不应该合入主干。6. 排查和避坑手动重打不等于低效6.1 看起来懂了但写不出来是哪一步出了问题很多人会有这种感觉LLM 生成的代码我读得懂但让我自己写就写不出来。这其实不是“不会写”而是“没有形成可提取的记忆”。读代码时上下文都摆在眼前变量名、函数名、参数顺序都在提示你。一旦盖住你没有这些提示就只能依靠自己对逻辑的理解。这种情况说明第一步读得不够仔细或者只看了主流程没有注意到分支和边界条件。解决方案不是继续硬写而是拆小。先把代码按功能拆成三段每次只重打一段。一段一段过完再尝试合并。合并的过程中如果出现断裂说明段与段之间的接口你没理解这是更重要的发现。6.2 环境、路径、权限、依赖版本才是翻车重灾区手动重打之后报错很多人第一反应是自己逻辑写错了。但实践中大量报错和逻辑无关而是环境问题。我排查的顺序一般是先看报错信息本身是语法错误、类型错误、还是找不到模块再看路径和文件名文件是否真的存在于指定路径大小写是否一致再看依赖版本本地安装的依赖版本是否和项目里 lock 文件一致再看权限脚本是否有执行权限配置文件是否能被读取最后才是逻辑对比把重打后的代码和原版逐段对比看差异在哪里。这个顺序能避免你在错误方向上浪费大量时间。尤其要注意依赖版本问题很多 LLM 生成的代码基于比较新的 SDK 或框架写法你的项目可能还在用旧版本直接复制很容易报“找不到方法”或“属性不存在”。手动重打也一样当你的写法基于当前项目的依赖版本时才更容易和现有代码保持一致。6.3 什么时候应该停止手动重打手动重打不是适用于所有场景的银弹。如果你发现自己在以下几种情况下可以暂时放弃重打代码量太大比如上千行手动重打不现实。这时候应该先分段阅读只重打核心函数。代码是一次性使用用完就不再维护。比如临时数据修复脚本。代码完全依赖特殊上下文比如某个框架自动生成的文件手动重打反而容易出错。你不打算长期维护这段代码只是借来参考思路。关键是不要把重打变成道德负担。它只是一个工具用来在“需要长期维护”和“需要深入理解”之间建立桥梁。当你已经理解了或者根本不需要长期维护时重打就不再有价值。6.4 一个完整的练习清单如果你现在就想开始练习手动重打下面这套清单可以直接用从现有代码里挑一个你不熟悉但经常改动的函数。先通读一遍标注出不理解的部分。关掉代码凭理解重新实现一遍。对比原版找出逻辑差异。为每个关键分支写下为什么这么写。改一个边界条件验证行为是否符合预期。隔半天或一天后凭记忆画出这段代码的调用关系图。如果画不出来回到第 3 步只重打遗漏的部分。坚持几次后你对 LLM 生成代码的感觉会明显变化。不再是“它给我什么我就用什么”而是“它在给我一个还能再改进的版本我能决定保留哪些、改掉哪些、扔掉哪些”。我个人更建议先把单条核心逻辑重打几十行验证一下这个流程是否适合自己再决定要不要扩展到更大范围。不要一上来就重打整段接口代码那样容易挫伤积极性。手动重打这个动作本身很简单难的是长期坚持。挑一个小函数开始跑通一次然后慢慢扩大范围认知债务就会一点点降下来。
返回列表