☰
ChatGPT辅助编程:加速开发与代码审查的工程实践
2026/10/27 13:23:25 网站建设 项目流程

凌晨一点半,屏幕上是一段跑不通的分页逻辑。我把它整段贴进对话框,补了三句话:这是报错堆栈,这是表结构,别改我的函数签名。三十秒后回来的版本依然有错,但它把问题从"我不知道从哪下手"变成了"我知道该看第几行"。这个转变,就是我现在愿意把 ChatGPT 放进日常开发链路的原因。所谓"用 ChatGPT 编写代码",说白了不是让它替你写完整个项目,而是把它当成一个随叫随到、读过海量代码、但从来没真正跑过一行程序的人——让它负责把"从零到能跑"这段最费手的时间压下去,你负责把"跑得对不对"这道关守住。加速开发和减少错误这两件事,其实是一体两面:省下来的时间必须重新投到验证上,否则省下来的时间会以线上事故的形式还回去。这篇东西写给三类人看:刚开始用对话式工具写代码、还被它的自信误导的新手;已经用过一阵、但总觉得"生成的代码不敢上线"的中间层;以及想把这套方式推给团队、又怕质量失控的技术负责人。下面全部是我自己踩过坑之后沉淀下来的流程、模板和判断标准,能直接抄。

1. 先把期待摆正:它到底加速了哪一段

1.1 一段代码从想法到合并,时间根本不在敲键盘上

很多人对"加速开发"的想象是打字变快,其实真正的耗时结构完全不是这样。一个中等复杂度的功能,从接到需求到合并进主干,时间大致分成几块:理解需求和确认边界、查文档和确认接口、写第一版能跑的代码、调试到跑通、补测试、评审返工。真正敲字符的时间可能只占两成,剩下的全在"想清楚"和"验证对"上。AI 工具能压缩的,主要是"查文档确认接口"和"写第一版能跑的代码"这两段,顺带把"调试到跑通"里的定位环节缩短一些。它压缩不了"理解需求",因为它不在你的业务现场;它也压缩不了"验证对",因为它没有运行环境,也没有你的数据。

想明白这一点,用法就变了。你不会再指望它一次给出完美方案,而是会把它安排在那些"机械但需要查资料"的环节上:样板代码、格式转换、正则表达式、SQL 拼装、单元测试骨架、报错解释、陌生库的入门示例、跨语言翻译。这些活儿的共同点是:有明确的正确性判据,且不需要太多隐性业务知识。反过来,凡是需要"知道我们这套系统里那个字段其实有三种脏数据"的活儿,它一定干不好,因为它不知道。

我在实际项目里做过一个粗略的对比:一个带分页、重试、字段映射的导出接口,自己从头写大约要两小时,其中一半时间花在翻文档确认某个库的参数名;把接口签名和数据结构先定死,再让它生成骨架和测试,整体压到四十分钟左右,但后面人工审查和补边界条件又花掉半小时。净收益大概是一半时间,前提是你得会审查。不会审查的人用这套方法,收益是负的。

1.2 三个实打实的收益,我确实吃到了

第一个收益是冷启动。接手一个没接触过的语言或框架,最痛苦的阶段是"不知道惯用写法长什么样"。这时候让它生成一个最小可运行示例,比翻半天官方文档快得多,因为示例是围绕你的具体场景生成的,你能立刻看到结构。我试过用一个完全陌生的模板引擎做渲染,让它先给一个"读取列表、循环渲染、处理空数据"的完整片段,五分钟就有了可运行的原型,剩下的时间全用来理解它为什么这么写。

第二个收益是测试补齐。写测试这件事,很多人不是不会写,是懒得写。让工具根据函数签名和分支逻辑先生成一版用例,你再往里补业务边界,效率提升非常明显。它生成的用例往往覆盖不到你真正关心的脏数据,但骨架、断言写法、mock 结构这些体力活它包了,你只需要做加法。

第三个收益是报错定位。把完整的堆栈、相关代码片段、依赖版本一起贴过去,让它给出三到五个可能原因,然后你逐个排除。它的猜测不一定对,但经常能提供一个你没想到的方向,尤其是那些"语法完全正确但行为诡异"的问题,比如时区、编码、浮点精度、异步顺序。

