AI辅助编程是近年来的热门话题,各种AI代码生成工具层出不穷。我们团队也在2020年开始尝试用AI辅助编程,确实提高了开发效率。但是有一次,AI生成的代码导致了一个严重的线上故障,让我至今记忆犹新。本文是这次故障的完整复盘,包括故障的发生过程、排查过程、根本原因、以及我们总结的经验教训。如果你也在用AI辅助编程,希望这篇文章能给你一些警示和启发。
一、背景:我们是怎么用AI辅助编程的
先说说我们团队使用AI辅助编程的背景吧。
2020年的时候,AI代码生成工具开始流行起来,虽然那时候的工具还不如现在这么强大,但是已经能帮我们生成一些简单的代码了,比如CRUD操作、数据转换、单元测试等。
我们团队是做后端开发的,日常工作中有很多重复性的代码。比如新增一个接口,要写Controller、Service、DAO、实体类、DTO类,还有各种配置文件。这些代码逻辑都差不多,但是写起来很繁琐,占用了很多时间。
于是我们开始尝试用AI辅助编程工具来生成这些重复性代码。刚开始的时候效果还不错,简单的代码生成得又快又好,开发效率提升了不少。大家都觉得AI辅助编程是个好东西,越来越依赖它。
我们甚至制定了一个规范:所有重复性的代码都优先用AI生成,然后人工检查一下就提交。这样确实节省了很多时间,团队的交付速度也提高了。
但是问题就出在这个"人工检查一下"上。因为AI生成的代码大部分时候都是对的,大家慢慢就放松了警惕,检查的时候越来越随意,有时候甚至看都不看就直接提交了。
终于有一天,出事了。
二、故障发生:一个普通的周五下午
那是一个普通的周五下午,大家都在忙着赶周末前的需求。我们要上线一个新功能,是关于用户积分的。用户完成某些任务之后可以获得积分,积分可以用来兑换礼品。
这个功能中有一个计算用户积分等级的逻辑,根据用户的总积分来判断用户是普通会员、银卡会员、金卡会员还是钻石会员。这个逻辑不复杂,就是几个if-else判断。
负责这个功能的同事小王,用AI辅助编程工具生成了这段计算逻辑的代码。AI生成的代码看起来没问题,小王大概看了一眼,觉得逻辑是对的,就提交了代码。
代码经过了代码审查,但是审查的同事也没仔细看,觉得这么简单的逻辑不会有问题,就通过了。然后代码顺利通过了测试,因为测试用例比较简单,只覆盖了正常的情况。
周五晚上,我们上线了这个功能。上线之后一切正常,大家都松了一口气,准备过周末。
但是周六早上,我接到了运维同事的电话,说线上出问题了。用户积分等级计算出错了,很多用户的等级显示不对,有的普通用户变成了钻石会员,有的钻石会员变成了普通用户。用户已经开始在客服渠道投诉了。
我一下子就清醒了,赶紧打开电脑排查问题。
三、排查过程:惊心动魄的几个小时
排查过程现在想起来还是惊心动魄的。
第一步:确认问题范围
我首先登录了线上环境,查看了几个用户的积分数据。发现确实有问题,很多用户的等级计算错了。但是也有一些用户是对的,看起来不是所有用户都受影响。
我统计了一下,大概有30%的用户等级计算错误。这个比例很奇怪,如果是逻辑完全错了,应该是100%的用户都错了。为什么只有30%呢?
第二步:查看代码
我赶紧查看了刚上线的积分等级计算代码。代码是这样的(简化版):
public String calculateLevel(int totalPoints) {
if (totalPoints >= 10000) {
return "DIAMOND";
} else if (totalPoints >= 5000) {
return "GOLD";
} else if (totalPoints >= 2000) {
return "SILVER";
} else {
return "NORMAL";
}
}我看了好几遍,觉得这段代码逻辑是对的啊。积分大于等于10000是钻石,大于等于5000是金卡,大于等于2000是银卡,否则是普通。这有什么问题吗?
但是线上数据确实有问题,我又仔细看了一遍代码,还是没发现问题。
第三步:查看数据库
我怀疑是不是数据库里的数据有问题。我查了几个等级错误的用户,发现他们的积分数据都是正常的,没有异常值。
比如有一个用户,总积分是8000,按照逻辑应该是金卡会员,但是系统显示的是钻石会员。还有一个用户,总积分是15000,应该是钻石会员,但是显示的是普通会员。
这就更奇怪了。8000分变成钻石,15000分变成普通,这完全不符合逻辑啊。
第四步:查看日志
我开始查看线上的日志,看看计算过程中到底发生了什么。我加了一些调试日志,重新部署了一个测试版本,然后用出错的用户数据来测试。
测试结果让我大吃一惊。当输入8000的时候,函数返回的是"DIAMOND"。当输入15000的时候,函数返回的是"NORMAL"。这完全不对啊。
我又仔细看了一遍代码,还是没发现问题。这时候我开始怀疑是不是AI生成的代码有什么隐藏的问题。
第五步:发现问题
我把代码复制到本地,一行一行地看。突然,我发现了一个细节。
AI生成的代码中,比较运算符用的不是普通的大于等于号,而是看起来像大于等于号的特殊字符!因为字体的原因,这些特殊字符看起来和普通的大于等于号一模一样,但是实际上它们是不同的Unicode字符。
这些特殊字符在Java中不是有效的比较运算符,但是因为某种原因(可能是编码问题或者IDE的自动转换),代码居然能编译通过,但是运行的时候行为完全不对。
具体来说,>=被替换成了一个看起来很像>=的特殊字符,这个字符在运行的时候被解析成了其他的含义,导致比较逻辑完全错误。这就是为什么有的用户对有的用户错,因为特殊字符的比较行为是不确定的。
我当时就出了一身冷汗。这么隐蔽的问题,如果不是一行一行仔细看,根本发现不了。AI生成代码的时候,不知道为什么引入了这些特殊字符,而我们所有人都没有看出来。
第六步:修复问题
找到问题之后,修复就很简单了。我把所有的特殊字符都替换成了普通的比较运算符,然后重新测试,确认逻辑正确了。
然后我紧急上线了修复版本,线上的问题很快就解决了。但是已经有很多用户看到了错误的等级,我们不得不发公告说明情况,并且给受影响的用户做了补偿。
这次故障从发现到修复,花了将近4个小时。整个过程惊心动魄,我至今还记得那种紧张的感觉。
四、根本原因分析
故障解决之后,我们做了一次深入的根本原因分析。
直接原因:AI生成的代码中包含了特殊的Unicode字符,这些字符看起来和普通字符一样,但是实际上行为不同,导致逻辑错误。
为什么AI会生成特殊字符:我们后来分析,可能是AI在训练数据中看到了一些包含特殊字符的代码,生成的时候就把这些特殊字符也带出来了。也可能是AI生成代码的时候的某种编码问题。具体原因我们也不是100%确定,但是AI生成的代码确实可能包含这种隐蔽的问题。
为什么代码审查没有发现:因为这些特殊字符在视觉上和普通字符一模一样,用肉眼根本看不出来。而且大家对AI生成的代码放松了警惕,觉得简单的逻辑不会有问题,审查的时候只是大概扫了一眼,没有仔细看。
为什么测试没有发现:因为测试用例不够全面,只覆盖了几个典型的值,没有覆盖边界值和各种异常情况。而且测试的时候用的是测试环境,测试环境的编码可能和生产环境不一样,特殊字符在测试环境中可能表现正常。
为什么没有静态检查:我们的项目中没有配置代码静态检查工具,如果有静态检查的话,这种特殊字符的问题应该能被检测出来。
五、我们采取的改进措施
这次故障给我们敲响了警钟。我们采取了一系列改进措施,防止类似的问题再次发生。
1. 严格的代码审查制度
我们重新强调了代码审查的重要性。不管是AI生成的代码还是人工写的代码,都必须经过严格的审查。审查的时候不能只看逻辑,还要注意细节,比如编码、特殊字符、边界条件等。
我们还规定,AI生成的代码必须标注出来,审查的时候要特别仔细。对于核心业务逻辑的代码,不允许完全用AI生成,必须人工编写。
2. 增加静态代码检查
我们在项目中引入了静态代码检查工具,比如SonarQube和Checkstyle。这些工具可以检测出代码中的各种问题,包括特殊字符、编码问题、潜在的bug等。代码提交之前必须通过静态检查,否则不能合并。
我们还写了一个自定义的检查规则,专门检测代码中是否包含不可见的特殊字符。这个规则后来真的帮我们发现了几次AI生成代码中的特殊字符问题。
3. 完善测试用例
我们加强了测试用例的编写要求,特别是边界值测试和异常情况测试。对于计算逻辑类的代码,必须覆盖所有的边界值,比如等于、大于、小于、零、负数、最大值等。
我们还引入了自动化测试,每次代码提交都会自动运行所有的测试用例,确保没有回归问题。
4. AI代码使用规范
我们制定了详细的AI辅助编程使用规范:
- AI生成的代码必须经过人工仔细检查,不能直接使用
- 核心业务逻辑、安全相关的代码不允许用AI生成
- AI生成的代码必须通过静态检查和单元测试
- 使用AI生成代码的时候,要明确指定编码格式,避免特殊字符问题
- 定期回顾AI生成代码的质量,总结常见的问题
5. 编码规范检查
我们在CI/CD流水线中加入了编码检查步骤,确保所有代码文件都是UTF-8编码,不包含特殊字符和BOM头。如果发现编码问题,流水线会直接失败,阻止代码合并。
六、AI辅助编程的正确姿势
经过这次故障,我们对AI辅助编程有了更深刻的认识。AI是一个很好的工具,但是不能盲目依赖。下面是我们总结的AI辅助编程的正确姿势。
1. AI是助手,不是替代品
AI可以帮我们提高效率,但是不能完全替代人工。AI生成的代码可能有各种问题,包括逻辑错误、安全漏洞、编码问题、性能问题等。必须经过人工的仔细检查和测试才能使用。
2. 简单的代码可以用AI,复杂的逻辑要自己写
对于简单的、重复性的代码,比如CRUD、数据转换、简单的工具函数,可以用AI生成,这样能节省时间。但是对于复杂的业务逻辑、核心算法、安全相关的代码,一定要自己写,不能依赖AI。因为这些代码如果出问题,后果会很严重。
3. 检查的时候要像检查敌人的代码一样
检查AI生成的代码的时候,不要有"AI应该不会错"的预设。要像检查一个你不信任的人的代码一样,仔细看每一行,每一个字符。特别要注意那些肉眼不容易发现的问题,比如特殊字符、编码问题、隐藏的逻辑漏洞等。
4. 测试不能少
不管是AI生成的代码还是人工写的代码,都必须有充分的测试。测试用例要覆盖正常情况、边界情况、异常情况。不要因为代码看起来简单就跳过测试。
5. 保持学习,不要因为有了AI就不思考
AI可以帮我们写代码,但是不能帮我们思考。如果我们什么都依赖AI,自己不思考,慢慢就会失去独立解决问题的能力。我们要把AI当成一个学习的工具,通过AI生成的代码来学习新的知识和技巧,而不是完全不动脑子。
七、写在最后
这次故障虽然已经过去很久了,但是我至今记忆犹新。它给我们整个团队都上了深刻的一课。
AI辅助编程是一个很好的工具,它确实能提高开发效率,让我们从重复性的工作中解放出来,专注于更有创造性的工作。但是AI不是万能的,它生成的代码可能有各种问题,我们必须保持警惕,不能盲目依赖。
技术的发展总是伴随着新的风险。AI辅助编程也是如此。我们在享受技术带来的便利的同时,也要认识到它的局限性,采取必要的措施来防范风险。
希望我们的这次故障复盘能给正在使用或者准备使用AI辅助编程的朋友一些警示。AI是一把双刃剑,用好了能大大提高效率,用不好可能会造成严重的后果。关键在于我们如何使用它。
最后用一句话结束本文:"工具永远是工具,人才是最终的决策者。"愿每一个开发者都能善用AI,让它成为我们的好帮手,而不是隐患的来源。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录