系列: 與 AI 一起學寫程式

當展示無聲地當掉時

讓引擎拒絕無聲地失效。

當展示無聲地當掉時 看起來完工了。什麼都不做。什麼都不說。 INFO scene loaded INFO prefab fallback... INFO ...298 more lines 埋在 INFO 層級裡 沒有人看得到 升級 ERROR PLACEHOLDER MODE: gameplay prefabs missing, demo will not function 通得過任何記錄篩選 grep 一下立刻找到
安靜的降級是一種瑕疵。後備行為必須放聲尖叫。

當機會告訴你有事情不對勁。無聲的失效則把一個精緻的空殼出貨給你的使用者——還有你的夥伴。贏得信任的引擎,是那個拒絕低聲細語的引擎。

有一類 bug 比當機更糟。當機至少告訴你有事情不對勁。這個星期六早上我坐下來面對的是另一種東西。展示開機了。場景載入了。UI 渲染了。暫停選單沒有反應。遊戲內容是佔位版。沒有錯誤。沒有例外。沒有任何高於 INFO 的記錄行。引擎無聲地失效了,還彬彬有禮地演著一場遊戲正常運作的戲。

這篇文章講的就是如何讓你的引擎拒絕這麼做。

在使用者眼中,展示長什麼樣子

乾淨開機。啟動畫面。選單畫面。點「Play」。場景切換。一個看起來乾乾淨淨的場景。中間一艘飛船。右上角一個分數顯示。

飛船不會動。輸入是死的。暫停選單叫得出來,畫面上存在,卻對點擊毫無反應。分數是零,而且一直是零。沒有敵人可以射。這個世界是一個彬彬有禮、空空如也、看起來能運作的殼。

對讀到這裡的開發者來說,三十秒內就能得出明顯的結論:這是佔位版。真正的遊戲 prefab 不見了。引擎退回到降級模式,卻忘了告訴任何人。

對使用者來說,這是最糟糕的一種失效。應用程式看起來不像壞掉。它看起來像完工了,而且很爛。

實際上出了什麼問題

兩個彼此獨立的問題,各自都很隱微。

場景載入器在無聲地降級。SceneContentLoader 負責找到遊戲 prefab 並把它們實例化到當前場景。當 prefab 不見時(因為建置問題、缺少資產包,或設定不匹配),它會退回到一套佔位設定。這個後備行為本該記錄一則清楚的錯誤。它卻是用 INFO 層級在記錄。錯誤訊息被埋在一條未經篩選的記錄流裡,混在其他三百行 INFO 之間。任何跑這個展示、掃一眼記錄的人,都看不到任何值得警覺的東西。

暫停選單的畫布缺了一個 GraphicRaycaster這是 Unity 那邊的東西。沒有 GraphicRaycaster 元件的畫布無法接收指標事件。暫停選單被正確地實例化、正確地渲染,然後對使用者輸入完全失聰。這個元件的缺席,是在一次把多個畫布設定合併為一的重構中悄悄溜進來的。合併時把其中一個畫布上的 raycaster 弄掉了。

兩個 bug 是同一種味道。某個東西安靜地出了錯。程式路徑繼續走。使用者看到的是看起來能運作、實際上不能的東西。

我是怎麼找到它們的

稽核花的時間比修復更長。修復花了一個星期六下午。稽核花了整個早上。我想把這個模式寫下來,因為它會重演。

第一個線索是分數卡在零。我以為是上週末的 bug(分數重複計數)被矯枉過正了。錯。分數是零是因為沒有敵人。沒有敵人是因為遊戲 prefab 沒有載入。prefab 沒有載入是因為場景內容載入器退回到了佔位版,而且是用 INFO 層級而不是 ERROR 層級在說這件事。

一旦知道了這一點,第二個 bug 就變得明顯。暫停選單沒反應是同一個展示裡的另一個問題,被同一個星期六的工作時段翻了出來。稽核的模式是:「如果你找到了一個無聲失效,就去找它旁邊的。」

我修了什麼

一組小而精準的變更。

