Codex提问急救卡:7个模板提升代码生成效率
1. 为什么“提问”本身成了程序员的新瓶颈写代码这件事过去十年最大的变化不是语言变多了而是写代码的入口变了。以前我们打开编辑器从第一行import开始敲现在很多人打开的是一个对话框先把需求描述清楚再让 Codex 这类代码生成工具吐出骨架然后自己接手改。这个转变听起来只是“换个地方打字”但实际用下来你会发现真正卡住效率的往往不是模型能力而是提问的质量。我见过太多类似的场景同一个 Codex有人三句话就能拿到能跑的代码有人写了三百字还在被反问“你希望用哪种语言实现”。差距不在谁更懂算法而在谁更懂怎么把脑子里的模糊需求翻译成模型能精确执行的指令。这就是所谓“提问急救卡”的价值——它不是让你背话术而是给你一套在卡壳时能立刻套用的结构把“我不知道怎么问”变成“我照着填就行”。这篇文章面向三类人第一类是刚接触 Codex 这类工具、经常得到“答非所问”结果的新手第二类是已经能用但效率不稳定、想系统化自己提问方式的中级开发者第三类是团队里需要把提问规范沉淀下来、让多人协作时输出风格统一的技术负责人。我会把 7 个常用模板拆开讲清楚每个模板解决什么问题、为什么这样设计、什么场景下用、以及怎么组合起来应对复杂需求。所有模板都来自实际项目里反复验证过的写法不是纸上谈兵。需要先说明一点Codex 的能力边界在持续变化但提问的结构化逻辑是相对稳定的。你今天学会的“角色约束示例输出格式”这套骨架换一个代码生成工具照样能用。所以下面讲的不只是“怎么问 Codex”而是“怎么把工程思维注入到每一次提问里”。2. 七个模板的底层逻辑先搞清楚模型在“听”什么在逐个拆模板之前有必要先讲清楚一件事当你向 Codex 提问时它到底在处理什么信息。很多人以为模型是在“理解你的意图”更准确的说法是它在根据你给的上下文预测最可能的代码续写。这意味着你给的每一个词、每一个约束、每一个示例都在缩小它的预测空间。提问的本质是主动缩小解空间。基于这个认知七个模板其实对应了七种不同的“缩小解空间”的策略。我把它们列在下面你先有个全局印象模板编号模板名称核心作用典型触发场景1角色锚定模板限定模型的知识域和表达风格需要特定技术栈或特定规范时2输入输出契约模板明确函数签名和数据结构写工具函数、API 封装时3分步拆解模板把复杂任务切成可验证的小步实现一个完整模块时4反例排除模板用“不要做什么”划边界模型总给出不想要的写法时5示例驱动模板给一两个样例让模型模仿输出格式要求严格时6约束清单模板把性能、兼容性等要求列清楚生产环境代码、库开发时7迭代追问模板在已有结果上做定向修改第一版不完美需要微调时这七个模板不是孤立的。实际使用中组合写法才是常态。比如“角色锚定输入输出契约约束清单”三件套基本能覆盖 80% 的日常编码需求。后面我会专门用一章讲组合策略这里先建立这个认知模板是积木不是填空题的标准答案。还有一个容易被忽略的点提问的时机。很多人习惯一上来就把所有要求堆上去结果模型顾此失彼。更有效的做法是分阶段——先确认理解再要方案最后要代码。这其实就是模板 3 和模板 7 的配合。我在实际项目里发现分阶段提问虽然多了一两轮对话但返工率明显下降总体反而更快。3. 模板一与模板二把“你是谁”和“要什么”说死3.1 角色锚定模板为什么“你是一个资深 Python 工程师”比“帮我写代码”有效角色锚定模板的写法很简单就是在提问开头加一句身份设定。比如你是一个有十年经验的 Python 后端工程师熟悉 FastAPI 和 SQLAlchemy 代码风格偏向显式优于隐式注释只写“为什么”不写“是什么”。这句话看起来像客套实际上在做三件事。第一它把模型的输出分布往特定技术栈上拉。你不说模型可能给你 Flask也可能给你 Django甚至给你一个纯http.server的示例。第二它限定了代码风格。同样实现一个功能“显式优于隐式”意味着它会倾向于写完整的类型注解、避免魔法方法、把副作用写清楚。第三它暗示了注释策略这对团队协作特别重要。我自己的经验是角色锚定里最值得花心思的是风格描述而不是头衔。你说“资深工程师”模型未必有感知但你说“函数不超过 30 行、每个公开函数必须有 docstring、异常要自定义而不是直接抛 Exception”这些是可执行的约束模型会认真对待。注意角色锚定不要写得太长。超过三行的身份描述模型反而会抓不住重点。把最关键的两三个特征写清楚就够了剩下的交给约束清单模板。3.2 输入输出契约模板函数签名先定代码自然就顺了这个模板解决的是“模型写出来的函数参数顺序不对、返回值结构不对”的问题。写法是先把签名和数据结构写出来再让模型填充实现请实现下面这个函数保持签名不变 def merge_intervals(intervals: list[tuple[int, int]]) - list[tuple[int, int]]: 合并重叠区间输入已按左端点排序。 返回合并后的区间列表同样按左端点排序。 要求 - 时间复杂度 O(n) - 不修改输入列表 - 空输入返回空列表为什么这样有效因为函数签名本身就是一种强契约。模型不需要猜你要 list 还是 tuple不需要猜返回是原地修改还是新对象。它只需要专注在算法逻辑上。实测下来带签名的提问比不带签名的提问一次通过率能高出不少尤其是涉及嵌套数据结构的时候。这里有个细节值得说类型注解要写全。list[tuple[int, int]]比list强太多因为模型能从中推断出元素是二元组、元素是整数。如果你用的是动态类型语言至少在注释里把结构写清楚。我见过有人只写“传入一个区间列表”结果模型返回了[[1,2],[3,4]]这种列表的列表而调用方期望的是元组列表白白多一轮修改。3.3 两个模板的配合先定角色再定契约单独用角色锚定模型知道“用什么风格写”单独用输入输出契约模型知道“写成什么形状”。两个一起用效果是叠加的。我常用的开头长这样你是一个注重可测试性的 Python 工程师偏好纯函数和显式类型。 请实现下面这个函数保持签名不变 [签名和 docstring]这样模型既不会给你写带全局状态的实现也不会在返回值上跟你玩猜谜。对于工具函数、数据转换、算法题这类场景这个组合基本是标配。4. 模板三与模板四拆解复杂任务同时堵住错误方向4.1 分步拆解模板为什么“一次只做一件事”反而更快分步拆解模板的核心是不要在一个提问里要求模型完成一个完整模块。而是先让它给出步骤规划你确认后再逐步实现。写法示例我要实现一个简单的任务队列支持 - 添加任务带优先级 - 按优先级取出任务 - 任务执行失败后重试最多 3 次 请先不要写代码先给我一个实现步骤清单 每一步说明要做什么、涉及哪些数据结构、以及这一步的验证方式。这个模板的价值在于把验证点前置。模型给出的步骤清单你可以快速判断它的思路对不对。如果它第一步就说“用 Redis 做持久化”而你只是想要一个内存队列你可以在写代码之前就纠正方向省下大量返工。我在实际项目里用这个模板时会特别关注模型列的“验证方式”。如果它说“这一步通过单元测试验证”我会追问“测哪些边界”。这一步追问往往能暴露出模型对需求理解的盲区。比如它可能没想到“优先级相同的任务按什么顺序取出”这种细节而这恰恰是需求里没写清楚、但实现时必须决定的地方。4.2 反例排除模板用“不要”来划边界有些时候你告诉模型“要什么”它理解得挺好但它总会顺手加上一些你不想要的东西。比如你让它写一个配置解析函数它给你加了一堆日志、加了一个全局缓存、还顺手做了类型转换。这时候反例排除模板就派上用场实现一个配置解析函数要求 - 只做解析不做类型转换 - 不要加日志 - 不要用全局变量或缓存 - 不要引入除标准库以外的依赖 - 解析失败时直接抛 ValueError不要吞异常“不要”清单为什么有效因为它直接压缩了模型的输出空间。模型在生成时每一个“不要”都对应一个它本来可能选择的路径现在这条路被堵死了。实测下来反例排除对控制代码“体积”特别有用。很多人抱怨模型写的代码太啰嗦其实是因为你没告诉它“简洁”的具体含义。把“不要日志、不要缓存、不要额外依赖”写出来代码立刻就瘦了。提示反例排除不要写太多条一般 3 到 5 条足够。写太多会让模型过度紧张反而在核心逻辑上缩手缩脚。优先排除那些“模型默认会做但你不需要”的行为。4.3 拆解与排除的组合复杂需求的标准打法当一个需求既复杂又有明确的“禁区”时我会把模板 3 和模板 4 组合起来。先用分步拆解拿到步骤清单确认方向后在每一步的实现提问里加上反例排除。比如按你刚才的步骤 2实现“按优先级取出任务”这个函数。 要求 - 不要修改队列内部结构以外的任何状态 - 不要用排序用堆实现 - 不要处理空队列以外的异常这样每一步的实现都是有边界的小任务模型不容易跑偏你也容易验证。这套打法在处理中等复杂度模块时特别顺手基本能做到“一次写对少量微调”。5. 模板五与模板六让输出格式听话让生产代码达标5.1 示例驱动模板给一个样例胜过十句描述示例驱动模板的逻辑很直白与其用文字描述你想要的输出格式不如直接给一个输入输出的例子。写法请实现一个函数把下划线命名转成小驼峰命名。 示例 输入: user_first_name 输出: userFirstName 输入: _private_field 输出: _privateField 输入: already_camel 输出: alreadyCamel 请处理以下边界连续下划线、首尾下划线、空字符串。为什么示例比描述有效因为自然语言描述格式时总有歧义。你说“转成小驼峰”模型可能把_private_field转成PrivateField也可能转成privateField。但给一个样例歧义立刻消失。而且样例还能隐含边界处理方式比如_private_field的输出保留了前导下划线这就告诉模型“不要无脑去掉所有下划线”。我在用这个模板时有个习惯样例要覆盖边界但不要覆盖全部。给两三个有代表性的例子剩下的边界用文字补充。如果样例给太多模型容易过拟合到样例上遇到没见过的输入反而不会处理。两三个样例加一句“请处理以下边界”是性价比最高的组合。5.2 约束清单模板生产代码和玩具代码的分水岭约束清单模板是把性能、兼容性、安全、可维护性等要求逐条列出来。这个模板在写库代码、公共组件、或者任何要上生产的代码时几乎是必须的。示例实现一个 LRU 缓存类约束如下 - 线程安全使用 threading.Lock - 容量为 0 时所有 get 返回 Noneput 不存储 - get 和 put 的时间复杂度均为 O(1) - 不使用 functools.lru_cache - 提供 __len__ 和 __contains__ - 类型注解完整兼容 Python 3.9这些约束每一条都对应一个容易被忽略的工程细节。线程安全不用说容量为 0 的边界很多人不处理O(1) 排除了用列表扫描的偷懒写法不用内置装饰器是为了可控性__len__和__contains__是为了让类用起来像容器。把这些写清楚模型给出的代码基本可以直接进代码库。我踩过的一个坑是约束清单里的“兼容 Python 3.9”这种版本要求一定要写。不写的话模型可能用上 3.10 才有的match语句或者X | Y类型语法在旧版本上直接报错。这种问题在本地跑没事一到 CI 就炸排查起来很烦。5.3 格式与约束的配合让模型输出“能直接用”的东西示例驱动管的是“形状”约束清单管的是“质量”。两个一起用模型输出的代码基本就是可提交状态。我常用的组合是先给两三个输入输出样例定格式再列五条左右的约束定质量。比如写一个日期解析函数请实现 parse_date 函数。 示例 parse_date(2024-01-15) - datetime.date(2024, 1, 15) parse_date(15/01/2024) - datetime.date(2024, 1, 15) parse_date(invalid) - 抛出 ValueError 约束 - 支持 ISO 格式和 DD/MM/YYYY 格式 - 不使用 dateutil 等第三方库 - 错误信息包含原始输入 - 类型注解完整这样写出来的函数格式和错误处理都符合预期拿过来改改变量名就能用。示例驱动和约束清单的组合是我个人认为投入产出比最高的提问方式尤其适合写那些“逻辑不复杂但细节多”的工具函数。6. 模板七迭代追问把“差不多”磨成“刚刚好”6.1 迭代追问模板不要重新提问要定向修改很多人拿到第一版代码后如果发现有问题习惯重新开一个提问把需求再描述一遍。这是效率最低的做法。正确的做法是在已有结果上做定向修改。写法你刚才给的实现里merge_intervals 函数在输入为空列表时返回了 None 但我要求返回空列表。请只修改这一处其他逻辑保持不变。这个模板的关键词是“只修改这一处”和“其他逻辑保持不变”。为什么因为模型在重新生成时如果没有这两个约束它可能顺手把其他能跑的逻辑也改了引入新的问题。定向修改能把变更范围控制住你验证起来也轻松。我自己的习惯是每次追问只提一个问题。如果发现三个问题就分三轮追问。听起来慢但实际上每轮改动小、验证快总体比一次性提三个问题然后发现模型改乱了要快得多。而且分轮追问能让你清楚知道每个改动对应哪个问题出问题也好回滚。6.2 追问的三种典型场景与对应写法迭代追问不是只有“改 bug”一种场景。我把它归纳为三类每类写法略有不同第一类修正错误。就是上面那种明确指出哪里不对、期望是什么、只改这里。写法重点是定位精确最好带上函数名和具体行为。第二类补充功能。比如“在现有基础上增加一个 clear 方法清空缓存并重置命中率统计”。写法重点是说明新增部分与现有部分的关系避免模型重写整个类。第三类优化性能或可读性。比如“这个函数现在是 O(n²)请优化到 O(n log n)保持接口不变”。写法重点是给出目标指标让模型知道优化到什么程度算完成。三类场景的共同点是都在已有上下文里做增量修改。这也是为什么我建议把一次完整任务的对话保持在一个会话里而不是每轮都新开。上下文连续模型才能准确理解“刚才那个实现”指的是什么。6.3 什么时候该放弃追问重新提问迭代追问虽好但有个边界当模型连续两轮都没理解你的意图时就该重新提问了。继续追问只会让对话历史越来越长模型被前面的错误带偏。这时候正确的做法是新开一个提问把需求重新组织一遍把之前踩过的坑作为约束写进去。我判断“该重开”的信号有三个一是模型连续两次修改都改错地方二是你发现自己的追问描述越来越长、越来越绕三是模型开始道歉但给出的代码还是老样子。出现任何一个果断重开把之前有效的约束保留无效的追问丢掉。这个经验帮我省下过不少时间。7. 组合写法把七个模板拼成一套工作流7.1 日常编码的“三件套”组合如果只记一个组合记这个角色锚定 输入输出契约 约束清单。这三件套覆盖了日常编码的绝大多数场景。我写一个完整的例子你可以直接套你是一个注重可测试性的 Python 工程师偏好纯函数和显式类型。 请实现下面这个函数保持签名不变 def group_by_key(items: list[dict], key: str) - dict[str, list[dict]]: 按指定 key 对字典列表分组key 不存在的元素归入 __missing__ 组。 返回的字典中每个列表保持原顺序。 约束 - 不修改输入列表和其中的字典 - 空输入返回空字典 - 不使用 itertools.groupby - 类型注解完整这个提问里角色锚定定了风格契约定了签名和语义约束清单堵住了常见坑。实测下来这种提问的首次通过率很高基本不需要追问。7.2 复杂模块的“分步 契约 排除”组合当任务是一个完整模块时三件套不够用需要加上分步拆解和反例排除。流程是先用分步拆解模板让模型给出步骤清单你确认方向。对每一步用输入输出契约模板定义接口。在每一步的实现提问里加上反例排除堵住不想要的写法。每步完成后用迭代追问模板做微调。这个流程走下来一个中等复杂度的模块比如一个带重试的 HTTP 客户端封装大概需要五到八轮对话但每一轮的产出都是可验证的不会出现“写了三百行结果方向全错”的情况。7.3 组合时的优先级先定边界再定细节组合多个模板时有个优先级原则先定边界再定细节。边界包括角色、约束、反例细节包括签名、示例、步骤。为什么因为边界决定了模型的“活动范围”范围定好了细节怎么填都不会太离谱。反过来如果先定细节再定边界模型可能已经按自己的理解写了一堆代码你再加约束它就得推翻重来容易改出四不像。我自己的提问顺序通常是角色锚定 → 约束清单 → 反例排除 → 输入输出契约 → 示例驱动 → 分步拆解 → 迭代追问。前三个是“圈地”中间三个是“建房”最后一个是“装修”。这个顺序不是死的但大方向是从粗到细、从边界到实现。8. 实测中那些“模板没写但很重要”的细节8.1 提问里的“隐含假设”要主动说出来模板能帮你把显性需求说清楚但隐性假设往往才是返工的根源。比如你让模型写一个排序函数它默认输入是列表但你的调用方传的是元组它默认元素可比较但你的元素是自定义对象。这些假设模型不会问你得主动说。我的做法是在约束清单里加一条“假设与前置条件”。比如假设 - 输入列表长度不超过 10^5 - 元素均为正整数 - 调用方保证列表非空把这些写出来模型就不会去处理那些你根本不关心的边界代码也更聚焦。这一条是我从多次返工里总结出来的加进去之后因为“模型处理了不该处理的边界”导致的代码臃肿问题少了很多。8.2 代码风格的一致性靠“锚点”维持多人协作或者长期项目里代码风格一致性是个大问题。模型每次生成的风格可能略有不同今天用snake_case明天可能给你camelCase。解决办法是在角色锚定里放一个风格锚点比如代码风格参考函数名用 snake_case类名用 PascalCase 私有方法前缀单下划线常量全大写导入按标准库、第三方、本地分组。这个锚点写一次后续所有提问都带上生成的代码风格就稳定了。如果团队有现成的风格文档把关键几条摘出来放进锚点里效果比让模型“自己看着办”好得多。8.3 遇到模型“过度设计”时怎么拉回来模型有个通病你让它写个小函数它给你搞出一堆抽象层。这时候除了用反例排除还有一个技巧是在提问里限定代码行数。比如“这个函数控制在 20 行以内”“整个实现不超过 50 行”。行数限制是个很硬的约束模型会为了满足它而砍掉不必要的抽象。我试过在写一个简单的配置合并函数时加一句“实现不超过 15 行”模型立刻从原来的三个辅助函数加一个类压缩成了一个函数加一个字典推导。当然行数限制不能滥用复杂逻辑硬压行数会牺牲可读性。我的经验是工具函数类限制在 20 行以内业务逻辑类限制在 50 行以内比较合理。8.4 把“验证方式”写进提问里最后一个细节也是我觉得最被低估的在提问里说明你打算怎么验证这段代码。比如“我会用 pytest 跑以下用例”“我会在 Python 3.9 和 3.11 上各跑一遍”“我会用 10 万条数据做性能测试”。把验证方式告诉模型它会主动往“容易验证”的方向写代码比如把纯逻辑和副作用分离、把边界条件显式处理。这个技巧我是从一个做库开发的朋友那里学来的。他说他每次提问都会加一句“这段代码会被单元测试覆盖请确保每个分支都可独立触发”。加了这句话之后模型生成的代码可测试性明显提升mock 起来也容易。对于要进代码库的代码这一句几乎成了我的固定结尾。9. 从“套模板”到“形成自己的提问直觉”七个模板讲完了组合写法也讲了但我最后想说的是模板的终点是让你不再需要模板。刚开始你可能是照着模板一条条填填多了之后你会发现自己提问时自然而然就会带上角色、约束、示例甚至不用刻意想“我现在该用哪个模板”。这个状态就是提问直觉形成了。形成直觉的标志有三个一是你能在提问前就预判模型可能在哪里跑偏提前用约束堵住二是你能根据第一轮的回答质量判断是该追问还是该重开三是你能把一次复杂需求拆成几轮对话每轮都有明确的验证目标。到了这个阶段模板已经内化成你的思考方式而不是外挂的话术。我自己的体会是提问能力和写代码能力是两种不同的技能但可以互相促进。你把提问练好了会发现自己对需求的理解也更清晰了因为能问清楚的前提是想清楚。反过来代码写得多了你也更知道模型会在哪里偷懒、哪里过度设计提问时就能精准地打补丁。如果非要给一个练习建议我会说从今天开始每次向 Codex 提问之前先花十秒钟想一下“我这次要用哪个模板为什么”。坚持两周你会发现自己提问的废话变少了拿到的代码能用的比例变高了。这个变化不会惊天动地但日积月累下来省下的返工时间相当可观。