讀 COBOL 原始碼之前該先掌握的最小知識

· 更新日期: · · COBOL, 舊技術, 業務系統, 維護, Mainframe

更新紀錄(2 筆,最後更新 2026年09月04日)

本文的修改紀錄。已保存的更新前版本,可透過附有 DOI 的永久連結閱讀。

已將繁體中文版改寫為日文原文的完整翻譯。先前的繁體中文版只譯出日文原文的一部分,遺漏了章節、表格、Mermaid 圖、圖說與 FAQ。本次依日文原文將這些內容全部補回,並新增本文的知識地圖章節。技術主張與日文版一致。 查看更新前的版本 (DOI: 10.5281/zenodo.22279445)
補上了日文原文中已有的諮詢引導(consultation_services)。內文沒有改動。 查看更新前的版本 (DOI: 10.5281/zenodo.21616317)
初次發布
引用本文(DOI: 10.5281/zenodo.21616316)

本文保存於 Zenodo。以下同時提供一律指向最新版本的 DOI,以及固定於您正在閱讀版本的 DOI。

Go Komura(2026)。〈讀 COBOL 原始碼之前該先掌握的最小知識〉。小村軟體有限公司。https://doi.org/10.5281/zenodo.21616316 https://comcomponent.com/zh-TW/blog/2026/03/17/001-cobol-minimum-reading-guide/

DOI(最新版本)
10.5281/zenodo.21616316
DOI(此版本)
10.5281/zenodo.22297154

交接、故障處理、廠商套裝軟體的維護。在這類場面裡,某一天突然就有一份 COBOL 原始碼丟到手上。

  • 檔案名稱是 .cbl 或 .cpy
  • 變數名稱全是大寫
  • 01、05、77、88 一路排下來
  • 出現 PIC S9(7)V99 COMP-3 這種介於咒語與會計軟體之間的寫法
  • 而且滿篇都是 COPY,只看打開的這個檔案根本看不到全貌

到這裡,多數人都會先卡住,不知道下一步該做什麼。

不過,要讀懂它所需要的地圖沒有那麼大。COBOL 在不同編譯器與產品之間確實有差異,但讀既有業務系統時該先掌握的骨架,跨編譯器相當一致。本文以 IBM 系與典型的業務 COBOL 為前提,梳理給突然要讀原始碼的人的最小組合。

卡住的原因與地圖的大小說明全大寫的變數名稱與層級編號、PIC S9(7)V99 COMP-3 這類寫法,以及滿篇 COPY 看不見全貌會讓人卡住,但讀既有業務系統時該先掌握的骨架跨編譯器相當一致,地圖沒有那麼大的圖。看不懂的寫法讓人卡住但骨架跨編譯器相當一致先拿到最小組合的地圖滿篇 COPY 看不見全貌

圖 1: 看起來像咒語的寫法,其實可以收攏成幾個共通概念。

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。這幾個讀得懂,迷路的機率就會下降很多。

圖中實線表示始終成立的關係,虛線表示附帶條件的關係(成立條件寫在詳細頁面中各關係的說明中)。關係的完整清單(共 21 條,附依據與可信度)以及主要概念的定義,彙整在知識地圖詳細頁面(日文)。資料:JSON-LD / Turtle

2. 先把 COBOL 當成「資料形狀」的語言

用 C# 或 Java 的感覺去讀,一開始會想先追 if、for 和函式呼叫。 但讀 COBOL 時,在走到那一步之前,先弄清楚「這支程式接收什麼樣的記錄、產生什麼樣的記錄、又持有哪些緩衝區」會更快。

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

  1. 從檔案或 DB 讀出記錄
  2. 放進 WORKING-STORAGE 上的項目
  3. 做條件分支
  4. 改填到另一筆記錄
  5. 寫出去

也就是說,比起演算法,先站上檯面的往往是記錄的排列方式。

典型業務 COBOL 的流程說明從檔案或 DB 讀出記錄、放進 WORKING-STORAGE 上的項目、做條件分支、改填到另一筆記錄再寫出去,這個典型業務 COBOL 流程的圖。讀出記錄放進 WORKING-STORAGE做條件分支改填到另一筆記錄寫出去

圖 2: 主角是記錄的流動,演算法夾在中間。

舉例來說,骨架長這樣。

       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. 先看四個 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 ... 時,這支程式很可能不是單獨完結,而是接收外部資料才動起來。