把佔位警告升級為 ERROR。當場景內容載入器找不到真正的遊戲 prefab、退回到佔位模式時,它現在會以 ERROR 記錄,訊息是「PLACEHOLDER MODE: gameplay prefabs missing, demo will not function correctly」。ERROR 的嚴重度讓它通得過任何合理的記錄篩選。「PLACEHOLDER MODE」這段確切文字,在 grep 記錄時絕不會認錯。

新增一個 EnsureCrossPlatformInputManager 步驟。即使在佔位模式下,輸入系統也應該能運作。如果開發者正在用佔位模式測試展示(因為他們在沒有完整資產包的情況下處理 UI),他們需要能夠與選單互動。開機引導程序現在會確保任何場景裡都存在一個 CrossPlatformInputManager 單例,佔位場景也不例外。

自動為需要的畫布加上 GraphicRaycaster防禦性的修法是讓畫布生成輔助程式檢查 GraphicRaycaster 元件,缺了就補上。這不會粉飾未來同樣形狀的 bug,但它把這個特定的失效模式從可能性中移除了。

AutoBootstrap 裡加上詳細的開機記錄。展示開機時,記錄現在會印出一份簡短摘要:載入了什麼場景、以什麼模式載入(真實版或佔位版)、有哪些子系統在場。讀記錄的開發者可以在十秒內回答「展示是不是以我預期的模式開機的」這個問題。在今天早上之前,答案得靠讀完三百行記錄再推論。

為畫布輔助程式加一個單元測試。因為這個 bug 是元件缺失,正確的測試就是在輔助程式跑完後斷言該元件存在。這個測試現在已在測試套件裡。如果有人再次以會弄掉 raycaster 的方式重構畫布輔助程式,測試會大聲失敗。

這能推廣到什麼

今天早上可以帶走三個模式。

無聲失效是最糟糕的失效模式。你的程式碼任何退回到降級模式的地方,後備行為都必須放聲尖叫。不是 INFO。是 ERROR。帶著一段 grep 找得到的字串。如果使用者分不出後備行為有沒有觸發,三天後讀記錄的開發者也分不出來。

設定驅動系統裡的元件缺失需要一個測試。Unity、Unreal,任何場景是用元件組裝出來的引擎:設定會無聲地漂移。防禦是一小組測試,斷言「X 類型的場景具有元件 A、B、C」。無聊的測試。關鍵的測試。值得寫。

以鄰近性稽核。當你找到一個無聲失效,去看每一個相鄰的系統。bug 會群聚。弄掉 raycaster 的那次重構,可能也弄掉了其他畫布上的其他元件。埋掉佔位警告的那次記錄紀律鬆懈,可能也埋掉了其他警告。調查整個街區,而不只是最初的目擊地點。

夥伴與開發者該從中帶走什麼

如果你在打造任何引擎具有多種降級模式的東西(資產缺失、網路斷線、供應商 SDK 無法使用),引擎必須每一次都大聲告訴你它處在哪個模式。安靜的降級是一種瑕疵。

如果你正在為合作評估一個引擎,問問那個團隊他們怎麼處理無聲失效。正確的答案是「我們用 ERROR 加上可 grep 的字串把它們浮上檯面,而且我們有稽核去找它們」。錯誤的答案是「我們還沒遇過那個問題」。

如果你在跑代理驅動的工作流程、你的代理在做重構,重構偶爾會不小心弄掉元件或調降記錄嚴重度。修法是一道小小的 CI 關卡,驗證關鍵元件存在、關鍵記錄訊息以正確的嚴重度存活。不光鮮。有效。

星期六下午。展示在出問題時會開口了。暫停選單又會回應點擊了。那場彬彬有禮的空殼展示派對結束了。

回去繼續蓋。

一個會告訴你它處在哪個模式的引擎

RakuAI 把每一種降級模式都大聲地、以 ERROR、帶著可 grep 的字串浮上檯面,並用稽核搶先找出無聲失效。這是空間執行時期欠在其上打造的團隊的可靠性紀律。

← 所有文章