錯誤處理最佳實踐

軟體錯誤處理:如何對生產系統中的錯誤進行分類、記錄和恢復

內部網路 2026 年 5 月 26 日 , , ,

錯誤處理並非系統正常運作後才會新增的功能,而是設計決策,它決定了系統在故障時的行為。在生產環境中,故障何時發生是必然的問題,而非是否會發生的問題。網路會逾時,資料庫會暫時無法使用,使用者提交的輸入可能違反了開發者的所有假設,外部服務會回傳意外回應,硬體也會發生故障。一個能夠可預測地處理所有這些情況,且不會損壞資料或洩漏敏感資訊的系統,就是設計良好的。而當系統出現上述任何一種情況時,如果出現崩潰、靜默地破壞狀態或洩漏內部實作細節,則表示其存在結構性問題,而這種問題無法透過任何功能開發來解決。

為整個程式碼庫進行錯誤處理

SMART TS XL 偵測環境中每種語言和平台上的未處理異常和錯誤處理漏洞。

產品總覽 SMART TS XL

錯誤處理不當的實際後果並非假設。如今,錯誤處理不當已被明確認定為軟體開發中最關鍵的安全風險之一:OWASP A10:2025(異常情況處理不當,重點關注系統遇到的錯誤處理不當、邏輯錯誤、故障開啟以及其他由異常情況引發的相關場景)。這是 OWASP Top 10 2025 中的一個新類別,反映了人們對錯誤處理失敗如何導致不僅運作不穩定,而且還會造成可利用的安全漏洞的認識日趨成熟。此類別中值得注意的缺陷包括 CWE-209 產生包含敏感資訊的錯誤訊息、CWE-476 空指標解引用和 CWE-636 未安全失敗。所有這些缺陷都可以透過在整個程式碼庫中一致地應用規範的錯誤處理實踐來避免。

軟體開發中的錯誤處理是什麼

錯誤處理是指軟體系統偵測、分類並回應阻止正常執行的各種情況的一系列機制。它包括異常捕獲、錯誤狀態管理、診斷日誌記錄、向使用者或下游系統傳遞故障訊息,以及對受影響進程進行受控恢復或終止。一個擁有完善錯誤處理的系統並非永遠不會發生故障,而是一個能夠以可預測的方式響應故障、避免資料損壞、避免洩露敏感訊息,並且不會將故障傳播到原本可以繼續運行的組件的系統。

可預測故障和無序故障之間的差異在實際操作中至關重要。可預測故障的系統會產生清晰的日誌,觸發既定的復原機制,並為維運團隊提供診斷和解決問題所需的資訊。而無序故障的系統則會產生不完整的日誌,允許靜默錯誤在任何可見故障出現之前破壞系統狀態,迫使值班團隊將大部分時間用於重構事件經過,而不是解決問題。十分鐘事件和三小時事件之間的差異往往不在於故障本身,而在於圍繞故障的錯誤處理的品質。

錯誤處理也直接關係到安全性。不當的錯誤處理導致的最常見安全問題是,向使用者顯示詳細的內部錯誤訊息,例如堆疊追蹤、資料庫轉儲和錯誤代碼。這些資訊會洩漏不應公開的實作細節,為駭客提供網站潛在漏洞的重要線索。有效的錯誤處理機制會嚴格區分內部記錄的診斷資訊和傳回給使用者或透過 API 公開的資訊。

軟體錯誤類型及其識別方法

軟體錯誤並非一成不變。它們在發生時間、檢測方式、所需回應以及回應是否可自動化等方面都存在差異。理解錯誤分類是設計針對每種錯誤類型製定相應處理策略的前提,而不是對所有錯誤都採用相同的機制。

語法錯誤

語法錯誤是指程式碼違反程式語言的語法規則。編譯器和解釋器會在執行前偵測到語法錯誤,因此語法錯誤是最容易處理的:在具有自動化建置流程的系統中,它們不會進入生產環境。然而,在像 Python 或 JavaScript 這樣的解釋型語言中,測試套件未覆蓋的程式碼路徑中的語法錯誤可能會進入生產環境,並在首次執行這些路徑時導致執行時故障。程式碼檢查工具和靜態分析工具會在部署前擷取這些環境中的語法錯誤。