LINKAGE SECTION 代表什麼說明看到 LINKAGE SECTION 和 PROCEDURE DIVISION USING 時,這支程式很可能不是單獨完結,而是接收外部資料才動起來的圖。有 LINKAGE SECTION可能是接收外部資料才動起來有 PROCEDURE DIVISION USING不是單獨完結的程式

圖 3: 看到接收口的定義,就要假設有呼叫端存在再往下讀。

4. 不要被固定格式的外觀嚇到

在舊的 COBOL 裡,原始碼一行之中欄的位置本身就有意義。不知道這件事就去看,永遠也搞不懂「為什麼左邊有一塊奇怪的空白」。1

固定格式大致是這樣分的。

  • 第 1 - 6 欄:序號
  • 第 7 欄:indicator
  • 第 8 - 11 欄:Area A
  • 第 12 - 72 欄:Area B

第 7 欄特別重要。

  • * 或 /:註解行
  • -:接續行
  • D:debugging line
  • *>:也可以寫在行中途的註解

把欄的位置和內容的對照關係加上刻度來看,就是下面這樣。第 1 行是十位數的刻度,第 2 行是個位數的刻度。

         1         2         3         4         5         6         7         8
12345678901234567890123456789012345678901234567890123456789012345678901234567890
SSSSSSIAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB........
  • S = 序號(第 1 - 6 欄)
  • I = indicator(第 7 欄)
  • A = Area A(第 8 - 11 欄)
  • B = Area B(第 12 - 72 欄)
  • . = 第 73 欄以後。有些編譯器會拿來當識別欄,但不影響程式的意義

套到實際的原始碼上,就是這樣。

000100* 這一行第 7 欄是 * 所以是註解
000200 IDENTIFICATION DIVISION.
000300 PROGRAM-ID. SAMPLE01.
000400 DATA DIVISION.
000500 WORKING-STORAGE SECTION.
000600 01  WS-ORDER.
000700     05  WS-ORDER-ID    PIC 9(8).
000800     05  WS-LONG-NAME   PIC X(30) VALUE 'ABCDEFGHIJKLMNOPQRST
000900-    'UVWXYZ0123'.

讀法的重點有 4 個。

  • 第 1 - 6 欄是序號。上面例子裡標成 000100 那樣的就是它,和程式的動作無關。也有整片留空白的情況。
  • 第 7 欄是 indicator。* 是註解,- 是接續行。上面例子的 000900 行就是接續行,接的是前一行字串常值的後半段。
  • 第 8 - 11 欄是 Area A。DIVISION、SECTION、段落名稱、FD,以及 01 和 77 這些層級編號,都要從這裡開始寫。上面例子的 IDENTIFICATION DIVISION. 和 01 WS-ORDER. 就是從 Area A 開始。
  • 第 12 - 72 欄是 Area B。一般的陳述式,以及 05 這類下層層級寫在這裡。上面例子的 05 WS-ORDER-ID 就是從 Area B 開始。

這裡的空白不是現代意義上的「排版」,有一部分就是語法本身。 在編輯器裡做 tab 轉換、把整段往左靠、或是隨手複製貼上,都會直接把它弄壞。 看舊的原始碼時,請先確認這個檔案是 fixed format 還是 free format。對 fixed 格式的原始碼套用現代的自動排版,Area A 和 Area B 的分界會亂掉,編譯就過不了。

動固定格式的原始碼之前說明看舊的原始碼時要先確認是 fixed format 還是 free format,對 fixed 格式的原始碼套用現代的自動排版或 tab 轉換,會讓 Area A 和 Area B 的分界亂掉而編譯不過的圖。fixed 格式free 格式打開了舊的原始碼fixed 還是 free欄的位置本身就是語法自動排版或 tab 轉換會弄壞欄的限制比較寬鬆

圖 4: 在排版之前,先把這個檔案的格式確定下來。

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 這個名字來讀它。

88 層級的真面目說明 88 層級不是獨立的 bool 變數,而是給前一個項目的值取名字的條件名稱,只是在 WS-STATUS 為特定值時可以用 WS-OK 這個名字讀它的圖。是 0 時是 9 時基底項目 WS-STATUS值是什麼以 WS-OK 這個名字成立以 WS-ERROR 這個名字成立並不存在另一塊區域