1.3 三个必须认账的代价,不认就会出事

第一个代价是自信的错误。它写错的代码和写对的代码,语气一模一样,没有任何"我不确定"的标记。新手最容易在这里翻车:看着逻辑通顺、命名规范、注释齐全,就直接用了,结果某个参数在新版本里早就改名了。我的应对办法是硬性规定:任何涉及外部库调用的代码,必须自己跑一遍;任何涉及金额、权限、删除操作的代码,一行都不许直接用。

第二个代价是上下文污染。一段对话聊得越久,早期的约束越容易被"稀释"。你在第一轮说了"不要引入新依赖",聊到第十轮它可能就给你import了一个没装过的包。这不是它故意违反,而是长对话里的信息权重会衰减。解决办法后面会讲,核心是"每轮固化结论、关键约束重复贴"。

第三个代价是审查成本转移。生成代码变便宜了,审查代码变贵了。你省下的敲键盘时间,会以"读别人代码"的形式花出去,而且读的是风格不统一、可能夹带私货的代码。如果团队没有评审习惯,这套工具带来的净效果就是代码量膨胀、质量下降。

2. 机制层面搞清楚:它为什么会对,又为什么会错

2.1 一个读过海量代码、但从没运行过一行的"实习生"

对话式模型的工作方式,本质是根据已有的上下文,一个片段一个片段地推测"接下来最可能出现什么"。它读过极大量的公开代码,所以对"常见的写法长什么样"有极强的模式识别能力。你给它一个函数名、几个参数、一句注释,它能补出符合社区惯例的实现,这就是它好用的根源。但它优化的是"像不像",不是"对不对"。它没有编译器,没有运行时,没有你的数据库,也没有你的日志。它不知道那个 API 在你的版本里已被废弃,也不知道你上游传进来的id可能是空字符串。

用实习生类比最贴切:这个实习生读完了图书馆里所有的编程书,能背出各种设计模式,但从来没在一个真实项目里提交过代码。他写的东西结构漂亮、注释规范,你让他解释他也能解释得头头是道,但你必须替他把代码跑一遍。你越早接受这个设定,用起来越顺。

2.2 上下文窗口与信息衰减:为什么必须学会"喂重点"

模型一次能"看见"的内容是有上限的,这个上限用 token 计量。中文一个字大约一到两个 token,代码里符号密集,token 消耗更快。这意味着你贴一整个几百行的文件进去,大部分内容其实是在挤占预算,真正关键的约束反而被淹没了。更麻烦的是位置效应:放在超长上下文中间部分的信息,被准确利用的概率明显低于开头和结尾。

实操上的结论很直接。第一,只贴相关片段,别贴整个文件,把无关的函数折叠掉,用注释标注"此处省略若干行"。第二,把最重要的约束放在最前面,并且在结尾再重复一遍,比如开头写"约束:不得引入新依赖",结尾再写"再确认一次:不许新增依赖"。第三,长对话要主动"结算",聊到一定轮次就让它输出一段"当前确定的方案与未决问题"的总结,然后开新对话,把总结贴进去当起点。第四,贴报错要贴全,从异常类型到最内层调用栈,缺一行都可能导致它猜错方向。

2.3 幻觉在代码里的四种典型形态

形态具体表现危险程度识别方法
编造接口调用一个不存在的方法名,或参数名拼错但看起来合理高跳转到定义,或查官方文档确认签名
版本错配用旧版本的写法,或用了新版本才有的参数高核对项目锁定的版本号,查该版本的变更记录
边界错误主体逻辑对,但空值、零、负数、超长输入处理错最高逐条过边界清单,补测试
注释与实现不符注释说"失败会重试三次",代码里根本没有重试中读代码而不是读注释

这四类里,第三类最难发现也最致命。因为前面三类你只要跑一次或者查一次文档就能暴露,而边界错误往往要等到特定数据出现才炸。我吃过的一次亏是批量处理脚本:它写的分批逻辑在数据量能被批大小整除时完全正常,一旦有余数,最后一批就丢了几条。测试数据是我随手造的整数条,正好没触发。后来补了"数量为批大小加一"的用例才暴露出来。

