在查看了汇编代码后,我确定是编译器导致了错误的结果,因此向Microsoft发出了一个bug报告——也是我提交的第一个编译器bug报告。很快我得到了回应,回想起来还真是让人惊讶:Microsoft的编译器在世界范围内是如此地流行,我的bug报告竟然得到了回应,而且非常之快!

  或许你能猜到——这不是一个bug,虽然我看了很久的代码,但是却还是忽略了一个小错误。我很疲惫——连续数周每天12小时以上的工作——所以没发现这是不可能工作的代码。一个单位不能既非“采伐者”又非“非采伐者”。Microsoft的测试人员礼貌地回复了我的失误,但那时我却感到被羞辱了,但幸好bug可以解决了。

  顺便说一下,压缩时间是一个失败的开发模式,我在博客上很多篇文章中都提到过,这里也一样:疲惫的开发者很容易犯一些低级错误。合理地安排工作时间才能得到更高的开发效率,所以,回家休息去吧,然后明天再以饱满的精神面来编写代码!当我和两个朋友开始创办ArenaNet时,“没有危机”正是我们开发的哲学基础,原因之一在于我们没有在办公室置办足球桌和街机。工作-回家休息-再工作!

  这回bug真的出在Microsoft身上了!

  几年后,在开发Guild War时,我们发现了一个灾难性的错误会导致游戏服务器在启动时崩溃。不幸的是,我们编程团队日常使用的“dev”(development)分支没有任何问题,测试团队后验证用的“stage”(“staging”)分支也没有问题。出现问题的地方在于“live”分支,也是玩家使用的分支。我们把这个版本“推送”给了终端用户,于是他们都玩不了游戏了!WTF!

  数千名愤怒玩家要求快点修复这个问题。幸运的是,我们可以把代码回滚到上一个版本,而这花不了多长时间,但仍然需要查清楚是哪里出了问题。终我们发现是多个错误共同导致了这个问题,这在编程中很常见。

  Microsoft Visual Studio 6(MSV6)中的有一个bug,而我们正是用的MSV6编译的游戏。对!不是我们的问题!自然,我们的测试无法找出问题。Whoops。

  在特定的情况下,该编译器会在处理模板时生成错误的结果。模板是什么?它们很有用,但是会让你很头痛;有胆量的话看看这个。

  C++是一个很复杂的编程语言,所以它的编译器有bug并不是什么奇怪的事情。实际上,C++比其它主流语言复杂得多,你可以看看C++和Ruby复杂度对比图。Ruby功能全面,所以很复杂,但如图所示,C++要复杂一倍,所以在其它一样的情况下,C++的bug也会多一倍。

  在研究这个编译器的bug时,我们发现其实自己早知道这个bug,而且Microsoft dev团队已经在MSVC6 Service Pack 5(SP5)中修复了这个问题,所有的程序员都已经升级到了SP5。悲剧的是,我们忽略了构建服务器,而它是集合代码、插图、游戏地图、等组件,并终组成游戏的地方。所以,虽然游戏在每个程序员的计算机上能够正常运行,却在构建服务器上出了巨大的问题,因此也只有live分支有问题。

  为什么只有live版本?嗯,理论上所有分支(dev、stage、live)同样有机会消除这样的bug,但实际上还是有区别的。首先,我们在live版本取消了很多编程和测试团队使用的调试功能,这样可以节省时间和金钱,但同样也会孕育出巨大的灾难,甚至导致游戏崩溃。

  我们想确保ArenaNet和NCsoft的员工在游戏中没有作弊的机会,因为每个玩家都应该在一个公平的游戏平台上娱乐。很多MMO公司都曾有员工因使用“GM特权”而被开除的情况,因此我们想通过删除该功能来解决这个问题。

  另外是我们清除了一些“sanity checking”代码,它们本是用于验证游戏是否在正常运行。这类代码被程序员称为断言(asserts or assertions),用来保证游戏状态在计算之后是合适并且正确的。断言会造成性能上的损失:每次例行检查都会花费时间;如果代码中嵌入了过多的断言,程序运行会变得缓慢。我们在live版本中禁用了断言以降低游戏服务器的CPU利用率,但无意间导致C++编译器生成了错误的结果,终造成游戏崩溃。

  这个bug修复起来很简单,只需要升级下构建服务器可以了,但终我们决定保持断言是开启状态,即使在live版本中也是如此。为了保证不再出现这样的bug,我们放弃了节省CPU利用率(或者更准确地说,未来需要的计算机数)。

  经验总结:每个人,包括程序员和构建服务器,都应该使用同样的工具!

  也可能是你的计算机坏了

  鉴于之前的bug误报,我实在是不好意思再向Microsoft提交bug报告了,开始怀疑是不是我或者其他组员的代码有问题。

  在Guild Wars(GW)的开发期间,我接收到并且检查了很多玩家返回的bug信息。GW的玩家可能会记得(好不记得),当游戏崩溃时会提供向我们的“实验室”发送bug报告的信息供分析。收到这些信息后,我们会筛选bug并并决定由谁来处理。这些bug的原因、程度都各不相同,有的没有专人负责,而是我们轮流负责处理。

  我们经常会遇到挑战信仰的bug,总是让人抓狂。bug的出现总是有原因的,我们首先可以假设可能的原因,并不涉及空间-时间统一性的重新定义。它看起来像是因为内存破坏或者线程竞争问题,但已知的信息告诉我们这不大可能。

  Mike O’Brien,ArenaNet的联合创始人之一,也是一名骇客,终想到这可能是电脑硬件故障引起的,而不是编程问题。更重要的是,他还给出了测试这一假设的方法,简直是一个杰出的科学家。

  他写了一个模块(“OsStress”),可以分配出一块内存,在那块内存中执行计算,然后和已知答案做比较。他把这块“压力测试”代码添加到主要的游戏循环中,这样每秒将执行30-50次这样的验证步骤。