運行時錯誤

執行時間錯誤是指程式在執行過程中遇到無法透過正常控制流程處理的情況時發生的錯誤,例如空指標解引用、除以零、檔案不存在、網路連線失敗或資料庫暫時無法使用等。由於運行時錯誤具有不可預測性,取決於程式碼無法控制的外部條件,並且可能在交易執行過程中的任何時刻發生,因此它們是生產系統中錯誤處理機制的主要目標。

運行時錯誤進一步分為可恢復和不可恢復兩種情況,這是錯誤處理系統必須做出的最重要的操作性分類。臨時資料庫連線故障屬於可恢復的運行時錯誤:短暫延遲後重試很可能成功。而損壞的設定檔導致應用程式無法初始化則屬於不可恢復的執行階段錯誤:重試無濟於事,正確的做法是控制終止並顯示清晰的診斷資訊。如果將這兩種情況同等對待,即對無法透過重試解決的情況應用相同的重試邏輯,則是生產系統中導致錯誤處理失控的最常見原因之一。

邏輯錯誤

邏輯錯誤是最危險的一類錯誤,因為它們對標準錯誤處理機制來說是看不見的。程式執行過程中不會拋出任何異常,但由於實作的邏輯與預期行為不符,因此會產生錯誤的結果。例如,循環中出現差一錯誤的價格計算、未考慮時區差異的日期比較、授予錯誤使用者存取權限的授權檢查:這些都是邏輯錯誤。它們不會觸發任何異常處理程序,也不會出現在任何錯誤日誌中,而且往往會在有人注意到問題之前,將錯誤結果傳播到多個下游系統。

檢測邏輯錯誤需要驗證結果,而不是捕獲異常。這意味著需要使用斷言來驗證後置條件,使用比較測試來驗證輸出是否與已知正確的參考值一致,以及使用監控在業務指標偏離預期範圍時發出警報。

系統錯誤

系統錯誤源自於應用程式程式碼之外:硬體故障、記憶體耗盡、作業系統資源限制、網路基礎設施故障等。這些錯誤通常無法僅靠應用程式自行解決,需要與基礎設施層協同回應:例如故障轉移到冗餘組件、優雅降級到功能受限的狀態,或在通知維運團隊的情況下進行受控關機。應用程式程式碼的作用是儘早檢測到這些情況,採取適當的降級措施而非災難性故障,並產生診斷訊息,以便基礎設施團隊了解發生了什麼。

下表將每種錯誤類型與其檢測機制和相應的反應策略進行了對應:

錯誤類型何時發生檢測機制因應策略
句法編譯/解釋時間編譯器、程式碼檢查器、靜態分析部署前修復
運行時(可恢復)執行try-catch 異常處理使用退避策略重試,回退路徑
運行時(不可恢復)執行try-catch 異常處理受控終止,升級
邏輯執行結果驗證、監控邏輯修正,資料審核
系統執行基礎設施監控、警報故障轉移,優雅降級

錯誤處理不當的後果

錯誤處理不當的後果可分為四大類,每一類都會對營運或業務產生直接影響。只有切實了解這些後果,才能證明在系統化錯誤處理方法上進行工程投資的必要性。

應用程式不穩定和級聯故障

未處理的異常如果傳播到呼叫堆疊頂部,會導致遇到該異常的進程或執行緒終止。在 Web 應用程式中,這表示使用者的請求將不會收到任何回應,或只會收到一個不提供任何有效資訊的通用錯誤回應。在具有活動事務或會話狀態的系統中,事務可能會處於部分完成的狀態,而從資料庫的角度來看,這種狀態是不一致的。

在微服務架構中,未處理的錯誤導致的應用程式不穩定會產生連鎖反應。如果一個服務未能對其外部相依性實施熔斷機制,那麼當這些依賴項運作緩慢或不可用時,該服務會耗盡自身的連線池,嘗試處理那些無法完成的請求。一旦連接池耗盡,無論根本原因是否與上游呼叫者有關,該服務都將無法被其上游呼叫者存取。糟糕的錯誤處理,例如吞噬異常、在錯誤訊息中洩露敏感資料或靜默失敗,是導致漏洞和安全隱患的常見原因。靜默失敗在分散式系統中尤其具有破壞性,因為它允許故障在任何警報觸發之前悄無聲息地傳播。