3. 任务分级:哪些活交给它,哪些必须自己扛

3.1 用"确定性 × 耦合度"做一张分工表

判断一个任务该不该交给它,我只看两个维度:这件事有没有明确的验收标准(确定性),以及这件事牵扯多少隐含的系统上下文(耦合度)。两个维度一交叉,四象限就出来了。

低耦合高耦合
高确定性放心交:正则、格式化、类型转换、单测骨架、文档注释半交半审:接口骨架、数据映射、查询语句
低确定性只当参考:技术方案对比、架构建议、性能猜测基本别交:核心业务规则、状态机、并发控制

左上角是它的主场,有明确对错、不需要知道你的系统长什么样,比如"把这段 JSON 里的驼峰字段转成下划线",跑一遍就知道对不对。右下角是雷区,比如"订单在什么状态下允许部分退款",这种规则藏在业务流程里,它写出来的东西看着合理,但一定漏了你没想到的例外。

3.2 我的默认黑白名单

白名单,也就是默认直接交给它的:正则表达式(配测试字符串)、日期与时区转换、CSV/JSON/XML 互转、SQL 单表查询与聚合、接口的错误码映射表、日志解析脚本、单元测试骨架、代码注释与文档、把一个函数从语言 A 翻到语言 B、解释一段看不懂的遗留代码。

黑名单,也就是默认自己写的:权限判定、金额计算与舍入、库存扣减、并发与锁、事务边界、缓存失效策略、涉及删除的批量操作、性能关键路径上的循环优化、任何需要读三个以上内部模块才能判断的改动。

中间地带最需要经验:数据映射和接口骨架可以交给它,但你必须自己定死输入输出的数据结构和字段语义,让它只填中间的转换逻辑。这个"定死"的动作,恰恰是最不能省的部分。

3.3 判断标准简化成一句话

如果非要压缩成一句可执行的判断:这件事出错之后,我能不能在十分钟内发现并回滚?能,就大胆交给它,用测试兜底;不能,就自己写,或者只让它写不涉及关键判断的部分。这个标准比任何"AI 能力清单"都实用,因为它衡量的是你的风险承受力,而不是模型的能力边界。

4. 提示词:把模糊需求翻译成可执行指令

4.1 五段式模板,我现在基本照这个填

写提示词和写技术方案是一回事,含糊的输入只会得到含糊的输出。我惯用的结构是五段:角色与目标、上下文、约束、输出格式、验收标准。这套结构的好处是把"我以为你懂"的部分全部显式化,减少来回拉扯。

【角色与目标】 你是熟悉 Python 3.10 的后端开发者。目标:实现一个把图片元数据批量整理成 CSV 的函数。 【上下文】 - 现有项目使用 requests + csv 标准库,不引入其他第三方依赖 - 输入是 list[dict],每条包含 id、width、height、tags(list[str])、created_at(ISO8601 字符串) - 已有一个工具函数 safe_get(d, key, default),可以直接调用 - 现有代码风格:4 空格缩进、类型注解齐全、异常向上抛 【约束】 - 不得修改公开函数签名:export_metadata(records, path) -> int - 不得引入新依赖 - 空输入必须返回 0 且不创建文件 - 单条记录缺字段时跳过并计数,不允许整批失败 - 不得吞掉异常,IO 错误必须抛出 【输出格式】 1. 完整函数实现 2. 变更说明(不超过 5 条) 3. 你做出的假设列表 【验收标准】 请同时给出覆盖以下情况的测试:空列表、单条完整记录、缺 tags 字段、 tags 为空列表、created_at 格式非法、目标路径无写权限。

这个模板里最值钱的是"约束"和"验收标准"两段。约束把它的自由发挥空间压到最小,验收标准则逼着它自己想边界。实测下来,加了验收标准之后,生成的测试用例质量提升非常明显,虽然仍然需要你补业务相关的脏数据,但通用的异常路径它基本能覆盖到。

4.2 三个烂提示与它们的改写

