如何快速处理[解决]一个bug

如何快速处理[解决]一个bug

这是魔兽之父说的:

Patrick Wyatt:代码没问题 程序却有bug

 

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

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

这个BugMicrosoft的错,还是……


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

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

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

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

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

但我在StarCraft的开发中最大的贡献在于修补bug。因为大家都在透支着自己的极限来编写代码,以至于整个开发过程都穿插着bug:每向前两步都会倒退一步。大多数团队成员都在做功能开发,所以我不得不花费大量时间来解决QAQuality 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  

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

也可能是你的计算机坏了


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

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

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

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

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

在正常的计算机中,这样的压力测试不会出问题,但有大约1%运行GW的计算机会出问题!1%听起来不是个很大的数字,但当有100万玩家时,意味着每天会有至少1万个崩溃bug,这样编程团队将需要几周来研究这一天的bug

压力测试失败时,GW会关闭游戏并打开一个“硬件问题”的网页,以此提示用户哪些常见的原因会导致这样的错误:

Memory failure: in the early days of the IBM PC, when hardware failures were more common, computers used to have “RAM parity bits” so that in the event a portion of the memory failed the computer hardware would be able to detect the problem and halt computation, but parity RAM fell out of favor in the early ’90s. Some computers use “Error Correcting Code” (ECC) memory, but because of the additional cost it is more commonly found on servers rather than desktop computers. Related articles: Google: Computer memory flakier than expected and doctoral student unravels ‘tin whisker’ mystery.

Overclocking: while less common these days, many gamers used to buy lower clock rate — and hence less expensive — CPUs for their computers, and would then increase the clock frequency to improve performance. Overclocking a CPU from 1.8 GHz to 1.9 GHz might work for one particular chip but not another. I’ve overclocked computers myself without experiencing an increase in crash-rate, but some users ratchet up the clock frequency so high as to cause spectacular crashes as the signals bouncing around inside the CPU don’t show up at the right time or place.

Inadequate power supply: many gamers purchase new computers every few years, but purchase new graphics cards more frequently. Graphics cards are an inexpensive system upgrade which generate remarkable improvements in game graphics quality. During the era when Guild Wars was released many of these newer graphics cards had substantially higher power needs than their predecessors, and in some cases a computer power supply was unable to provide enough power when the computer was “under load”, as happens when playing games.

Overheating: Computers don’t much like to be hot and malfunction more frequently in those conditions, which is why computer datacenters are usually cooled to 68-72F (20-22C). Computer games try to maximize video frame-rate to create better visual fidelity; that increase in frame-rate can cause computer temperatures to spike beyond the tolerable range, causing game crashes.

在大学期间,我的Mac上有个扩展硬盘,经常会在春夏因为温度过高而出故障。因此我买了一个4英尺长的SCSI电缆,足够从我的计算机连到冰箱(我叫它Julio)了,并且全年将它存放在冰箱里,后来就再也没出过问题!

于是每当GW支持团队收到过热问题的反馈,都会鼓励玩家去改善空气流动、增加散热风扇,或者清理一下计算机中的灰尘,这些做法通常都很奏效。

这个计算机压力测试不仅完成了它的使命,还获得了丰厚的回报:我们能够识别电脑产生虚假的bug报告并且忽视这些崩溃。一周内有数百万玩家在玩我们的游戏,即使很低的故障率也会产生很多bug报告,以至于超过编程团队的处理极限。通过这些减少bug反馈信息的措施,编程团队能够更专注于开发玩家想要的新功能而不是去给bug分类。

当然还有更多bug


我认为现在还没有到计算机程序不会出现bug的阶段——用户期望的增长要比高级程序员的数量更快。Warcraft I大约有20万行代码(包括内部工具),而GW I的代码量已经超过了650万行(也包括工具)。尽管可以降低每行代码中bug出现的几率,但代码行数的巨大增长仍然会导致问题数的剧增。但我们仍在努力。

最后,我想分享一下在Blizzard时的同事——Bob Fitch的一句玩笑话,他说道:所有代码都可以优化,但所有程序都有bug,因此所有程序都可以被优化为一行代码,只不过无法运行。这就是为什么我们总有bug

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值