今天要分享的故事关于一些我职业生涯中真正遇到的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修复,有时候一天能修复数百个,有时候一个都解决不了。
有一天我正在检查一段无法运行的代码:我们本希望它能按游戏单位类型选择行为(“采伐单位”、“飞行单位”、“地面单位”等等)和状态(“活动的”、“伤残的”、“受攻击”、“繁忙的”、“闲置的”)。因为时间太过久远,我记不清具体的细节了,有几行代码可能是这样的:
- if(UnitIsHarvester(unit))
- returnX;
- if(UnitIsFlying(unit)){
- if(UnitCannotAttack(unit))
- returnZ;
- returnY;
- }
- ...
- if(!UnitIsHarvester(unit))
- returnQ;
- returnR;<<<BUG:永远不会执行到这行代码
在观察这个问题几个小时后,我猜测可能是编译器bug引起的,于是我又开始查看汇编代码。
对于非程序员来说,编译器只是将程序员编写的代码转换成可以由CPU直接执行的机器语言的工具。
- //AddtwonumbersinC,C#,C++orJava
- A=B+C
- ;Addtwonumbersin80386assembly
- moveax,[B];moveBintoaregister
- addeax,[C];addCtothatregister
- mov[A],eax;saveresultsintoA
在查看了汇编代码后,我确定是编译器导致了错误的结果,因此向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次这样的验证步骤。
在正常的计算机中,这样的压力测试不会出问题,但有大约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。
原文链接:Code of Honor
相关推荐
标题 "Wyatt-Petryshen:我的网站的源代码" 提示我们这是一份个人网站的源代码,可能包含了创建网站所需的各种文件和资源。描述中的 "由从模板收集的模板" 暗示该网站可能使用了现成的HTML模板来构建,这通常是为了...
I'm going to talk about engineering robust software, but because I only have an hour, most of what I'm doing will be *evangelizing* robust software. But that's just as important, because it is ...
我从 Adam Wyatt 的 FROG 程序(在本网站上)开始,但后来添加并编辑得面目全非。 然而,我确实减去了一个有用的功能:这个程序,正如所写的,只是 SHG-FROG 和 GRENOUILLE! (Adam Wyatt 的程序更通用。) -->...
标题“wyatt”和描述“wyatt”都比较简洁,没有提供明确的IT知识点,但根据提供的标签“字体”,我们可以推测这是一个与字体设计、排版或者字体资源相关的主题。在IT领域,字体扮演着至关重要的角色,无论是网页设计...
《Wyatt Bar(Wbar)的商业编程源码解析》 在商业编程领域,源码的分析和理解是提升技术水平、优化代码效率的关键步骤。本资料包“商业编程-源码-Wbar(Wyatt Bar)的实现.zip”提供的是Wyatt Bar项目的核心源码,这...
标题中的“wyatt-stuff:可能使用端点的杂项”暗示了这是一个关于软件开发的项目,特别是涉及到网络端点的使用。端点是API(应用程序接口)的一部分,用于接收和发送数据。在这个项目中,可能包含了一系列用于处理...
wyatt_netsuite 悦ERP Netsuite插件, NetSuite解放阵线为您带来
wbar:这是用C写的一个小应用程序,功能是将最常用的应用程序列在Wyatt Bar中,方便用户启动这些应用程序,类似于IE 4+中的“快速启动工具栏”。水平/垂直、停靠、透明以及“Stay on Top”效果任你设置。她没有什么...
这是Wyatt风格的服务应用开发模板 特性 支持构建 Docker 镜像 预置的支持初始化配置文件的 CLI 工具 默认支持 mocha 测试框架 默认支持 nyc 覆盖率测试工具 自动初始化 git 仓库 支持 changelog 的 commit 规范 携带...
3. 订正算法实现:订正方法可能有多种,如Thorne-Wyatt方法、Renfrew方法、HOMogenization (HOM) 算法等。这些方法旨在通过统计分析找出系统性变化,并对其进行校正。例如,可能会计算站点间的风速差异,然后应用...
Wyatt Toure的GitHub仓库"wyatt-toure-main"可能包含了他的项目源代码,这些项目可能涉及使用Python或R解决实际问题,如数据分析、算法实现或者构建小型应用。通过查看他的代码,我们可以学习到如何组织项目结构,...
wyatt-toure.github.io:个人网站
1. **面向对象编程**:C++支持类和对象的概念,使得游戏中的战舰、棋盘、射击等元素可以被抽象成独立的对象,每个对象都有其特定的属性和行为,增强了代码的可读性和可维护性。 2. **数据结构**:棋盘的表示通常...
hankel变换matlab代码PyHank - Python 的准离散 Hankel 变换 爱德华·罗杰斯 PyHank 是由 Manuel Guizar-Sicairos 和 Julio C. Guitierrez-Vega 开发的准离散 Hankel 变换的 Python 实现 “用于传播光波场的整数阶准...
巧克力包装 这是我为维护的软件包的存储库。 作者 作者:: Patrick Wyatt ( ) 贡献者 贡献者::艾伦史蒂文斯( ) 贡献者:: Rogerio Prado de Jesus ( ) 贡献者:: Nicolas Arnaud-Cormos ( )
@authors:怀亚特·蒙大拿州(Wyatt Montana),凯里·麦克马洪(Kerri McMahon) 客户端应用程序与我们的Oracle SQL数据库进行通信以获取存储的信息。 我学到的东西: 与前端应用程序的数据库通信数据可视化数据库...
:默认在您的节点的run_list包含instrumental : { " run_list " : [ " recipe[instrumental::default] " ]}作者作者:: Jamie Winsor( ) 作者:: Patrick Wyatt( )
Denton博士编写,已由Wyatt Davis在Java中使用开源物理库进行了重大修改。 该版本收集有关自我规避步行聚合物构象的数据。 粗粒度模型将链状聚合物近似为椭球体。 模拟结果将发表在《化学物理学杂志》上。 观看演示 ...
- **代码审查与调试**:利用ROS提供的调试工具,检查程序逻辑错误。 ### 总结 通过以上分析,《一种系统化的学习机器人编程方法》不仅为初学者提供了全面的理论知识,更重要的是教会了如何将理论应用于实践。无论是...