突然要讀 COBOL 原始碼時該知道的最低限度 - 先整理 DIVISION / PIC / COMP-3 / COPY / PERFORM
· 小村 豪 · COBOL, 舊技術, 業務系統, 維護, Mainframe
交接、故障對應、廠商包裝產品的維護。在那樣的場面中,有一天突然 COBOL 原始碼飛來的情況。
- 檔案名是
.cbl或.cpy - 變數名全部大寫
01、05、77、88排列- 出現
PIC S9(7)V99 COMP-3這種咒語和會計軟體中間的敘述 - 而且
COPY滿滿,只開了檔案也看不見全體
這一帶腦袋稍微變粉末。
但讀的地圖沒那麼大。COBOL 雖有處理系或產品的差異,但讀既有業務系統時該先掌握的骨架相當共通。本文以 IBM 系或典型的業務 COBOL 為念頭,整理 給突然要讀原始碼的人的最小組合。
1. 先講結論(一句話)
先相當粗略但實務上有用的說法如下。
- COBOL 是 邏輯的語言 之前,更強地是 記錄定義的語言
- 只讀
PROCEDURE DIVISION只能懂一半。先看DATA DIVISION PIC是 項目的形,USAGE是 以什麼表現持有COMP-3是 packed decimal。在金額或件數的世界常見88不是別變數,是 給前一項目的值命名的條件名REDEFINES是 以不同形式看同一記憶體 的機制。不是複製- 若有
COPY,現在打開的原始碼還未完成。不看 copybook 看不見全體 - 追
PERFORM、IF、EVALUATE、READ、WRITE、CALL就能大致掌握流程 - 舊原始碼是 欄位位置有意義的固定格式。外觀的空白不是單純的裝飾1
簡言之,DIVISION、PIC、USAGE、COMP-3、REDEFINES、OCCURS、88、COPY、PERFORM。這些讀得懂,迷路率大幅下降。
2. COBOL 先當「資料形式」的語言
用 C# 或 Java 的感覺讀,最初會想追 if 或 for 或函式呼叫。
但 COBOL 在那之前先掌握 「這程式接收什麼記錄、做出什麼記錄、持有什麼緩衝」 比較快。
典型的業務 COBOL 大致下列流程。
- 從檔案或 DB 讀記錄
- 放到
WORKING-STORAGE上的項目 - 條件分支
- 重新填到別記錄
- 寫出
也就是 比起演算法,佈局先站。
例如這種骨架。
IDENTIFICATION DIVISION.
PROGRAM-ID. SAMPLE01.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT SALES-FILE ASSIGN TO ...
DATA DIVISION.
FILE SECTION.
FD SALES-FILE.
01 SALES-REC.
05 SALE-ID PIC 9(8).
05 SALE-AMOUNT PIC S9(7)V99 COMP-3.
WORKING-STORAGE SECTION.
01 WS-EOF PIC X VALUE 'N'.
88 EOF VALUE 'Y'.
PROCEDURE DIVISION.
PERFORM UNTIL EOF
READ SALES-FILE
AT END
SET EOF TO TRUE
NOT AT END
PERFORM PROCESS-SALE
END-READ
END-PERFORM
STOP RUN.
讀這段程式時先該看的不是 PERFORM,而是 SALE-AMOUNT 的形或 EOF 的意義。
COBOL 以這個順序讀就突然安靜。
3. 先看 4 個 DIVISION
COBOL 原始碼先大致分成 4 個 DIVISION。
| DIVISION | 先看的 |
|---|---|
IDENTIFICATION DIVISION |
程式名、舊註解、由來 |
ENVIRONMENT DIVISION |
檔案、外部資源、輸入輸出的前提 |
DATA DIVISION |
記錄定義、工作區、引數 |
PROCEDURE DIVISION |
實際的處理步驟 |
特別重要的是以下。
FILE SECTION有輸入輸出檔案的記錄定義WORKING-STORAGE SECTION有平時使用的變數、旗標、計數器、工作緩衝LOCAL-STORAGE SECTION可能有每次呼叫都初始化的區域LINKAGE SECTION可能有從外面傳來的引數或子程式的接收口
看到 LINKAGE SECTION 和 PROCEDURE DIVISION USING ... 的話,該程式可能不是單獨完結,而是接收外部資料動作。
4. 不要害怕固定格式的外觀
在舊 COBOL 中,原始碼 1 行的 欄位位置本身 有意義。不知道這個看,「為什麼左邊有奇怪的空白」永遠不懂。1
固定格式中,粗略如下。
- 1 - 6 欄: 序號
- 7 欄: indicator
- 8 - 11 欄: Area A
- 12 - 72 欄: Area B
第 7 欄特別重要。
*或/: 註解行-: 延續行D: debugging line*>: 途中也可寫的註解
為降低外觀壓力,粗略畫成圖如下。
1234567 8901 23456789012345678901234567890
* 註解
IDENTIFICATION DIVISION.
PROGRAM-ID. SAMPLE01.
這裡的空白不是現代意義的「整形」,部分是語法。 編輯器 tab 轉換、靠左、粗略複製貼上會普通地壞。 看舊原始碼時,先懷疑該檔案是 fixed format 還是 free format。fixed 卻套現代 formatter,會相當景氣地爆炸。
5. DATA DIVISION 的最低限
5.1 層級編號
COBOL 的資料定義不是用縮排,而是用 層級編號 做階層。2
01 WS-ORDER.
05 WS-ORDER-ID PIC 9(8).
05 WS-AMOUNT PIC S9(7)V99 COMP-3.
05 WS-STATUS PIC X.
88 WS-OK VALUE '0'.
88 WS-ERROR VALUE '9'.
77 WS-COUNT PIC 9(4).
至少記以下就夠。
01: 一整包最上位的記錄、群組02-49: 其下的階層77: 獨立的單項目88: condition-name。給前一項目的值命名366:RENAMES用。相遇率不高但存在
重要的是不要把 88 當成 bool 的別變數。
不是 WS-OK 這區域另外存在,而是 WS-STATUS 為 '0' 時能以 WS-OK 這個名字讀的感覺。
另一個重要的是 決定階層的不是空白而是層級編號。
外觀的縮排可參考,但最終要信的是 01 / 05 / 10 / 88。2
5.2 PICTURE
PIC 表示該項目的 形。
最常見的如下。
| 記法 | 粗略意義 |
|---|---|
X |
文字 |
9 |
數字 |
S |
有符號 |
V |
只在邏輯上有小數點 |
X(10) |
10 個字 |
9(5) |
5 位數值 |
S9(7)V99 |
有符號,整數 7 位 + 小數 2 位 |
例如,
PIC X(10)→ 10 個字PIC 9(5)V99→ 5 位整數 + 2 位小數PIC S9(7)V99→ 有符號 7 位整數 + 2 位小數
。
這裡特別重要是 V。
V 沒有實際的 . 字元。
PIC 9(5)V99 被視為「小數點 2 位的數」,但資料上不含點字元。
所以把檔案或傾印當「外觀的字串」解釋通常會跌倒。
5.3 USAGE / DISPLAY / COMP / COMP-3
若 PIC 是形,USAGE 是 以什麼表現持有。
至少掌握以下就能讀相當多。45
| 記法 | 粗略意義 | 讀時注意 |
|---|---|---|
DISPLAY |
作為字元可見的外部 10 進位 | mainframe 中可能以 EBCDIC 為前提6 |
COMP / BINARY |
2 進數 | 外觀位數和內部表現不同 |
COMP-3 / PACKED-DECIMAL |
packed decimal | 當字元讀會看起來壞了 |
例如,
01 WS-AMOUNT-DISP PIC S9(7)V99.
01 WS-AMOUNT-BIN PIC S9(7) COMP.
01 WS-AMOUNT-PACK PIC S9(7)V99 COMP-3.
這 3 個全是「數值」,但 內容的持有方式不同。
實務上最有效的是看到 COMP-3 瞬間的反應。
- 它是 packed decimal
- 可能是金額、稅額、件數、比率系
- 當文字看壞了是理所當然
- 以 CSV 或 UTF-8 心情看會出事
有這理解,看傾印或二進位檔案的表現時不會徒然 panic。
再補充一個,DISPLAY 也未必是 ASCII 字串。
z/OS 系以 EBCDIC 為前提,就算數字看起來像文字,位元組值與 ASCII 的 '0' - '9' 不同 的情況存在。6
5.4 REDEFINES / OCCURS / COPY / FILLER
這 4 個是讀時的卡點。
REDEFINES
REDEFINES 是 以不同形式看同一區域 的機制。不是複製。7
01 REC-BUF.
05 REC-TYPE PIC X.
05 REC-DATA PIC X(99).
01 HEADER-REC REDEFINES REC-BUF.
05 HDR-TYPE PIC X.
05 HDR-DATE PIC 9(8).
05 FILLER PIC X(91).
這接近 C 系的 union 感覺。
「把 1 個 100 位元組以不同記錄種別區分」 這種寫法常見。
OCCURS
OCCURS 是陣列。COBOL 中常稱 table。
05 WS-ITEM OCCURS 12 TIMES.
10 WS-PRICE PIC 9(5).
還有若出現 OCCURS DEPENDING ON,是 可變長度表格。
此情況可能影響到後續項目的位置,以固定長度心情追會踩空。8
COPY
COPY 是 compile time 的 include。
也就是現在打開的原始碼 可能還不是完成形。9
COPY CUSTOMER-REC.
COPY ERROR-MAP.
記錄定義、共通旗標、SQL 用的 host variable、外部介面被塞進 copybook 相當平常。
COPY 多難讀時,確認是否能看展開後原始碼或 compiler listing 比較快。IBM Enterprise COBOL 有個叫 MDECK 的選項,用於寫出函式庫處理後的輸入原始碼。10
FILLER
FILLER 是沒名字的項目。
但「因為不參照所以無意義」不是。
- 保留區
- 舊規格的相容用孔
- 記錄長對齊
REDEFINES用的空白
都普通有效。
FILLER 只是沒名字但位元組數存在。 忘這個,與外部檔案的對應會 1 位元組 1 位元組地偏移。
6. PROCEDURE DIVISION 的最低限
DATA DIVISION 是地圖的話,PROCEDURE DIVISION 是移動路線。
6.1 PERFORM
PERFORM 是 COBOL 的基本控制移動。
粗略說是 呼叫處理後返回。11
常見形式如下。
PERFORM INIT-PROC
PERFORM UNTIL EOF
PERFORM READ-PROC
IF NOT EOF
PERFORM EDIT-PROC
PERFORM WRITE-PROC
END-IF
END-PERFORM
PERFORM 大致有 2 系。
- 指定段落或節的 out-of-line
PERFORM - 當場寫塊的 inline
PERFORM ... END-PERFORM
舊程式碼中還有 PERFORM A-100 THRU A-199 這種 範圍指定 普通出現。
這方便但中途加段落會發生牽連事故,讀時好好看範圍的終端。
6.2 IF / EVALUATE / 範圍
條件分支基本是 IF。
EVALUATE 想成 switch/case 式大致對。
要注意的是 範圍的結束方式。12
END-IFEND-PERFORMEND-READ
有 明確終端 的程式碼還好讀。
問題是舊程式碼。COBOL 中 . 作為 隱性 scope terminator 動作,一口氣結束還沒關閉的語句。12
也就是說,僅 1 個句號,
- 到哪是
IF - 到哪是
PERFORM - 在哪移到下個 sentence
會變。
此外 NEXT SENTENCE 和 CONTINUE 不同。
NEXT SENTENCE 進到下個句號後面,所以後續 . 的位置會改變飛往哪。12
讀舊 COBOL 時,不看行尾而看句號 剛好。
6.3 READ / WRITE / CALL
業務 COBOL 頻繁出現的是這一帶。
READWRITEREWRITESTARTCALL
特別 READ ... AT END ... 是王道。
READ IN-FILE
AT END
SET EOF TO TRUE
NOT AT END
PERFORM PROCESS-REC
END-READ
若有 CALL 'SUBPGM' USING ... 會飛到別程式。
那時看被呼叫側的 LINKAGE SECTION 和 PROCEDURE DIVISION USING,傳遞的形相當可見。
7. COBOL 的外側的東西
COBOL 原始碼單獨不完整世界的情況相當多。
- 檔案定義
- 執行環境
- DB 連線
- 交易環境
- job 控制
分在外側。
至少掌握以下會較好讀。
檔案和 FILE STATUS
ENVIRONMENT DIVISION 的 FILE-CONTROL 和 DATA DIVISION 的 FILE SECTION / FD 成套讀。13
SELECT IN-FILE ASSIGN TO ...
FILE STATUS IS WS-FS.
FD IN-FILE.
01 IN-REC.
05 ...
若有 FILE STATUS,各 I/O 後的結果碼會進入。
讀檔案系的故障或 EOF 判定,不看這個無法開始。14
EXEC SQL
出現這個的話是嵌入 SQL。
EXEC SQL
SELECT ...
END-EXEC.
此情況 COBOL 是「host variable 的容器」,實際取得條件或更新對象在 SQL 側。
所以 把 EXEC SQL 的內容當普通 SQL 讀 是捷徑。
EXEC CICS
出現這個的話是 CICS 的交易脈絡。15
EXEC CICS
RECEIVE MAP(...)
END-EXEC.
這瞬間不是單純的批次解讀了。 需要包含畫面、交易、回應碼、COMMAREA 等外部脈絡一起讀。
JCL 或執行定義
mainframe batch 中,實際分配哪個資料集 或 job 以什麼順序流 在 COBOL 原始碼外的情況不罕見。 只看原始碼不懂「這檔案在哪」,不是程式碼差,是看的範圍不夠,這很普通。
8. 最低限的讀取順序
突然要讀 COBOL 時,以下順序安全。
- 盤查所有
COPY能開 copybook 就開。不行的話找 listing 或展開後原始碼 - 撿
01層級的記錄定義 把FILE SECTION、WORKING-STORAGE、LINKAGE SECTION的最上位列表化 - 讀
PIC和USAGE辨識金額、日期、件數、代碼、旗標 - 搜尋
READ/WRITE/REWRITE/CALL/EXEC SQL/EXEC CICS先掌握輸入輸出和外部邊界 - 只追第一條主路徑
從
PROCEDURE DIVISION的開頭追PERFORM鏈 - 看
88和 status 項目 EOF、正常/異常、種別碼的意義變好讀 - 在
REDEFINES/OCCURS DEPENDING ON/COMP-3做記號 之後一定有效,先當危險物標記 - 若是檔案就看
FILE STATUS能大幅減少 I/O 錯誤系的誤讀
這順序不用一開始全文精讀。 COBOL 不如從 100% 理解開始,先掌握 記錄、外部邊界、主路徑 3 點後再進細節輕鬆得多。
9. 常見的卡點
最後整理新手很高機率卡的地方。
以為 REDEFINES 是「別的變數」
不對。 以不同形式讀同一區域。改寫一邊,另一邊的表現也變。7
以為 88 是「獨立的 bool」
不對。
只是給前一項目的值命名。SET WS-OK TO TRUE 在背後對基底項目寫入對應值。3
忽視 COPY 只讀本文
那是帶著一半的地圖進山。 欄位定義、共通旗標、host variable 常成群在外面。9
以為 MOVE 是單純賦值
MOVE 不是單純的 memcpy。
視接收側的型,可能加入轉換、位數對齊、零填、截斷、編輯・反編輯。16
輕視 . 的影響
COBOL 的 . 比想像重。
沒明確終端的舊程式碼中,這句號關到哪 誤判會誤讀控制流。12
把 packed decimal 或 EBCDIC 當「亂碼」
以為 OCCURS DEPENDING ON 後面是固定位置
可變長度表格的後續項目位置會依值動。 以固定長度頭腦讀,偏移計算全部偏掉。8
10. 先看的速查表
| 找到的詞 | 先想的 |
|---|---|
01 |
記錄或群組的最上位。從這抓全體 |
88 |
旗標或狀態碼的意義名。讀分支的關鍵 |
PIC X(...) |
文字項目 |
PIC 9(...) / S9(...)V... |
數值項目。確認位數和小數位置 |
COMP |
binary |
COMP-3 |
packed decimal。可能是金額・件數 |
REDEFINES |
以不同解釋看同一區域 |
OCCURS |
陣列・table |
OCCURS DEPENDING ON |
可變長度。注意後續位置 |
FILLER |
沒名字但有長度 |
COPY |
不看 copybook 看不見完成形 |
PERFORM |
主路徑的骨架 |
READ / WRITE / REWRITE |
檔案 I/O |
EXEC SQL |
DB 處理 |
EXEC CICS |
交易處理 |
FILE STATUS |
I/O 的結果碼 |
11. 總結
COBOL 不是因為舊所以難。 只是 資料定義、外部檔案、執行脈絡緊密結合,最初入口難看。
再次整理讀的最小組合如下。
- 用
DIVISION抓地圖 - 先讀
DATA DIVISION - 用
PIC和USAGE讀項目的形 - 在
COMP-3、REDEFINES、OCCURS、88、COPY做記號 - 追
PERFORM、READ、WRITE、CALL - 用
FILE STATUS、EXEC SQL、EXEC CICS掌握外部邊界 - 不輕視
.的影響
看見這些,COBOL 從「謎之古代魔法」變成「記錄處理的語言」。 舊技術不是因名字舊而可怕,只是 一開始搞錯縮尺就突然難懂。縮尺對了意外普通能讀。
12. 參考資料
本文主要參考。
-
IBM, “Reference format” https://www.ibm.com/docs/en/cobol-zos/6.4.0?topic=structure-reference-format / IBM, “Area A or Area B” https://www.ibm.com/docs/en/cobol-zos/6.4.0?topic=format-area-area-b / Micro Focus, “Fixed Format” https://www.microfocus.com/documentation/visual-cobol/vc60/DevHub/HRLHLHINTR01U904.html ↩ ↩2
-
IBM, “Level-numbers” https://www.ibm.com/docs/en/cobol-zos/6.3.0?topic=entry-level-numbers ↩ ↩2
-
IBM, “Format 2: condition-name value” https://www.ibm.com/docs/en/cobol-zos/6.3.0?topic=vc-format-2 ↩ ↩2
-
IBM, “Examples: numeric data and internal representation” https://www.ibm.com/docs/en/cobol-zos/6.3.0?topic=data-examples-numeric-internal-representation ↩ ↩2
-
IBM, “PACKED-DECIMAL (COMP-3)” https://www.ibm.com/docs/en/cobol-zos/6.4.0?topic=v6-packed-decimal-comp-3 ↩
-
IBM, “The EBCDIC character set” https://www.ibm.com/docs/en/zos-basic-skills?topic=mainframe-ebcdic-character-set / IBM, “Handling differences in ASCII SBCS and EBCDIC SBCS characters” https://www.ibm.com/docs/en/cobol-linux-x86/1.2.0?topic=fdcbdr-handling-differences-in-ascii-sbcs-ebcdic-sbcs-characters ↩ ↩2 ↩3
-
IBM, “REDEFINES clause” https://www.ibm.com/docs/en/cobol-zos/6.4.0?topic=entry-redefines-clause ↩ ↩2
-
IBM, “OCCURS DEPENDING ON clause” https://www.ibm.com/docs/en/cobol-zos/6.4.0?topic=clause-occurs-depending ↩ ↩2
-
IBM, “COPY statement” https://www.ibm.com/docs/en/cobol-linux-x86/1.2.0?topic=statements-copy-statement ↩ ↩2
-
IBM, “Enterprise COBOL compiler options” https://www.ibm.com/docs/en/cobol-zos/6.3.0?topic=guide-enterprise-cobol-compiler-options ↩
-
IBM, “PERFORM statement” https://www.ibm.com/docs/en/cobol-zos/6.4.0?topic=statements-perform-statement / IBM, “Procedure division structure” https://www.ibm.com/docs/en/cobol-zos/6.3.0?topic=division-procedure-structure ↩
-
IBM, “Scope terminators” https://www.ibm.com/docs/en/cobol-aix/5.1.0?topic=division-scope-terminators / IBM, “Coding a choice of actions” https://www.ibm.com/docs/en/cobol-zos/6.3.0?topic=actions-coding-choice ↩ ↩2 ↩3 ↩4
-
IBM, “Describing the structure of a file in detail” https://www.ibm.com/docs/en/cobol-linux-x86/1.2.0?topic=files-describing-structure-file-in-detail ↩
-
IBM, “FILE STATUS clause” https://www.ibm.com/docs/en/cobol-linux-x86/1.2.0?topic=section-file-status-clause / IBM, “Using file status keys” https://www.ibm.com/docs/en/cobol-zos/6.4.0?topic=operations-using-file-status-keys ↩
-
IBM, “Coding COBOL programs to run under CICS” https://www.ibm.com/docs/en/cobol-zos/6.3.0?topic=cics-coding-cobol-programs-run-under ↩
-
IBM, “Elementary move rules” https://www.ibm.com/docs/en/cobol-zos/6.3.0?topic=moves-elementary-move-rules ↩
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
整理 Windows 的字元編碼與換行符 - Shift_JIS / UTF-8 / UTF-16、亂碼、CRLF / LF,為何混亂
本文整理 Windows 上字元編碼與換行符容易混亂的核心:bytes、UTF-8 與 CP932、UTF-16LE、BOM、CRLF 與 LF 是不同軸的概念,亂碼源於以錯誤前提 decode,且誤儲存後無法還原。讀完即可在規格中寫出明確的 encoding 與換行約定,...
偽隨機數與真正隨機數的差異 - 如何區分的整理
本文整理偽隨機數與真正的隨機數差異,重點不在輸出外觀而在產生器結構:一般 PRNG 重視可重現性、CSPRNG / DRBG 主打不可預測性、NRBG 則以物理熵源為基礎。文中說明 seed 與 reseed、health test 的角色,並給出資安、模擬、抽籤等用途的選...
GS1 等條碼規格中,標準定義了什麼、實務運用上要注意什麼 - GTIN · AI · GS1-128 · GS1 DataMatrix 的整理
整理 GS1 條碼規格中被標準化的範圍,把 GTIN、AI、GS1-128、GS1 DataMatrix、GS1 QR Code 的差異與適用場景分層說明,並彙整 FNC1、掃描器輸出、商品主檔運用等接收端容易踩雷的重點,協助讀者把條碼當作可共享的業務介面來設計。
開發 COM 元件、OCX/ActiveX 時常見的坑 - 整理 Visual Studio 的 32bit/64bit、註冊、管理員權限
整理開發 COM、OCX、ActiveX 元件時最容易卡關的四個面向:宿主行程的 32bit/64bit、Visual Studio 2022 變成 64bit 後的設計時整合、regsvr32 與 Regasm 的註冊位置、以及管理員權限與 HKCU/HKLM 的關係,協...
應該在哪裡 `catch` 例外並輸出日誌、進行錯誤處理 - 以實務向整理呼叫階層的邊界與職責
本文整理在呼叫階層中應該於哪一層 catch 例外、輸出主日誌與進行錯誤處理的實務判斷標準。深層 helper 不寬泛接捕,外部 I/O 邊界負責翻譯例外,UseCase 把預期內失敗結果化,UI 與請求邊界輸出 1 次主日誌並決定回應,未處理例外處理器只承擔最終記錄與終止...
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
作者檔案
本文作者的個人檔案頁面。
Go Komura
小村軟體有限公司 代表
以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。