烂写法问题在哪改写方向
"帮我写个登录功能"没有技术栈、没有会话方案、没有错误处理要求说清楚框架、认证方式、失败次数限制、返回结构
"优化一下这段代码"没有优化目标,它可能给你改成完全不同的风格指定目标:降低圈复杂度、减少重复查询、去掉嵌套
"这个报错怎么解决"没贴版本、没贴堆栈、没贴上下文贴完整异常、贴相关代码、贴依赖版本、说明已经试过什么

第二个坑我踩得最多。早期我总说"优化一下",结果它把一段直白的循环改成了一堆生成器和推导式,行数少了,可读性差了一大截,团队评审直接打回来。后来我改成"在不改变可读性的前提下,把三层嵌套的 if 合并成提前返回",输出质量立刻不一样。给它一个可衡量的目标,比给它一个形容词有用得多。

4.3 上下文怎么喂才不浪费预算

几个我固定执行的小动作。第一,贴代码时保留函数签名和类型注解,删掉实现细节,用# ... 省略标注。第二,报错一定贴全,包括异常类型、消息、最内层三到五帧调用栈,缺一帧它就可能猜错方向。第三,把项目的风格约定用两三条示例说明,比如"我们统一用早返回,不用嵌套 if",比描述半天有效。第四,明确要求它"先给方案再给代码",尤其是改动超过两个文件的场景,先看思路能省掉大量返工。第五,要求它列出假设,这一条极其有用——它列出的假设往往就是它理解偏差的地方,你一眼就能发现。

还有个小技巧:如果它给的方案明显不对,别急着说"不对,重写",而是指出具体哪里不对。比如"你在第三步假设了 id 唯一,但实际可能重复",这种针对性反馈能让它在同一轮里修正,比重新开一轮效率高。

5. 一条完整链路:从需求到提交的实操记录

下面用一个虚构的小项目走一遍完整流程,项目叫"模拟项目 X",做的事情是批量整理图片元数据并导出。走完这一遍,你能看到每个环节人该做什么、工具该做什么。

5.1 第一步:人先把接口和数据流定死

这一步绝对不能交给工具。你要确定的是:函数签名、输入数据结构、输出格式、失败策略。我一般会手写一个空壳文件,把类型注解和异常约定先落下来,再让工具填中间。

from typing import Any def export_metadata(records: list[dict[str, Any]], path: str) -> int: """把图片元数据导出为 CSV,返回成功写入的行数。 约定: - records 为空时返回 0,不创建文件 - 单条记录缺关键字段时跳过并计入 skipped,不中断整批 - 目标路径不可写时抛出 IOError,不做静默降级 """ raise NotImplementedError

为什么坚持先定签名?因为签名是人和工具之间的契约。签名一旦固定,工具就只能填内部逻辑,不会顺手把你的函数改成异步、不会擅自加参数、不会把返回值从 int 改成 dict。我见过太多人直接说"写个导出函数",结果拿回来的实现参数名、返回类型、异常策略全是它自己定的,改起来比从头写还累。

5.2 第二步:先要测试,再要实现

顺序很重要。先让它依据签名和约定生成测试,你审一遍测试,确认覆盖了你关心的场景,再让它写实现。这样做的好处是:测试成了验收标准,实现跑不跑得通有客观判据,而不是靠"看着像对"。

import pytest def test_empty_records_returns_zero_and_no_file(tmp_path): target = tmp_path / "out.csv" assert export_metadata([], str(target)) == 0 assert not target.exists() def test_missing_tags_is_skipped(tmp_path): records = [ {"id": "1", "width": 100, "height": 200, "tags": ["a"], "created_at": "2024-01-01T00:00:00Z"}, {"id": "2", "width": 100, "height": 200, "created_at": "2024-01-01T00:00:00Z"}, ] target = tmp_path / "out.csv" assert export_metadata(records, str(target)) == 1 def test_invalid_created_at_does_not_break_batch(tmp_path): records = [ {"id": "3", "width": 1, "height": 1, "tags": [], "created_at": "not-a-date"}, {"id": "4", "width": 1, "height": 1, "tags": [], "created_at": "2024-01-01T00:00:00Z"}, ] target = tmp_path / "out.csv" assert export_metadata(records, str(target)) == 1

