员工写了个比删库更可怕的Bug! (员工写了个比喻句)
想必大家都听说过删库跑路吧,我之前一直把它当一个段子来看。可万万没想到,就在昨天,我们公司的某位员工,竟然写了一个比删库更可怕的Bug!给大家分享一下(不是公开处刑),希望朋友们引以为戒。
一、Bug起因
事情是这样的,昨天中午11点左右,突然用户群里的小伙伴反馈:自己直接成为了鱼聪明网站的管理员!接下来,陆续有更多同学反馈:大家都成管理员了!
看到这里,我立刻就去查了下数据库,结果看到的是:
update user set userRole=admin
好家伙,早起脑供血不足的我立刻高血压上来了,怎么所有的用户都变成管理员了?!我赶紧问下我所有的员工,这特么是谁干的!!!
然后员工小A大叫:我X,是我今天执行单元测试更新数据的时候,少加了个where条件!
本来的预期:
update user set userRole=admin where id=1
实际上执行:
update user set userRole=admin
于是导致整个库里的所有用户都变成了管理员,大家可以愉快地薅鱼毛了。
二、紧急处理
后来据这位写Bug的同学的回忆,由于她之前没有遇到过类似的情况,第一时间脑袋是一片空白、头嗡嗡的,完全不知道接下来要怎么做。
不过我是很冷静的,因为之前在公司处理过类似的情况,毕竟曾经凌晨4-5点的时候都被叫起来过。。。
所以立刻就给他发了一段处理方式:
解释一下,就跟我们在路上看到一起交通事故一样,第一时间要么是保护现场,放一个小牌牌不让大家进到事故发生地;要么就是防止扩大影响,人工疏导不让更多人围观、阻塞交通。一般这两件事情是同时执行的,由于我知道怎么能够判定哪些用户本来是VIP(比如通过VIP信息)、而且程序又有详细的日志,所以第一时间是让员工先把user表的所有角色设置为普通用户权限,防止有人继续利用管理员权限去做一些不好的事情。
接下来就是立刻停止了线上的前后端服务,一方面是为了后面好恢复数据,另外也是防止一些同学发现自己突然从会员变成了普通用户,增加大量的人工咨询成本。
所以当时很多同学访问鱼聪明时,看到了这样的截图:
稳定现场后,接下来就是想办法恢复数据到正常的状态,好在我给数据库设置了分钟级别的备份,可以直接把数据恢复到事故发生前的最近正常的时间点。
有了备份后的老数据,还要考虑恢复这个时间点后新增的用户数据。有很多种恢复策略,我优先选择了逻辑最简单的策略:直接更新用户updateTime>2023-07-20 10:00:00的数据,根据id点对点覆盖除了userRole之外的数据列;如果没有对应的id,新增一条数据。
也就是使用类似saveOrUpdate的方法。理想很丰满,现实很残酷。万万没想到,由于updateTime是一个发生数据修改时自动更新的字段,导致所有的数据updateTime全是最新的,相当于要把数据库全量的数据都去比较一遍。
于是我的员工呢,写了类似下面这样的程序:
List<User> userList = userService.list();
List<UserBak> userBakList = userList.stream().map(user -> {
user.setUserRole(null);
UserBak userBak = newUserBak();
BeanUtils.copyProperties(user, userBak);
return userBak;
}).collect(Collectors.toList());
然后就开始执行了,结果执行了很久很久,数据都没更新完。看来单线程还是太慢了,于是我用并发编程的方式改进了同步的过程。先把所有用户分组,然后多线程同时执行saveOrUpdateBatch方法。示例代码如下:
void restoreUserTable() {
List<User> userList = userService.list();
List<UserBak> userBakList = userList.stream().map(user -> {
user.setUserRole(null);
UserBak userBak = newUserBak();
BeanUtils.copyProperties(user, userBak);
return userBak;
}).collect(Collectors.toList());
int batchSize = 1000; // 使用lambda表达式将userList每1000条数据分为一组
List<List<UserBak>> userBakGroupList = Lists.partition(userBakList, batchSize);
List<CompletableFuture<Void>> futures = new ArrayList<>();
for (List<UserBak> userBakGroup : userBakGroupList) {
futures.add(CompletableFuture.runAsync(() -> {
userBakService.saveOrUpdateBatch(userBakGroup);
}));
}
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
}
这次执行速度快了很多,很快就同步完了数据。
![员工写了个比删库更可怕的Bug!](https://tyye.cn/zb_users/theme/tpure/style/images/lazyload.png)
三、后续处理
数据恢复正常后,为了防止类似的事情再次发生,我们做了以下几件事:
- 修改了单元测试代码,添加了where条件。
- 对数据库的所有表添加了外键约束。
- 对数据库的所有写操作都增加了日志记录。
- 制定了数据恢复预案。
- 加强了员工的培训和教育。
希望通过这些措施,能够避免类似的事件再次发生。
四、总结
这是一次非常严重的Bug,给我们带来了很大的损失。但也让我们吸取了教训,学到了很多东西。希望大家引以为戒,加强自己的代码审查和测试,避免类似的事件发生。
最后,感谢我们的员工小A,虽然这次写了一个大Bug,但是她能及时发现并向我汇报,而不是隐瞒错误,这是非常值得表扬的。只有勇于承认错误,我们才能不断进步。
领导说,个人写的代码超过8个bug就要开除,是否太严格了?
这确实是太严格了,这样可能会导致员工消极怠工,员工都把时间精力用在检查是否有bug上面了,这样员工一天写的代码估计都没有多少。其实可以奖励buy数最少、写代码最多的员工,把奖品设置地足够诱人,大家为了得到奖品,写得多同时出的错也少,这比开除员工显得更加人性化了。
曾经有一个研发部门有好几个项目组,加起来总共几百名程序员,领导想要考核他们的绩效。领导觉得他们上班太懒散了,还老是迟到,上班的时候一直对着电脑,但是别人又不知道他们做的怎么样,所以才要对他们进行考核。考核需要有一个标准,领导一开始设定代码行数为标准,代码行数越多绩效越高,程序员十分机智,打了很多空行滥竽充数,这样行数就提升上来了。
最后被发现了,领导禁止利用空行,但是程序员总有别的方法来应对老板的考核,总之就是各种滥竽充数,最后写出来的代码并不会增多。后来领导直接大手一挥,说程序员写的代码超过8个bug就要被开除,这可让程序员难办了。他们为了不出bug,一个月只写了几个代码而已,老板差点没被气出内伤。
所以个人写的代码超过8个bug就要开除这种方法其实是不可行的,这样无异于阻碍公司的发展。领导想要程序员有质有量的完成任务,还是要想出别的对策。
我想弄出个删除员工号的结果,遇到了点bug
! 1. 记得有这么个缺陷,以后再遇到的时候可能就会了解发生的原因。 2. 尽力去查找出错的原因,比如有什么特别的操作,或者一些操作环境等。 3. 程序员对程序比测试人员熟悉的多,也许你提交了,即使无法重现,程序员也会了解问题所在。 4. 无法重现的问题再次出现后,可以直接叫程序员来看看问题。 5. 对于测试人员来说,没有操作错误这条.既然遇到,就是问题。 即使真的操作错了,也要推到程序员那里,既然测试人员犯错误,用户也可能会犯同样的错误。 错误发生的时候, Tester 最大。 二、程序不是测试人员写的,出问题也不是测试人员的原因。 至于无法重现,可能的原因很多,因为测试人员看到的只是程序的外部,无法深入程序内部,所以把责任推给测试人员是不对的。 测试人员的任务只是尽力重现问题,而不是必须重现!! 三、下次再遇到的时候,拉他们来看就可以了。 因为问题如果无论如何无法重现,程序员确实也没有什么好的解决方法。 而且此类问题即使程序员说修改了,测试员也没有好的方法去验证是不是。 四、你可以告诉程序员,测试过程是没有错误的。 测试人员只是检查程序中可能存在的问题,虽然测试人员使用一定的手段方法努力去覆盖所有的情况,但这些都是理论的推测。 在实际中,可能因为人员、环境、配置等种种原因出现各种各样的问题,在测试人员这里发现问题是公司内部的事情,程序发到外面可就是公司的形象问题了。 需要让程序员理解,测试人员是帮助他们的,不是害他们的。 客户那里发现问题比测试员发现问题结果要严重的多。 五、测试部门是独立于开发部门的呀,真的打交道,也是经理对经理。 在我们这里,工作上面的事情,和程序员相互只能商议解决,并没有谁高谁低。 问题无法重现,也要提出,程序员那里可以回复无法再现。 问题放在那里,等到再次出现的时候,就立刻叫程序员过来查看。 实在没有再次出现,最后可以写到报告中,说出现了什么现象,但无法再现(比较严重的问题才如此处理,小问题经理之间商量商量可能就算了)。 至于测试人员必须重现bug,你杀了我好了,我每次测试项目都有无法重现的问题,很多我能找到大概的原因,有些根本无法重现(仅仅出现一次)。 这种事情是无法避免的,并不能说测试人员无法重现问题,就是工作不到位(哼,程序有 bug,是否可以说程序员工作不到位的呀)。 六、测试部门要独立,最好不受开发的制约。 其实真正要重视,就应该有否决的权利。 我们公司就是项目承包,要拿最后的项目尾款,就要测试部签字通过,这样就避免了很多的问题。 其实只要自己尽到心就可以了,管别人怎么说呢。 七、我们使用的状态有: 程序员处理的状态(由测试员提交的Action):等待处理的,再次出现的。 测试员处理的状态(由程序员提交的 Action):已经修改的,暂不修改的,系统限制的,使用错误的,无法再现的。 测试员可以修改记录。 经理处理的状态(由测试员提交Action):管理员处理的。 经理还可以删除记录。 按照比较标准的说法,其实对于缺陷还应该有“等待确认的”、“已经确认的”和“重复提交的”的状态,我们为了省事,统一使用了“等待处理的”。 最后结项的时候,缺陷的状态对我们来说有两种,“已经关闭的”(由测试员或经理确认)和“暂不修改的”(比如下一个版本处理等)。 呵呵,状态多,有些烦琐,特别是程序员很多的时候都不清楚应该回复什么状态,但我个人觉得对测试人员来说,这些状态比较清晰明了,容易处理。 八、一个叫 doer_ljy(可战)的网友回复了一些内容,我个人认为不很妥当,就回复了一些内容,绿颜色的是doer_ljy(可战)的内容: 关于“无法重现”我看是有这么个问题存在。 首先如果你在测试之前有严格的测试计划,就很难出现“无法重现”这种现象。 “无法重现”的意思是不知道怎么操作才能再次看见这个BUG。 那么这个BUG 多半是“计划外”的。 不清楚你是否是测试人员。 “计划外”这个词,对测试员来说应该不存在。 测试用例的粒度一直是个在讨论中的问题,测试人员很难有时间和精力写出包含内容、数据、步骤等等全部操作一切的测试用例(说白了,只要一个长手识字的人,按照测试单做,就能发现所有的问题,呵呵,有软件蓝领的感觉了)。 即使真的有,意义也不大,测试很多的时候,是发散性的思维,带点创造性,想事先考虑完全,很难。 所以更多时候,是在测试过程中逐步对用例等进行完善,所以说“计划外”最好不要提。 说说我现在测试的一个项目,有一个业务,首先查询出人员,有个“全选”按钮,“全选”后,再用鼠标一个一个取消选择,这个时候进行业务办理的时候,就会提示“没有选择人员”,至今为止一切都正常,但是这个时候再次点选人员进行业务处理,仍然会提示“没有选择人员”,这就是一个缺陷了。 这个问题我想一般人都不会在测试用例中考虑到吧,因为发生的条件很苛刻:不用“全选”按钮的时候不会发生;全选后点击“取消全选”按钮再办理业务不会发生;全选全消后,先点击人员再办理业务也不会发生。 其次,成熟的测试人员即使无法再现 BUG,也能准确的描述出 BUG 发生之前几个步骤的操作方法,测试用例情况。 这些对开发人员分析BUG 原因很重要。 所谓的BUG 发现环境。 呵呵,看来我不是成熟的测试人员。 手工测试,比较熟练的时候,和打字可以说差不多,应该进行到哪里,心中是有数的,但让我完全从头到尾的重复,不容易呀。 写测试缺陷报告单的时候,也只是说明操作步骤和发生的现象。 其实无法重现的问题,既然说“无法重现”,也就是测试人员已经对这个现象进行了多次的验证,一般从程序外部来说,测试人员的操作比程序员要熟练的。 最后,我不同意测试人员不假思索把发现的“问题”直接推给编码人员的做法。 毕竟是大家合作,目标是一致的。 测试人员总是处在 BUG 发生的第一现场,应该帮助分析出现问题的原因。 确认是不是自己的此时Miss. 测试人员提交任何一个问题,都会经过反复的验证,如果容易重现,早就提出来了。 绝对不是在推脱责任,还是那句话,对程序的结构,做的人当然比不做的人要清楚。 另外,除非程序员询问,否则我不会给程序员提出修改分析和建议!!测试人员的任务是发现问题,解决问题是程序员的事情。 这么做可能会影响程序员思考问题的思路;而且测试人员做的多了,程序员不但不感激,可能反而会反感(好像程序员对测试人员有好印象的不多)。 再说两个我这两天遇到的问题。 第一个就是我们的程序有一个锁定数据的功能。 锁定后,在其它的业务,此数据将不能再使用。 我当时发现这个功能无效,而且经过了几次的验证都不行,我当然就提出了。 但是程序员那里说此功能好使,我再验证的时候,就没有问题了,这个问题当时可以重现(但是我不可能遇到问题就拉程序员来看吧),后来却没有了,只能放在那里,最后关闭掉。 第二个就是在一个界面中,录入有顺序要求,必须先选择一个ListBox (必填)再进行Edit 的录入,但一次操作我没有选择 ListBox 就录入的Edit,也正常保存了。 后来无论我怎么操作此问题都没有出现(不够成熟呀),我就放弃了,也没有提交记录(为了避免麻烦)。 测试人员的时间是有限的,进度给的都很少,一般连用例都没有时间写,还要去花很多时间验证“无法重现”的问题?反正 10 分钟如果试验不出来,我就会放弃。 严重的就提交,不影响的就当不知道。 下面是其它一些人的观点: doublefalse(散诸怀抱):如果不能重现的 bug 确实比较麻烦,但最好在测试过程中注意干净环境、正确的操作、相同的数据源,只要真的有问题,一定能否复现的。 呵呵,多试试!!!我们以前一直有客户反映入库的数据经常有无关数据,但在家里测试没有问题,后来才发现是汉字编码错位,这样同样的字,错位后就变成另外的东西了。 liuxiaoyuzhou(蟀哥):遇到过同样的问题!主要是记住BUG 出现的环境!测试的时候这是关键! 在我们这里不能重现的BUG,是测试人员的工作不到位!我们这里程序员比测试人员说话有力度!郁闷呀! ericzhangali(另一个空间):首先一定要提交bug;其次不要企图RD 一定去解这个bug;某些时候还得关闭这个bug。 如果RD 认为是测试错误,(不明白什么叫测试错误,是不是说他从测时要告诉你千万不要怎么怎么做,否则后果自负啊,)那也没什么办法,如果沟通解决不了,爱咋认为就咋认为吧。 darkcat_c(错了重来):没有 bug 是不可以重现的,bug 本事是建立在标准的规程上所出现的异常,如果你按test case 步骤做的话不太可能出现此类bug。 作为测试人员一定要具备良好的记忆能力,一旦出现一些不知如何产生的bug,至少你要知道刚才你大致进行了那些操作。 良好的分析能力,尽管你只是测试,但你应该全面的了解程序的架构,和一些重要的内部细节,不然你这个测试就是不合格的。 定位bug 是开发的事情,而重现一个bug 是测试的本职工作,不要把所有的事情推给开发,不然你的确比开发要低一等。 (编者按:这种话,不愿意去辩驳,标准开发人员的看法,也许应该让他们也来做做测试) liyan_1014(雁子):我觉得应该是这么处理: 1、一定提交bug,必须由负责bug 的tester 详细描述测试操作步骤,bug 发生的症状,并将 bug 发生的具体环境也描述清楚;这样对于再次重现也有一定的参考性。 2、测试和开发之间是需要良好沟通的,如果得到的回复是操作错误,那么请开发人员解释,为什么会允许存在操作错误,一般来说,对于错误控制,开发那边应该能很好的把握。
免责声明:本文转载或采集自网络,版权归原作者所有。本网站刊发此文旨在传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及版权、内容等问题,请联系本网,我们将在第一时间删除。同时,本网站不对所刊发内容的准确性、真实性、完整性、及时性、原创性等进行保证,请读者仅作参考,并请自行核实相关内容。对于因使用或依赖本文内容所产生的任何直接或间接损失,本网站不承担任何责任。