突然要讀 COBOL 原始碼時該知道的最低限度 - 先整理 DIVISION / PIC / COMP-3 / COPY / PERFORM

· · COBOL, 舊技術, 業務系統, 維護, Mainframe

交接、故障對應、廠商包裝產品的維護。在那樣的場面中,有一天突然 COBOL 原始碼飛來的情況。

  • 檔案名是 .cbl.cpy
  • 變數名全部大寫
  • 01057788 排列
  • 出現 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 看不見全體
  • PERFORMIFEVALUATEREADWRITECALL 就能大致掌握流程
  • 舊原始碼是 欄位位置有意義的固定格式。外觀的空白不是單純的裝飾1

簡言之,DIVISION、PIC、USAGE、COMP-3、REDEFINES、OCCURS、88、COPY、PERFORM。這些讀得懂,迷路率大幅下降。

2. COBOL 先當「資料形式」的語言

用 C# 或 Java 的感覺讀,最初會想追 iffor 或函式呼叫。 但 COBOL 在那之前先掌握 「這程式接收什麼記錄、做出什麼記錄、持有什麼緩衝」 比較快。

典型的業務 COBOL 大致下列流程。

  1. 從檔案或 DB 讀記錄
  2. 放到 WORKING-STORAGE 上的項目
  3. 條件分支
  4. 重新填到別記錄
  5. 寫出

也就是 比起演算法,佈局先站

例如這種骨架。

       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 SECTIONPROCEDURE 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。給前一項目的值命名3
  • 66 : RENAMES 用。相遇率不高但存在

重要的是不要把 88 當成 bool 的別變數。 不是 WS-OK 這區域另外存在,而是 WS-STATUS'0' 時能以 WS-OK 這個名字讀的感覺。

另一個重要的是 決定階層的不是空白而是層級編號。 外觀的縮排可參考,但最終要信的是 01 / 05 / 10 / 882

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 位小數

這裡特別重要是 VV 沒有實際的 . 字元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 / 範圍

條件分支基本是 IFEVALUATE 想成 switch/case 式大致對。

要注意的是 範圍的結束方式12

  • END-IF
  • END-PERFORM
  • END-READ

明確終端 的程式碼還好讀。

問題是舊程式碼。COBOL 中 . 作為 隱性 scope terminator 動作,一口氣結束還沒關閉的語句。12

也就是說,僅 1 個句號,

  • 到哪是 IF
  • 到哪是 PERFORM
  • 在哪移到下個 sentence

會變。

此外 NEXT SENTENCECONTINUE 不同。 NEXT SENTENCE 進到下個句號後面,所以後續 . 的位置會改變飛往哪。12

讀舊 COBOL 時,不看行尾而看句號 剛好。

6.3 READ / WRITE / CALL

業務 COBOL 頻繁出現的是這一帶。

  • READ
  • WRITE
  • REWRITE
  • START
  • CALL

特別 READ ... AT END ... 是王道。

       READ IN-FILE
           AT END
               SET EOF TO TRUE
           NOT AT END
               PERFORM PROCESS-REC
       END-READ

若有 CALL 'SUBPGM' USING ... 會飛到別程式。 那時看被呼叫側的 LINKAGE SECTIONPROCEDURE DIVISION USING,傳遞的形相當可見。

7. COBOL 的外側的東西

COBOL 原始碼單獨不完整世界的情況相當多。

  • 檔案定義
  • 執行環境
  • DB 連線
  • 交易環境
  • job 控制

分在外側。

至少掌握以下會較好讀。

檔案和 FILE STATUS

ENVIRONMENT DIVISIONFILE-CONTROLDATA DIVISIONFILE 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 時,以下順序安全。

  1. 盤查所有 COPY 能開 copybook 就開。不行的話找 listing 或展開後原始碼
  2. 01 層級的記錄定義FILE SECTIONWORKING-STORAGELINKAGE SECTION 的最上位列表化
  3. PICUSAGE 辨識金額、日期、件數、代碼、旗標
  4. 搜尋 READ / WRITE / REWRITE / CALL / EXEC SQL / EXEC CICS 先掌握輸入輸出和外部邊界
  5. 只追第一條主路徑PROCEDURE DIVISION 的開頭追 PERFORM
  6. 88 和 status 項目 EOF、正常/異常、種別碼的意義變好讀
  7. REDEFINES / OCCURS DEPENDING ON / COMP-3 做記號 之後一定有效,先當危險物標記
  8. 若是檔案就看 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 當「亂碼」

未必壞了。 可能原本不是字串或不是 ASCII。46

以為 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
  • PICUSAGE 讀項目的形
  • COMP-3REDEFINESOCCURS88COPY 做記號
  • PERFORMREADWRITECALL
  • FILE STATUSEXEC SQLEXEC CICS 掌握外部邊界
  • 不輕視 . 的影響

看見這些,COBOL 從「謎之古代魔法」變成「記錄處理的語言」。 舊技術不是因名字舊而可怕,只是 一開始搞錯縮尺就突然難懂。縮尺對了意外普通能讀。

12. 參考資料

本文主要參考。

  1. 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

  2. IBM, “Level-numbers” https://www.ibm.com/docs/en/cobol-zos/6.3.0?topic=entry-level-numbers  2

  3. IBM, “Format 2: condition-name value” https://www.ibm.com/docs/en/cobol-zos/6.3.0?topic=vc-format-2  2

  4. 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

  5. IBM, “PACKED-DECIMAL (COMP-3)” https://www.ibm.com/docs/en/cobol-zos/6.4.0?topic=v6-packed-decimal-comp-3 

  6. 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

  7. IBM, “REDEFINES clause” https://www.ibm.com/docs/en/cobol-zos/6.4.0?topic=entry-redefines-clause  2

  8. IBM, “OCCURS DEPENDING ON clause” https://www.ibm.com/docs/en/cobol-zos/6.4.0?topic=clause-occurs-depending  2

  9. IBM, “COPY statement” https://www.ibm.com/docs/en/cobol-linux-x86/1.2.0?topic=statements-copy-statement  2

  10. IBM, “Enterprise COBOL compiler options” https://www.ibm.com/docs/en/cobol-zos/6.3.0?topic=guide-enterprise-cobol-compiler-options 

  11. 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 

  12. 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

  13. 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 

  14. 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 

  15. 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 

  16. IBM, “Elementary move rules” https://www.ibm.com/docs/en/cobol-zos/6.3.0?topic=moves-elementary-move-rules 

共用相同標籤的最新文章。能以相近的主題延伸理解。

偽隨機數與真正隨機數的差異 - 如何區分的整理

本文整理偽隨機數與真正的隨機數差異,重點不在輸出外觀而在產生器結構:一般 PRNG 重視可重現性、CSPRNG / DRBG 主打不可預測性、NRBG 則以物理熵源為基礎。文中說明 seed 與 reseed、health test 的角色,並給出資安、模擬、抽籤等用途的選...

與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。

本文連結到以下服務頁面,歡迎從最接近的入口查看。

作者檔案

本文作者的個人檔案頁面。

Go Komura

小村軟體有限公司 代表

以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。

回到部落格一覽