圖 5: 88 不是變數,而是給值取的好讀別名。

另一個重點是,決定階層的不是空白,而是層級編號。 看到的縮排可以當參考,但最後該相信的是 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 位小數的數值」處理,但資料裡並沒有點字元。 所以把檔案或傾印當成「看得到的字串」來解讀,大多會跌一跤。

V 是邏輯上的小數點說明 PIC 9(5)V99 的 V 是邏輯上的小數點,並不會真的帶一個點字元,資料裡也沒有點,所以把檔案或傾印當成看得到的字串來解讀就會跌一跤的圖。PIC 9(5)V99當成有 2 位小數的數值處理資料裡不會放進點字元當成看得到的字串就會跌一跤

圖 6: 小數點只在定義裡,不在資料裡。

5.3 USAGE / DISPLAY / COMP / COMP-3

如果說 PIC 是形狀,USAGE 就是用什麼表示法保存。 最少掌握下面這些,就能讀懂相當多了。45

寫法 大致的意思 讀的時候要注意
DISPLAY 以字元形式呈現的外部十進位 在 mainframe 上有時是以 EBCDIC 為前提6
COMP / BINARY 二進位 看到的位數和內部表示法是兩回事
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 個全都是「數值」,但內容的保存方式不同。

同樣的數值,保存方式卻不同說明同樣形狀的帶正負號數值項目,DISPLAY 是以字元形式呈現的外部十進位、COMP 是二進位、COMP-3 是 packed decimal,會因為 USAGE 不同而讓內容的保存方式不同的圖。同樣形狀的數值項目DISPLAY〔以字元形式呈現〕COMP〔二進位〕COMP-3〔packed decimal〕當成字元讀會看起來壞掉

圖 7: 就算 PIC 一樣,USAGE 一變,位元組就是另一回事。

實務上最派得上用場的,是看到 COMP-3 那一瞬間的反應。

  • 它就是 packed decimal
  • 大概是金額、稅額、件數、比率這一類
  • 當成文字看會壞掉是理所當然的
  • 用 CSV 或 UTF-8 的心情去看就會出事

先有這層理解,看到傾印或二進位檔案的樣子時,就不會白白慌張。

看到 COMP-3 當下的反應說明看到 COMP-3 就知道它是 packed decimal,多半是金額、稅額、件數或比率這類項目,當成文字看會壞掉是理所當然的,因此不要用 CSV 或 UTF-8 的心情去看的圖。找到了 COMP-3知道它是 packed decimal懷疑是金額或件數的項目就算文字看起來壞掉也不慌張

圖 8: 光是養成這個反射,在傾印面前慌張的次數就會減少。

COMP-3 實際上會變成什麼樣的位元組

這一段只要親手推一次,之後看東西的方式就會不一樣。

packed decimal 的規則只有 2 條。45

  1. 一個位元組塞進 2 位十進位數字
  2. 但最右邊那個位元組例外,用最低 1 位和正負號佔掉 1 個位元組

正負號用 4 位元的值表示,C 是正,D 是負,F 是不帶正負號。

用 IBM 手冊上刊載的例子來核對。4

定義 值 位元組
PIC S9(4) PACKED-DECIMAL +1234 01 23 4C
PIC S9(4) PACKED-DECIMAL -1234 01 23 4D
PIC 9(4) PACKED-DECIMAL 1234 01 23 4F

1234 是 4 位數,因為規則 2 的關係,開頭會多出 1 位的空位。那就是最前面的 0。

用同樣的方式,來推一次本文出現過很多次的 PIC S9(7)V99 COMP-3。

  • S9(7)V99 是整數 7 位 + 小數 2 位 = 9 位
  • V 只是表示小數點的位置,一個位元組也不會消耗
  • 9 位每 2 位塞一個位元組是 4 個位元組,剩下的 1 位和正負號再用 1 個位元組。合計 5 個位元組

值如果是 +12345.67,補齊成 9 位就是 001234567,所以會變成這樣。

值       : +12345.67
9 位表示 : 0 0 1 2 3 4 5 6 7  與正負號
位元組   : 00 12 34 56 7C
                        ^ 正負號 C = 正

如果是負值 -12345.67,只是最後變成 7D 而已。

位元組   : 00 12 34 56 7D