資料完整性損壞

如果多步驟寫入操作過程中發生的錯誤沒有被封裝在原子事務中,則可能導致系統處於不一致的狀態。一個典型的例子是支付處理:如果向用戶的支付方式扣款成功,但創建相應的訂單記錄失敗且未觸發補償事務,則用戶會被收取一筆系統中不存在的購買款項。事後解決此問題需要手動核對,這既費時費力又容易出錯,而且無法徹底解決問題。

由於錯誤處理不當導致的資料完整性故障往往在事後很久才被發現,此時下游系統已經基於錯誤資料採取了相應的操作。修復成本會隨著錯誤發生到被發現之間的延遲而增加,因此,透過原子事務設計進行預防遠比事後糾正成本低得多。

錯誤輸出帶來的安全漏洞

資料庫錯誤處理不當導致敏感資料洩露,向用戶暴露完整的系統錯誤訊息,使攻擊者能夠獲取所需信息,從而發起更有針對性的攻擊。 OWASP 2025 已將此正式列為十大安全風險之一。 HTTP 回應中暴露的堆疊追蹤資訊包含框架版本、檔案路徑、類別名稱和方法簽章。資料庫錯誤訊息則包含表名、列名和查詢結構。這些細節使得攻擊者能夠更精準地定位目標,而非僅僅依靠猜測,從而大大降低成功發動 SQL 注入或路徑遍歷攻擊所需的難度。

修復此問題需要兩點:首先,所有面向使用者的異常處理程序都應僅傳回適合使用者的訊息,而不應傳回內部詳細資料;其次,內部診斷資訊應記錄在具有適當存取控制的日誌系統中,而不是被丟棄。用戶訊息和診斷訊息用途不同,應獨立產生。

因錯誤處理不一致而產生的維護債務

缺乏標準化錯誤處理方法的程式碼庫會隨著規模的成長而累積大量的維護債務。每個開發人員都採用自己的約定:有的使用自訂異常,有的回傳錯誤代碼,有的在錯誤發生時記錄日誌,有的則不記錄就直接傳播。結果就是,在這樣的系統中,要重現生產環境中的故障​​原因,需要閱讀多個格式不相容的日誌文件,理解不同模組和編寫者之間不同的錯誤處理約定,並且經常會發現,真正的根本原因並沒有被記錄下來,因為相關的 catch 區塊為空,或者只記錄了一條通用訊息,忽略了原始的異常上下文。

軟體工程錯誤處理最佳實踐

以下最佳實務並非風格偏好。每項實踐都針對一種特定的故障模式,當缺少該實踐時,就會發生生產事故。它們按從基礎到高級的順序排列,反映了建構或改造錯誤處理系統的團隊應遵循的順序。

在檢測點將錯誤分類為可恢復或不可恢復。

所有錯誤處理決策都始於一個簡單的分類:此錯誤能否在無需人工幹預的情況下解決,還是需要升級處理或終止進程?此分類應在首次檢測到錯誤時進行,而不應推遲到調用堆疊的更高層級,因為在更高層級中,用於提供分類資訊的上下文資訊已經遺失。

可恢復錯誤是指可以透過重試、回退到備用路徑或降低功能回應來可接受地完成操作的錯誤。不可恢復錯誤是指繼續執行會導致錯誤結果、資料損壞或造成安全漏洞的錯誤。缺少必需的設定檔、關鍵儲存體中偵測到資料損壞以及資源耗盡且沒有備用方案均屬於不可回復錯誤。瞬時網路逾時、來自外部 API 的速率限制回應以及暫時無法使用的輔助服務均屬於可復原錯誤。

將不可恢復的錯誤錯誤分類為可恢復的錯誤並對其應用重試邏輯會導致重試風暴:這是一個無限循環的過程,因為重試無法改善當前狀況,從而消耗本可用於處理其他請求的資源。將可恢復的錯誤錯誤分類為不可恢復的錯誤並終止該過程會導致不必要的停機時間。錯誤分類是一個設計決策,應該針對每種錯誤類型進行記錄,而不是在每個 catch 程式碼區塊中隨意決定。

實作集中式錯誤處理

