开发出色的软件并不取决于完美的提示语,而在于遵循一套严谨的开发流程。在这篇文章中,我会分享自己用于编写安全代码的工作流程:明确目标、分析各种边缘情况,并通过可运行的测试逐步完成代码开发。
以Node.js购物车为例,我将说明为什么服务器端验证和测试驱动的开发方式总是比“一次性”生成的AI代码更可靠。让我们一起来探讨如何让AI成为你最可靠的合作伙伴吧。
一些背景知识
上周有那么一会儿,我觉得自己做了一件非常了不起的事情……不过这种兴奋感只持续了大约五秒钟。我使用了一个AI工具,输入了一句话,结果它就为我的电子商务应用生成了一个完整的购物车模块。其中包含了大量的文件、代码,甚至还有文件夹和模板结构,看起来确实非常专业。
但随后我意识到一个问题:问题并不在于“AI编写代码的速度有多快”,而在于“我如何才能确定这段代码是正确的?”
事实就是:那些你并没有亲自编写的庞大代码堆并不会成为捷径。对大多数开发者来说,这些代码反而会带来额外的工作负担——你必须阅读它们、理解它们的含义,同时还要努力找出其中隐藏的错误。
因此,今天我不会再讲那些关于“AI即将改变一切”的老套话题。相反,我会向大家展示一个简单的流程,任何级别的开发者都可以按照这个流程来逐步利用AI获得更好的开发效果,而不会陷入困境。我还会用一个可以在单个文件中运行的实际例子来演示这个流程。
我们将会讨论的内容:
- 那短暂的兴奋感(以及真正的问题所在)
- 黄金法则:永远不要相信用户提供的价格
- 思维方式的转变:不要再要求获得完整的应用程序
- AI编码流程(七步工作法)
- 应用这个流程:实现一个服务器端的购物车总价计算器
- 一个可以在单个文件中运行的示例(其中故意包含了错误的代码版本)
- 如何利用失败的测试来发现问题
- 可复用的提示语模板
- 冷静地审视这些炒作:为什么基础知识现在更加重要
- 总结
五秒钟的高效工作方式(以及真正的问题所在)
很多人对人工智能编程存在误解。他们认为编程的主要工作就是打代码,但实际上,关键在于清晰地思考问题。如今,打代码已经变得非常容易,而真正的挑战在于思考。
当人工智能一次性生成了一个“外观完美”的模块时,实际的工作并没有结束。这些工作会继续向下进行:
-
你仍然需要理解它生成了什么内容。
-
你还需要验证它是否符合你的要求。
-
你还需要找出那些隐藏在“看起来很完美的代码”中的错误。
如果你无法验证这些结果,那么你就不能真正掌握这个模块。而如果不能掌握它,你就无法安全地将其投入使用。
提示:对待人工智能生成的成果,要像对待互联网上陌生人的代码一样:虽然有用,但在未经验证之前不可完全信任。
黄金法则:永远不要相信用户提供的价格
我当初也是像初学者一样开始的。我打开人工智能工具,然后输入了一个模糊的要求:
为我设计并开发一个电子商务购物车模块。
人工智能生成了大量的代码,看起来非常整齐。如果你是新手,你可能会想:“哇,它果然解决了这个问题。”
但随后我问了自己一个问题:在现实生活中,这种方案最容易出什么问题?答案也很简单:“有人可能会篡改价格。”因为购物车系统有一个重要的黄金法则:永远不要相信用户提供的价格。
如果浏览器告诉你“T恤的价格是1美元”,而你接受了这个价格,那么别人就可以用1美元买到原本价值20美元的商品。而当人工智能快速生成大量代码时,这类错误很容易隐藏在“看起来很完美的代码”中。
警告:任何接受用户提供价格的系统,实际上都在为价格被篡改创造机会。
思维方式的转变:不要要求人工智能开发整个应用程序
因此,我没有直接接受人工智能生成的全部代码,而是改变了我的方法。我这样要求它:
我不会让人工智能来开发整个应用程序。我会把这个大任务分解成小部分,然后像真正的工程师一样指导它来完成这些任务。
这就是第一次思维方式的转变。在人工智能时代,你的价值不在于你打代码的速度有多快,而在于你是否能够很好地完成以下三件事:
-
清晰地定义问题所在。
-
把问题分解成小部分。
-
验证最终结果是否正确。
大型系统是由许多正确的、小规模的组件构成的。这才是真正的工程实践,而不是所谓的“提示工程”。
人工智能编程流程(七步工作法)
以下是我使用的流程。这些步骤用简单的英语表述出来,你可以将其复制并应用于任何项目中:
-
用一句话明确你的目标。
-
写出规则——即哪些条件必须是成立的。
-
给出两个示例,展示输入与输出之间的关系。
-
考虑两种极端情况,分析可能出现的问题。
-
要求人工智能只完成其中的一小部分工作,而不是整个项目。
-
编写测试用例并运行它们。
-
如果出现问题,就修改提示内容,然后重新开始。
就是这样。这就是那个循环流程。下面是它的可视化展示:

提示:循环流程才是真正的核心技能。工具可能会发生变化,但这个循环流程本身是不会改变的。
应用这个循环流程:一个服务器端的购物车总价计算器
现在让我们把这个循环流程应用到购物车的例子中。我没有要求开发一个完整的购物车模块,而是只写了一条简短的需求说明:
我们需要在服务器端实现一个购物车总价计算器。用户会提供
产品编号和数量,但我们必须忽略用户提供的价格信息,同时要使用我们自己的产品目录。我们需要处理未知产品以及无效的数量值,还要计算小计、折扣、税费>以及最终的总价,并且要确保金额的计算结果正确无误。最后,我们还必须为这个功能编写测试用例。
这条需求说明并不复杂,只是简洁明了地列出了所需的功能。
然后我只要求AI完成其中的一个小功能:
-
不需要开发用户界面
-
也不需要处理数据库相关逻辑
-
更不需要设计整个系统架构
-
只需要实现一个带有测试用例的功能即可
因为构建实际可运行的系统,最快的方法就是一次只完成一个小功能。我们在需求说明中已经写下了所有需要考虑的内容,现在再把这些内容以可视化的形式表现出来会更好。这样,我们就可以准备一份清晰、有文档记录的需求说明书,将其保存在项目的GitHubREADME.md文件中。
在下面的示意图中,左侧是浏览器,右侧是服务器。浏览器/用户提供的输入是不可信的,他们可能会发送产品编号、数量,甚至可能是虚假的价格,但服务器只能将产品编号和数量作为有效输入进行处理,而必须忽略客户端提供的价格信息。服务器会从自己可信的产品目录中查询实际价格,验证数量是否正确,然后根据服务器端的数据计算出最终总价。这就是所谓的“信任边界”:价格数据应该来自服务器,而不是客户端。

需求说明的核心要素(小功能、严格的约束条件)
我使用的需求说明就是这样的:
需要创建一个可以用Node.js运行的JavaScript文件。
目标:
计算购物车商品的总价。
规则:
-
输入的数据必须包含
产品编号和数量。 -
绝对不能信任用户提供的价格信息。
-
必须使用我们自己的产品目录。
-
数量至少为1。
-
折扣百分比和税费百分比都不能为负数。 -
先计算折扣,再计算税费。
-
金额需保留2位小数。
示例:
-
2件T恤(每件20美元)+ 1个杯子(12.50美元)=> 小计52.50美元
-
折扣10%,税率8% => 先打折再征税
功能:
-
实现一个功能
-
使用Node内置的assert函数进行简单测试
-
输出一个示例结果
一个小改变就能产生巨大的效果:规则 + 示例 + 测试。人工智能仍然会尽力快速提供帮助,但现在它有了约束机制。如果它仍然犯了错误,你也能发现,因为你已经要求它提供证明。
以下是“购物车总价计算流程”的可视化展示,涵盖了购物车总价计算过程中涉及的所有使用场景。