重點是把這串當成文字打開時會看到什麼。硬把 00 12 34 56 7C 一個位元組對一個字元地對到 ASCII 上,

  • 00 是 NUL,12 是控制字元,本來就沒辦法當成字元顯示
  • 34 是 4,56 是 V,7C 是 |

就會變成這樣。也就是說,畫面上看起來像是「幾個無法顯示的字元後面接著 4V|」。12345.67 這個排列在任何地方都不會出現。

這就是「看起來像亂碼,其實沒有壞掉」的真面目。打開傾印遇到意義不明的排列時,請先懷疑那個項目的 USAGE 是不是 COMP-3。

COMP-3 的位元組是怎麼組出來的說明 packed decimal 把十進位數字每 2 位塞進 1 個位元組,只有最右邊的位元組用最低 1 位和正負號,所以 9 位的數值是 5 個位元組,而把它當成 ASCII 打開時原本的數值排列不會出現在任何地方的圖。十進位數字每 2 位塞進 1 個位元組最後一個位元組放最低 1 位和正負號9 位就是合計 5 個位元組用 ASCII 打開看不到原本的數值看起來像亂碼,其實沒有壞掉

圖 9: 知道這兩條塞法規則,意義不明的傾印就變成讀得懂的一排位元組。

順便附上從位數換算位元組數的速查表。位元組數用「9 的個數除以 2 無條件捨去,再加 1」就算得出來。

PICTURE 裡 9 的個數 COMP-3 的位元組數
1 1
2 - 3 2
4 - 5 3
6 - 7 4
8 - 9 5
10 - 11 6
12 - 13 7

同一個位元組數會排到 2 種位數,是因為只有 9 的個數是偶數時,開頭才會多出 1 位的空位。S9(4) 會變成 01 23 4C 這 3 個位元組,而 S9(5) 同樣塞得進 3 個位元組,原因就在這裡。

和外部檔案核對排列方式時,少了這張表就會一個位元組一個位元組地錯開下去。

再補充一點,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 的感覺。 常見的寫法是「把同一塊 100 個位元組,當成不同的記錄種別來分辨」。

REDEFINES 是同一塊區域的另一種解讀說明 REDEFINES 不是複製,而是用另一種形狀看同一塊記憶體區域的機制,同一個緩衝區既可以當成一般記錄也可以當成標頭記錄來讀,改寫其中一邊另一邊看起來也會跟著變的圖。同一塊記憶體區域當成 REC-BUF 來看當成 HEADER-REC 來看寫了其中一邊,兩邊看到的都會變

圖 10: 就算有兩份定義,實體的位元組也只有一份。

OCCURS

OCCURS 就是陣列。在 COBOL 裡習慣叫它 table。

       05  WS-ITEM OCCURS 12 TIMES.
           10  WS-PRICE    PIC 9(5).

再進一步,看到 OCCURS DEPENDING ON 就是可變長度的 table。 這種情況連後續項目的位置都可能受影響,用固定長度的心情去追就會踩空。8

OCCURS DEPENDING ON 的注意事項說明 OCCURS 是陣列,加上 DEPENDING ON 就變成可變長度的 table,連後續項目的位置都可能隨值移動,因此用固定長度的心情去算位移就會踩空的圖。沒有加有加找到了 OCCURS有沒有加 DEPENDING ON固定次數的 table可變長度的 table後續項目的位置也可能移動

圖 11: DEPENDING ON 這三個字一出現,位移計算的前提就變了。

COPY

COPY 是 compile time 的 include。 也就是說,現在打開的這份原始碼可能還不是完成形。9

       COPY CUSTOMER-REC.
       COPY ERROR-MAP.

記錄定義、共通旗標、SQL 用的 host variable、外部介面被塞進 copybook,是相當常見的事。

COPY 太多讀不下去時,先查看能不能拿到展開後的原始碼或 compiler listing 會比較快。IBM Enterprise COBOL 還有一個叫 MDECK 的 option,可以把函式庫處理後的輸入原始碼寫出來。10

有 COPY 的原始碼怎麼讀說明 COPY 是編譯時期的 include,打開的原始碼可能不是完成形,因此要打開 copybook 確認,讀不下去時再去找展開後的原始碼、compiler listing 或 MDECK 選項的輸出的圖。讀不下去時找到了 COPY打開的原始碼可能還不是完成形打開 copybook 確認找展開後的原始碼或 listing

