Patrick Wyatt:代碼沒問題 程序卻有bug?

摘要:相信每個程序員都遇到過“不可能的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??

在查看了匯編代碼后,我確定是編譯器導致了錯誤的結果,因此向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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/449785.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/449785.shtml
英文地址,請注明出處:http://en.pswp.cn/news/449785.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

視差滾動(Parallax Scrolling)插件補充

13. Windows Windows (github) 是一個讓你用占據整個屏幕的section來構建單面網站的插件。該插件提供給你一些回調函數&#xff0c;當新的section出現在可視區并且并且處理快照時被調用&#xff0c;所以你可以輕松的繼承它來自定義導航菜單或更多的東西。下面是一個例子&#x…

主流瀏覽器內核

IE trident Firefox Gecko Google chrome Webkit/blink Safar i Webkit Opera presto轉載于:https://www.cnblogs.com/codezhao/p/10451030.html

Quartz使用總結、Cron表達式

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 Quartz可以用來做什么&#xff1f; Quartz是一個任務調度框架。比如你遇到這樣的問題 想每月25號&#xff0c;信用卡自動還款想每年4月…

股票數據庫建立

import akshare as ak import baostock as bs import pandas as pd import datetime bs.login()stk_list_place D:/stk_list.csv #股票代碼表存儲地址 stk_place D:/Data/ #股票數據存儲地址 def update_stk_list(dateNone):#獲取指定日期的指數、股票數據stock_rs bs.qu…

利用redis實現分布式鎖:加鎖與解鎖

待補充轉載于:https://www.cnblogs.com/csuliujia/p/10451462.html

MVC日期格式化,后臺使用Newtonsoft.Json序列化日期,前端使用”f”格式化日期

MVC控制器中&#xff0c;經常使用Newtonsoft.Json把對象序列化成json字符串傳遞到前端視圖。當對象中有DateTime類型的屬性時&#xff0c;前后臺如何處理才能把DateTime類型轉換成想要的格式呢&#xff1f; 有這樣的一個類具有DateTime類型屬性&#xff1a; using System; name…

多股回測(backtrader+quantstats+akshare)

導包 #引入技術指標數據 from __future__ import (absolute_import ,division,print_function,unicode_literals) import datetime #用于datetime對象操作 import os.path #用于管理路徑 import sys #用于在argvTo[0]中找到腳本名稱 import backtrader as bt #引入backt…

Cron表達式、定時任務

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 Cron表達式。但這個表示式本身就夠復雜了。下面會有說明。 例子&#xff1a; cronSchedule("0 0/2 8-17 * * ?") // 每天8:0…

【轉載】ASP.NET自定義404和500錯誤頁面

在ASP.NET網站項目實際上線運行的過程中&#xff0c;有時候在運行環境下會出現400錯誤或者500錯誤&#xff0c;這些錯誤默認的頁面都不友好&#xff0c;比較簡單單調&#xff0c;其實我們可以自行設置這些錯誤所對應的頁面&#xff0c;讓這些錯誤跳轉到我們指定的路徑。此文將介…

年薪15萬的80后小本科:只要6分鐘,告訴你少走6年彎路

這個社會是很殘酷的&#xff0c;尤其是對于那些剛剛步入社會的80后而言。當很多人都在抱怨這個社會競爭壓力太大、沒有自己的追求&#xff0c;并因此而喪失斗志的時候&#xff0c;一個年薪15W的80后小本卻發出了這個的感慨&#xff0c;“一個人的成就&#xff0c;與歲月無關&am…

Google Go Programming In Eclipse

http://www.tutorialsavvy.com/2013/04/google-go-programming-in-eclipse.html/ Google Go Programming In Eclipse The new “Go” programming language is from Google co.It has many features better then other languages.Go language features are:-– High Speed Comp…

pycharm打開ipynb顯示為文本格式解決辦法

然后進入 添加類型 jupyter notebook 然后下方添加 *.ipynb

quartz各版本MySQL數據庫存儲建表SQL語句

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 用quartz管理任務計劃很方便&#xff0c;但是當使用數據庫作為存儲介質的時候&#xff0c;必須要先創建表&#xff0c;不然就會報錯。1.…

[基礎篇]ESP32-RTOS-SDK教程(一)之Windows環境搭建

當下正是物聯網最好的時代&#xff0c;學習新的技術怎么能只學習ESP8266呢&#xff1f;要知道ESP8266還有一個孿生兄弟呢&#xff0c;最重要的是這個孿生兄弟要比ESP8266是更厲害的&#xff0c;所以我們也是非常有必要學習一下的&#xff0c;其實這篇文章去年就已經寫了&#x…

對話Linus Torvalds:大多黑客甚至連指針都未理解

摘要&#xff1a;Linus Torvalds坦言那些狡詐的通過文件名查找高速緩存&#xff0c;然后又抱怨自己能力一般的內核“惡魔”才是他欣賞的&#xff1b;相反&#xff0c;很多人連低水平的內核編程都還沒學好。 幾周前&#xff0c; Linus Torvalds在Slashdot上回答了一些問題。其中…

總結學習(提綱)

之前在私募做期權量化學習了那么久&#xff0c;趁著畢業找工作這段時間&#xff0c;對之前學習的東西&#xff0c;制作的函數等進行一個系統性的總結&#xff0c;順便每天更新的時候&#xff0c;記錄下自己的體重與波比跳次數。 1.MC的學習與策略編寫 2.python基礎學習資料的…

安卓系統上的遠程 JS 調試 Remote JavaScript Debugging on Android

每當在 Android 移動設備上調試網頁時&#xff0c;開發人員往往都會不自覺陷入調試的泥潭中去。《Android開發指南》提供了一個解決方案&#xff0c;卻有點繁瑣復雜。因此&#xff0c;許多 Web 開發人員會傾向于使用類似 Firefox Firebug 的或像 WebKit 的 Web Inspector 之類的…

js關于表單校驗完善

<!DOCTYPE html><html> <head> <meta charset"UTF-8"> <title>注冊頁面</title> <style type"text/css"> .left{ width: 100px; …

Python高效編程技巧

摘要&#xff1a;作者有多年的Python編程經驗&#xff0c;并且有很多的編程小技巧和知識&#xff0c;其中大多數是通過閱讀很流行的開源軟件&#xff0c;如Django, Flask, Requests中獲得的。 我已經使用Python編程有多年了&#xff0c;即使今天我仍然驚奇于這種語言所能讓代碼…

quartz 任務調試 建表 sql 語句、create table語句

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; DROP TABLE IF EXISTS QRTZ_SCHEDUL…