在图中,购物车总价的计算遵循一个固定的流程。首先,验证输入数据(已知的`productId`、有效的`qty`、非负的折扣/税率)。接下来,从可信的产品目录中获取`subtotal`。然后应用折扣得到折后金额。之后,对折后金额征税(而不是对原始的小计金额征税)。最后,正确地对数值进行四舍五入并返回结果(`subtotal`、`discount`、`tax`和`total`)。关键在于顺序:先打折再征税。
单文件可执行示例(故意使用错误版本)
现在你可以立即运行这个单文件示例了。无需任何设置,只需要Node.js环境即可。创建一个名为`cart.js`的文件,将以下代码粘贴进去,然后运行`node cart.js`。
其中包含了两个版本:
-
一个错误版本,该版本会信任用户提供的价格(这就是我们想要从中吸取教训的错误)
-
一个正确版本,该版本会使用可信的产品目录数据进行计算
// cart.js
// 运行方式:node cart.js
const assert = require("node:assert/strict");
// 可信的产品目录数据(服务器端确认的信息)
const PRODUCTS = {
tshirt: { name: "T-shirt", priceCents: 2000 }, // 20.00美元
mug: { name: "Mug", priceCents: 1250 }, // 12.50美元
book: { name: "Book", priceCents: 1599 }, // 15.99美元
};
function money(cents) {
return (cents / 100).toFixed(2);
}
// 错误版本:信任用户提供的价格
function cartTotal_wrong(cartItems, discountPercent = 0, taxPercent = 0) {
let subtotalCents = 0;
for (const item of cartItems) {
const priceCents = Math.round((item.price ?? 0) * 100); // 用户可能会提供虚假的价格
subtotalCents += priceCents * item qty;
}
const discountCents = Math.round(subtotalCents * (discountPercent / 100));
const afterDiscount = subtotalCents - discountCents;
const taxCents = Math.round(afterDiscount * (taxPercent / 100));
const totalCents = afterDiscount + taxCents;
return totalCents;
}
// 正确版本:使用可信的产品目录数据进行计算,并进行验证
function cartTotal(cartItems, discountPercent = 0, taxPercent = 0) {
if (!Array.isArray/cartItems))
throw new Error("cartItems必须是一个数组");
if (typeof discountPercent !== "number" || discountPercent < 0) throw new Error("discountPercent必须是非负数"); if (typeof taxPercent !== "number" || taxPercent < 0) throw new Error("taxPercent必须是非负数"); let subtotalCents = 0; for (const item of cartItems) { const { productId, qty } = item || {}; if (typeof productId !== "string" || !PRODUCTS[productId]) { throw new Error("未知的productId: " + productId); } if (typeof qty !== "number" || qty < 1) throw new Error("qty必须大于或等于1"); subtotalCents += PRODUCTS[productId].priceCents * qty; } const discountCents = Math.round(subtotalCents * (discountPercent / 100)); let afterDiscountCents = subtotalCents - discountCents; if (afterDiscountCents < 0) afterDiscountCents = 0; const taxCents = Math.round(afterDiscountCents * (taxPercent / 100)); const totalCents = afterDiscountCents + taxCents; return { subtotalCents, discountCents, taxCents, totalCents }; } function runTests() { // 正常情况示例 const cart = [ { productId: "tshirt", qty: 2 }, {productId: "mug", qty: 1 }, ]; const r = cartTotal(cart, 10, 8); assert.equal(r.subtotalCents, 5250); // 52.50美元 assert_equal(r.discountCents, 525); // 52.50美元的10% assert.equal(r.taxCents, 378); // 47.25美元的8% assert.equal(r.totalCents, 5103); // 51.03美元 // 恶意攻击示例:用户尝试提供虚假的价格 const attackerCart = [ { productId: "tshirt", qty: 2, price: 1 }, {productId: "mug", qty: 1, price: 1 }, ]; const wrong = cartTotal_wrong(attackerCart, 0, 0); assert.equal(money(wrong), "3.00"); // 在现实生活中,这个结果肯定是错误的 const safe = cartTotal.attackerCart, 0, 0); assert_equal(money(safe.totalCents), "52.50"); // 结果是正确的,因为程序忽略了用户提供的虚假价格 // 边缘情况测试 assert.throws(() => cartTotal([{productId: "unknown", qty: 1 }], 0, 0)); assert.throws(() => cartTotal([{productId: "tshirt", qty: 0 }], 0, 0)); assert.throws(() => cartTotal(cart, -1, 0)); assert.throws(() => cartTotal/cart, 0, -1)); } runTests(); console.log("所有测试都通过了。"); const example = cartTotal( [ { productId: "tshirt", qty: 1 }, {productId: "book", qty: 2 }, ], 15, 5, ); console.log("示例中的小计金额:", money(example.subtotalCents)); console.log("示例中的折扣金额:", money(example.discountCents)); console.log("示例中的税额:", money(example.taxCents)); console.log("示例中的总金额:", money(example.totalCents));
在这段代码中,我们并没有施展什么神奇的技巧,而是进行了一些扎实的工程工作:
-
我们将一个复杂的问题分解成了若干较小的部分。
-
我们制定了明确的规则,以确保人工智能不会凭猜测来解决问题。
-
我们还提供了具体的示例,帮助人工智能理解这些规则的含义。
-
我们设计了测试用例,以便验证我们的解决方案是否有效。
-
最后我们运行了这些测试,确认代码确实能够按预期工作。
这套方法可以应用于任何项目之中。
如何利用失败的测试来发现问题
很多开发者都会忽略这一环节。他们要求提供代码,但却不要求看到验证结果。当你运行测试时,通常会出现以下两种情况之一:
-
测试通过:很好,这说明你的代码是正确的。
-
测试失败:这反而更好,因为这样你能清楚地知道哪里存在问题。
一个失败的测试其实就像一盏指路明灯,它能准确地指出你的思路或代码中哪些地方需要改进。而不是简单地说“人工智能犯错了”,它会给你提供一个具体的问题:
是哪条规则不够清晰、缺失了,或者存在矛盾之处呢?
然后你就可以根据这个问题的提示进行相应的调整:
-
添加更严格的规则。
-
补充能够消除歧义的示例。
-
添加一些边界情况,以确保代码在特殊条件下也能正确运行。
-
只需修改有问题的那部分代码,而无需重新编写整个项目。
可复用的提示模板
这里有一个可供你直接复用的提示模板(详见图片下方内容):

只需编写一个小的功能模块,而不是整个应用程序。
目标:
(一句话)
规则:
(3到7条具体规定)
示例:
(2个输入与输出对应的例子)
边界情况:
(2种可能导致程序出错的特殊情况)
最终成果:
- 生成一个可运行的文件
- 包含使用Node.js的断言测试代码
- 打印出一个示例输出结果
在提交代码之前,请先列出可能出现的错误,并确认所有规则都已得到遵守。
最后那一行要求非常重要。它迫使人工智能在编写代码之前先思考可能出现的问题。
冷静审视:为什么基础技能如今更为重要
网上有很多内容会让人误以为:“既然人工智能已经能够编写代码了,那么学习编程就不再必要了。”但这种想法其实是个陷阱。因为虽然人工智能确实可以编写代码,但它无法替代你作为开发者和工程师应承担的责任。
如果你发布了一个有缺陷的产品,可能会损失金钱;如果你发布了不安全的代码,可能会遭到黑客攻击;如果你提供了不可靠的软件,用户就会离开你的产品。在现实生活中,没有人会接受“这是人工智能写的”这种借口。
在人工智能时代,学习编程的重要性并没有降低,反而更高了——只不过其侧重点有所不同。我们的目标不是成为打字速度很快的人,而是成为思维缜密、具备解决问题能力的人。
基础技能比以往任何时候都更加重要:
-
了解数据在系统中的流动方式。
-
学会如何将复杂的问题分解成较小的部分来处理。
-
掌握如何制定清晰、明确的规则和要求。
-
学会如何进行测试和验证。
-
注意识别那些可能引发问题的边界情况。
-
学会思考安全问题。
-
真正理解自己所使用的工具,而不仅仅是机械地复制别人的答案。
<平均而言,各种软件将会无处不在。它们会价格低廉,会被人们复制,而且制作起来也会非常容易。因此,唯一真正有价值的软件,应该是那些安全、可靠、质量高,并且是经过深思熟虑后开发出来的软件。
<这对那些认真学习的人来说无疑是个好消息,因为最优秀的工程师将会变得更加宝贵,而不是相反。
一个简单的练习(只要尝试一次,你就能掌握这项技能)
<<假设你在购物车中添加了某项商品,此时可以再加入一条规则,例如:
-
购买数量不能超过10件
-
先编写测试用例,然后让人工智能来修改相关功能,最后再运行测试。
-
这才是培养真正的人工智能技能的正确方法:不是直接给予提示,而是通过引导和验证来帮助学习者掌握知识。
-
让人工智能负责编写代码,而你则负责思考问题、分析逻辑并验证结果。
-
这样的学习方式才是有效的。
-
不要让人工智能来开发整个应用程序
-
应该把问题分解成一个个小部分来处理
-
需要制定明确的规则、提供示例以及考虑各种边缘情况,这样人工智能就不会盲目猜测了
-
一定要编写测试用例并运行它们
总结
把那些失败的测试结果当作检查问题的工具
不断重复这个过程,直到你对自己发布的代码有足够的信心为止
这就是当前的游戏规则。如果你能很好地遵守这些规则,那你就不是落后者,反而会处于领先地位。
结语
如果你觉得这里提供的信息很有用,请随时分享给那些可能会从中受益的人吧。
我非常期待你的反馈——你可以在X平台上@sumit_analyzen或Facebook上@sumit_analyzen与我联系;也可以观看我在YouTube上的编程教程,或者直接在LinkedIn上与我建立联系。
你还可以访问我的官方网站sumitsaha.me,了解更多关于我的信息。