LLM 辅助编程:从直接生成到测试驱动的质量跃迁
一、直接让 LLM 生成代码会发生什么
以命令行参数解析器为例(源自 Robert C. Martin《Clean Code》第十四章),需求是解析 -l(布尔)、-p(整数)、-d(字符串)、-g(列表)等带类型的命令行参数,支持默认值和错误提示。
将需求直接转化为提示词模板交给 LLM,几秒钟内就能得到一段有相当复杂度的 Java 代码。但问题随之而来:我们无法在几秒钟内判断这段代码是否正确。这就意味着,无论是否了解测试驱动开发,LLM 的特性都会把注意力引导到测试上------必须为生成的代码构造测试,才能验证它是否符合要求。
于是继续让 LLM 生成测试代码,然后执行,发现失败。
二、调试过程揭示的三类典型问题
第一次调试:逻辑错误。 传入 -l 时期望得到 true,实际得到 false。根因是 LLM 将"标志存在但无值"的情况处理为返回 false,而正确逻辑应该是返回 true。将错误信息直接贴回给 LLM,它能根据错误信息修改代码。
第二次调试:设计缺陷。 传入 -p 8080 期望得到整型 8080,实际得到字符串 "8080"。根因是 LLM 生成的代码忽略了不同参数对应不同类型这一设计要求,试图从结果推断类型。这类问题 LLM 无法自行发现,需要人工给出设计建议:明确指定不同参数的类型,根据类型去解析参数。
第三次调试:边界处理。 负数值 -3 被误识别为参数名而非参数值。根因是参数类型硬编码在 switch 语句中,无法支持可配置的参数类型。人工指出问题后,LLM 将参数类型改为构造函数注入的可配置方式。
三、LLM 辅助开发的核心结论
这个过程揭示了一个关键事实:LLM 快则快矣,质量堪忧。LLM 生成代码的速度惊人,但生成的代码存在逻辑错误、设计缺陷和边界问题,需要多轮迭代才能达到可用状态。
哪怕漏洞百出,这仍然展示了巨大的效率提升------自己从头编写同等复杂度的代码,所需时间远超与 LLM 迭代调试的总时间。
但这也说明,使用 LLM 辅助软件开发时,更多的精力要放到质量控制上,而不是一味关注速度。验证 LLM 生成结果是否正确,成为整个流程的核心瓶颈。
四、测试是与 LLM 协作的必要基础设施
从这个例子可以看出,与 LLM 协作的有效模式是:
- 让 LLM 生成初始代码
- 让 LLM 生成对应测试(或自己编写测试)
- 执行测试,将失败信息反馈给 LLM
- LLM 根据错误信息修改代码
- 重复直到测试通过
在这个循环中,测试承担了两个关键角色:一是验证 LLM 生成结果的正确性 ,二是为 LLM 提供精确的反馈信号。错误信息比自然语言描述更准确,LLM 能更有效地根据错误堆栈定位和修复问题。
自动化测试的投资回报率远高于手动测试------由于需要与 LLM 交互迭代多次,每次都手动验证的成本极高,而自动化测试可以在每次迭代后立即给出反馈。