集中式錯誤處理是指系統中由單一位置負責接收錯誤、對其進行分類、使用標準化元資料記錄錯誤,並確定回應策略。各個模組負責偵測和傳播錯誤,但不負責日誌格式、警報閾值或回應策略。這些參數在集中式處理程序中定義一次,並且一致地應用。

在 Web 應用程式中,集中式錯誤處理通常採用中間件元件的形式,該元件捕獲請求邊界處所有未處理的異常,並將它們連同請求上下文(使用者標識符、請求標識符、端點、持續時間)一起記錄下來,應用分類邏輯,並傳回與錯誤類別相對應的回應。語言框架為此提供了介面:例如 Node.js 中的 Express 中間件。 @ControllerAdvice Spring 中的錯誤邊界元件,React 中的錯誤邊界元件, app.errorhandler 在燒瓶中。

其優點在於一致性。系統中記錄的每個錯誤都採用相同的格式。所有跨越使用者邊界的錯誤都會經過相同的清理邏輯進行過濾。所有超過預設嚴重性閾值的錯誤都會觸發相同的警報。正是這種一致性使得日誌分析和事件反應高效,而非隨意拼湊。

實現帶抖動的指數退避演算法進行重試

不使用退避機制的重試會加劇其試圖解決的問題。如果資料庫暫時過載,一百個客戶端同時以一秒的間隔重試失敗的請求,重試流量可能會完全阻止資料庫復原。指數退避機制會逐步增加重試之間的延遲,從而減輕故障組件的重試壓力,並為其恢復爭取時間。

抖動透過在延遲中引入隨機性來防止重試雪崩:如果所有客戶端都使用相同的確定性退避策略,它們會在每個延遲週期結束後同時重試,從而重現同步問題。將延遲隨機化到一個範圍內,可以確保來自多個客戶端的重試流量在時間上分散,而不是同步發生。

只有當被重試的操作是冪等的時,重試才是安全的,也就是說,多次執行該操作與執行一次產生相同的結果。讀取操作本質上是冪等的。寫入操作必須透過設計來確保冪等性,通常是在請求中包含一個冪等鍵,伺服器使用該鍵來對同一請求的多次發送進行去重:

蟒蛇

import time
import random

def with_retry(operation, max_attempts=4, base_delay_seconds=1.0):
    """
    Execute an operation with exponential backoff and jitter.
    Only retries on recoverable IOError and TimeoutError.
    Propagates all other exceptions immediately without retry.
    """
    for attempt in range(max_attempts):
        try:
            return operation()
        except (IOError, TimeoutError) as exc:
            if attempt == max_attempts - 1:
                raise  # exhausted retries, propagate
            delay = base_delay_seconds * (2 ** attempt) + random.uniform(0, 0.5)
            print(f"Attempt {attempt + 1} failed ({exc}). Retrying in {delay:.1f}s")
            time.sleep(delay)
        except Exception:
            raise  # unrecoverable, do not retry

使用結構化日誌記錄和完整的診斷上下文

如果日誌條目僅包含異常訊息,而沒有關於正在執行的操作、接收到的輸入以及系統當時狀態的上下文訊息,則偵錯工程師必須重現錯誤才能理解其原因。在生產環境中,重現錯誤往往是不可能的。結構化日誌將錯誤捕獲為具有定義字段的物件:ISO 8601 格式的時間戳、嚴重級別、唯一錯誤標識符、模組和函數、完整的堆疊跟踪,以及特定於操作的上下文字段,例如用戶標識符、請求標識符和與失敗操作相關的參數。

這種結構使得我們可以對日誌系統進行一些非結構化日誌文字無法實現的查詢:例如,過去三十分鐘內支付模組的所有逾時錯誤、過去 24 小時內影響用戶 ID 12345 的所有請求錯誤,以及堆疊追蹤中包含對特定函數引用的所有錯誤。這些查詢使得事後分析更有效率。

面向使用者的錯誤訊息與內部日誌條目是兩個不同的概念。日誌條目應包含診斷所需的所有資訊。面向使用者的錯誤訊息不應包含任何洩漏實作細節的內容,而應告知使用者發生了什麼事、是否需要採取任何措施,以及如果問題仍然存在該怎麼辦。