审测试的时候重点看两件事:断言是不是真的在验证行为(而不是只验证不抛异常),以及有没有漏掉你真正担心的场景。上面这版是工具生成的骨架加上我补的两条,原版没有"非法日期不中断整批"这一条,而这恰恰是我最关心的。

5.3 第三步:跑、贴报错、迭代收敛

实现拿回来先跑测试,失败的输出原样贴回去,别只贴一句"报错了"。这个阶段通常会来回两三轮,每轮都要重新贴一次关键约束,防止它改着改着把"不引入新依赖"给忘了。收敛之后,我会做一次人工通读,重点看三处:异常处理是不是吞了错、循环边界有没有差一、资源有没有正确释放。

顺带说两个涉及参数的场景,这也是最容易出问题的地方。批量处理的分批大小,我不会让它拍脑袋定,而是自己算:单条记录含元数据大约 1.5KB,目标峰值内存控制在 64MB 以内,理论批大小约 42000 条,但下游接口有速率限制,实际取 2000 条一批、并发 2 路,峰值内存约 2 × 2000 × 1.5KB ≈ 6MB,留足余量。这个计算过程要写进注释,不然半年后没人知道 2000 是哪来的。重试策略同理:基础间隔 200ms、倍数 2、最多 5 次,总等待约 6.2 秒,再叠加 ±20% 的随机抖动,避免大批任务在同一时刻集中重试。这些数字都不能让它随便给,因为它不知道你的内存预算和下游限流。

5.4 第四步:读 diff、重构、提交

最后一步是把生成的代码当外部贡献来审。我固定看五样东西:命名是否符合项目习惯、有没有多余抽象、日志和异常是否合理、注释是否与实现一致、有没有偷偷引入依赖。看完之后通常要做一次小重构,把它的风格拉回团队习惯。提交信息里我会注明哪部分由工具生成、哪部分是人工修改,这不是形式主义,出问题时能快速定位。

6. 把错误挡在合并之前:三道闸门

6.1 边界条件清单,照着问一遍

这份清单我贴在便签上,每次审生成代码都过一遍。空值、空列表、零、负数、超大值、重复值、超长字符串、非法编码、时区、浮点数精度、并发重入、超时、下游返回异常结构、权限不足、磁盘写满。每一条都问一句"这段代码遇到这个会怎样",答不上来就补测试。

其中最容易漏的是重复值和并发。生成代码默认假设输入是干净的、调用是串行的,而真实系统里这两个假设几乎总是错的。我现在的习惯是:只要函数涉及写操作,一律补一条"同一请求重复执行两次"的测试,看它是幂等还是会产生脏数据。

6.2 类型、静态检查与测试的分工

这三样东西挡的是不同的错误,不能互相替代。类型检查挡的是接口层面的错配,比如把可选值当必填用;静态分析挡的是代码异味,比如未使用的变量、过深的嵌套、可疑的异常捕获;测试挡的是行为错误。生成代码在类型层面通常写得挺漂亮,因为类型注解是它最擅长的模式匹配;真正容易出问题的是行为层面,所以测试的权重必须拉高。

我的做法是要求所有新函数至少有一条正常路径和一条异常路径的测试,覆盖率不追求数字,但分支必须走到。CI 里把类型检查、静态分析、测试三道都设成阻断项,任何一道不过就不许合并。这道防线一旦立起来,工具带来的风险会被压得很低。

6.3 依赖与安全:它最爱在这里挖坑

这是最需要警惕的一块。生成代码时它可能推荐一个不存在的包、一个早已停止维护的包、或者一个名字和常用包极其相似的包。我的固定动作是:任何新增依赖,先查官方仓库的最近更新时间、下载量、issue 活跃度,确认不是空壳;确认它在项目的版本约束内能装上;确认许可证与项目兼容。三条里有一条不满足,就改成用标准库自己实现。

还有一类隐蔽风险是它给出的示例里带着占位密钥、示例域名、调试开关。这些直接复制进代码,轻则配置错乱,重则泄露信息。我的习惯是合并前全局搜一遍常见关键词:password、secret、token、localhost、debug,确认没有残留。

