2.4 案例分析(第 2 天)
接着来看一下项目第 2 天中发生的事情。项目会怎么样呢?
2.4.1 问题 5 :不运行系统就无法察觉问题
第二天早上,刚坐到椅子上准备继续开发新功能时,测试人员来到了你的座位前。说是昨天提交的版本有问题。在提交的 4 个 bug 中,能够确认其中 3 个已经得到了修正,但另外 1 个还是有问题。
并且还有报告说发生了退化,以前修正好的 bug 因为这次的修改又再次出现了。可昨晚明明自己和组员一起努力把邮件中提到的 4 个 bug 都修正并提交了啊,而且还在各自的环境中对修正进行了确认呢。
不过重新回想一下的话,确实没有将全员的代码集中到一起运行过。因为在测试人员的环境中是第一次将所有的修改合并到一起运行的,所以就出现了上述状况。
2.4.2 问题 6 :覆盖了其他组员修正的代码
下载最新的代码并运行,发现确实如测试人员所说:3 个修正了,1 个没有修正,并且过去修正了的 bug 又复活了。你觉得实在是很奇怪,就看了下代码库的提交记录 8 。
8 这里以 Subversion 为例。
rev: 245 Author: ikeike443 <ikeike443@gmail.com> Date: Mon Dec 24 23:59:59 2012 +0900 修正了发送邮件的逻辑 rev: 244 Author: okamura <hogehoge@gmail.com> Date: Mon Dec 24 21:04:57 2012 +0900 修正了申请时扣款处理失败的问题 rev: 243 Author: ikeike443 <ikeike443@gmail.com> Date: Mon Dec 24 19:55:55 2012 +0900 修正了某些时间点无法进行申请处理的bugRev244 和 243 都修改了申请处理相关的部分,觉得这里有些奇怪,就查看了下 Diff9 ,才发现你在 Rev243 中提交的修改被 Rev244 覆盖掉了。
9 原指比较文件并输出文件之间的差异的程序。这里指差异本身。
你所提交的修改如下所示 10 。
10 这段代码自身原本就有如下列举的这些问题。有必要从根本上提高代码的质量。
• 使用了魔数(magic number)
• 没有确认 application.submit 的返回结果
• 没有处理异常
• 代码的实现有副作用(side effect)
Rev243和Rev244的Diff(在Rev243中增加的修改) - if(user.status == 3) { - application.submit() - } else { + //考虑到用户状态是Null的情况 + if(user.status != null && user.status == 3 ) { + application.submit() + } else {这个修正被之后的提交覆盖,如下所示。
Rev243和Rev244的Diff(在Rev244中增加的修改) - //考虑到用户状态是Null的情况 - if(user.status != null && user.status == 3) { - application.submit() - } else { + //使用信用卡的用户的情况下,在申请的同时进行扣款处理 + if(user.status == 3 && user.useCredit == true) { + application.submit(); + + billing.creditStatus = 1; + billing.execute(); + + } else {在 Rev243 中特地加上的 user.status 的 Null 检查 11 ,被 Rev244 覆盖后 Null 检查就没有了。为什么会发生这样的事情呢?
11 “user.status != null”这一部分。可见在 Rev244 中被删除了。
结果,原以为已经修正的 bug 还是有问题,原因是代码被覆盖而造成了退化 12 。向覆盖代码的开发者询问事情的缘由,对方却只是回答道:“向代码库提交在自己的本地环境中修正的代码时发生了冲突(conflict)13 ,我只是把冲突改掉了。”
12 由于添加功能或者修改 bug 而造成其他已经实现了的功能无法运行或速度变慢的现象。这次出现的现象是,因为 Rev244 的修正使得 Rev243 中修正的代码被复原了。
13 对同一处代码进行了不同的修改,造成了代码修改冲突。
这时你很想斥责对方:“那是你修正冲突的方法有问题!”但还是控制住了自己的脾气,包括那个开发者的修正在内,你对代码做了如下修正。
Rev245和最新的Rev246的Diff(这次的修正) - //使用信用卡的用户的情况下,在申请的同时进行扣款处理 - if(user.status == 3 && user.useCredit == true) { - application.submit(); - - billing.creditStatus = 1; - billing.execute(); - - } else { + //使用信用卡的用户的情况下,在申请的同时进行扣款处理 + //考虑到用户状态是Null的情况 + if(user.status != null && + user.status == 3 && user.useCredit == true) { + application.submit(); + + billing.creditStatus = 1; + billing.execute(); + + } else {2.4.3 问题 7 :无法自信地进行代码重构
这么修改姑且合并(Merge)14 成功了,但因为 if 语句发生了变化,所以程序的动作也会发生变化。这时你觉得进行一下代码重构比较好,但是却没有信心能够在确保不发生退化的情况下进行代码重构。没有办法,只能增加 if 条件,用条件分支来处理。
14 将多件物品整合到一起。
Rev245和最新的Rev246的Diff(重写了这次的修正) - //使用信用卡的用户的情况下,在申请的同时进行扣款处理 - if(user.status == 3 && user.useCredit == true) { - application.submit(); - - billing.creditStatus = 1; - billing.execute(); - - } else { + //使用信用卡的用户的情况下,在申请的同时进行扣款处理 + //考虑到用户状态是Null的情况 + if(user.status != null && + user.status == 3 && user.useCredit == true) { + application.submit(); + + billing.creditStatus = 1; + billing.execute(); + + //不使用信用卡的用户的情况下 + } else if(user.status != null && user.status == 3 ) { + application.submit() + } else {至此昨天发生的 4 个 bug 应该都修正好了。为避免测试人员再像今天早上那样不给你好脸色看,所以重新对所有的用例进行测试。没有准备自动测试环境,只能手动进行测试。测试结果没有问题,程序也能正常运行,所以就把代码提交了。
2.4.4 问题 8 :不知道 bug 的修正日期,也不能追踪退化
还有一件事情:过去的 bug 又出现了。这究竟是怎么回事呢?向测试人员问了下这个 bug 原本是什么时候发生的,具体是怎样的 bug,回答说是大约半年前直接收到客户的邮件后,让其他开发人员修正了的 bug。直到现在才第一次听说这件事情。过去修正了的 bug 再度发生,可以说是发生了退化,当然不可能就这样进行发布。
没办法,只能让测试人员把过去的邮件翻出来,找出和用户交流的内容。根据好不容易才找到的邮件日期,在版本管理系统的代码库中查找对应的提交版本,再和当时负责修改的开发人员一起来确认,终于找到了当时的提交版本。
但似乎这个提交在大约 3 个月之前就被其他的修正发布覆盖掉了。正式环境发生退化长达 3 个月之久,期间谁都没有察觉出来,这实在是非常糟糕的情况。这次能够在客户提出之前发现,实在是不幸中的万幸。
重新修正并提交。想着这下应该没问题了,但还是觉得不安心,于是再次手动进行了测试,确认退化已经被修正了。
啊!差点忘了,昨天发生的 4 个 bug 的修正没关系吧。修正了过去的退化,却导致了别的退化发生,这可不是闹着玩的。想到这里,再次对 4 个 bug 进行了测试。这次确实没问题了,于是提交了代码(图 2.5)。
图 2.5 至今为止的提交状况
2.4.5 问题 9 :没有灵活使用分支和标签
在这样那样的“斗争”中,不知不觉已经是傍晚了。本来应该做的新功能开发还没有开始动手。总觉得每天都在干同样的事。
不管怎么说总算告一段落了,该回到新功能的开发上了。用昨天重命名的目录,重新开始开发。
稍等一下,从昨天开始斗争了两天的 bug 修正会怎么样?如果就这样在昨天的目录的基础上进行新功能开发,并提交到版本管理系统的代码库中的话,感觉还是会发生大范围的退化 15 。真是太险了。你注意到了这点,开始将昨天的修正合并到手头的新功能开发版本中(图 2.6)。幸运的是合并操作可以使用工具机械地进行,但编译却出了问题 16 。
15 “如果合理使用 Subversion 或 Git 等版本管理系统的话就不会发生这样的事情”,你的这一想法是正确的。
16 聪明的读者可能已经注意到了,因为忘记了合并图 2.6 的“代码库”部分中的“别的组员的两个提交”,所以发生了编译错误。没有合理地对分支进行管理,就会发生这样的事情。
图 2.6 没有忘记对修改进行合并,但 ……
一边发愁一边继续修改,终于编译是能通过了,但确认昨天之前的程序动作是否正常时却遇到了困难,因为昨天的事情几乎已经不记得了。总算编译也通过了,程序也能运行了,觉得应该没有问题,于是就着手继续新功能的开发了。
虽然也觉得正是因为反复出现这样的事情才导致了现在的状态,但却不知道应该怎么做。就这样怀揣着说不清的不安,继续回到了工作中。
2.4.6 问题 10 :在测试环境、正式环境上无法运行
夜幕降临,新功能的编码工作终于步入了正轨,这时测试人员发来消息,说刚才提交的版本在 staging 环境的特定条件下无法正常运行。没办法,只好和测试人员一起对 staging 环境进行了确认。
的确,程序自身启动起来了,过去 bug 的退化也确认修正了,但其他的功能却出现了“Internal Server Error”17 。刚才明明在本地确认过了,为什么还会出现错误呢?
17 网站服务器内部发生的错误。
于是决定在本地环境上确认同样的操作。但由于现在本地机器上的开发环境是新功能开发用的环境 18 ,因此只能再一次重命名目录,回到刚才还在使用的修正 bug 用的目录中进行确认。
18 为了保险起见进行确认时,发现该项目中没有用于验证的环境,所以只能在本地机器上通过目录重命名的方式来验证。
在本地机器上确认动作,没有发现问题,可以正常运行。应该是本地环境和 staging 环境有什么地方不一样吧。这时突然想了起来,昨天修正 bug 的时候添加了新的库,而 staging 环境上还没有安装过新添加的库。
的确,在本地确认完后,忘记让测试人员安装库了。用邮件将本地安装的库发送给测试人员进行安装后,staging 环境能正常运行了。总算松了口气,但觉得这么下去不是个办法,难道没有更有效的方法来管理库,并且能防止遗漏吗?
2.4.7 问题 11 :发布太复杂,以至于需要发布手册
总算 staging 环境上的测试也 OK 了,准备向正式环境进行发布。由于发生了之前遗漏库的问题,负责发布的人员要求你提供发布手册。于是你就和测试人员一起制作了发布手册,并在制作过程中特别留意了以下几点。
应该下载版本管理系统的代码库中的哪个版本
应该如何更新 DDL、依赖的库以及配置文件
每次都需要写这样的手册。有没有什么更高效的做法呢?
因为这次的发布中包括重大的 bug 修正,作为开发者的你被要求一同在场。每次发布都要花费一天的时间,彻夜进行。虽然你有些不情愿,但也没有办法。今晚看来是回不去了。
在彻夜的发布作业中问题频发。正式环境的数据库由于经历了至今为止的开发过程,已经和测试环境以及 staging 环境有了很大的差异,只能通过手动修改来处理。配置文件也是只有正式环境的写法不一样,不得不一边谨慎地用肉眼核对,一边进行合并。
所依赖的库的版本不一样的情况也有很多。为了让正式环境运行起来,需要很多额外的作业,并且这些作业的内容没有被记录在版本管理系统中,所以恐怕下一次发布的时候还是会发生同样的问题。
就这样,死亡行军仍在继续。