軟體平台應如何通知使用者錯誤

有效的面向使用者的錯誤溝通應遵循四個原則。首先,用使用者能夠理解的語言描述問題,而不是用反映系統內部結構的術語。 「我們目前無法處理您的付款」比「交易回溯:訂單表約束衝突」更合適。其次,指出問題是暫時的還是需要使用者操作的。如果是暫時的服務中斷,則應說「請稍後再試」。如果是驗證錯誤,則應說「請檢查您的卡號是否正確」。第三,對於影響正在進行的交易的錯誤,應明確確認交易的狀態。如果付款未成功,則應明確說明。如果訂單未成功提交,則應明確說明。交易狀態的不確定性是用戶不信任的重要原因。第四,如果使用者無法自行解決問題,則應提供支援途徑。

這些原則的實施要求面向使用者的邊界處的錯誤處理代碼能夠存取錯誤分類(以確定要顯示的訊息類型)、錯誤上下文(使訊息與使用者正在執行的操作相關)以及一個範本系統,該系統可在整個應用程式中產生一致的訊息格式。

設計故障安全機制:當安全控制發生錯誤時拒絕訪問

錯誤處理不當導致的常見安全問題是「失敗開放」安全檢查。所有安全機制都應該在獲得明確授權之前拒絕訪問,而不是在被拒絕之前就授予訪問權限,這正是導致「失敗開放」錯誤的一個常見原因。當身份驗證檢查拋出意外異常時,正確的行為是拒絕訪問。當授權檢查因資料庫錯誤而無法擷取使用者權限時,正確的行為也是拒絕存取。如果拒絕存取的機制失敗,卻傳回授予存取權限的結果,這就是「失敗開放」的定義,OWASP 2025 的 A10 類別明確將其列為嚴重漏洞模式。

在安全控制中實作故障安全錯誤處理意味著將控制項封裝在一個錯誤處理程序中,該處理程序在發生任何異常時都預設執行最嚴格的處理結果。這意味著在安全敏感的上下文中,絕不使用允許程式繼續執行的裸露 catch 區塊。而且,這意味著對安全控制中的錯誤路徑進行與正常路徑同樣嚴格的測試。

分散式系統的錯誤處理設計模式

斷路器模式

熔斷器模式可防止一項服務的故障波及到其所有使用者。當某項服務依賴項的錯誤率超過預設閾值時,熔斷器會開啟並停止向該依賴項轉發請求,立即傳回錯誤或回退回應,而無需等待該依賴項回應。經過一段可設定的等待時間後,熔斷器進入半開狀態,允許少量探測請求通過。如果這些探測請求成功,熔斷器關閉,正常流量恢復。如果探測請求失敗,熔斷器重新打開,等待時間重設。

如果沒有熔斷機制,緩慢或不可用的依賴項會導致呼叫服務的執行緒阻塞,等待可能永遠不會到來的回應。線程池會被填滿,新的請求無法處理,最終呼叫服務本身也會變得不可用。熔斷機制可以將級聯故障轉換為有界故障:依賴項不可用,但呼叫服務仍然能夠正常運行,並且可以處理不依賴該特定依賴項的請求。

隔板圖案

隔板模式透過依賴關係隔離資源池,因此一個資源池的耗盡不會影響不依賴該資源池的請求。在一個呼叫三個外部 API 的服務中,為每個 API 指派獨立的執行緒池意味著,即使大量慢速請求湧向 API A,也只會耗盡 API A 的執行緒池。而對 API B 和 API C 的請求仍能正常處理,因為它們的執行緒池是獨立的。

隔離邊界可以應用於執行緒池級別、連接池級別或進程級別,這取決於隔離的緊迫性以及每種方法引入的開銷。所有方法的原則都相同:一個依賴項的故障不應消耗其他依賴項所需的資源。

分散式事務的 Saga 模式

在業務操作跨越多個服務的分散式系統中,當某個步驟失敗時,維護資料完整性需要一種補償策略。 Saga 模式定義了一系列本地事務,每個事務都有一個對應的補償事務來抵消其影響。如果 Saga 的第 N 步失敗,Saga 將按相反的順序執行第 N-1 步到第 1 步的補償事務,從而將系統恢復到 Saga 執行前的狀態。

