开发出色的软件并不取决于完美的提示语,而在于遵循一套严谨的开发流程。在这篇文章中,我会分享自己用于编写安全代码的工作流程:明确目标、分析各种边缘情况,并通过可运行的测试逐步完成代码开发。

以Node.js购物车为例,我将说明为什么服务器端验证和测试驱动的开发方式总是比“一次性”生成的AI代码更可靠。让我们一起来探讨如何让AI成为你最可靠的合作伙伴吧。

一些背景知识

上周有那么一会儿,我觉得自己做了一件非常了不起的事情……不过这种兴奋感只持续了大约五秒钟。我使用了一个AI工具,输入了一句话,结果它就为我的电子商务应用生成了一个完整的购物车模块。其中包含了大量的文件、代码,甚至还有文件夹和模板结构,看起来确实非常专业。

但随后我意识到一个问题:问题并不在于“AI编写代码的速度有多快”,而在于“我如何才能确定这段代码是正确的?”

事实就是:那些你并没有亲自编写的庞大代码堆并不会成为捷径。对大多数开发者来说,这些代码反而会带来额外的工作负担——你必须阅读它们、理解它们的含义,同时还要努力找出其中隐藏的错误。

因此,今天我不会再讲那些关于“AI即将改变一切”的老套话题。相反,我会向大家展示一个简单的流程,任何级别的开发者都可以按照这个流程来逐步利用AI获得更好的开发效果,而不会陷入困境。我还会用一个可以在单个文件中运行的实际例子来演示这个流程。

我们将会讨论的内容:

五秒钟的高效工作方式(以及真正的问题所在)

很多人对人工智能编程存在误解。他们认为编程的主要工作就是打代码,但实际上,关键在于清晰地思考问题。如今,打代码已经变得非常容易,而真正的挑战在于思考。

当人工智能一次性生成了一个“外观完美”的模块时,实际的工作并没有结束。这些工作会继续向下进行:

  • 你仍然需要理解它生成了什么内容。

  • 你还需要验证它是否符合你的要求。

  • 你还需要找出那些隐藏在“看起来很完美的代码”中的错误。

如果你无法验证这些结果,那么你就不能真正掌握这个模块。而如果不能掌握它,你就无法安全地将其投入使用。

提示:对待人工智能生成的成果,要像对待互联网上陌生人的代码一样:虽然有用,但在未经验证之前不可完全信任。

黄金法则:永远不要相信用户提供的价格

我当初也是像初学者一样开始的。我打开人工智能工具,然后输入了一个模糊的要求:

为我设计并开发一个电子商务购物车模块。

人工智能生成了大量的代码,看起来非常整齐。如果你是新手,你可能会想:“哇,它果然解决了这个问题。”

但随后我问了自己一个问题:在现实生活中,这种方案最容易出什么问题?答案也很简单:“有人可能会篡改价格。”因为购物车系统有一个重要的黄金法则:永远不要相信用户提供的价格。

如果浏览器告诉你“T恤的价格是1美元”,而你接受了这个价格,那么别人就可以用1美元买到原本价值20美元的商品。而当人工智能快速生成大量代码时,这类错误很容易隐藏在“看起来很完美的代码”中。

警告:任何接受用户提供价格的系统,实际上都在为价格被篡改创造机会。

思维方式的转变:不要要求人工智能开发整个应用程序

因此,我没有直接接受人工智能生成的全部代码,而是改变了我的方法。我这样要求它:

我不会让人工智能来开发整个应用程序。我会把这个大任务分解成小部分,然后像真正的工程师一样指导它来完成这些任务。

这就是第一次思维方式的转变。在人工智能时代,你的价值不在于你打代码的速度有多快,而在于你是否能够很好地完成以下三件事:

  • 清晰地定义问题所在。

  • 把问题分解成小部分。

  • 验证最终结果是否正确。

大型系统是由许多正确的、小规模的组件构成的。这才是真正的工程实践,而不是所谓的“提示工程”。

人工智能编程流程(七步工作法)

以下是我使用的流程。这些步骤用简单的英语表述出来,你可以将其复制并应用于任何项目中:

  • 用一句话明确你的目标。

  • 写出规则——即哪些条件必须是成立的。

  • 给出两个示例,展示输入与输出之间的关系。

  • 考虑两种极端情况,分析可能出现的问题。

  • 要求人工智能只完成其中的一小部分工作,而不是整个项目。

  • 编写测试用例并运行它们。

  • 如果出现问题,就修改提示内容,然后重新开始。

就是这样。这就是那个循环流程。下面是它的可视化展示:

AI编码循环工作流程

提示:循环流程才是真正的核心技能。工具可能会发生变化,但这个循环流程本身是不会改变的。

应用这个循环流程:一个服务器端的购物车总价计算器

现在让我们把这个循环流程应用到购物车的例子中。我没有要求开发一个完整的购物车模块,而是只写了一条简短的需求说明:

我们需要在服务器端实现一个购物车总价计算器。用户会提供产品编号数量,但我们必须忽略用户提供的价格信息,同时要使用我们自己的产品目录。我们需要处理未知产品以及无效的数量值,还要计算小计折扣税费以及最终的总价,并且要确保金额的计算结果正确无误。最后,我们还必须为这个功能编写测试用例。

这条需求说明并不复杂,只是简洁明了地列出了所需的功能。

然后我只要求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,了解更多关于我的信息。

Comments are closed.