圖 12: 現在看得到的行數,未必就是這支程式的全部。

FILLER

FILLER 是沒有名字的項目。 但這不代表「因為不會被參考所以沒有意義」。

  • 保留區域
  • 為了和舊規格相容而留的空洞
  • 湊記錄長度
  • REDEFINES 用的空白

這些用途都實際派得上用場。

FILLER 只是沒有名字,位元組數是實際存在的。 忘了這件事,和外部檔案的對應關係就會一個位元組一個位元組地整片錯開。

漏數 FILLER 會發生什麼事說明 FILLER 只是沒有名字,位元組數是實際存在的,會作為保留區域或湊記錄長度發揮作用,因此漏數之後和外部檔案的對應關係會一個位元組一個位元組地錯開的圖。算進去了忘記算了FILLER 是沒有名字的項目位元組數是實際存在的有沒有算進排列方式和外部檔案對得上一個位元組一個位元組地錯開

圖 13: 不會被參考的項目,也擔著長度這個職責。

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 這種範圍指定。 這很方便,但在中間插入段落很容易把不該執行的段落也捲進去,所以讀的時候要好好確認範圍的終點。

PERFORM 的三種形式說明 PERFORM 有指定段落或節、呼叫後再回來的 out-of-line 形式,也有直接在原地寫一段區塊的 inline 形式,舊程式碼還有用 THRU 做範圍指定的形式,而範圍指定在中間插入段落很容易把不該執行的段落捲進去的圖。找到了 PERFORM呼叫段落的 out-of-line寫在原地的 inline用 THRU 的範圍指定一定要確認範圍的終點

圖 14: PERFORM 是哪一種形式,決定了該追的返回點與範圍。

6.2 IF / EVALUATE / 範圍

條件分支基本上用 IF。 EVALUATE 想成類似 switch/case 的東西,大致上不會錯。

要注意的是範圍是怎麼結束的。12

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

有這類明確終止子的程式碼還算好讀。

問題出在舊的程式碼。在 COBOL 裡 . 會當成隱含的 scope terminator,把還沒有結束的陳述式一次全部收掉。12

也就是說,只憑一個句點,

  • IF 管到哪裡
  • PERFORM 管到哪裡
  • 在哪裡進到下一個 sentence

就會跟著改變。

還有,NEXT SENTENCE 和 CONTINUE 不一樣。 NEXT SENTENCE 會跳到下一個句點的後面,所以後面 . 的位置一變,跳過去的地方也跟著變。12

讀舊的 COBOL 時,抱著不看行尾、只看句點的態度剛剛好。

句點的份量說明有 END-IF 這類明確終止子的程式碼比較好讀,但舊程式碼裡句點會當成隱含的 scope terminator,把還沒結束的陳述式一次收掉,因此一個句點就能改變 IF 或 PERFORM 的範圍的圖。有 END-IF 之類沒有的舊程式碼有沒有明確的終止子範圍好讀句點就是隱含的終止子一個句點的位置就改變範圍不看行尾,只看句點

圖 15: 舊程式碼的控制流程,是由句點的位置掌控的。

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 SECTION 和 PROCEDURE DIVISION USING,參數傳遞的形狀就看得相當清楚。

找到 CALL 之後怎麼追說明只要有 CALL 就會跳到另一支程式,因此去看被呼叫端的 LINKAGE SECTION 和 PROCEDURE DIVISION USING,就能看清楚引數傳遞的形狀的圖。找到了 CALL會跳到另一支程式看被呼叫端的 LINKAGE SECTION用 USING 掌握傳遞的形狀

圖 16: 呼叫的意義,要和被呼叫端的接收口一起讀。

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

檔案定義要成套一起讀說明 ENVIRONMENT DIVISION 中 FILE-CONTROL 的 SELECT 和 DATA DIVISION 的 FD 要成套一起讀,只要有 FILE STATUS,每次輸入輸出之後的結果碼就會寫進去,因此檔案相關的故障和 EOF 判斷都從這裡讀起的圖。FILE-CONTROL 的 SELECT兩者合起來是一份檔案定義FILE SECTION 的 FDFILE STATUS 會寫進結果碼解讀故障與 EOF 判斷的起點

圖 17: 檔案的樣貌,是分寫在兩個 DIVISION 裡的。

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 原始碼外面,一點也不稀奇。 只看原始碼卻搞不清楚「這個檔案在哪裡」時,常常不是程式碼有問題,而是看的範圍還不夠大。

