1.为什么要检验 AI 写的代码
AI 编程助手几秒钟就能写出像样的代码,语法整洁,解释也显得很有把握。但 AI 生成的是“常见样子的代码”,并不是在你的数据、你的情况下实际运行之后给出的答案。所以,它可能给出在普通输入下正确、遇到空值或边界值就出错的代码,也可能给出能运行但有风险的代码。
运行代码并使用结果的人是你自己。销售合计算错导致报告出错,或者含有密码的代码被公开,都不能用“是 AI 这么写的”来搪塞。核对结果、承担责任的,是使用这段代码的人和组织。到第9讲为止学过的值、条件、循环、函数、数组和字符串知识,正是做这种检验的工具。只要自己能读懂短小的代码,就能更安全、更好地使用 AI。
检验大致分四个方面:读懂代码在做什么;用自己设计的测试确认结果;检查安全风险;确认许可证、个人信息这类代码之外的问题。这一讲就按这个顺序逐一练习。
- 阅读:这个函数接收什么、返回什么
- 测试:在普通输入、空值、0、边界值下,是否得到预期的值
- 安全:有没有机密信息、未经验证的输入、
eval、陌生的软件包 - 代码之外:是否遵守了许可证条件、公司或学校的规定以及个人信息保护
2.逐行阅读——输入、处理、输出
阅读 AI 给的代码时,不要从头到尾一扫而过,而是套用第1讲学过的“输入 → 处理 → 输出”框架。先看函数名和参数,弄清它接收什么;再找到 return,弄清它返回什么。然后逐行查看中间的代码,用语言说出“这一行执行完后,变量里装的是什么”。说不下去的那一行,正是需要确认的地方。
边读边问“如果……会怎样”:数组是空的呢?值以字符串形式传进来呢?是0或负数呢?下面的平均值函数在普通分数下是对的,但遇到空数组就是 0 / 0,得到 NaN;如果传进来的是从 CSV 里取出的字符串分数,+ 就变成拼接,算出离谱的值。它连错误信息都没有就给出错误的值,所以更危险。
遇到看不懂的语法,也可以问 AI“请给初学者解释一下这一行在做什么”。不过听完解释后,要按第8讲学的方法用 console.log 打印中间值,亲自确认解释和实际情况是否一致。解释也是 AI 的回答,同样需要确认。
// AI 编程助手写的函数(原样)
function averageScore(scores) {
let sum = 0;
for (const s of scores) {
sum += s;
}
return sum / scores.length;
}
console.log(averageScore([80, 90, 100]));
console.log(averageScore([]));
console.log(averageScore(["80", "90"]));90 NaN 4045
3.亲手设计测试——边界值能找出错误
测试就是事先写下“输入这个,就应该得到这个值”,再用代码来确认。预期值要在看代码之前由你亲手定下来。如果连测试也交给 AI,它可能把代码里的同一个误解也写进测试,所以至少要亲手写几个。像下面的 check 函数这样,把得到的值和预期值比较后打印“通过/失败”,有这样一个小工具就足够了。
好的测试集合除了普通情况,还要包含边界情况:空数组、空字符串、0、负数、只有一个值的情况、条件刚好发生变化的那个值(第3讲的边界值)、位数不同的数、多个值相同的情况等等。错误往往就藏在这些边缘处。
下面是“AI 写的取最高分函数”。只用两位数的分数测试时会通过,看起来是正确的代码。可是一混进100分就出错了。JavaScript 的 sort() 如果不给比较函数,会把值转成字符串按字典顺序排序,所以 "100" 会排在 "80" 前面。而且 sort 会改变原数组的顺序,连调用这个函数的一方的数据也被悄悄改掉了。
function check(name, got, want) {
const same =
JSON.stringify(got) === JSON.stringify(want);
console.log(same ? "通过" : "失败", name, got);
}
// AI 写的函数:取最高的 n 个分数
function topScores(scores, n) {
return scores.sort().reverse().slice(0, n);
}
check("两位数", topScores([70, 90, 85], 2), [90, 85]);
check("100分", topScores([100, 95, 80], 2), [100, 95]);
check("n为0", topScores([70, 90], 0), []);
const mine = [70, 90, 85];
topScores(mine, 1);
console.log("原数组", mine);通过 两位数 [ 90, 85 ] 失败 100分 [ 95, 80 ] 通过 n为0 [] 原数组 [ 90, 85, 70 ]
(a, b) => b - a 对副本排序,从大到小排列。function topScores(scores, n) {
const copy = [...scores];
copy.sort((a, b) => b - a);
return copy.slice(0, n);
}
const mine = [100, 95, 80];
console.log(topScores(mine, 2));
console.log(topScores([], 3));
console.log("原数组", mine);[ 100, 95 ] [] 原数组 [ 100, 95, 80 ]
// AI 写的函数(原样)
function shippingFee(price) {
return price > 50000 ? 0 : 3000;
}
for (const p of [49999, 50000, 50001]) {
console.log(p, shippingFee(p));
}49999 3000 50000 3000 50001 0
- 第1步 从规则中找出边界。条件发生变化的值是50,000韩元。
- 第2步 选出边界的前一个、边界本身和后一个:49,999韩元 → 3,000,50,000韩元 → 0,50,001韩元 → 0。
- 第3步 再加上极端值和奇怪的值:先定好输入0韩元、负数、字符串 "50000" 时应该怎样处理。
- 第4步 运行后发现,50,000韩元时得到的是3,000。读代码才发现写的是
>而不是>=。
> 改成 >=。4.安全——机密信息、输入验证、`eval`
首先要遵守的原则是:不要把密码、API 密钥、访问令牌这类机密信息写进代码或提示词。写在代码里的密钥,很容易因为共享文件、公开代码仓库或截图而泄露出去。向 AI 助手询问错误时把含有密钥的代码整段粘贴进去,也是同样的风险。密钥的位置要换成 "YOUR_API_KEY" 这样的假值再给别人看,真正的密钥则按各服务指引的方式(环境变量、配置文件、密钥管理功能等)放在代码之外。如果密钥已经泄露,光删除是不够的,要在该服务中作废密钥并重新申请。
第二是输入验证。来自用户输入、文件或其他程序的值,都要当作随时可能和预想的不一样,在使用前确认它的样子和范围。AI 写的代码常常只假设“正常的输入”。像下面这样先定好允许的样子,其余的值一律返回 null 拒绝,是比较安全的做法。“只接受好的值”比“挑出坏的值”更不容易遗漏。
第三,不能把不可信的输入交给 eval 或 new Function。eval 会把字符串当作代码执行,输入里藏着的命令可能就这样被执行了。如果你请 AI 写计算器或读取设置,它却给了用 eval 的代码,就请它换一种方法。如果目的是交换数据,用第6讲的 JSON,通过 JSON.parse 读取即可。JSON.parse 只读取数据,不执行代码,格式不对时会报错。
^\d+$ 的意思是“从头到尾只有数字”。允许范围之外的一律是 null。// 只接受0~150之间的整数年龄
function parseAge(input) {
const s = String(input).trim();
if (!/^\d+$/.test(s)) return null;
const n = Number(s);
if (n > 150) return null;
return n;
}
const tries = ["35", " 7 ", "-3", "2.5", "abc", "", "999"];
for (const x of tries) {
console.log(JSON.stringify(x), "→", parseAge(x));
}"35" → 35 " 7 " → 7 "-3" → null "2.5" → null "abc" → null "" → null "999" → null
JSON.parse 不会执行看起来像代码的字符串,而是用 SyntaxError 拒绝。try { … } catch (e) { … } 这种语法让程序出错时不停下,而是在 catch 部分接住错误并处理。const input = '{"name": "小明", "score": 90}';
const data = JSON.parse(input);
console.log(data.name, data.score);
try {
JSON.parse("console.log('偷偷执行')");
} catch (e) {
console.log("拒绝:", e.name);
}小明 90 拒绝: SyntaxError
5.不存在的软件包——安装前先确认
实际工作中的代码,经常要安装别人制作并公开的软件包(库)来使用。可是 AI 助手有时会编造听起来像样、实际上并不存在的包名,或者推荐早已停止维护的包。更大的问题是,有人可能抢先用这些常被编造的名字,或者和知名软件包只差一个字母的名字,上传恶意软件包。不加确认就照着执行安装命令,别人的代码就会在你的电脑上运行。
所以在执行安装命令之前,要确认几件事:这个软件包是否真的存在于官方软件包仓库,名字的拼写是否和官方文档完全一致,最近是否仍有人维护,是否有公开的源代码仓库和文档,是否被广泛使用。公司或学校可能另有允许使用的软件包清单或安全审查流程,要先遵守那些规定。
如果是小事,也可以问问能否不用软件包就解决。第9讲的文本整理和合计计算,只用 JavaScript 的基本功能就足够了。依赖的软件包越少,要确认的东西越少,可能出问题的地方也越少。不过,像读取真正的 CSV 文件、加密这类规则复杂的工作,与其自己写,不如选择经过验证的软件包更安全。
- 官方软件包仓库里是否真的有这个名字
- 拼写是否和官方文档完全相同(警惕只差一个字母的假名字)
- 最近是否有更新、是否仍在维护
- 源代码仓库、文档和许可证是否公开
- 是否遵守了公司或学校的允许清单和安全流程
6.著作权、许可证与个人信息
公开的代码,并不等于可以随意使用。开源代码附带许可证,许可证是“遵守这些条件就可以使用”的约定。条件因许可证而异。常见的条件是保留著作权声明和许可证文本;有的许可证还要求,在发布包含该代码的程序时,以相同条件公开源代码。完全没有标明许可证的代码,即使公开了,也不一定意味着允许使用。
AI 生成的代码也有可能和现有的公开代码非常相似。在把长段代码原样放进产品之前,要看看能否确认出处,公司或学校有没有关于 AI 生成代码和使用开源软件的规定。下表只是常见类型的一般特点,并非法律建议。实际条件要读许可证原文,难以判断时,向所在机构的负责部门或专业人士确认。
个人信息也是代码之外的重要问题。请 AI 写代码时,如果把真实的客户名单、学生成绩、电话号码粘贴进去,这些信息就脱离了你的控制。写代码需要的是数据的“样子”,而不是真实的值。像下面这样做出假数据给 AI 看,真实数据只在自己的电脑上运行。示例地址使用 example.com 这类专为文档保留的域名,就不会联系到真实的人。分享结果时,只展示必要的部分,比如把电话号码中间几位遮住。
function fakeCustomers(n) {
const list = [];
for (let i = 1; i <= n; i++) {
const no = String(i).padStart(4, "0");
list.push({
id: i,
name: "客户" + i,
phone: "010-0000-" + no,
email: "user" + i + "@example.com",
});
}
return list;
}
console.log(fakeCustomers(2));[ { id: 1, name: '客户1', phone: '010-0000-0001', email: '[email protected]' }, { id: 2, name: '客户2', phone: '010-0000-0002', email: '[email protected]' } ]| 类型 | 例子 | 常见条件 |
|---|---|---|
| 宽松型 | MIT, BSD | 广泛允许复制、修改和商业使用。需保留著作权声明和许可证文本 |
| 宽松型(含专利条款) | Apache-2.0 | 和 MIT 一样广泛允许。需保留许可证副本和声明,在修改过的文件中注明改动,并含有与专利相关的条款 |
| 著佐权 | GPL 系列 | 发布包含该代码的程序时,须以相同许可证提供源代码等,条件更严格 |
| 未标明 | 没有许可证文件的公开代码 | 即使公开,也不一定意味着允许使用。使用前要确认 |
7.把检验步骤变成习惯
拿到 AI 给的代码后,如果每次都按同样的顺序检查,就能减少遗漏。首先阅读代码,看你拜托的事和代码实际做的事是否一致;不懂的行,听完解释后用 console.log 确认。接着设计由自己定下预期值的测试,普通情况两三个、边界情况三四个,然后运行。
测试失败时,把失败的输入、得到的值和预期值整理好再去问 AI 就行。正如第8讲学过的,比起说“错了”,说“输入 [100, 95, 80],预期 [100, 95],实际 [95, 80]”能更快修好。拿到修改后的代码,要把最初的测试全部重新运行一遍,看看是否修好一处又弄坏了另一处。把修改前后的代码放进文本比较工具,就能准确看出改了什么。
最后做安全和代码之外的检查:是否还残留机密信息,是否验证了输入,有没有 eval,陌生的软件包确认过没有,是否遵守了许可证和个人信息方面的规定。经过这些步骤的代码,才是“自己能负责的代码”。编程基础课程到此结束。现在你已经能阅读、测试和修改短小的代码了,不妨让 AI 当助手,把重复的工作一件一件改写成代码。
- 确认目的:拜托的事和代码做的事是否一致
- 逐行阅读:输入、处理、输出,卡住的行用
console.log确认 - 测试:预期值由自己定,一定要加入边界值
- 重新运行:修改之后,从头运行全部测试
- 安全与代码之外:机密信息、输入验证、
eval、软件包、许可证、个人信息
📌 要点总结
- AI 的代码可能在常见输入下正确、在边界处出错——使用的人要负责检验
- 先由自己定下预期值,把空值、0、边界值和位数不同的值放进测试
sort()没有比较函数时会按字符串顺序排序,并且会改变原数组- 机密信息不写进代码和提示词,输入要验证,不可信的输入不用
eval - 确认软件包真实存在且有人维护,确认许可证条件和相关规定,用假数据代替个人信息
🧪 代码练习
用测试找出看似 AI 写的代码里隐藏的错误并修正,再写出输入验证和遮盖个人信息的函数。请先看测试结果中失败的输入。
代码只在本浏览器内的隔离环境中运行,不会发送到服务器。无法访问网络,超过2秒会自动停止。改过的代码只保存在此浏览器中。按 Ctrl+Enter(⌘+Enter)运行,Tab 键输入两个空格(先按 Esc 再按 Tab 可跳到下一项)。
浏览器脚本未开启,无法运行代码,但题目、初始代码、自动检查项和参考答案都可以直接阅读。
1修正平均值函数的边界
AI 写的 average(nums) 遇到空数组时返回 NaN。请修改为:空数组时返回0,其他情况返回平均值。数组里只会有数字。
average([80,90,100])期望值90average([])期望值0average([0,0])期望值0average([-10,10,30])期望值10average([7])期望值7
💡 提示
在函数开头,如果 nums.length === 0,就立刻 return 0;。
查看参考答案
function average(nums) {
if (nums.length === 0) return 0;
let sum = 0;
for (const x of nums) {
sum += x;
}
return sum / nums.length;
}2最高分函数的排序错误
AI 写的 topScores(scores, n) 应该按从大到小的顺序返回最高的 n 个分数,但一混进100分就出错。请改成按数字大小排序,并且不要改变原数组。n 大于数组长度时,有多少就返回多少。
topScores([70,90,85], 2)期望值[90,85]topScores([100,95,80], 2)期望值[100,95]topScores([5,100,20,9], 3)期望值[100,20,9]topScores([60,70], 0)期望值[]topScores([], 3)期望值[]topScores([88], 5)期望值[88]
💡 提示
用 [...scores] 复制后,用 sort((a, b) => b - a) 从大到小排序,再 slice(0, n)。
查看参考答案
function topScores(scores, n) {
const copy = [...scores];
copy.sort((a, b) => b - a);
return copy.slice(0, n);
}3验证订购数量的输入
请完成函数 parseQuantity(input)。去掉字符串 input 的首尾空格后,如果是只由数字组成的整数,并且在1到99之间,就返回这个数字,否则返回 null。"2.5"、"-1"、"1e3" 和空字符串都返回 null。
parseQuantity("3")期望值3parseQuantity(" 12 ")期望值12parseQuantity("0")期望值nullparseQuantity("100")期望值nullparseQuantity("2.5")期望值nullparseQuantity("1e3")期望值null
💡 提示
用 /^\d+$/.test(s) 检查是否只有数字,如果 n < 1 || n > 99 就返回 null。还要注意 Number("") 是0。
查看参考答案
function parseQuantity(input) {
const s = input.trim();
if (!/^\d+$/.test(s)) return null;
const n = Number(s);
if (n < 1 || n > 99) return null;
return n;
}4遮住电话号码中间几位
请写一个在分享结果时遮盖个人信息的函数 maskPhone(s)。如果 s 是 010-1234-5678 这样的3-4-4位数字格式,就把中间四位换成 ****,返回 010-****-5678;如果不是这个格式,就返回 null。
maskPhone("010-1234-5678")期望值"010-****-5678"maskPhone("010-0000-0001")期望值"010-****-0001"maskPhone("01012345678")期望值nullmaskPhone("")期望值nullmaskPhone("010-12-5678")期望值null
💡 提示
正则表达式 /^\d{3}-\d{4}-\d{4}$/ 只放行“3位-4位-4位数字”。不通过就返回 null。
查看参考答案
function maskPhone(s) {
if (!/^\d{3}-\d{4}-\d{4}$/.test(s)) return null;
const parts = s.split("-");
return parts[0] + "-****-" + parts[2];
}🤖 可以这样问 AI
复制后把 [ ] 部分换成你的情况。不要直接相信回答,请与正文对照核实。
想读懂 AI 给的代码时
请像给初学者讲解一样,逐行解释下面的 JavaScript 代码。用示例输入[示例输入]跟踪每一行执行完后主要变量里装的是什么,并指出传入空值、0或字符串时会发生什么。 [代码]
想不遗漏地找出要测试的情况时
下面这个函数的目的是[目的]。请分成普通情况、边界值、空值、错误输入几类,列出需要测试的输入清单。预期值我会自己定,所以只写输入和“为什么这个情况很重要”。 [函数代码]
想请人从安全和许可证角度检查代码时
请从安全角度检查下面的代码。把代码中残留的机密信息、未经验证的输入、`eval` 之类的危险功能,以及如何确认所用软件包真实存在且仍在维护,整理成清单。如果有从开源代码中拿来的部分,也请一般性地告诉我需要确认的许可证条件(不是法律建议也没关系)。代码里的密钥和个人信息我已经换成假值了。 [代码]
🧰 相关工具
可以当场检查、整理本讲涉及的 JSON、正则表达式和文本的工具。请不要粘贴真实的个人信息或密码。
- MDN Web Docs — Array.prototype.sort、JSON.parse、eval 参考文档(包括避免使用 eval 的说明)
- OWASP — 关于输入验证和机密信息管理的一般指南
- Open Source Initiative — 开源许可证(MIT、Apache-2.0、GPL 等)原文与一般说明
达成全部学习目标后,请点击完成。
此浏览器无法保存记录,仅在当前页面显示。