COBOL 的文件處理表面上看起來非常簡單。您只需在環境部 (ENVIRONMENT DIVISION) 中聲明文件,在資料部 (DATA DIVISION) 中描述其記錄佈局,然後在過程部 (PROCEDURE DIVISION) 中使用 OPEN、READ 或 WRITE 以及 CLOSE 語句。四個動詞,一個生命週期。問題在於,COBOL 程式很少能保持簡單,它們會在數十年的維護過程中累積邏輯、異常處理路徑、條件分支和 PERFORM 呼叫。最初簡潔的單次開啟、單次關閉模式會悄悄演變成更複雜的情況:在同一執行路徑中開啟兩次檔案、在沒有檔案開啟時執行 CLOSE 語句、錯誤退出時跳到完全超出 CLOSE 語句的分支。
這些問題都不會立即顯現。程式可以編譯,而且在你測試的輸入條件下通常都能正常運作。但在你從未預料到的輸入組合下,程式會悄無聲息地崩潰,或者批次視窗的效能會逐漸下降,以至於沒有人能將原因與結果聯繫起來。靜態分析會在這些問題發生之前就發現它們。
值得發現的四個文件狀態缺陷
在深入探討檢測機制之前,這裡先列出一個清楚的分類,說明你需要尋找的內容。並非所有的 COBOL 文件問題都相同。
| 缺陷 | 運行時發生了什麼 | 嚴重性 |
|---|---|---|
| 雙開 | 對已開啟的檔案執行 OPEN 操作 → 檔案狀態 41 或異常終止 | 高 |
| 未事先開盤即關閉 | 關閉一個從未開啟過的檔案 → 檔案狀態 42 或異常終止 | 高 |
| 正常出口缺少關閉按鈕 | 停止執行時檔案仍處於開啟狀態 → 作業系統會將其關閉,但未刷新的緩衝區有資料遺失的風險。 | 媒材 |
| 缺少錯誤退出時的 CLOSE 語句 | 程式在開啟檔案時異常終止 → VSAM 檔案可能出現記錄損壞 | 高 |
| 循環中冗餘的 OPEN/CLOSE 操作 | 每次迭代都開啟和關閉檔案 → 嚴重的 I/O 開銷 | 中等偏上 |
關鍵要點:最嚴重的缺陷(雙重 OPEN 和無 OPEN 的 CLOSE)會導致立即運行時故障或資料完整性風險。效能缺陷(循環級開啟/關閉)則不會產生任何錯誤,只會導致批次作業運作緩慢,且無人能追蹤到原因。
是什麼讓這個內容難以手動找到?
難點不在於辨識模式。任何開發人員都知道不應該兩次開啟同一個檔案。困難在於 COBOL 控制流的非線性特性,這使得手動追蹤變得不可靠。
考慮一個具有以下結構的程序:
科博爾
PROCEDURE DIVISION.
MAIN-LOGIC.
PERFORM INITIALIZATION
PERFORM PROCESS-RECORDS
PERFORM TERMINATION
STOP RUN.
INITIALIZATION.
OPEN INPUT CUSTOMER-FILE
OPEN OUTPUT REPORT-FILE
MOVE 0 TO WS-ERROR-FLAG.
PROCESS-RECORDS.
READ CUSTOMER-FILE INTO WS-CUSTOMER-REC
AT END MOVE 1 TO WS-EOF-FLAG
END-READ
IF WS-ERROR-FLAG = 1
PERFORM ERROR-EXIT
END-IF
PERFORM UNTIL WS-EOF-FLAG = 1
PERFORM PROCESS-ONE-RECORD
READ CUSTOMER-FILE INTO WS-CUSTOMER-REC
AT END MOVE 1 TO WS-EOF-FLAG
END-EXEC
END-PERFORM.
ERROR-EXIT.
DISPLAY 'Error in processing'
CLOSE CUSTOMER-FILE *> CLOSE here...
STOP RUN.
TERMINATION.
CLOSE CUSTOMER-FILE *> ...and CLOSE here too
CLOSE REPORT-FILE
STOP RUN.
無需追蹤每一條執行路徑就能發現缺陷嗎? WS-ERROR-FLAG 內部設定為 1 PROCESS-ONE-RECORD (或許透過呼叫子程序),控制流向 ERROR-EXIT這會關閉文件並停止操作。沒錯。但是如果 WS-ERROR-FLAG is 不會 循環結束。 PROCESS-RECORDS 回報,以及 TERMINATION 運行,也關閉。 CUSTOMER-FILE. 兩次關閉,一次開啟。運行時文件狀態 42。
這是一個簡單的三段式程式。而實際的程式通常包含二十個段落,有條件 PERFORM 調用,遺留程式碼中還有 GO TO 語句,以及本身也會調用其他例程的異常處理例程。手動追蹤不僅容易出錯,而且難以處理。
靜態分析模型如何文件狀態
靜態分析透過建立程式的控制流程圖並沿著每條路徑傳播檔案狀態來偵測這些缺陷。
分析過程會為每個文件分配一個狀態變量,該變量有三個可能的值:
CLOSED文件尚未打開,或已成功關閉OPEN文件已開啟但尚未關閉UNKNOWN狀態無法靜態確定(例如,在未完全分析的分支中條件性開啟)
每次執行 OPEN 語句時,分析都會檢查:此檔案是否已處於該狀態 OPEN如果是,則為雙開路缺陷。
每次 CLOSE 語句執行時:此檔案是否處於該狀態 CLOSED or UNKNOWN如果已關閉 → 不會出現開路缺陷。
在每個執行 STOP RUN 或 EXIT PROGRAM 的路徑處:是否有任何檔案仍處於該狀態 OPEN如果是,則表示缺少關閉缺陷。
對於迴圈,分析會檢查 OPEN 或 CLOSE 語句是否被迴圈頭支配,這表示它會在每次迭代中執行。
小心: 對於任何分析器來說,最棘手的情況都是對共享例程的 PERFORM 呼叫。
ERROR-HANDLER程式中十二個不同的位置都會呼叫 PERFORM 函數,呼叫 PERFORM 函數時的檔案狀態決定了是否關閉內部檔案。ERROR-HANDLER正確或錯誤。不透過 PERFORM 鏈傳播狀態的分析器會產生漏報(漏掉真正的缺陷)或誤報(標記正確的程式碼)。
循環級 OPEN/CLOSE 操作:一種沒有錯誤代碼的效能缺陷
這種模式不會產生執行階段錯誤,也不會出現檔案狀態異常。它只是運行速度很慢,可能比必要的速度慢幾個數量級。
科博爾
*> PROBLEMATIC: file opened and closed on every iteration
PROCESS-ALL-REGIONS.
PERFORM VARYING WS-REGION-ID FROM 1 BY 1
UNTIL WS-REGION-ID > 10
OPEN INPUT CUSTOMER-FILE
PERFORM PROCESS-REGION-RECORDS
CLOSE CUSTOMER-FILE
END-PERFORM.
除非文件按區域進行實體分區,否則這種方法會造成不必要的開銷。實際上,更好的做法是只開啟檔案一次,讀取所有記錄,然後在記憶體中或透過邏輯進行過濾。
每次對 VSAM 檔案執行 OPEN 操作都會涉及目錄查找、ACB 初始化和緩衝區池分配。每次 CLOSE 操作都會重新整理緩衝區、更新目錄並釋放 ACB。對於包含十個區域的文件,這需要執行十次操作,而不是一次。對於包含 10,000 筆記錄和 1,000 個區域的文件,計算量將非常龐大。
科博爾
*> CORRECT: open once, filter inside the loop
PROCESS-ALL-REGIONS.
OPEN INPUT CUSTOMER-FILE
PERFORM VARYING WS-REGION-ID FROM 1 BY 1
UNTIL WS-REGION-ID > 10
PERFORM PROCESS-REGION-RECORDS
END-PERFORM
CLOSE CUSTOMER-FILE.
靜態分析透過辨識迴圈主導的 OPEN 和 CLOSE 語句來偵測這種情況,也就是說,每次執行迴圈體都會執行 OPEN 或 CLOSE 語句。這種偵測要求控制流程圖能夠表示循環結構,而不僅僅是語句順序。
文件狀態:程式的文件狀態訊號,如果您檢查它的話
每次 COBOL 檔案操作都會設定 FILE-CONTROL 條目中定義的 FILE STATUS 欄位。程式在每次 OPEN、READ、WRITE 和 CLOSE 操作後檢查 FILE STATUS,就能在執行時偵測並回應所有檔案狀態異常。而忽略 FILE STATUS 的程式則如同盲人摸象。
清單:每個 COBOL 開發人員都應該知道的文件狀態代碼
00成功完成10文件結束(讀取:沒有更多記錄)35文件未找到(OPEN INPUT:文件不存在)41檔案已開啟(對處於開啟狀態的檔案執行 OPEN 操作)42文件未開啟(對處於關閉狀態的文件執行 CLOSE 或 READ 操作)47嘗試讀取未開啟的檔案 INPUT 或 IO。48嘗試對未開啟 OUTPUT、IO 或 EXTEND 的檔案執行 WRITE 操作97文件已成功開啟(IBM 特有,某些環境)
科博爾
FILE-CONTROL.
SELECT CUSTOMER-FILE
ASSIGN TO CUSTFILE
FILE STATUS IS WS-CUST-FILE-STATUS.
WORKING-STORAGE SECTION.
01 WS-CUST-FILE-STATUS PIC XX.
PROCEDURE DIVISION.
OPEN INPUT CUSTOMER-FILE
IF WS-CUST-FILE-STATUS NOT = '00'
DISPLAY 'OPEN failed: ' WS-CUST-FILE-STATUS
PERFORM ABEND-ROUTINE
END-IF.
小心: 常見的維護錯誤是在 FILE-CONTROL 條目中聲明了 FILE STATUS,但從未引用
WS-CUST-FILE-STATUS在過程部中。該欄位在每次文件操作後都會被填充,但如果沒有程式碼檢查它,程式會靜默地忽略錯誤。靜態分析可以標記出這種模式:文件狀態已聲明,但從未在條件邏輯中引用。
三種導致缺陷的真實 COBOL 模式
模式 1:共享錯誤退出問題
一個程式包含多個處理段,每個處理段都有自己的錯誤處理程序。有些錯誤處理程序會在返回前關閉檔案。正常的終止例程也會關閉檔案。如果發生錯誤,且復原程序在錯誤處理程序之後繼續進行,則檔案會關閉兩次。
科博爾
VALIDATE-RECORDS.
READ CUSTOMER-FILE INTO WS-REC
AT END MOVE 1 TO WS-EOF
END-READ
IF WS-CUST-FILE-STATUS NOT = '00' AND '10'
CLOSE CUSTOMER-FILE *> closes here on read error
PERFORM WRITE-ERROR-LOG
GO TO TERMINATION *> skips to close in TERMINATION?
END-IF.
TERMINATION.
CLOSE CUSTOMER-FILE *> double close if GO TO reached here
CLOSE REPORT-FILE
STOP RUN.
这 GO TO TERMINATION 將控制權直接轉移給 TERMINATION它執行自己的 CLOSE CUSTOMER-FILE文件狀態 42。
解決方法:使用單一的集中式關閉例程。每個退出路徑都會呼叫同一個例程,該例程在關閉檔案前會檢查檔案是否已開啟。
模式 2:條件開放
文件僅在特定處理模式下才會有條件地開啟。但 CLOSE 操作是無條件的,它會在所有執行路徑中發生,包括從未開啟該檔案的路徑。
科博爾
INITIALIZATION.
IF WS-PROCESSING-MODE = 'FULL'
OPEN OUTPUT AUDIT-FILE *> only opened in FULL mode
END-IF.
TERMINATION.
CLOSE AUDIT-FILE *> ALWAYS closes -- FILE STATUS 42 in PARTIAL mode
STOP RUN.
如果測試始終以完整模式運行,則此缺陷在測試中不可見。它會在生產環境中首次以部分模式運作時顯現出來。
模式 3:跨編譯單元執行
在使用 CALL 呼叫子程式的程式中,主程式中開啟的檔案可能會被傳遞給子程式關閉,然後主程式再次關閉該檔案。
科博爾
*> Main program
CALL 'SUBPROG1' USING CUSTOMER-FILE-STATUS
*> SUBPROG1 closes CUSTOMER-FILE internally
CLOSE CUSTOMER-FILE *> double close
這種模式需要進行過程間分析,追蹤 CALL 邊界上的檔案狀態,從而了解子程式的行為,而簡單的程式內分析無法偵測到這種行為。
靜態分析器要做好這一點需要哪些條件
並非所有靜態分析工具都能對 COBOL 檔案進行同等深度的分析。以下是評估分析能力的決策清單:
該工具是否支援 PERFORM 鏈? COBOL 程式使用 PERFORM 來呼叫指定的段落和節。 PERFORM 目標內部的檔案操作必須對呼叫者的狀態追蹤可見。
該工具是否支援內聯執行可變操作(循環)?需要進行循環檢測以識別以循環為主的開啟/關閉操作。
它是否能追蹤 GO TO 指令之間的狀態?傳統的 COBOL 程式大量使用 GO TO 指令。如果分析器無法追蹤控制流程圖中的 GO TO 邊,就會遺漏整類缺陷。
它是否支援 COPY 和 REPLACE 操作?文件處理程式碼通常位於 COPY 成員中。如果分析器在分析前不展開 COPY 成員,則會遺漏包含程式碼中定義的操作。
它是否檢查文件狀態的使用情況?完整的分析不僅檢查文件狀態是否已聲明,還檢查每次操作後是否實際檢查了文件狀態。
它是否執行過程間分析?執行檔案操作的被呼叫子程序會建立跨程式狀態依賴關係。真正的覆蓋率需要追蹤呼叫鏈。
關鍵要點:僅檢查單一段落或章節的工具會遺漏大多數實際存在的缺陷。那些在生產環境中真正重要的缺陷,那些需要數年才能顯現、數小時才能診斷出來的缺陷,往往跨越多個章節、條件分支和呼叫例程。
如何處理結果:優先排序框架
靜態分析發現的缺陷並非都同等重要。以下是如何對缺陷進行分類:
立即修復(在下次批量運行之前):
- 在任何執行路徑中,如果檔案狀態碼為 41,則雙重開啟操作會導致異常終止。
- 在生產環境中執行的路徑中,缺少 OPEN 的 CLOSE 語句會導致 CLOSE 語句被停用。
- VSAM 檔案錯誤退出路徑缺少 CLOSE 語句(寫入完整性風險)
下一階段的日程安排:
- 循環主導的 OPEN/CLOSE 操作,對批次視窗有可衡量的影響
- 關鍵文件(審計、交易、報告)缺少文件狀態檢查
- 多模式程式中的條件開啟與無條件關閉
在下一輪現代化改造中追蹤並解決以下問題:
- 缺少文件狀態聲明
- 僅在正常退出路徑上呼叫 CLOSE 函數(作業系統會處理,但這不是好的做法)。
- 程式間缺陷,其修復需要協調多個程式之間的變更
SMART TS XL 分析 COBOL 文件狀態
SMART TS XL“ 靜態程式碼分析 它為環境中每個 COBOL 程式建立控制流程模型,展開 COPY 成員,追蹤 PERFORM 鏈,追蹤 GO TO 邊,並將檔案狀態傳播到每個條件語句的每個分支。它不是模式匹配掃描;它是程式在執行過程中每個可達點實際執行操作的結構模型。
應用程式依賴關聯式映射功能將此擴展到過程間維度:當 COBOL 程式呼叫執行檔案操作的子程式時,依賴關係映射會表示這種關係,從而實現跨編譯單元邊界的分析。跨越主程式和被呼叫子程式的雙重 OPEN 缺陷在結構模型中清晰可見,而這在任何程式內部分析中都無法實現。
企業級搜尋功能可大規模地實現結果的可操作性:在幾秒鐘內,即可在數百萬行 COBOL 程式碼中找到組合中開啟特定資料集的每個程式、包含 CLOSE 語句的每個 COPY 成員、以及包含 OPEN 語句的 PERFORM 目標的每個程式。對於準備將批次工作負載遷移到雲端的現代化團隊而言,此搜尋功能可將耗時數週的手動審核簡化為一次精確的查詢。
JCL擴充功能增加了操作上下文:哪些 JCL 作業步驟呼叫每個程序,每個 DD 語句引用哪些資料集,以及 COBOL 程式碼中的檔案處理如何連接到 JCL 中定義的實體資料集。 JCL 定義為關鍵輸出資料集的檔案上的 CLOSE 缺陷優先權高於暫存工作檔案上的相同缺陷,而這種優先權排序需要了解每個 COBOL 程式的 JCL 上下文。
更廣泛的啟示:文件狀態只是其中一個維度
冗餘檔案操作是一個更普遍問題的具體實例:經過幾十年發展演變的 COBOL 程式包含狀態相關的行為、檔案狀態、開關狀態、計數器狀態,只有透過追蹤整個控制流程圖中的每條可能的執行路徑才能正確理解這些行為。
人工審查這類程式碼速度慢、不全面且缺乏一致性。不同的開發人員會發現不同的缺陷。即使是同一個開發人員,兩次審查同一個程式也會發現不同的缺陷。靜態分析則系統化:它對每個程式中的每個路徑都應用相同的規則,每次都如此,不會出現疲勞或假設。
對於管理大型 COBOL 程式碼庫的團隊而言,無論是日常維護、遺留系統現代化改造,還是合規性審計,系統化文件狀態分析的回報不僅僅在於發現缺陷,更在於確保程式碼庫已得到全面分析,並且所有遺留缺陷都已被發現而非隱藏。