7. 常见问题与排查速查表

7.1 六类高频故障的现场处理

第一类,代码跑不通但看不出哪错。九成是版本问题。处理方式:把依赖版本、完整堆栈、调用代码一起贴回去,明确要求"只在这个版本下给方案"。

第二类,改一处坏一处。它在你原有代码上做局部修改时,容易破坏周边的隐式约定。处理方式:把修改范围圈死,只贴目标函数,改完自己跑全量测试。

第三类,越改越长。聊到后面它开始加抽象、加配置、加兼容层。处理方式:直接说"删掉所有非必要抽象,回到最简实现",或者干脆开新对话重来。

第四类,测试全绿但线上出错。通常是环境差异或者数据差异。处理方式:把真实的一条脱敏数据贴进去,让它按这条数据推演。

第五类,长对话后它开始忘记约束。处理方式:固化结论,开新对话,把结论和约束一起贴进去。

第六类,生成的东西看起来对但读不懂。处理方式:要求它逐行解释,解释不通的地方就是你没想清楚的地方,别急着用。

7.2 一张表走完排查

现象最可能的原因第一步动作
报"方法不存在"版本错配或编造接口查该版本官方文档确认签名
类型检查一片红注解与实际返回不符优先修类型,别急着加any
单测通过但结果不对测试断言太弱补一条验证具体数值的断言
处理大批量时内存飙升一次性加载全部数据改成分批,明确批大小来源
偶发失败无法复现并发或时间相关加日志打出入参与时间戳
提交后发现多出依赖未审查 diff回滚,改为标准库实现

7.3 几条写在笔记里的心得

第一条,永远不要相信它给的"这个 API 接收三个参数",除非你自己查过。第二条,它写的注释比代码更不可信,注释是它想象的意图,代码才是实际行为。第三条,让它解释自己的代码,比自己读快,但解释里可能夹着新的幻觉,需要交叉验证。第四条,同一个问题换一种问法,往往能得到完全不同的方案,卡住的时候不妨换个角度重问。第五条,也是最值钱的一条:当你发现自己在反复纠正同一个错误时,问题通常不在它,而在你的需求描述里有一个你没说出口的假设。把它找出来写进约束,往往一次就通了。

8. 团队里怎么用才不添乱

8.1 把规范沉淀成常驻上下文

个人用靠记忆,团队用靠文档。我们会在项目根目录放一份简短的约定文件,内容包括技术栈与版本、目录结构、命名习惯、异常处理策略、禁止引入的依赖类型、测试要求。每次开新对话,先把这份文件的核心几行贴进去。这件事的价值不在于让工具变聪明,而在于让所有人的输出风格趋同,降低评审成本。

8.2 评审关注点该往哪移

有了工具之后,评审的重点应该从"语法和风格"往"意图和边界"移。语法问题交给静态检查和格式化工具,人只做机器做不了的判断:这段逻辑符不符合业务规则、这个异常策略会不会掩盖真实故障、这个改动的影响范围有没有被低估。我在评审时固定会问三个问题:这个函数在输入异常时行为是什么、这个改动会不会影响存量数据、这段代码如果三个月后要改,改的人能不能看懂。

8.3 怎么判断它真的有用

别用"感觉快了"来判断,用几个朴素指标:单个需求的从开始到合并的耗时、合并后两周内的返工次数、评审时的评论条数、缺陷密度。我自己的观察是,用了这套流程之后,第一版代码产出时间明显下降,返工次数基本持平,缺陷密度略降但主要归功于测试补齐而不是生成质量。也就是说,真正的收益来自"更愿意写测试",而不是"代码写得更对"。这个结论挺反直觉,但很符合逻辑:工具降低了写测试的门槛,测试降低了缺陷率。

最后分享一个小技巧。我在每次准备提交前,会把它生成的代码单独打印出来读一遍,不带任何上下文,就当读陌生人写的代码。读的过程中一旦出现"这里为什么要这么写"的疑问,就回去查。这个动作平均每次能揪出一到两个问题,比任何自动化检查都有效——因为疑问本身,就是你的经验在报警。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询