位在原始碼外面的世界說明出現 EXEC SQL 時實際的取得條件在 SQL 那一側,出現 EXEC CICS 時畫面與交易等外部脈絡在外面,mainframe batch 的資料集配置和 job 流動則寫在 JCL 裡,整個世界分在 COBOL 原始碼外側的圖。EXEC SQL連原始碼外面也要看EXEC CICSJCL 與執行定義SQL 的條件、畫面脈絡、job 的流動

圖 18: 看不懂未必是程式碼的錯,有時只是看的範圍太窄。

編譯器實作的差異

本文是以 IBM 系為前提寫的,但實務上也會遇到 Micro Focus,或是跑在 Linux / Windows 上的 COBOL。骨架共通,所以讀法不變,但「寫法一樣,結果卻不同」的地方就是固定那幾個,先掌握起來就不會出事。

要看的地方 z/OS 系(IBM Enterprise COBOL) 開放系統(Micro Focus、Linux / Windows 版等)
字元編碼 以 EBCDIC 為前提6 以 ASCII 為前提6
參考格式 fixed 格式是傳統上的預設。也可以選 free 格式1 fixed 和 free 兩種都有,預設是哪一種要看建置設定1
copybook 的尋找方式 指定函式庫 用編譯器選項指定搜尋路徑
方言 — 有可以切換「要配合哪一個編譯器」的選項

影響最大的是字元編碼。同樣是 PIC X(10),在 z/OS 上寫出來的檔案直接拿到 Windows 這邊讀,連數字都會變成不同的位元組值。「搬過去之後全部變亂碼」多半就是這個原因,和 COMP-3 是兩回事。

還有一個地方也容易出現差異,就是數值的內部表示法。在 IBM Enterprise COBOL 裡,BINARY / COMP-4 會依照 PICTURE 上寫的位數進行截斷,而 COMP-5 則是一路持有到 2 / 4 / 8 個位元組這種原生二進位容量為止,截斷也發生在二進位大小那一側。16 也就是說,PIC S9(4) COMP 和 PIC S9(4) COMP-5 看起來一樣,能放進去的值的上限卻不同。如果在直接以二進位和其他系統交換數值的程式碼裡看到 COMP-5,請當成作者是刻意這樣寫的來讀。

要和自己的環境核對時,最短的步驟如下。

  1. 先打開建置定義(makefile、JCL、專案設定)。 用的是哪一個編譯器、哪些選項編譯的,會比讀原始碼更早弄清楚。
  2. 確定參考格式(fixed / free)。 這裡沒弄清楚就在編輯器裡排版,檔案會壞掉。
  3. 確定字元編碼。 是 EBCDIC 還是 ASCII,傾印的讀法完全不同。
  4. 在 COMP 系列的項目上做記號。 編譯器差異幾乎都出在這裡。
和自己的環境核對的最短步驟說明先打開建置定義確認是哪一個編譯器與哪些選項,接著確定參考格式是 fixed 還是 free,再確定字元編碼是 EBCDIC 還是 ASCII,最後在容易出現編譯器差異的 COMP 系列項目上做記號的步驟的圖。先打開建置定義確定參考格式確定字元編碼在 COMP 系列項目上做記號

圖 19: 讀原始碼之前的這四步,就能擋掉編譯器差異造成的事故。

8. 最低限度的閱讀順序

突然要讀 COBOL 時,照下面的順序比較安全。

  1. 把 COPY 全部盤點一遍 copybook 打得開就打開。打不開就找 listing 或展開後的原始碼
  2. 撿出 01 層級的記錄定義 把 FILE SECTION、WORKING-STORAGE、LINKAGE SECTION 的最上層整理成清單
  3. 讀 PIC 和 USAGE 辨識出金額、日期、件數、代碼、旗標
  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 點再往細節走,會輕鬆得多。

安全的閱讀順序說明先把 COPY 盤點一遍並確認 copybook,把 01 層級的記錄定義整理成清單,用 PIC 和 USAGE 讀出項目的形狀,用搜尋掌握輸入輸出與外部邊界,再從 PROCEDURE DIVISION 開頭沿著 PERFORM 鏈追主路徑,最後在危險物上做記號的閱讀順序的圖。把 COPY 全部盤點一遍把 01 層級整理成清單用 PIC 和 USAGE 讀出形狀搜尋輸入輸出與外部邊界追主路徑的 PERFORM 鏈先在危險物上做記號

