
最近AI编程工具层出不穷从Copilot到Cursor再到各种Agent开发者们似乎每天都在被新的“效率革命”轰炸。但问题来了这些工具真的能无缝融入你的日常开发流吗还是说它们只是看起来很美用起来却总差那么一点“手感”知名开发者Matt Pocock最近分享了一套他称之为“AI编程技能集”的实践方法在Theo的t3.gg频道引发了热烈讨论。这套方法没有鼓吹某个特定的AI工具而是聚焦于一个更本质的问题如何将AI真正“训练”成理解你项目上下文、编码风格和业务逻辑的可靠搭档。很多人以为AI编程就是问ChatGPT写代码但Matt Pocock和Theo的实践揭示了一个更深层的真相最实用的“技能”往往不是AI本身的能力而是开发者教会AI如何与自己协作的那套“元方法”。本文将带你深入实测这套“AI编程技能集”的核心拆解其中最实用、最能落地的一环——我们称之为“精准拷问”The Grill Skill并为你提供从环境配置到实战演练的完整指南。1. 这篇文章真正要解决的问题从“问代码”到“教AI”如果你用过GitHub Copilot或Cursor可能经历过这样的挫败感AI生成的代码乍看正确但一放入项目就出现类型错误、依赖冲突或者完全不符合团队的代码规范。你不得不花费大量时间来回修改提示词甚至最后自己重写。这背后的核心问题是AI缺乏对你特定项目上下文的深度理解。Matt Pocock提出的“AI编程技能集”正是为了解决这个问题。它不是关于某个工具的使用技巧而是一套系统性的方法论旨在提升开发者与AI的协作效率。其核心思想是将AI视为一个需要被“引导”和“训练”的新手同事而你的任务就是通过清晰的指令和持续的上下文反馈让它变得越来越懂你和你的项目。本文将重点剖析其中被Theo认为“最实用”的技能——“精准拷问”Grill Skill。我们将解决以下具体问题概念混淆什么是“精准拷问”它和普通的代码生成提示有何本质区别实操缺失如何在主流的AI编程工具如Cursor、Claude for VS Code中实践这一技能效率瓶颈如何构建可复用的“拷问”模板避免每次重复劳动质量保障如何通过“拷问”确保AI生成的代码符合生产环境要求如类型安全、错误处理和性能通过本文你将学会的不仅是一个技巧而是一种能够持续提升你与AI协作质量的工程化思维。2. 基础概念什么是“精准拷问”Grill Skill在深入实操前我们必须厘清几个关键概念避免与常见的AI代码生成混淆。2.1 AI编程技能集 (AI Programming Skill Set)这是一个宏观框架指开发者为了高效利用AI辅助编程所需掌握的一系列能力和方法。它可能包括提示工程 (Prompt Engineering)编写清晰、无歧义的指令。上下文管理 (Context Management)如何有效地向AI提供代码库、文档、错误信息。迭代与反馈 (Iteration Feedback)如何审查AI的输出并给出修正指令。“精准拷问” (The Grill Skill)本文核心一种主动、系统性质询AI以暴露其假设、验证其方案的方法。2.2 “精准拷问” vs. 普通代码生成这是理解其价值的关键。对比维度普通代码生成 (Generic Code Generation)“精准拷问” (The Grill Skill)交互模式单次请求被动接受。如“写一个React登录组件”。多轮对话主动质疑。像一个严格的代码审查者。目标快速得到一个能运行的代码片段。得到一段健壮、可维护、符合特定约束的代码。关注点功能实现。边界条件、错误处理、类型定义、性能影响、与现有代码的集成方式。开发者角色下达指令的用户。引导对话的导师与审查者。典型问题“怎么做X”“你刚才的方案如果遇到Y情况会怎样”、“这里的类型为什么是any”、“这个函数有内存泄漏的风险吗”2.3 一个简单的类比想象你要装修厨房。普通代码生成你告诉设计师“我要一个现代风格的厨房”。他给你一张效果图你直接施工。结果可能发现插座不够、动线不合理。“精准拷问”你拿到效果图后会追问“油烟机的排风管道怎么走橱柜的板材环保等级是多少这个角落的收纳空间是否足够水电图是否符合规范” 通过一系列问题确保方案在美观之外更是安全、实用、可落地的。“精准拷问”的精髓在于不假设AI第一次给出的答案就是最优或正确的而是通过结构化的质疑引导AI进行更深层次的思考暴露出它可能忽略的细节和潜在问题。3. 环境准备选择你的“拷问”战场“精准拷问”是一种方法论不绑定于特定工具但在集成了AI的编辑器中实践效果最佳。以下是两个主流选择的环境配置。3.1 选项一Cursor IDECursor是当前对“精准拷问”工作流支持最友好的编辑器之一它深度集成了AI对话能力。安装访问 Cursor 官网 下载对应操作系统的安装包。配置模型打开Cursor进入设置 (Cmd/Ctrl ,)搜索“Model”。你可以选择OpenAI GPT-4需要填入自己的API Key性能强大但会产生费用。Claude 3 (Sonnet/Opus)同样需要API Key来自Anthropic。本地模型如果你有强大的显卡可以配置Ollama等本地模型但响应速度和能力可能不及云端大模型。关键设置确保“Codebase Indexing”已开启。这允许Cursor分析你的整个项目为AI提供丰富的上下文这是“精准拷问”能问出深入问题的前提。3.2 选项二VS Code 扩展如果你不想离开熟悉的VS Code可以通过扩展实现类似功能。安装VS Code确保你使用的是最新稳定版。安装Claude for VS Code扩展在扩展市场搜索“Claude”。选择由Anthropic官方发布的“Claude”扩展并安装。安装后侧边栏会出现Claude图标你需要登录Anthropic账户并授权。安装GitHub Copilot扩展可选但推荐搜索“GitHub Copilot”并安装。登录GitHub账户并完成订阅激活。Copilot用于代码自动补全Claude扩展用于深度对话和“拷问”两者可配合使用。3.3 项目准备无论选择哪个环境请准备或创建一个用于练习的项目。一个包含以下内容的TypeScript Node.js项目是理想的实验场清晰的目录结构src/,tests/。一个package.json文件。一些现有的模块和类型定义。这能让AI有足够的上下文供你“拷问”。4. 核心流程拆解四步掌握“精准拷问”“精准拷问”不是随机的提问而是一个有章可循的流程。下面我们将其拆解为四个可重复的步骤。4.1 第一步提出明确的核心任务你的第一个提示词必须清晰、具体包含必要的上下文。坏例子“帮我写个用户管理的API”。好例子“在我的Node.js Express TypeScript项目中需要创建一个用户管理模块。项目已使用Prisma ORM模型定义如下附上Prisma schema片段。请创建一个RESTful API路由实现用户的创建POST /users和查询GET /users/:id。请特别注意输入验证、错误处理并确保返回类型与现有响应格式一致。”关键点提供技术栈、现有工具Prisma、具体的端点、以及非功能性要求验证、错误处理、格式一致性。4.2 第二步审查与第一轮“拷问”针对实现逻辑AI生成代码后不要直接接受。像做Code Review一样针对其实现逻辑发起第一轮拷问。问题模板包括边界情况“如果创建用户时邮箱地址已存在你的代码会怎么处理目前看起来会直接抛出Prisma错误我们希望返回一个格式统一的409 Conflict错误。”安全性“在GET用户信息时你返回了完整的用户对象包括密码哈希。这安全吗我们应该如何过滤敏感字段”性能与优化“这个查询使用了findUnique如果id不存在Prisma会返回null。对于频繁访问的不存在ID是否有被恶意攻击的风险是否需要考虑请求频率限制”与现有代码集成“我们项目里有一个统一的错误处理中间件errorHandler。你生成的代码中直接使用了res.status(500).json(...)这会不会绕过这个中间件应该如何修改以利用现有架构”4.3 第三步深入第二轮“拷问”针对代码质量与类型在逻辑问题解决后进入代码质量层面。这是TypeScript项目的重点。类型严格性“我看到这个函数参数类型是any。请根据Prisma生成的UserCreateInput类型给出精确的类型定义。”异步错误处理“你用了async/await但错误处理是在顶层try-catch。如果数据库连接失败这个catch能捕获到吗是否需要更细粒度的错误处理”代码风格与一致性“我们团队使用ESLint配置Airbnb风格。你生成的代码中对象解构的换行方式似乎不符合规范。请根据附上的.eslintrc规则调整代码格式。”依赖注入与可测试性“这个路由直接导入了Prisma客户端。这会使单元测试变得困难。能否将其重构为接收Prisma客户端作为参数以提高可测试性”4.4 第四步收尾与生成辅助资产当代码本身令人满意后你可以通过“拷问”让AI生成配套资产提升整体工程质量。生成测试“请为这个创建用户的API端点编写一个Jest单元测试模拟Prisma客户端测试成功创建和邮箱冲突两种情况。”生成文档“请为这个POST /users端点生成一个OpenAPI/Swagger格式的文档片段。”生成变更说明“用一句话描述这个修改适合填入Git提交信息。”通过这四步你从AI那里获得的不仅仅是一段代码而是一个经过思考、审查、优化并配备了测试和文档的完整功能单元。5. 完整示例实战“拷问”一个数据查询函数让我们在一个真实的场景中演练。假设我们有一个简单的任务编写一个函数根据用户ID和状态从数据库查询订单列表。5.1 初始任务提出我们在Cursor中打开一个TypeScript项目然后向AI提出请求“请帮我写一个TypeScript函数函数名为getUserOrders。它接受userId: string和可选的status?: ‘pending’ | ‘shipped’ | ‘delivered’参数。使用Prisma客户端已全局初始化prisma从Order表中查询数据。需要包含基本的错误处理。”AI可能会生成如下初始代码// 初始AI生成代码 import { PrismaClient } from prisma/client; const prisma new PrismaClient(); export async function getUserOrders(userId: string, status?: pending | shipped | delivered) { try { const whereClause: any { userId }; if (status) { whereClause.status status; } const orders await prisma.order.findMany({ where: whereClause, orderBy: { createdAt: desc }, }); return orders; } catch (error) { console.error(Failed to fetch orders:, error); throw new Error(Could not retrieve orders); } }5.2 第一轮“拷问”逻辑与安全现在我们开始“拷问”。拷问1类型安全“你使用了whereClause: any。这失去了TypeScript的类型优势。请根据Prisma的OrderWhereInput生成一个类型安全的where对象。”拷问2错误处理“当前的错误处理只是打印日志并抛出一个通用错误。如果查询因用户ID不存在或数据库连接问题失败调用方无法区分。能否改进错误处理至少区分‘业务查询无结果’和‘系统异常’”拷问3安全性“这个函数会返回完整的Order对象。如果Order模型包含敏感字段如totalPrice我们是否应该定义一个安全的返回类型来选择字段”5.3 AI的改进与我们的再次“拷问”根据我们的问题AI修改了代码// AI第一次改进后的代码 import { PrismaClient, Prisma } from prisma/client; const prisma new PrismaClient(); export type SafeOrder PickPrisma.OrderGetPayload{}, id | userId | status | createdAt; export async function getUserOrders( userId: string, status?: pending | shipped | delivered ): PromiseSafeOrder[] { try { const where: Prisma.OrderWhereInput { userId }; if (status) { where.status status; } const orders await prisma.order.findMany({ where, orderBy: { createdAt: desc }, select: { id: true, userId: true, status: true, createdAt: true }, // 使用select过滤字段 }); return orders; } catch (error) { if (error instanceof Prisma.PrismaClientKnownRequestError) { // 处理已知的Prisma错误如唯一约束冲突 throw new Error(Database error: ${error.code}); } console.error(Unexpected error:, error); throw new Error(An unexpected error occurred); } }5.4 第二轮“拷问”深入优化代码已经好多了但我们还可以继续。拷问4依赖注入“函数内部直接使用了全局的prisma实例。这不利于测试。请重构此函数将prisma客户端作为参数传入并提供一个默认参数为全局实例的便利版本。”拷问5分页“如果用户的订单数量很多findMany可能会返回大量数据。请增加分页支持接受skip和take参数。”5.5 最终优化后的代码经过多轮“拷问”我们得到了一个健壮、可测试、功能完善的版本// 最终优化版本 import { PrismaClient, Prisma, Order } from prisma/client; // 1. 定义安全返回类型 export type SafeOrder PickOrder, id | userId | status | createdAt; // 2. 定义函数参数类型 export interface GetUserOrdersParams { userId: string; status?: pending | shipped | delivered; skip?: number; take?: number; } // 3. 核心函数接收PrismaClient作为依赖 export async function getUserOrders( prisma: PrismaClient, params: GetUserOrdersParams ): PromiseSafeOrder[] { const { userId, status, skip 0, take 50 } params; const where: Prisma.OrderWhereInput { userId }; if (status) { where.status status; } try { const orders await prisma.order.findMany({ where, orderBy: { createdAt: desc }, skip, take, select: { id: true, userId: true, status: true, createdAt: true }, }); return orders; } catch (error) { // 更精细的错误处理 if (error instanceof Prisma.PrismaClientKnownRequestError) { // 可以根据error.code进行特定处理例如 P2025 (Record not found) throw new Error(Database request failed: ${error.message}); } // 对于未知错误包装后抛出避免泄露底层细节 throw new Error(Failed to retrieve user orders due to an internal error); } } // 4. 提供一个使用默认全局实例的便利函数 const defaultPrisma new PrismaClient(); export async function getUserOrdersWithDefaultClient( params: GetUserOrdersParams ): PromiseSafeOrder[] { return getUserOrders(defaultPrisma, params); }6. 运行结果与效果验证如何验证我们“拷问”出来的代码是可靠的我们需要编写测试。6.1 编写单元测试Jest示例利用AI的“拷问”能力我们可以让它为最终版本的函数生成测试。“请为上面最终的getUserOrders函数编写Jest单元测试。使用jest-mock-extended来模拟PrismaClient。需要测试1. 正常查询返回数据2. 带状态过滤3. 分页参数生效4. Prisma抛出已知错误时的处理5. Prisma抛出未知错误时的处理。”AI生成的测试框架可能如下// __tests__/getUserOrders.test.ts import { PrismaClient } from prisma/client; import { mockDeep, DeepMockProxy } from jest-mock-extended; import { getUserOrders, GetUserOrdersParams } from ../src/orderService; describe(getUserOrders, () { let prismaMock: DeepMockProxyPrismaClient; beforeEach(() { prismaMock mockDeepPrismaClient(); }); it(should return orders for a user, async () { const mockOrders [ { id: 1, userId: user123, status: pending as const, createdAt: new Date() }, ]; prismaMock.order.findMany.mockResolvedValue(mockOrders); const params: GetUserOrdersParams { userId: user123 }; const result await getUserOrders(prismaMock, params); expect(prismaMock.order.findMany).toHaveBeenCalledWith({ where: { userId: user123 }, orderBy: { createdAt: desc }, skip: 0, take: 50, select: { id: true, userId: true, status: true, createdAt: true }, }); expect(result).toEqual(mockOrders); }); it(should filter by status when provided, async () { // ... 测试状态过滤 }); it(should apply pagination parameters, async () { // ... 测试分页 }); it(should throw a specific error for known Prisma errors, async () { const prismaError new Error(Record not found); prismaError.name PrismaClientKnownRequestError; (prismaError as any).code P2025; prismaMock.order.findMany.mockRejectedValue(prismaError); await expect(getUserOrders(prismaMock, { userId: user123 })).rejects.toThrow( Database request failed: Record not found ); }); it(should throw a generic error for unexpected errors, async () { prismaMock.order.findMany.mockRejectedValue(new Error(Network failure)); await expect(getUserOrders(prismaMock, { userId: user123 })).rejects.toThrow( Failed to retrieve user orders due to an internal error ); }); });6.2 运行与验证确保项目已安装Jest和相关依赖 (npm install --save-dev jest ts-jest types/jest jest-mock-extended)。运行测试npx jest __tests__/getUserOrders.test.ts预期结果所有测试用例都应通过。绿色对勾是“精准拷问”流程成功的最终验证。它不仅验证了代码功能更验证了错误处理、类型安全和可测试性等非功能性需求。7. 常见问题与排查思路在实践中你可能会遇到一些典型问题。下表提供了快速排查指南。问题现象可能原因排查方式解决方案AI生成的代码完全跑不通语法错误多。1. 初始提示词过于模糊。2. AI模型选择不当如使用了能力较弱的模型。3. 项目上下文未正确加载。1. 检查初始提示词是否包含技术栈、关键约束。2. 在Cursor设置中检查并切换AI模型。3. 在Cursor中检查右下角是否显示“Codebase Indexed”。1. 使用更具体、包含示例的提示词。2. 切换到GPT-4或Claude 3等更强模型。3. 在Cursor中通过Cmd/Ctrl K打开Chat时确保选中了“codebase”以引用整个项目。“拷问”时AI总是忘记之前的对话上下文给出矛盾的回答。1. 对话轮次过长超出模型上下文窗口。2. 在VS Code等工具中每次对话可能是独立的。1. 观察AI的回答是否开始变得简短或重复。2. 检查是否开启了“新对话”。1. 开启新对话并将最关键的前序代码和结论作为新提示词的一部分粘贴进去。2. 在Cursor中利用“文件”引用和“codebase”功能来维持上下文而非完全依赖对话历史。AI无法理解项目特定的类型或库如Prisma的WhereInput。1. 项目索引不完整。2. AI未获知关键的类型定义文件。1. 询问AI一个关于项目特定类型的问题看它能否正确回答。2. 检查相关.d.ts或prisma/client是否在索引范围内。1. 在提问时手动提供关键类型的简短示例。例如“在我们的项目中Prisma的OrderWhereInput类型结构大致是...”。2. 确保node_modules/prisma/client没有被.cursorignore或.gitignore完全排除Cursor需要索引它。“拷问”过程效率低下感觉在和AI“扯皮”。1. 问题过于开放或抽象。2. 没有聚焦于可执行的代码修改。回顾你的“拷问”问题是否是“为什么”多于“如何改”。将抽象问题转化为具体的修改指令。例如不要问“这个函数可测试性怎么样”而是问“请将这个函数重构使其接收数据库连接作为参数而不是依赖全局变量以便于单元测试。”生成的测试代码无法运行依赖或Mock方式不对。1. AI不了解你项目的测试框架配置。2. Mock的API与实际不符。1. 检查生成的测试代码中import的路径和库是否正确。2. 运行测试根据具体报错信息定位。1. 在要求生成测试时明确指定测试框架、Mock库和项目结构。例如“使用Jest和jest-mock-extended测试文件放在__tests__目录下”。2. 将测试运行的具体错误信息反馈给AI让它修正。8. 最佳实践与工程建议将“精准拷问”从临时技巧升级为团队工程实践需要遵循以下原则8.1 构建个人或团队的“拷问”提示词库将反复验证有效的“拷问”问题保存为模板。例如代码审查类“检查此函数对空值/未定义参数的处理。”、“是否存在潜在的竞态条件”、“时间复杂度是多少数据量大时是否会成为瓶颈”TypeScript专项“请用更精确的联合类型或泛型替代此处的any或as断言。”、“这个接口的属性是否都应可选请根据使用场景调整。”安全与架构“这个API端点有没有进行身份验证和授权检查”、“这里直接拼接SQL/用户输入了吗请使用参数化查询或ORM的安全方法。”8.2 设定清晰的“拷问”边界不是所有代码都值得深度“拷问”。建立优先级高优先级必须拷问核心业务逻辑、数据处理函数、安全相关的代码认证、授权、输入清洗、公共工具函数。中优先级建议拷问UI组件、配置代码、简单的CRUD操作。低优先级可省略一次性的脚本、原型验证代码、明显简单的工具函数。8.3 将AI输出视为“初稿草案”永远不要直接复制粘贴AI生成的代码到生产环境。它的角色是“高级实习生”产出需要资深开发者你的审查和批准。“精准拷问”就是你作为导师的审查过程。最终合并到代码库的必须是经过你理解和认可的代码。8.4 结合版本控制在进行重要的AI辅助重构或功能开发时使用特性分支。每次重要的“拷问-修改”循环后进行一次提交信息可以简化为“AI: 优化错误处理”、“AI: 增加分页支持”。这保留了修改历史便于回滚和追溯。8.5 持续学习与更新AI模型和工具在快速迭代。定期关注像Matt Pocock、Theo (t3.gg) 这些前沿实践者的新分享。同时将你在项目中遇到的新问题、新“拷问”模式补充到你的知识库中。最实用的技能永远是在解决真实问题中沉淀下来的。掌握“精准拷问”技能意味着你不再是被动接受AI代码的“用户”而是主动塑造产出的“工程师”。这不仅能极大提升代码质量更能将你的时间从低层次的语法编写中解放出来投入到更高层次的设计、架构和问题解决中去。开始在你的下一个功能或Bug修复中尝试这一流程你会发现最实用的AI编程技能恰恰是让你更像一位思考者的那些方法。