摘要:相信每个程序员都遇到过“不可能的bug”,代码没有任何问题却出错了!问题肯定是出在操作系统上,或者是工具,甚至是因为计算机硬件的问题?!?当然,魔兽之父也不例外,他在本文中分享了多个处理异常bug的经验。

  要分享的故事关于一些我职业生涯中真正遇到的bug。

  这个Bug是Microsoft的错,还是……?

  Diablo发布后几个月,StarCraft团队开始加班来保证游戏的按时完工。那时“距离游戏发布只剩两个月了”,所以每天多加几个小时的班完全是正常的(有时候也得加班),有很多工作要完成,因为Warcraft II的游戏引擎基本上得从系统层面返工。大家故意不按日程办事(包括我自己),所以后游戏延期了超过一年。(不清楚的可以看参考之前的文章。)

  开始的时候,我并不是StarCraft开发团队的一部分,但在Diablo发布后,StarCraft获得了更多的人力资源,于是我加入了进来。但由于没给我安排固定的任务,我只有自己“使用武力”来驱动项目进展。

  我打算实现一些有意思的功能,比如AI,但AI主要还是Bob Fitch在做。其中一个功能是系统需要判定哪里是适合聚集武装的地方,AI部队会在那里集结并防守或者准备区域进攻。幸运的是,已经有成熟的API供我调用了,我可以直接使用路径寻找算法查询哪块地图区域是结合在一起的,以及敌人会在哪里集结重兵、准备进攻,以及加强易被突破区域的布兵情况。

  我重新实现了某些组件,包括之前Craft系列延续的“战争迷雾”系统。StarCraft需要拥有比Warcraft II更好的战争迷雾系统,因为地图的分辨率更高了。所以我们打算实现视线计算,位置更高的单位将会获得更好的使用,同时也增加了游戏战术的复杂度:如果你不知道对手在做什么,想要赢变得更加困难。同样,躲在角落里的单位也将不会被外面的人看见。

  新的战争迷雾系统是StarCraft项目中令我感兴趣的地方,我需要做一些快速学习来保证系统功能实现和快速运行。上一个程序员的成果让我很不开心,运行起来非常之慢导致游戏几乎无法运行。我学习了纹理滤波算法和Gouraud描影,终写出了我职业生涯中好的x386汇编程序——几乎是现代游戏开发必备的技术。和大家一样,我也希望StarCraft终能够开源,这样我能看到自己喜欢的编码成果,不过我记忆中的代码也许要更好!

  但我在StarCraft的开发中大的贡献在于修补bug。因为大家都在透支着自己的极限来编写代码,以至于整个开发过程都穿插着bug:每向前两步都会倒退一步。大多数团队成员都在做功能开发,所以我不得不花费大量时间来解决QA(Quality Assurance,质量保证)团队捕捉到的问题。

  高效修复bug的诀窍在于探索可靠地重现这个问题的方法。一旦你知道如何重现一个bug,很容易分析bug出现的原因,通常离bug修复不远了。不幸的是,重现“will o’ the wispbug”这样偶尔才出现一次的bug需要几天甚至几周的努力。更糟的是,因为很难甚至不能提前预估修复一个bug会花多长时间,这又会在会议日程上花费更多时间。我说得多的一句话是“嗯,还在找”。通常我会从早晨开始办公,然后整天都在做bug修复,有时候能修复数百个,有时候一个都解决不了。

  有我正在检查一段无法运行的代码:我们本希望它能按游戏单位类型选择行为(“采伐单位”、“飞行单位”、“地面单位”等等)和状态(“活动的”、“伤残的”、“受攻击”、“繁忙的”、“闲置的”)。因为时间太过久远,我记不清具体的细节了,有几行代码可能是这样的:


1.if (UnitIsHarvester(unit))
2.    return X;
3.if (UnitIsFlying(unit)) {
4.    if (UnitCannotAttack(unit))
5.        return Z;
6.    return Y;
7.}
8.9....
10.11.if (! UnitIsHarvester(unit))
12.    return Q;
13.return R;   <<< BUG:永远不会执行到这行代码
 


  在观察这个问题几个小时后,我猜测可能是编译器bug引起的,于是我又开始查看汇编代码。

  对于非程序员来说,编译器只是将程序员编写的代码转换成可以由CPU直接执行的机器语言的工具。


1.// Add two numbers in C, C#, C++ or Java 
2.A = B + C 
3.; Add two numbers in 80386 assembly 
4.mov     eax, [B]    ; move B into a register 
5.add     eax, [C]    ; add C to that register 
6.mov     [A], eax    ; save results into A