Saga 模式無法保證資料庫層面的原子性:它透過補償而非回滾來實現最終一致性。這意味著,在某個步驟成功執行到其補償執行之間的一段時間內,系統可能處於任何業務規則都未預期的狀態。每個步驟的錯誤處理都必須考慮到這一點:補償事務必須是冪等的,並且 Saga 編排器必須能夠應對故障並從上次一致的狀態恢復。

如何防止不安全的輸出處理

在 Web 應用程式中,錯誤訊息處理中的不安全輸出是最常見的漏洞類型之一。攻擊模式非常直接:透過發送格式錯誤的輸入、意外的資料類型或觸發異常路徑的邊界值,強制應用程式產生錯誤。讀取錯誤訊息或 HTTP 回應體,提取其中的實作細節,並利用這些細節來最佳化攻擊。

防止不安全的輸出處理需要滿足以下條件:

切勿在使用者導向的回覆中包含內部異常詳情。 使用者收到的 HTTP 回應體、JSON 錯誤物件和 HTML 錯誤頁應包含適合使用者的錯誤訊息,並可選擇性地包含一個錯誤參考程式碼,以便支援人員尋找內部日誌條目。它們絕不應包含堆疊追蹤、SQL 語句、檔案路徑、類別名稱或框架版本。

驗證錯誤處理程式碼是否經過測試。 針對錯誤情況的單元測試應該斷言錯誤回應不包含哪些內容,以及它包含哪些內容。如果測試僅確認回應狀態為 500,而沒有驗證響應體中是否不包含堆疊追蹤訊息,則該測試對於此漏洞而言是不完整的。

始終使用結構化的錯誤回應格式。 採用標準化的錯誤回應方案,並統一應用於所有端點,可以更輕鬆地審核傳回的訊息,並更容易確保不包含內部細節。而臨時性的錯誤回應格式則容易導致不一致和意外洩漏。

將完整的診斷詳情記錄在內部。 不應出現在使用者回應中的診斷資訊必須記錄在工程團隊可以存取的位置。具有結構化欄位和適當存取控制的日誌系統是合適的儲存位置。日誌記錄呼叫和使用者回應產生應該是錯誤處理程式碼中明確獨立的兩個操作,不應共用同一個訊息字串。

以下具體的 Java 範例展示如何將診斷日誌記錄和麵向使用者的回應分開:

Java的

@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleUnexpectedError(
        Exception ex, HttpServletRequest request) {

    // Full diagnostic context logged internally; never sent to the user
    String errorId = UUID.randomUUID().toString();
    log.error("Unhandled exception [errorId={}] [path={}] [userId={}]",
            errorId,
            request.getRequestURI(),
            getCurrentUserId(),
            ex);  // full stack trace captured in the log entry

    // User-facing response: error ID for support lookup, no internal details
    ErrorResponse response = new ErrorResponse(
            "An unexpected error occurred. Reference: " + errorId,
            Instant.now()
    );
    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}

此模式可確保將堆疊追蹤、異常類別和所有內部上下文擷取到日誌中,而使用者只會收到一個參考程式碼,支援人員可以使用該程式碼檢索對應的日誌條目。

靜態程式碼分析中的錯誤處理缺陷

最容易導致生產事故的錯誤處理缺陷並非程式碼審查員能夠輕易發現的那些顯而易見的缺陷。它們是隨著程式碼庫的成長而悄悄累積的結構性模式:例如,吞噬異常而不記錄日誌的空 catch 區塊、記錄通用訊息而丟棄原始異常的 catch 區塊、呼叫者未檢查的錯誤回傳值,以及在安全敏感程式碼路徑中允許程式在失敗後繼續執行的異常處理程序。除非程式碼審查員專門查找,否則這些模式對他們來說是不可見的。而在大型程式碼庫中,審查每一個 catch 區塊是不切實際的。

靜態程式碼分析工具系統性地解決了這個問題。它們無需執行程式碼,就能將原始程式碼解析成抽象語法樹,並查詢該結構以查找與錯誤處理不當相關的模式。 SonarQube 和類似的工具可以偵測原始程式碼中不安全且不可靠的錯誤處理模式,包括空的 catch 區塊、暴露的堆疊追蹤以及缺少的驗證。這種分析只需一次即可覆蓋整個程式碼庫,而不僅僅是最近更改過的檔案或最近發生過問題的模組。

