Patrick Wyatt:代码没问题 程序却有bug?
今天要分享的故事關(guān)于一些我職業(yè)生涯中真正遇到的bug。
這個(gè)Bug是Microsoft的錯(cuò),還是……?
Diablo發(fā)布后幾個(gè)月,StarCraft團(tuán)隊(duì)開始加班來保證游戲的按時(shí)完工。那時(shí)“距離游戲發(fā)布只剩兩個(gè)月了”,所以每天多加幾個(gè)小時(shí)的班完全是正常的(有時(shí)候周末也得加班),有很多工作要完成,因?yàn)閃arcraft II的游戲引擎基本上得從系統(tǒng)層面返工。大家故意不按日程辦事(包括我自己),所以最后游戲延期了超過一年。(不清楚的可以看參考之前的文章。)
最開始的時(shí)候,我并不是StarCraft開發(fā)團(tuán)隊(duì)的一部分,但在Diablo發(fā)布后,StarCraft獲得了更多的人力資源,于是我加入了進(jìn)來。但由于沒給我安排固定的任務(wù),我只有自己“使用武力”來驅(qū)動(dòng)項(xiàng)目進(jìn)展。
我打算實(shí)現(xiàn)一些有意思的功能,比如AI,但AI主要還是Bob Fitch在做。其中一個(gè)功能是系統(tǒng)需要判定哪里是最適合聚集武裝的地方,AI部隊(duì)會(huì)在那里集結(jié)并防守或者準(zhǔn)備區(qū)域進(jìn)攻。幸運(yùn)的是,已經(jīng)有成熟的API供我調(diào)用了,我可以直接使用路徑尋找算法查詢哪塊地圖區(qū)域是結(jié)合在一起的,以及敵人會(huì)在哪里集結(jié)重兵、準(zhǔn)備進(jìn)攻,以及加強(qiáng)易被突破區(qū)域的布兵情況。
我重新實(shí)現(xiàn)了某些組件,包括之前Craft系列延續(xù)的“戰(zhàn)爭(zhēng)迷霧”系統(tǒng)。StarCraft需要擁有比Warcraft II更好的戰(zhàn)爭(zhēng)迷霧系統(tǒng),因?yàn)榈貓D的分辨率更高了。所以我們打算實(shí)現(xiàn)視線計(jì)算,位置更高的單位將會(huì)獲得更好的使用,同時(shí)也增加了游戲戰(zhàn)術(shù)的復(fù)雜度:如果你不知道對(duì)手在做什么,想要贏就變得更加困難。同樣,躲在角落里的單位也將不會(huì)被外面的人看見。
新的戰(zhàn)爭(zhēng)迷霧系統(tǒng)是StarCraft項(xiàng)目中最令我感興趣的地方,我需要做一些快速學(xué)習(xí)來保證系統(tǒng)功能實(shí)現(xiàn)和快速運(yùn)行。上一個(gè)程序員的成果讓我很不開心,運(yùn)行起來非常之慢導(dǎo)致游戲幾乎無法運(yùn)行。我學(xué)習(xí)了紋理濾波算法和Gouraud描影,最終寫出了我職業(yè)生涯中最好的x386匯編程序——幾乎是現(xiàn)代游戲開發(fā)必備的技術(shù)。和大家一樣,我也希望StarCraft最終能夠開源,這樣我就能看到自己最喜歡的編碼成果,不過我記憶中的代碼也許要更好!
但我在StarCraft的開發(fā)中最大的貢獻(xiàn)在于修補(bǔ)bug。因?yàn)榇蠹叶荚谕钢е约旱臉O限來編寫代碼,以至于整個(gè)開發(fā)過程都穿插著bug:每向前兩步都會(huì)倒退一步。大多數(shù)團(tuán)隊(duì)成員都在做功能開發(fā),所以我不得不花費(fèi)大量時(shí)間來解決QA(Quality Assurance,質(zhì)量保證)團(tuán)隊(duì)捕捉到的問題。
高效修復(fù)bug的訣竅在于探索可靠地重現(xiàn)這個(gè)問題的方法。一旦你知道如何重現(xiàn)一個(gè)bug,就很容易分析bug出現(xiàn)的原因,通常離bug修復(fù)就不遠(yuǎn)了。不幸的是,重現(xiàn)“will o’ the wispbug”這樣偶爾才出現(xiàn)一次的bug需要幾天甚至幾周的努力。更糟的是,因?yàn)楹茈y甚至不能提前預(yù)估修復(fù)一個(gè)bug會(huì)花多長時(shí)間,這又會(huì)在會(huì)議日程上花費(fèi)更多時(shí)間。我說得最多的一句話是“嗯,還在找”。通常我會(huì)從早晨開始辦公,然后整天都在做bug修復(fù),有時(shí)候一天能修復(fù)數(shù)百個(gè),有時(shí)候一個(gè)都解決不了。
有一天我正在檢查一段無法運(yùn)行的代碼:我們本希望它能按游戲單位類型選擇行為(“采伐單位”、“飛行單位”、“地面單位”等等)和狀態(tài)(“活動(dòng)的”、“傷殘的”、“受攻擊”、“繁忙的”、“閑置的”)。因?yàn)闀r(shí)間太過久遠(yuǎn),我記不清具體的細(xì)節(jié)了,有幾行代碼可能是這樣的:
在觀察這個(gè)問題幾個(gè)小時(shí)后,我猜測(cè)可能是編譯器bug引起的,于是我又開始查看匯編代碼。
對(duì)于非程序員來說,編譯器只是將程序員編寫的代碼轉(zhuǎn)換成可以由CPU直接執(zhí)行的機(jī)器語言的工具。
在查看了匯編代碼后,我確定是編譯器導(dǎo)致了錯(cuò)誤的結(jié)果,因此向Microsoft發(fā)出了一個(gè)bug報(bào)告——也是我提交的第一個(gè)編譯器bug報(bào)告。很快我就得到了回應(yīng),回想起來還真是讓人驚訝:Microsoft的編譯器在世界范圍內(nèi)是如此地流行,我的bug報(bào)告竟然得到了回應(yīng),而且非常之快!
或許你能猜到——這不是一個(gè)bug,雖然我看了很久的代碼,但是卻還是忽略了一個(gè)小錯(cuò)誤。我很疲憊——連續(xù)數(shù)周每天12小時(shí)以上的工作——所以沒發(fā)現(xiàn)這是不可能工作的代碼。一個(gè)單位不能既非“采伐者”又非“非采伐者”。Microsoft的測(cè)試人員禮貌地回復(fù)了我的失誤,但那時(shí)我卻感到被羞辱了,但幸好bug可以解決了。
順便說一下,壓縮時(shí)間是一個(gè)失敗的開發(fā)模式,我在博客上很多篇文章中都提到過,這里也一樣:疲憊的開發(fā)者很容易犯一些低級(jí)錯(cuò)誤。合理地安排工作時(shí)間才能得到更高的開發(fā)效率,所以,回家休息去吧,然后明天再以飽滿的精神面來編寫代碼!當(dāng)我和兩個(gè)朋友開始創(chuàng)辦ArenaNet時(shí),“沒有危機(jī)”正是我們開發(fā)的哲學(xué)基礎(chǔ),原因之一在于我們沒有在辦公室置辦足球桌和街機(jī)。工作-回家休息-再工作!
這回bug真的出在Microsoft身上了!
幾年后,在開發(fā)Guild War時(shí),我們發(fā)現(xiàn)了一個(gè)災(zāi)難性的錯(cuò)誤會(huì)導(dǎo)致游戲服務(wù)器在啟動(dòng)時(shí)崩潰。不幸的是,我們編程團(tuán)隊(duì)日常使用的“dev”(development)分支沒有任何問題,測(cè)試團(tuán)隊(duì)最后驗(yàn)證用的“stage”(“staging”)分支也沒有問題。唯一出現(xiàn)問題的地方在于“l(fā)ive”分支,也就是玩家使用的分支。我們把這個(gè)版本“推送”給了終端用戶,于是他們都玩不了游戲了!WTF!
數(shù)千名憤怒玩家要求快點(diǎn)修復(fù)這個(gè)問題。幸運(yùn)的是,我們可以把代碼回滾到上一個(gè)版本,而這花不了多長時(shí)間,但仍然需要查清楚是哪里出了問題。最終我們發(fā)現(xiàn)是多個(gè)錯(cuò)誤共同導(dǎo)致了這個(gè)問題,這在編程中很常見。
Microsoft Visual Studio 6(MSV6)中的有一個(gè)bug,而我們正是用的MSV6編譯的游戲。對(duì)!不是我們的問題!自然,我們的測(cè)試無法找出問題。Whoops。
在特定的情況下,該編譯器會(huì)在處理模板時(shí)生成錯(cuò)誤的結(jié)果。模板是什么?它們很有用,但是會(huì)讓你很頭痛;有膽量的話就看看這個(gè)。
C++是一個(gè)很復(fù)雜的編程語言,所以它的編譯器有bug并不是什么奇怪的事情。實(shí)際上,C++比其它主流語言復(fù)雜得多,你可以看看C++和Ruby復(fù)雜度對(duì)比圖。Ruby功能全面,所以很復(fù)雜,但如圖所示,C++要復(fù)雜一倍,所以在其它一樣的情況下,C++的bug也會(huì)多一倍。
在研究這個(gè)編譯器的bug時(shí),我們發(fā)現(xiàn)其實(shí)自己早就知道這個(gè)bug,而且Microsoft dev團(tuán)隊(duì)已經(jīng)在MSVC6 Service Pack 5(SP5)中修復(fù)了這個(gè)問題,所有的程序員都已經(jīng)升級(jí)到了SP5。悲劇的是,我們忽略了構(gòu)建服務(wù)器,而它是集合代碼、插圖、游戲地圖、等組件,并最終組成游戲的地方。所以,雖然游戲在每個(gè)程序員的計(jì)算機(jī)上能夠正常運(yùn)行,卻在構(gòu)建服務(wù)器上出了巨大的問題,因此也只有l(wèi)ive分支有問題。
為什么只有l(wèi)ive版本?嗯,理論上所有分支(dev、stage、live)同樣有機(jī)會(huì)消除這樣的bug,但實(shí)際上還是有區(qū)別的。首先,我們?cè)趌ive版本取消了很多編程和測(cè)試團(tuán)隊(duì)使用的調(diào)試功能,這樣可以節(jié)省時(shí)間和金錢,但同樣也會(huì)孕育出巨大的災(zāi)難,甚至導(dǎo)致游戲崩潰。
我們想確保ArenaNet和NCsoft的員工在游戲中沒有作弊的機(jī)會(huì),因?yàn)槊總€(gè)玩家都應(yīng)該在一個(gè)公平的游戲平臺(tái)上娛樂。很多MMO公司都曾有員工因使用“GM特權(quán)”而被開除的情況,因此我們想通過刪除該功能來解決這個(gè)問題。
另外就是我們清除了一些“sanity checking”代碼,它們本是用于驗(yàn)證游戲是否在正常運(yùn)行。這類代碼被程序員稱為斷言(asserts or assertions),用來保證游戲狀態(tài)在計(jì)算之后是合適并且正確的。斷言會(huì)造成性能上的損失:每次例行檢查都會(huì)花費(fèi)時(shí)間;如果代碼中嵌入了過多的斷言,程序運(yùn)行就會(huì)變得緩慢。我們?cè)趌ive版本中禁用了斷言以降低游戲服務(wù)器的CPU利用率,但無意間導(dǎo)致C++編譯器生成了錯(cuò)誤的結(jié)果,最終造成游戲崩潰。
這個(gè)bug修復(fù)起來很簡單,只需要升級(jí)下構(gòu)建服務(wù)器就可以了,但最終我們決定保持?jǐn)嘌允情_啟狀態(tài),即使在live版本中也是如此。為了保證不再出現(xiàn)這樣的bug,我們放棄了節(jié)省CPU利用率(或者更準(zhǔn)確地說,未來需要的計(jì)算機(jī)數(shù))。
經(jīng)驗(yàn)總結(jié):每個(gè)人,包括程序員和構(gòu)建服務(wù)器,都應(yīng)該使用同樣的工具!
也可能是你的計(jì)算機(jī)壞了
鑒于之前的bug誤報(bào),我實(shí)在是不好意思再向Microsoft提交bug報(bào)告了,開始懷疑是不是我或者其他組員的代碼有問題。
在Guild Wars(GW)的開發(fā)期間,我接收到并且檢查了很多玩家返回的bug信息。GW的玩家可能會(huì)記得(最好不記得),當(dāng)游戲崩潰時(shí)會(huì)提供向我們的“實(shí)驗(yàn)室”發(fā)送bug報(bào)告的信息供分析。收到這些信息后,我們會(huì)篩選bug并并決定由誰來處理。這些bug的原因、程度都各不相同,有的沒有專人負(fù)責(zé),而是我們輪流負(fù)責(zé)處理。
我們經(jīng)常會(huì)遇到挑戰(zhàn)信仰的bug,總是讓人抓狂。bug的出現(xiàn)總是有原因的,我們首先可以假設(shè)可能的原因,并不涉及空間-時(shí)間統(tǒng)一性的重新定義。它看起來像是因?yàn)閮?nèi)存破壞或者線程競(jìng)爭(zhēng)問題,但已知的信息告訴我們這不大可能。
Mike O’Brien,ArenaNet的聯(lián)合創(chuàng)始人之一,也是一名駭客,最終想到這可能是電腦硬件故障引起的,而不是編程問題。更重要的是,他還給出了測(cè)試這一假設(shè)的方法,簡直是一個(gè)杰出的科學(xué)家。
他寫了一個(gè)模塊(“OsStress”),可以分配出一塊內(nèi)存,在那塊內(nèi)存中執(zhí)行計(jì)算,然后和已知答案做比較。他把這塊“壓力測(cè)試”代碼添加到主要的游戲循環(huán)中,這樣每秒將執(zhí)行30-50次這樣的驗(yàn)證步驟。
在正常的計(jì)算機(jī)中,這樣的壓力測(cè)試不會(huì)出問題,但有大約1%運(yùn)行GW的計(jì)算機(jī)會(huì)出問題!1%聽起來不是個(gè)很大的數(shù)字,但當(dāng)有100萬玩家時(shí),意味著每天會(huì)有至少1萬個(gè)崩潰bug,這樣編程團(tuán)隊(duì)將需要幾周來研究這一天的bug!
壓力測(cè)試失敗時(shí),GW會(huì)關(guān)閉游戲并打開一個(gè)“硬件問題”的網(wǎng)頁,以此提示用戶哪些常見的原因會(huì)導(dǎo)致這樣的錯(cuò)誤:
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.
在大學(xué)期間,我的Mac上有個(gè)擴(kuò)展硬盤,經(jīng)常會(huì)在春夏因?yàn)闇囟冗^高而出故障。因此我買了一個(gè)4英尺長的SCSI電纜,足夠從我的計(jì)算機(jī)連到冰箱(我叫它Julio)了,并且全年將它存放在冰箱里,后來就再也沒出過問題!
于是每當(dāng)GW支持團(tuán)隊(duì)收到過熱問題的反饋,都會(huì)鼓勵(lì)玩家去改善空氣流動(dòng)、增加散熱風(fēng)扇,或者清理一下計(jì)算機(jī)中的灰塵,這些做法通常都很奏效。
這個(gè)計(jì)算機(jī)壓力測(cè)試不僅完成了它的使命,還獲得了豐厚的回報(bào):我們能夠識(shí)別電腦產(chǎn)生虛假的bug報(bào)告并且忽視這些崩潰。一周內(nèi)有數(shù)百萬玩家在玩我們的游戲,即使很低的故障率也會(huì)產(chǎn)生很多bug報(bào)告,以至于超過編程團(tuán)隊(duì)的處理極限。通過這些減少bug反饋信息的措施,編程團(tuán)隊(duì)能夠更專注于開發(fā)玩家想要的新功能而不是去給bug分類。
當(dāng)然還有更多bug
我認(rèn)為現(xiàn)在還沒有到計(jì)算機(jī)程序不會(huì)出現(xiàn)bug的階段——用戶期望的增長要比高級(jí)程序員的數(shù)量更快。Warcraft I大約有20萬行代碼(包括內(nèi)部工具),而GW I的代碼量已經(jīng)超過了650萬行(也包括工具)。盡管可以降低每行代碼中bug出現(xiàn)的幾率,但代碼行數(shù)的巨大增長仍然會(huì)導(dǎo)致問題數(shù)的劇增。但我們?nèi)栽谂Α?/span>
最后,我想分享一下在Blizzard時(shí)的同事——Bob Fitch的一句玩笑話,他說道:“所有代碼都可以優(yōu)化,但所有程序都有bug,因此所有程序都可以被優(yōu)化為一行代碼,只不過無法運(yùn)行。”這就是為什么我們總有bug。
原文鏈接:Code of Honor
總結(jié)
以上是生活随笔為你收集整理的Patrick Wyatt:代码没问题 程序却有bug?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 君正集团股票发行价是多少
- 下一篇: 通过公共汽车站