圖 20: 不是全文精讀,而是照這個順序由外往內收攏。

8.1 練習:讀讀看這段記錄定義

光講讀法不容易記住,所以這裡放一段小型的記錄。請先自己算出答案,再看下面的解答。

       01  CUST-REC.
           05  CUST-ID          PIC X(8).
           05  CUST-NAME        PIC X(20).
           05  CUST-KBN         PIC X.
               88  CUST-NORMAL  VALUE '0'.
               88  CUST-VIP     VALUE '1'.
           05  CUST-BALANCE     PIC S9(7)V99 COMP-3.
           05  CUST-HIST OCCURS 3 TIMES.
               10  HIST-DATE    PIC 9(8).
               10  HIST-AMOUNT  PIC S9(5)V99 COMP-3.
           05  FILLER           PIC X(4).

問題

  1. CUST-REC 整體是幾個位元組?
  2. CUST-BALANCE 從記錄開頭數起,是從第幾個位元組開始?
  3. 第 2 筆的 HIST-AMOUNT 從開頭數起,是從第幾個位元組開始?
  4. CUST-KBN 裡放的是 '1' 時,會成立的條件名稱是哪一個?
  5. 用文字編輯器打開這個檔案時,看起來會壞掉的是哪些項目?
  6. 請舉出 2 件光看這段定義無法知道的事。

解答

  1. 是 74 個位元組。明細如下。

    項目 計算 位元組數
    CUST-ID X(8) 8
    CUST-NAME X(20) 20
    CUST-KBN X 1
    CUST-BALANCE 9 位的 COMP-3 5
    CUST-HIST (8 + 4) × 3 次 36
    FILLER X(4) 4
    合計   74

    88 層級是條件名稱,所以不會消耗位元組。把它也算進去是很常見的錯誤。FILLER 只是沒有名字,那 4 個位元組確確實實存在。

  2. 從第 30 個位元組開始。前面有 8 + 20 + 1 = 29 個位元組,所以從下一個開始。

  3. 從第 55 個位元組開始。CUST-HIST 是從第 35 個位元組開始,1 筆是 12 個位元組,所以第 1 筆是第 35 - 46 個位元組,第 2 筆是第 47 - 58 個位元組。其中開頭 8 個位元組是 HIST-DATE,所以 HIST-AMOUNT 是從第 55 個位元組開始。

  4. 是 CUST-VIP。並不是另外存在一塊叫 CUST-VIP 的區域,而是當 CUST-KBN 是 '1' 時可以用那個名字讀它。

  5. 是 CUST-BALANCE 和 HIST-AMOUNT。兩個都是 COMP-3,當成字元讀就會變成沒有意義的排列。HIST-DATE 是 PIC 9(8) 的 DISPLAY,在 ASCII 環境下可以像 20260317 一樣當成數字讀出來。不過在 EBCDIC 環境下,看起來像數字,位元組值卻和 ASCII 不同。

  6. 例如下面這些。

    • 這段定義本身是不是由 COPY 帶進來的。 不看 copybook 那一側,就不知道實際使用的是不是這一版。
    • 檔案的字元編碼是 EBCDIC 還是 ASCII。 PIC X 和 PIC 9 DISPLAY 呈現出來的樣子會不一樣。
    • 維運上 CUST-KBN 會不會出現 '0' 和 '1' 以外的值。 條件名稱只定義了 2 個,但這並不保證不會有其他值進來。
    • 檔案本身的屬性(記錄長度、是不是可變長度、FILE STATUS)。 這要看 ENVIRONMENT DIVISION 和 FD 才知道。

第 1 題和第 3 題答錯的話,請回到 5.3 的位數與位元組數對照表。COBOL 的誤讀,多半就是從這裡開始的。

9. 常見的卡關處

最後歸納一下初學者很高機率會卡住的地方。

把 REDEFINES 當成「另一個變數」

不對。 它是用另一種形狀讀同一塊區域。改寫其中一邊,另一邊看起來也會跟著變。7

把 88 當成「獨立的 bool」

不對。 它只是給前一個項目的值取了名字。SET WS-OK TO TRUE 在背後是把對應的值寫進基底項目。3

