本文綜合整理自知乎問答帖《你在程式開發過程中有沒有遇到無法解釋的靈異現象?》和 伯樂線上的兩篇舊文。知乎題主的補充:
大部分都是人禍。但是有沒有至今還找不到原因,無法理解,完全超出認知。理論上不可能發生的事例呢?
下面是 mu mu 的分享,(伯樂線上已徵得許可)
有天寫了一行長一點的code,ide提示語法有錯,我看了看,不可能,一定是ide腦殘了。編譯,ide顯示error,語法有錯。
不可能呀,我一個字一個字的看,不可能出錯,一定是ide腦殘的太厲害了,重啟ide,還是報錯。
我換了一行,一模一樣的敲了一遍,沒報錯。
頓時覺得世界不真實了,開始懷疑是不是在做夢,甚至有點朝matrix那個方向想了。深呼吸,喝口水,擦了擦螢幕上的灰塵… 咦,怎麼擦不掉?
原來,不知為什麼在那一行前面有個 `,我一直以為是灰塵,就沒管它。
論講衛生的重要性。
下面是 Dave Baggett 的分享,
《一次因量子力學而 Debug 的痛苦經歷》,由 伯樂線上 – @熊鐸 翻譯
回想起這個bug,仍然讓我有些痛苦。作為一個程式設計師,在發現bug時,你學會了首先在自己程式碼中找問題,或許在測試一萬次之後,你會把問題歸咎於編譯器。只有在這所有的都不起作用之後,你才會把問題歸咎於硬體。
這是我遭遇一個硬體bug的故事。
拋開別的不說,我曾為《Crash Bandicoot》寫儲存卡(讀寫)程式碼。對於一個自大的遊戲程式設計師,這就像是在公園裡散步一樣輕鬆愉快,我認為只要幾天就寫完了,最終除錯用了六個禮拜。在此期間我做一些其他的事情,但我一直回來處理這個bug——幾天內每天幾個小時。這個bug實在煩人。
這個bug的症狀是,當你需要儲存你的進度時,程式碼會訪問儲存卡,而大部分情況下沒有什麼問題…但是偶爾讀寫會超時…沒有任何明顯的原因。一個短小的寫入經常毀掉儲存卡。玩家要儲存進度,我們不僅不儲存,還擦除他們儲存卡上的全部東西。天哪。
過了一段時間,我們在Sony的製作人Connie Booth慌了。我們顯然不能帶著這個bug釋出遊戲,而六個星期之後我對於問題出在哪一點線索都沒有。通過Connie我們向其他 PS1 開發者求助:有沒有人出現過像我們這樣的情況?沒有。絕對沒有任何人在儲存卡系統上出現任何問題。
在你絞盡腦汁之後,你能做的唯一一個除錯方法就是分而治之:一點點去除程式中的程式碼,直到留下的程式碼很少但你仍然出問題。像木雕一樣去除沒有問題的程式碼,留下的就是你的bug所在。
在這樣的背景下挑戰在於,視訊遊戲是很難去除某一部分的。在你刪除模擬重力或者顯示字元的程式碼後,如何執行遊戲?
你必須做的是用一個假裝做真正的事情,但實際上只是做很簡單的不會出現bug事情的東西來替換掉整個模組。你必須寫新的支撐程式碼來讓這些玩意正常工作。這是一個緩慢而痛苦的過程。
長話短說:我做完了。我移除了大片大片的程式碼,相當多,只留下了初始化程式碼——就是準備遊戲執行系統,初始化底層硬體等等。當然,我不能顯示載入/儲存選單,因為我截除了所有的影像程式碼。但是我能夠假裝使用者使用(不可見的)載入/儲存螢幕並且請求儲存,然後寫入卡中。
我最終以一個帶有這個bug的很少量的程式碼結束——但問題仍然隨機出現!在大多數情況下沒啥問題,但是偶爾會失效。基本上所有的Crash的實際程式碼都被移除了,但還是這樣。這實在是莫名其妙:留下來的程式碼基本上都沒做什麼事。
在那時——估計是凌晨3點——一個想法蹦了出來。讀寫(I/O)涉及精確定時。無論是硬碟、儲存卡、藍芽傳送器——隨便啥——做讀寫的底層程式碼都是根據時鐘來的。
時鐘讓不直接連線到CPU的硬體裝置和cpu執行的程式碼同步。時鐘決定了波特率——資料從一頭傳到另一頭的速率。如果計時有什麼問題,硬體或者軟體或者兩者都會亂七八糟的。這真的,真的很糟糕,並且通常導致資料損壞。
如果我們的初始化程式碼以某種方式弄亂了計時會怎麼樣?我又看了一遍測試程式中和計時有關的程式碼,並注意到我們將PS1上的可程式設計計時器設定到了1kHz(1000跳每秒)。這是比較快了,當PS1啟動的時候,預設狀態大概是100Hz。因此,大多數遊戲將他們的計時器設定為100Hz。
這個遊戲的帶頭(和除我外的唯一)開發者Andy,將計時器設定為1kHz,使得Crash的動作計算更加準確。Andy喜歡矯枉過正,如果我們要模擬重力,我們應該儘可能的提高精度!
然而如果提高計時器頻率莫名其妙的干擾了整個程式的計時,故而將這個計時器設定到儲存卡的波特率上會怎樣呢?
我將計時器程式碼註釋掉。然後我就無法復原這個bug了。但是這並不表示bug被修復了,這個問題是隨機發生的。萬一我只是運氣好呢?
幾天過去了,我還是在玩我的測試程式。Bug沒有再出現。我回到全部的Crash程式碼中,修改了載入/儲存程式碼,在訪問儲存卡之前將可程式設計計時器重置為預設設定(100Hz),之後設定回1kHz。從此之後沒有發現問題再次出現。
但是…為什麼?
我重新回到測試程式上,試著檢測當計時器設定為1kHz時出現的那些錯誤的模式。終於,我注意到這些錯誤出現在使用PS1手柄的人身上。因為我自己很少這樣做,所以我沒有注意到(為啥我要在測試載入/儲存程式碼的時候用手柄)。但是有一天我們的美工等我去完成測試(我確定那時候我在爆粗口),而他緊張的擺弄著手柄。卡損壞了。“等下,怎麼回事?喂,再來一次!”
一旦我發現了這兩件事是聯絡著的,就很容易重現bug:開始寫入儲存卡,動一下手柄,儲存卡損壞。在我看來完全是硬體bug。
我去找Connie告訴他我的發現。她轉述給設計過PS1的硬體工程師。她被告知:“不可能,這不可能是硬體問題。”我跟她說問一下我能不能直接和他說。
那個工程師給我打電話了,他用著他的爛英語,我用著我更爛的日語,我們爭論一會。我最後說:“我給你一個30行的測試程式,讓你在動手柄的時候能夠出現這問題。”他答應了。他向我保證,這是浪費時間,而他正在一個新專案上很忙,但因為我們是Sony很重要的開發者,他會試的。
第二天晚上(我們在洛杉磯,而他在東京,所以對於我來說是晚上而他是到了第二天),他給我打電話,不好意思的向我道歉。這是個硬體問題。
我還是沒有完全搞清楚問題到底在哪,但是我的印象中,從Sony總部的反饋聽到的是,如果將可程式設計計時器設定到足夠高的時脈頻率,會影響到主機板上時鐘晶振附近的一些東西。這些東西之一就是儲存卡的波特率控制器,同時也設定手柄的波特率。我不是搞硬體的,所以對於細節我相當模糊。
但是主旨是主機板上兩個獨立部分的串擾,以及手柄介面和儲存卡介面資料傳送的結合在 1KHz 的時脈頻率下會導致丟位,從而資料丟失,以致卡損壞。
這是我全部程式設計生涯中,唯一一次因為量子力學而 debug 的問題。
下面是 gyrovague 博主的分享,
《遇到一個詭異 Bug,每逢週三就崩潰》,由 伯樂線上 – @Erucy 翻譯
拿點兒喝的坐好,是時候講講我最喜歡的 bug 的故事了。
那是我第一份 IT 相關的工作:在一個生產重要醫療裝置的廠商擔任軟體開發的暑期實習生。那些裝置主要是麻醉給藥系統和病患監控裝置,後者就是在臥床患者旁邊放著的發出“嗶嗶”聲的那種盒子,上面會以圖形方式顯示患者的脈搏、血壓、呼吸等等。如果心電圖變成一條直線的話還會立刻召喚護士。當時的辦公室裡全是 2 米高的裝著笑氣的罐子,還有長著超級大鬍子的嵌入式系統大拿,整屋子的人都在給各種裝置準備文件,為了讓它們通過 FDA 的認證。時不時還有人小聲提到 10 年前沒能在測試中發現的一個 bug,它導致了一臺麻醉機在手術過程中間重啟了。不用說,對於像我這種十幾歲的新手,所有的生產系統肯定是不會讓我們碰的。
(伯樂線上補註:一氧化二氮(Nitrous Oxide),又稱笑氣,無色有甜味氣體,是一種氧化劑,化學式N₂O,在一定條件下能支援燃燒(同氧氣,因為笑氣在高溫下能分解成氮氣和氧氣),但在室溫下穩定,有輕微麻醉作用,並能致人發笑。)
不過他們還是給我安排了一份讓人羨慕的工作,去測試一個在 1997 年聽起來還十分時髦的原型專案:一個用 C++ 編寫的伺服器,它會監聽患者監控裝置的串列埠,然後把一些需要關注的事件轉存到 SQL Server 資料庫中,之後通過 CORBA 把資料傳送到 Java Applet,於是醫生或者相關人員就能通過網際網路看到這個患者的狀態了,它既能看到實時的資料,也能瀏覽之間的資料記錄。帥氣!只是那個時候我對這些語言和系統都一無所知!
接下來的幾個星期就像殺豬一樣的折騰,主要時間都花在了讀懂讓人頭疼的 Visibroker ORB 手冊,還有超級普通的型別轉換 bug,不過我終於讓我的“辛普森”系統磕磕絆絆地跑起來了,它用“Homer”(注:辛普森一家裡的老爸)來記錄和提供資料,然後用“Bart”(注:辛普森一家裡的熊孩子)來進行顯示。這幾個星期讓我覺得 CORBA 複雜得讓人想死、AWT 讓人頭疼欲裂(比如 GridBagLayouts,嘔)、applet 慢得像只蝸牛,不過 Java 看起來倒還像是個挺不錯的語言。不過還有個小麻煩:C++ 伺服器時不時就會突然崩潰掉,然後我開始嘗試去搞明白到底是為什麼。
因為我監聽的那臺監控裝置在另一間屋子裡,所以我絕大部分的開發和測試都是通過手動的“演示”模式來完成的,比如在一個迴圈裡模擬一次心臟停跳之類的,據我所知,我的伺服器從來沒在這個過程中當機過。不過在我或者別人手動擺弄那些控制器的時候,它確實崩潰過,尤其是在實際機器上操作的時候,不過我想盡辦法也沒能找到一個方法能讓它穩定重現,甭管怎麼做都不行。我把所有事件日誌都記錄到磁碟上,想找到在崩潰之前到底發生了什麼,不過我小心翼翼地按照事件序列精確地手動重複了每一次事件(比如:把過濾器設定為 X,把控制器旋鈕向右擰三個刻度,點選按鈕……),我在兩間屋子裡跑來跑去(因為我在擺弄患者監控裝置的時候是看不見我電腦上的日誌的),但始終都沒能讓崩潰重現。不管是什麼“鬼事件”(對我就是這麼叫它的),它肯定是在造成崩潰的同時還逃過了所有日誌。是不是有什麼串列埠 I/O 或者硬體問題中斷了事件?難道是宇宙射線把我 PC 上的資料位給改變了?
我把整天整天的時間都用來嘗試去重現這個錯誤,但是毫無結果,在經歷了幾個星期的挫折之後,我最後乾脆在所有從串列埠收到事件和寫入資料庫的操作中間都加了 printf 語句,在這個過程中,我重新檢查了每一行程式碼,然後終於逐漸見到了曙光。
當我建立資料庫結構的時候,為了節省空間而犯了一個錯誤,一個新手常犯的錯誤:把時間戳當成主鍵了。所以如果兩個事件在一個毫秒內發生的話,資料庫就會丟擲主鍵唯一性約束的異常(譯註:SQL Server 的 datetime 型別的精度其實不是1毫秒,而是3.33毫秒)。我之前注意到這個問題了,不過我覺得這種情況非常罕見,而且只會在沒那麼重要的環境中發生(比如在鼓搗監控裝置內部配置的時候),所以我只是加了個 catch 語句,在日誌中寫了一條警告資訊,然後繼續執行後面的操作。
但是!這是個老派的程式碼,記錄日誌使用 C 語言風格的程式碼編寫的,把日誌字串記錄到了一個長度為 80 個字元的緩衝區中。唯一性異常這個訊息本身是個常量,而日誌的時間戳是格式化的,也就是實用了完整的英文的星期拼寫(%E),所以輸出就類似於“Monday, July 17, 1997, 10:38:47.123”。最後就是因為英文裡面星期幾的拼寫有個有意思的屬性:
星期幾 | 單詞長度 |
---|---|
Sunday | 6 |
Monday | 6 |
Friday | 6 |
Tuesday | 7 |
Thursday | 8 |
Saturday | 8 |
Wednesday | 9 |
明白了吧?星期三(Wednesday),而且只在星期三的時候,如果有人在監控器配置那兒手動進行了一個特定操作的話,就會在同一毫秒內產生兩個事件,於是導致資料庫丟擲異常,而這個異常的訊息包括字串結尾的終結符的話,則剛剛好 81 個字元,導致了 80 個字元的緩衝區溢位,把程式搞掛了!
在那之後,在所有需要使用的資料庫表中,我都會確保去用一個專門的、自增的整數 ID 作為主鍵,然後用 ISO 格式(也就是 YYYY-MM-DD)而不是星期幾來記錄所有日誌。這些年來,我學到了不管一個 bug 看上去多麼隨機和不可預測,如果你挖得足夠深的話,總是能找到一個符合邏輯的解釋,極少有真的“不相關”的錯誤,幾乎都是你特麼自己的錯。
下面是 Devymex Wang 的回覆分享,(伯樂線上已徵得許可)
你們說的根本就不是靈異事件好嗎?下面是真事!
某公司有個碼農工作壓力太大,天天晚上加班到半夜,最後受不了跳樓死了,他的機位從此就一直空著。但令大家都感到非常奇怪的是,有幾次早上來上班時卻發現這臺機子竟然開著!大概是因為電源有問題吧,但這個專案經理是個疑神疑鬼的人,每次經過這裡都繞著走。
到了新一屆招人,一個女孩被分到這個專案組。專案經理讓她坐這個空著的機位,誰也沒敢告訴她之前的事,只是讓她接手原來同事的工作。過了沒幾天,她寫的程式碼被測出來一個bug,可她水平確實比較差,怎麼都調不通。她又不敢問同事,只好向男友求助(她男友是另一家公司的大牛)。男友說現在很忙,晚上10點以後才有空。
那天她只好等到晚上10點,男友終於有空了。這時公司裡只剩下她和專案經理兩個人。她男友通過遠端桌面幫她除錯的時候,她要去個廁所便起身離開了坐位。過了一會專案經理下班回家,經過這裡時用餘光看到螢幕的上的程式碼好像在動!他定睛一看,螢幕上的程式碼正在一行一行的往下寫,可是機位上並沒有人!!!
第二天這個專案經理沒來上班,而是給HR發了封郵件,提交了辭職申請。