1. 先别急着写代码:v2.0与v1.0的分水岭
过去一年,我几乎每天都在用AI辅助编程。工具从一个聊天窗口变成IDE里的常驻插件,从写正则、翻译代码到搭项目骨架,AI能干的事越来越多。但说实话,用了大半年之后我发现一个尴尬的事实:AI生成代码的速度越快,我返工的时间反而越长。原因很简单——我压根没用对方法,一直在用v1.0的方式和AI协作。
1.1 我第一阶段用AI的失败复盘
v1.0时期,我的典型操作是这样的:打开一个文件,把整个需求复制给AI,让它"帮我写一个某某工具",几秒钟后拿到几百行代码,粘贴、运行、报错,再把报错丢回去,循环往复。这个过程看起来高效,实际上充满了隐性成本。
第一个坑是一次性让AI处理过大的上下文。写一个完整的命令行工具时,我往往把需求描述得很模糊:"写一个日志分析工具,支持统计每个IP的请求次数。"AI确实能生成一个能跑的东西,但它的实现思路和我项目里现有的代码风格严重不一致,异常处理逻辑也随意,日志输出格式想一出是一出,依赖库用了好几种。等我拿到代码,想改其中一个功能,AI又把其他区域的代码也动了一遍。改完A坏了B,修了B又碰到C。一个半小时后,我还在对着错误窗口发愁。
第二个坑是把AI当成"代码搜索引擎"。遇到问题就粘贴错误信息,得到答案就复制,完全不管上下文逻辑。结果AI给的是通用解法,和我的项目结构根本对不上,看起来能解决,实际集成进去又是一堆兼容性毛病。
第三个坑是没有验收标准。AI生成代码后,我第一反应是"能跑就行"。实际上,代码能跑和代码正确是完全两回事。边界条件没处理、错误路径没覆盖、命名不规范、缺少日志,这些问题只有在需求叠加、代码膨胀之后才集中爆发,届时改起来已经是成本和风险双高。
1.2 一个关键转变:把AI当"会接话的同事"
我用过的比较有效的思考方式,是把AI当作一个知识面广、反应快、但不太可靠的实习生——它什么都懂一点,给出答案的速度极快,但如果不把任务说明白,它会按自己的想象自由发挥。你需要给它明确的边界、分步的目标和可验证的产出。
这听起来简单,但做起来很反直觉。因为我们用惯了搜索引擎,习惯性地认为"给出问题就有答案",而AI更像是"给出任务才能有交付物"。问题越模糊,交付物越跑偏;问题越结构化,交付物越能复用。
从v1.0到v2.0,最大的变化不是我学会了更多工具快捷键,而是我把工作方式从"把需求丢给AI"改成了"和人结对编程一样,先把任务拆解成一个小步骤,再和AI逐个击破"。
1.3 v2.0流程的全貌
我目前执行的v2.0流程可以概括为五个阶段:
- 前置约束:先拆需求、明确验收标准,把模糊目标转成结构化任务列表。
- 编码执行:将大任务切分成小块,让AI分块产出,每块都要经过验证再合并。
- 调试纠错:把错误样本变成AI的学习材料,引导它做原因分析而不是直接给答案。
- 测试补全:让AI生成测试用例,但人工校验断言质量,防止"假绿"。
- 资产沉淀:把每次对话中的有效决策、提示词模板、踩坑记录沉淀为团队可复用的工程文档。
这个方法我拿去指导了团队里几位同事。一位后端同事以前让AI写SQL,隔三差五把where和group by的顺序搞得一团糟,改用结构化提问后基本没再犯过同类问题。还有一位前端同事用AI生成组件代码,以前是"生成一张页面,完了",现在是"先给组件接口设计,再让我确认,再生成实现",代码可读性和复用率都提升了一个档次。
核心变化不在于工具,而在于人怎么定义任务。
下面我按这五个阶段,把每个环节的具体做法和一些关键细节展开说。
2. 需求侧的AI协作:提示词不是咒语,是需求文档
很多人把提示词叫"咒语",觉得改几个词AI给的结果就天差地别。在我的实践里,与其说是"咒语",不如说提示词是一份结构化的需求文档。你文档写得越清楚,AI交付物就越接近预期。
2.1 为什么先写清楚任务边界
我在v1.0阶段吃过一个教训。当时想写一个批量重命名文件的Python脚本,我给AI的提示词是"帮我写一个批量重命名文件的脚本"。AI给了一段能用的代码,但存在三个问题:一是它默认了只在当前目录操作,没有递归子目录;二是它没处理文件名冲突;三是它没有dry-run预览功能。我拿到代码后在真实目录里直接跑,结果把所有文件都改了,虽有备份但恢复过程很折腾。
问题不在AI,而在我的需求描述里,"批量重命名文件"这个任务的边界根本不存在。子目录要不要递归?重命名规则是什么?冲突怎么处理?需不需要日志?需不需要回滚?这些信息缺一不可。
后来我把任务写清楚再交给AI,同一个脚本一次通过,连测试用例都写得像那么回事。所以我在自己的流程里定了条铁律:AI编程的第一步不是写提示词,是写任务说明书。
2.2 我沉淀的提示词五要素
如果一个开发任务要交给AI去做,我现在会按照这五个要素组织提示词:
- 角色:告诉AI以什么身份回答(例如"你是资深Python后端工程师")。这有助于AI调用对应的知识经验和代码风格。
- 场景:描述这段代码要解决的问题、所在模块、调用关系。场景越具体,AI生成的代码兼容性越好。
- 约束:说明技术栈、编码规范、不允许用的依赖、性能要求、异常处理方式等。
- 输入与输出:明确函数的输入参数、返回值、边界行为。最好有一个核心用例示例。
- 验收标准:告诉AI怎么判断"做完",例如"跑通以下测试用例"、"满足日志规范"等。
一个典型的prompt模板长这样:
你是一名熟悉Python 3.11的后端工程师。我正在开发一个内部日志聚合工具"logsum",项目使用click库编写命令行接口。请帮我写一个
parse_line函数,它接收一条Nginx access log字符串,返回一个dict,包含ip、timestamp、method、path、status、latency六个字段。注意:timestamp要转成ISO 8601格式;路径参数需要去除URL中的查询字符串;latency单位统一为毫秒;解析失败时抛出自定义异常LogParseError并附上原始日志行。请提供函数实现及三条典型日志的测试用例。
这个提示词和"帮我写一个解析nginx日志的函数"相比,信息量不是一个量级。AI能基于这些信息直接产出可用的函数,而不是给你一个需要考虑各种变量名怎么兼容的抽象模板。
2.3 从模糊到精确的提示词演化案例
我经常在团队分享的一个对比案例:
第一版提示词:
帮我写一个函数,读取配置文件。
AI大概率会给你一个用os.getenv读取环境变量的实现。如果你项目里用的是YAML配置文件,这就不匹配了。
第二版提示词:
帮我写一个函数,从config.yaml读取配置,返回dict。
这一版有了文件格式和返回类型,但没说默认值、异常处理、配置校验。
第三版提示词(接近我实际使用的版本):
在项目config目录下有个config.yaml,内容是一个嵌套结构,包含database、cache、api三个节点。请写一个load_config函数,使用PyYAML读取这个文件并做简单校验(database.host必须存在且非空,cache.ttl必须是大于0的整数),校验失败时抛出ConfigError并说明缺失字段。如果传入的路径不存在,返回内置的默认配置(默认配置中database.host为localhost,cache.ttl为30)。同时提供一个配置文件示例,保证函数可以单测。
同样一个任务,三版提示词得到的实现质量差距非常大。第一版基本不能直接用,第二版能跑但缺少健壮性,第三版几乎可以无修改合入项目。
写提示词的时间,其实是在减少后面调试修改的时间。这个时间投入非常值得。
3. 写码阶段的节奏控制:分块交付替代一次性生成
任务说明书有了,进入实际编码阶段。这个阶段我最大的经验就是控制交互粒度。
3.1 为什么30分钟是一个合理的交互周期
很多人在让AI写完整个模块、然后一头扎进去调bug,就是v1.0的做法。我现在的节奏是:单个任务的产出预估在15~30分钟内验证完,超出这个预估,就再拆一层。
我为什么强调这个粒度?两个原因。
第一,AI生成代码后的验证成本主要由我承担,上下文越大,验证越难。一个300行的模块,逐行审可能还来得及;一个2000行的服务,你很难在合并之前找出所有坑。小步验证能让问题在早期暴露,修复成本低得多。
第二,AI的上下文窗口有限。一个大任务,多轮交互后AI可能会忘掉前面你提的约束,产生"前后矛盾"的代码。把任务拆小,每轮对话的上下文都是紧凑有效的,AI跑偏的概率大幅下降。
举个具体的拆分案例。假设要写一个"日志聚合分析工具",我不会让AI一口气写完。我拆成这些子任务:
- 定义数据模型与解析函数(parse_line、parse_log_batch)
- 实现日志文件扫描与多文件聚合
- 实现按IP、时间范围、状态码的过滤查询
- 实现汇总统计输出(表格形式打印)
- 编写测试用例与README
每次只让AI完成其中一项,完成后马上做本地验证,验证通过再进入下一项。
3.2 接口先行:让AI先出设计再出实现
我比较推荐的一种方式,是让AI先输出接口设计,你再确认,然后再让它写实现。
同样以日志聚合工具为例。我不会上来就说"写一个函数统计日志里的每个IP请求次数",而是先问AI:
请为logsum设计三个函数的接口:scan_logs(pattern: str) -> list[str]、parse_line(line: str) -> LogRecord、aggregate_by_ip(records: list[LogRecord]) -> dict[str, int]。请先说明每个函数的参数、返回值、错误行为,并给出一个最小的调用示例。确认接口后再给出实现。
理由很简单:接口是模块的骨架,骨架正确,后续填内容才能顺利。如果一上来就是几百行实现,你很难在不了解全局的情况下判断结构是否合理。接口先行相当于先达成一份"合作协议",AI和你的预期在动手前就对齐了。
这一步也大大降低返工概率。有一次我让AI写一个CLI工具,第 一步就要求它提供命令行参数设计,AI给了我一个用argparse的版本,但我项目里已经用了click,于是我在第二步直接让它改成click风格,然后再往下走。如果一上来就让它写完整实现,改起来就是伤筋动骨。
3.3 合并之前的检查清单
AI生成的代码块,我合并到主分支之前有一套固定的检查动作:
- 伪造数据验证:用几个典型case(正常输入、空输入、异常输入)跑一遍,看是否符合预期,而不是只测"不报错"。
- 异常路径覆盖:检查AI是否处理了文件不存在、权限不足、依赖缺失等异常路径。AI很容易忽略这些,而这些恰恰是线上事故的主要来源。
- 风格一致性:看AI生成代码的命名、注释、换行风格是否和项目现有代码一致。不一致就会增加后续维护成本。
- 依赖是否收敛:看AI是否引入了不必要的第三方库。很多时候标准库就能解决,不需要额外依赖。
以parse_line函数为例,AI给出实现后,我会用三条日志测试:
- 正常日志:
127.0.0.1 - - [10/Oct/2024:13:55:36 +0000] "GET /api/user?id=123 HTTP/1.1" 200 0.052 - 无查询参数的日志
- 畸形日志(格式解析失败)
三条都通过,才进入下一步。三个里面有一个不通过,就带着具体的报错信息和预期行为回去让AI修。
这一步看起来简单,但它真正决定了工作流的产出质量。没有检查清单的AI编码,本质上是把不可控带进了代码库。
4. 调试环节的反向思维:让AI当老师而不是生成器
编码阶段的AI,像一个听话的执行者;到了调试阶段,它的角色应该转换成一个分析伙伴。但很多人依然在用"生成器"的方式调试——报错就丢给AI让它改改,改完还报错,再丢回去,如此循环好几次。
4.1 正确提问的调试案例
我的一个实测经历。当时用AI生成了一段解析Nginx日志的函数,其中包含正则匹配。测试时,有一个日志行解析失败,报错信息指向正则无法匹配。第一次,我把报错机械地粘贴过去,AI给了一段调整过的正则,但运行后发现还有边角案例没覆盖。
第二次,我换了一种问法:
以下这行日志在我使用parse_line函数时抛出了LogParseError,报错显示正则匹配失败:<具体日志行>。请帮我分析:这行日志的格式和普通Nginx日志有什么差异?是哪个字段导致正则不匹配?是先修正则还是先做预处理?请给出你的分析过程,再提供修改方案。
这个问法有一个明显的差别:我不再让AI"直接改到不报错",而是要求它先分析原因再给方案。AI返回了一个关键发现——这条日志带有一个自定义的日志前缀,正则没有考虑到前缀,导致了后续字段错位。它给出的方案是调整正则,同时建议在parse_line入口先剥离自定义前缀。这个建议比第一版直接改正则有效多了,因为它针对的是根因而非表面症状。
4.2 让AI解释自己的代码,发现逻辑漏洞
AI生成了代码,你拿过来跑通了,就真的理解这段代码了吗?很多时候不是。它生成了一段看起来合理的代码,但内部逻辑可能隐藏着坑。
我现在的习惯是:拿到AI生成的代码后,先让它给我讲讲关键函数的设计思路。让它解释:
- 为什么要这么处理边界条件?
- 这个循环的时间复杂度是多少?在数据量大的场景下会不会有性能问题?
- 这里用并发会不会有数据竞争?
这个"解释"不是学术要求,而是排查逻辑漏洞的有效手段。有一次,AI给日志聚合工具写了一个分组统计的函数,它在解释时提到自己用了defaultdict(list)来批量收集记录,然后一次性求平均。我接着问:如果日志文件特别大,比如几十万行,这个实现会不会有内存问题?AI意识到问题后,建议改用流式累加:统计总数和计数,而不是保存所有原始记录。如果我不追问,这个隐患就会一直留在代码里。
所以在我的工作流里,"解释代码"和"写代码"同等重要。让AI解释代码不是教学时间,而是代码审查的延伸。
4.3 实测翻车:那些看似合理但实际错误的高危回答
我也要坦白,v2.0流程里我依然踩过AI的坑。下面三类问题是我遇到最多的:
第一类:看似正确但实际错误的API用法。有一次我让AI生成一个日期处理的函数,它使用了datetime模块的一个方法,当时的运行环境是Python 3.11,这个方法在3.11版本里可用,但项目里有其他同事在用的环境是3.8,这就导致兼容性问题。所以AI给的代码,我会额外检查依赖版本,不能只盯着语法对不对。
第二类:忽略业务规则。有一次让AI写一个功能,用来过滤响应时间超过阈值(比如500ms)的日志。AI的实现是直接比较latency字段大于500。但它忽略了一个业务细节:这个字段在API网关日志里有单位、在应用日志里又是另一种单位。AI拿到的是统一处理后的数据,而真实场景中数据来源并不统一。这不怪AI,是我在任务说明里没有补充这个业务规则。后来我学会了在任务说明里明确数据的单位、范围、来源。
第三类:自以为聪明的"优化"。有时候你让AI做一个简单的操作,它可能会顺手"优化"成一段复杂的、更通用的代码。看起来炫技,但可读性和可维护性都下降了。现在我在任务描述里会主动加一条约束:"尽量用最简单的实现,不过度设计。"
这类翻车不能靠提示词完全规避,最终还是要靠审查和测试兜底。AI能做到90分的答案,但剩下的10分偏差,往往就是线上事故和完美交付的分界线。
5. 测试与验收的AI辅助:补齐断言思维
不少人的测试代码也是让AI写的,但我发现大家容易忽略一个关键点:AI生成的测试用例数量不少,但断言质量参差不齐。哪怕覆盖率跑到80%,依然测不出真实问题。
5.1 AI生成单测的边界判断
我给AI派测试任务时,会给出具体的用例类别要求:
- 正常输入:典型输入、边缘输入(空列表、空字符串、None)。
- 异常输入:格式错误、类型错误、超范围值。
- 状态转换:有状态模块的初始态、中间态、结束态。
- 错误路径:依赖的服务异常、文件不存在、权限不足。
以aggregate_by_ip为例,我会让AI生成单测,要求至少包含下面几类断言:
def test_aggregate_by_ip_normal(): records = [ LogRecord(ip="127.0.0.1", timestamp="...", method="GET", path="/", status=200, latency=10), LogRecord(ip="192.168.1.1", timestamp="...", method="POST", path="/login", status=201, latency=20), LogRecord(ip="127.0.0.1", timestamp="...", method="GET", path="/api", status=500, latency=200), ] result = aggregate_by_ip(records) assert result["127.0.0.1"] == 2 assert result["192.168.1.1"] == 1 def test_aggregate_by_ip_empty(): assert aggregate_by_ip([]) == {}这么做的价值有两个:一是AI生成的边角用例能帮你发现实现上的漏洞;二是测试用例本身成为需求文档的一部分,让别人(或未来的你)通过测试看懂这个函数的功能边界。
5.2 一个测试补全实例
有一次我给AI开发的日志聚合工具加了一个新功能:输出Top N耗时接口。AI生成了实现,也生成了单测。测试用例覆盖了普通场景、空数据、以及接口耗时完全相同的情况,看起来挺完整。
但只有一个致命缺陷:没有测试接口路径中含查询字符串的情况。我的实现逻辑是先去掉查询字符串再统计,但测试用例里没有这个场景,于是回归测试看起来一切正常。我后来补了一条测试:
def test_top_latency_strips_query_string(): records = [ LogRecord(ip="1", timestamp="...", method="GET", path="/api/user?id=1", status=200, latency=300), LogRecord(ip="2", timestamp="...", method="GET", path="/api/user?id=2", status=200, latency=100), ] result = top_latency_endpoints(records, top_n=1) assert result[0].path == "/api/user" # 注意:不带查询字符串就因为这一条测试,我立刻发现AI生成的实现没有对path做去除查询字符串的处理,统计结果把同一个接口的不同参数当成了不同接口。测试的价值在这一刻体现得淋漓尽致。
5.3 覆盖率之外,更应关注断言质量
我见过一些团队把覆盖率当作质量指标,追求100%覆盖率。但我个人的观点是:覆盖率只是必要不充分条件,断言质量比覆盖率重要得多。
什么是断言质量?就是测试用例是否真正验证了行为的正确性,而不是只验证"程序没有崩"。比如:
- 验证返回值是否精确匹配预期;
- 验证异常类型的正确性(是ValueError而不是泛泛的Exception);
- 验证副作用(例如日志文件是否写入正确内容);
- 验证边界条件(空输入、最大输入、特殊字符等)。
在AI辅助测试的场景下,我要求AI生成的测试断言尽可能"严格"。严格到什么程度?如果断言有可能因为无关因素失效,就进一步缩小断言范围。比如测试时间相关逻辑时,不要断言具体时间戳,而是断言时间差值范围,避免因为运行时延迟导致测试不稳定。
一个测试用例写得好的标准,我认为是:运行100次都稳定通过,并且在真实bug存在时,有超过90%的概率能暴露问题。如果只是跑着不报错,测试就没有意义了。
6. 可持续运转:把AI工作流固化为团队资产
v2.0工作流跑顺之后,我发现一个之前没注意到的问题:AI对话记录是知识资产,但我一直没有把它管理起来。
6.1 对话记录如何变成项目文档
每次完成一个功能,AI对话里那些有价值的信息——任务拆解、提示词、演进的版本、踩坑记录——都值得沉淀。我的做法是,在项目docs目录下建一个ai-collab-logs/文件夹,按功能分文件,每个文件包含:
- 任务目标与验收标准
- 使用的提示词模板(最终版)
- AI给出的设计方案(含被否决的方案)
- 关键决策点及原因(例如"放弃并发方案,因为单线程已满足性能需求")
- 运行时观察到的问题与修复方法
有同事问我,这样写文档会不会太耗时?我的回答是:如果不写,下次遇到同样问题,你又要花一小时重新和AI对话、验证方案。写文档只需要10分钟,但省下的可能是30分钟甚至更多。而且对于团队,这些文档就是最好的培训和交接材料。
6.2 团队提示词库的建立与维护
在团队里推行这套工作流时,我建议每个小组维护一个团队提示词库。形式可以是一个Git仓库下的.md文件,分门别类存放高频场景的提示词模板:
- 后端接口模板(含统一异常处理、日志规范、参数校验)
- 前端组件模板(含受控组件规范、样式约定、测试要求)
- 测试用例模板(含边界条件、异常路径、断言规范)
- 数据库变更模板(含迁移文件规范、回滚脚本)
维护这套仓库的关键不是"写得有多漂亮",而是每次实际使用后顺手把有效版本沉淀下来。如果某个提示词生成的代码有明显问题,就在旁边备注原因。这样,团队的AI使用水平会随着时间整体抬升,而不是各人凭借各自的"咒语"各搞各的。
6.3 从个人流程到组织流程:下一步怎么走
v2.0这套流程解决了我个人以及团队内从"用AI写代码"到"用AI高质量交付"的核心问题。但还有几个方向值得继续探索:
- AI辅助评审:让AI充当第二个代码评审官,先在我自己合入前扫描一遍常见问题(空指针、并发访问、资源泄漏),再交给人工评审。
- AI辅助重构:在既有代码上做大规模重构时,用AI分析调用链,找出隐藏依赖,降低重构风险。
- 本地化知识库:把项目私有的架构文档、业务规则、历史决策录入AI可检索的知识库,让提示词在回答时能带上项目上下文,而不仅仅依赖通用知识。
我个人的体会是,AI编程工具的能力边界还在快速扩展,但决定产出质量的始终是人的工程判断。流程的意义在于让AI的每一次输出都经过明确约束和验证,把不可控的"惊喜"降到最低。v2.0只是我当前实践的一个快照,它的框架是通用的——任务拆解、结构化提示、小步验证、测试兜底、资产沉淀。你完全可以在自己的项目里套用这套思路,再根据团队技术栈和实际场景做裁剪。
最后分享一个小技巧:所有和AI的对话,尽量在聊天工具里保留原始记录。很多解决方案是在反复追问和推理中浮现出来的,回头看这些记录时,往往能发现比最终代码更值得借鉴的思路。这比任何花哨的提示词模板都更能帮你建立对AI的"手感"。