忽略 COPY 只讀本文

打開的檔案還只是整體的一半。 欄位定義、共通旗標、host variable 整批都在外面,是很平常的事。9

把 MOVE 當成單純的指派

MOVE 不是單純的 memcpy。 會依照接收端的型別,加入轉換、位數對齊、補零、截斷、編輯與反編輯等處理。17

小看 . 的影響

COBOL 的 . 比想像中重得多。 在沒有明確終止子的舊程式碼裡,看錯這個句點收到哪裡為止,就會把控制流程讀錯。12

把 packed decimal 或 EBCDIC 當成「亂碼」

不一定是壞掉了。 很多時候只是它從一開始就不是字串,或者只是不是 ASCII 而已。46

以為 OCCURS DEPENDING ON 後面是固定位置

可變長度 table 後面的項目,位置會隨著值而移動。 用固定長度的思路去讀,位移計算會全部錯開。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 就會從「謎樣的上古魔法」變成「處理記錄的語言」。 舊技術可怕的地方不在於名字老,而是一開始挑錯了看它的比例尺,就會突然變得難懂。地圖的比例尺對上了,其實讀起來意外地普通。

對準比例尺再讀說明先用 DIVISION 抓住地圖,先讀 DATA DIVISION 掌握項目的形狀,再用 PERFORM 和 READ 等追流程,最後用 FILE STATUS、EXEC SQL、EXEC CICS 掌握外部邊界,照這個順序對準比例尺之後,COBOL 就能當成處理記錄的語言正常讀懂的圖。用 DIVISION 抓住地圖用 DATA DIVISION 讀出形狀用 PERFORM 和輸入輸出追流程掌握外部邊界謎樣的上古魔法變成處理記錄的語言

圖 21: 難的不是語言太老,而是一開始選的比例尺。

12. 參考資料

這是本文主要的參考來源。內文的上標編號本身就是連到這份清單的連結,從每一項末尾的箭頭可以回到原本的位置。

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

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

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

常見問題

整理諮詢這個主題時常見的問題。

COBOL 的原始碼該從哪裡開始讀?
只讀 PROCEDURE DIVISION 只能懂一半。與其說 COBOL 是邏輯的語言,不如說它在很大程度上是記錄定義的語言,所以要先看 DATA DIVISION。安全的閱讀順序是:把 COPY 全部盤點一遍並確認 copybook,把 01 層級的記錄定義整理成清單,用 PIC 和 USAGE 讀出項目的形狀,搜尋 READ、WRITE、CALL、EXEC SQL、EXEC CICS 掌握輸入輸出與外部邊界,最後才從 PROCEDURE DIVISION 開頭沿著 PERFORM 鏈只追主路徑。
PIC S9(7)V99 COMP-3 是什麼意思?
PIC 表示項目的形狀,USAGE 表示用什麼表示法保存。S9(7)V99 是帶正負號、整數 7 位加小數 2 位的數值,但 V 只是邏輯上的小數點,資料裡並沒有點字元。COMP-3 就是 packed decimal,常出現在金額、稅額、件數、比率這類項目上。它當成文字看會壞掉是理所當然的,用 CSV 或 UTF-8 的心情去看傾印就會出事。
COBOL 的 88 層級和 REDEFINES 該怎麼理解?
88 不是獨立的 bool 變數,而是給前一個項目的值取名字的條件名稱(condition-name)。SET WS-OK TO TRUE 在背後是把對應的值寫進基底項目。REDEFINES 是用另一種形狀看同一塊記憶體區域的機制,不是複製,感覺上比較接近 C 系語言的 union。因為改寫其中一邊,另一邊看起來也會跟著變,所以常用在把同一塊區域依記錄種別分辨的寫法上。
COPY 陳述式太多、看不到全貌時該怎麼辦?
COPY 是編譯時期的 include,所以現在打開的原始碼可能還不是完成形。記錄定義、共通旗標、SQL 用的 host variable、外部介面被塞進 copybook,是相當常見的事。讀不下去時,先查看能不能取得展開後的原始碼或 compiler listing 會比較快;IBM Enterprise COBOL 也有一個叫 MDECK 的選項,可以把函式庫處理後的輸入原始碼寫出來。

作者檔案

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

Go Komura

小村軟體有限公司 代表

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

回到部落格一覽