编程提示工程:让 AI 写出可维护的代码

把一句「帮我写个登录功能」丢给 AI,得到的往往是一段能跑但不可维护的代码。问题不在模型,而在请求本身太模糊。编程场景的提示工程(Prompt Engineering for Coding)关心的是:如何用更精确的自然语言,把你的意图、约束和上下文一次性讲清楚,让 AI 产出接近生产质量的代码。

本文侧重「写代码」,不重复调试细节。本站 ai-debugging 教程已介绍过 Copilot 的 Explain 范式(让 AI 解释某段代码再据此改),这里我们在其基础上扩展到生成阶段。

为什么编程场景需要专门提示工程

自然语言「写个函数」与工程上的「实现某个契约」之间,存在巨大鸿沟。AI 不知道你的项目用了哪种语言版本、哪种代码风格、哪些第三方库被允许,也不会主动替你想好边界条件。如果没有明确指令,它会选择一个概率上最通用的写法,这通常意味着:缺少错误处理、忽略并发安全、引入你不需要的依赖。

提示工程的目标,是把这部分隐含假设显式化。下面按从简单到系统的顺序,给出几条可直接套用的范式。

注释驱动开发

与其让 AI 凭空生成整个文件,不如先由你用自然语言把函数职责、参数、返回值写成注释或签名,再让 AI 补全实现。你掌握设计,AI 负责填充样板。

def parse_duration(text: str) -> int:
    """把人类可读的时长字符串解析为秒数。

    支持形如 "1h30m"、"45s"、"2m" 的输入。
    非法格式抛出 ValueError,负数不合法。
    """
    # TODO: 让 AI 补全实现

对应提示词:

请补全上面的 parse_duration 函数实现。
要求:只用标准库,不引入第三方依赖;
非法输入统一抛 ValueError 并说明原因;
给出 3 行以内的自测示例。

这种方式把「设计决策」留给人类,把「机械翻译」交给模型,既保证你理解每一行代码,又减少返工。

明确输入输出契约

「写一个函数」是最弱的描述。强描述会给出类型、边界条件与异常行为。契约越具体,输出越稳定。

/**
 * 计算购物车总价
 * @param {Array<{price:number, qty:number, discount?:number}>} items
 * @returns {number} 保留两位小数(分单位整数)
 * 边界:空数组返回 0;qty 为 0 或负数时跳过该项;
 * 折扣超过 1 视为非法,抛 RangeError。
 */
function cartTotal(items) {
  // 实现
}

提示要点是列出「边界」与「异常」两类信息,而不是只描述正常路径。AI 在训练数据中见过大量健壮实现,但只有在你点名时才会主动应用。

TDD 提示:先写测试再实现

测试即规格。让 AI 先产出测试用例,再实现让测试通过的代码,能显著降低歧义。

为下面的 email_validator 写一套单元测试(使用 pytest)。
覆盖:空字符串、无 @、多个 @、合法邮箱、带加号别名。
然后实现 email_validator 函数本身,使全部测试通过。
只输出测试与实现,不输出额外说明。

这种做法把「正确」的定义交给了可执行的测试,AI 的优化目标变得明确:通过你列出的用例。它也比事后补测试更不容易遗漏你真正关心的场景。

复现与报错:把错误完整交给 AI

即使侧重生成,也常在拿到代码后立刻遇到报错。此时不要概括错误,而要把完整报错与最小复现一起提供。这与 Claude Code 官方建议一致:For bugs, paste an error message or describe the symptom(粘贴报错或描述症状)。

运行单元测试时报错,完整 traceback 如下:

  ValueError: invalid literal for int() with base 10: 'NaN'
    at parse_duration (utils.py:12)

最小复现:parse_duration("NaN")。
我已确认输入来自配置文件,可能含非数字片段。
请定位根因并给出修复,保持函数签名不变。

关键动作是「完整」与「最小」:完整保证上下文不丢失,最小保证 AI 注意力不被无关代码稀释(见下文上下文裁剪)。

角色与约束设定

给 AI 一个明确身份和一组硬约束,能显著收敛输出风格。

你是一位资深 Python 工程师,熟悉类型注解与 PEP 8。
请实现上面的函数,并遵守以下约束:
1. 只输出代码,不要解释;
2. 遵循项目现有风格,不使用 dataclass 以外的实验特性;
3. 不新增任何第三方依赖;
4. 所有公开函数带类型注解与 docstring。

常见约束还包括指定语言版本(「用 Go 1.21 的泛型写法」)、指定禁止项(「不要使用全局变量」)、指定安全基线(「用户输入必须参数化,禁止字符串拼接 SQL」)。把约束写成清单,比在段落里顺带提一句更有效。

上下文裁剪

AI 的注意力是有限的。把整仓库塞进提示,反而会让无关代码稀释关键信号。只贴相关的文件片段、函数签名或报错,必要时配一句「相关背景」。这也是 Cursor 等工具鼓励用 @ 引用具体文件与目录的原因——把上下文精确钉在你关心的范围。

相关文件只有以下两段,其余不必考虑:
[utils.py] parse_duration 的签名与注释(见上)
[config.py] 第 30 行会调用 parse_duration,传入从 YAML 读取的字符串

请基于这两段实现,不要假设其他模块存在。

逐步分解大任务

大需求(「做一个博客系统」)一次性生成,几乎必然结构混乱。更好的做法是把任务拆成小函数,分多次生成:先数据模型,再存储层,再接口层,每层都基于上一层已确认的输出继续。

第一步:只定义 Post 与 Comment 两个数据类的字段与类型,
        不写方法,不写数据库代码。
确认后我会让你继续写存储层。

每轮只解决一个明确子问题,单次的复杂度降低,AI 出错概率随之下降,你也更容易在每一步做审查。

常见陷阱

需求模糊导致返工:不写边界、不写异常,拿到代码才发现不符合预期。

未指定语言版本:同一特性在不同版本语法不同,不说明版本 AI 会猜。

忽略安全:让 AI「连数据库查用户」,它可能直接拼接 SQL,埋下注入隐患;处理文件路径时可能允许路径穿越。任何涉及外部输入的地方,都要在提示里点名安全要求。

过度信任未验证输出:AI 给出的代码应通过你自己的测试与审查,不要直接合入主干。

小结

编程提示工程的核心,是把脑中隐含的设计假设显式写进请求:用注释驱动开发保留你的设计权,用输入输出契约锁定边界与异常,用 TDD 把正确性交给可执行测试,用完整报错加最小复现加速修复,用角色与约束收敛风格,用上下文裁剪聚焦注意力,用逐步分解控制单次复杂度。模型能力相近时,提示精度决定产出质量。

参考与延伸阅读

GitHub Copilot Chat 文档(解释代码、生成单元测试、建议修复等能力)— 已核验 https://docs.github.com/en/copilot/using-github-copilot/copilot-chat

Claude Code 官方概览(用自然语言描述需求、粘贴报错描述症状、用 CLAUDE.md 设定编码规范)— 已核验 https://docs.anthropic.com/en/docs/claude-code/overview

Cursor 官方文档(理解代码库、规划与构建功能、定位并修复缺陷)— 已核验 https://docs.cursor.com/

Cursor 用 @ 引用具体文件与目录以注入上下文的具体语法 — 待核实(官方路径有变动,建议以 Cursor 文档站内搜索 @ 为准)

本站 ai-debugging 教程(Copilot Explain 范式的展开与调试场景)— 已核验(站内既有内容)

本文累计阅读