對於混合使用多種語言的企業系統,分析必須涵蓋環境中存在的所有語言。例如,一個能夠正確處理錯誤的 Java 服務,如果透過一個不將錯誤從主機層傳遞過來的介面呼叫 COBOL 程序,則其錯誤處理有缺陷,而僅使用 Java 的靜態分析無法發現這一缺陷。正如在…的上下文中所討論的 跨語言的企業靜態程式碼分析對系統中每種語言進行統一分析,是找出系統層級而非檔案層級錯誤處理缺陷的技術前提。

對於遺留系統而言,錯誤處理債務通常集中在程式碼庫中最古老的部分,這些部分的錯誤處理約定是在現代實踐標準化之前建立的。如分析所考察的: 遺留系統的現代化改造與錯誤處理從分散、不一致的錯誤處理方式遷移到集中、標準化的方法是一項現代化任務,而自動化工具能夠在進行任何更改之前識別當前狀態,這將使這項任務受益匪淺。

SMART TS XL 解決系統規模下的錯誤處理問題

SMART TS XL 該模型建立了一個統一的軟體環境交叉引用模型,涵蓋了包括 COBOL、JCL、Java、.NET、Python、JavaScript、TypeScript 和 SQL 在內的所有語言和平台的原始程式碼,並建立了一個表示所有元件之間關係的結構化索引。對於錯誤處理分析,該模型可以回答單語言工具無法解答的問題:COBOL 程式中的哪些函數會將錯誤傳播給其呼叫者,這些函數的哪些呼叫者會處理傳播的錯誤,以及系統中哪些路徑可以在呼叫鏈中沒有任何錯誤處理的情況下到達面向使用者的輸出。

該平台的分析影響功能將其擴展到變更評估:在修改共享組件的錯誤處理行為之前,分析影響會識別系統中所有依賴當前行為的其他組件,以便可以分階段進行變更並進行驗證,而不是在未知下游後果的情況下部署變更。這就是文中所描述的分析。 影響分析解決方案 IN-COM 為企業環境提供的解決方案,專門用於在進行錯誤處理邏輯變更之前,了解變更該變更會產生哪些影響。

SMART TS XL企業級搜尋功能使分析過程更易於操作:查詢系統中所有捕獲異常但未記錄日誌的函數,即可返回特定的文件位置和函數名稱,並按語言和缺陷嚴重程度(基於調用該函數的次數)進行排序。這種優先排序使得錯誤處理缺陷的修復工作切實可行,而非令人不知所措。

錯誤處理作為系統級屬性

有效的錯誤處理並非單一模組的固有屬性。即使某個模組能夠正確處理自身的錯誤,但如果其運行的系統缺乏集中式日誌記錄、外部依賴項的熔斷機制以及多步驟寫入操作的原子事務設計,仍然會導致難以診斷的生產事故。模組層面的正確性固然必要,但並非充分條件。

使整個應用程式的錯誤處理有效的系統級屬性包括:一致的錯誤分類,以便在每一層都對可恢復和不可恢復的情況進行不同的處理;集中式日誌記錄,以便將所有錯誤事件捕獲到一個具有標準化元資料的可查詢系統中;所有外部依賴項上的斷路器,以便一個依賴項的故障不會耗盡其他依賴項所需的資源;所有多步驟寫入的原子事務設計,以便部分完成不會產生不一致的狀態;以及所有安全敏感程式碼路徑中的故障安全預設值,以便存取控制檢查中的錯誤拒絕而不是授予存取權限。

將這些特性建構到目前尚不具備的系統中是一個循序漸進的過程,而非一次性的重構事件。切實可行的方法是:首先進行靜態分析以識別當前存在的缺陷,然後根據這些缺陷對穩定性和安全性的潛在影響對其進行優先排序,最後從風險最高的模式開始逐步修復。最終目標是建立一個系統,在這個系統中,工程師無需在編寫每個新功能時都考慮錯誤處理,因為這些模式已經標準化,框架會強制執行,並且持續整合 (CI) 管線會驗證新程式碼是否引入了團隊已達成共識要消除的反模式。