Минимум знаний перед чтением исходного кода COBOL

· Обновлено: · · COBOL, легаси, бизнес-системы, сопровождение, Mainframe

История изменений (1 обновлений, последнее 30 Aug 2026)

Журнал изменений этой статьи. Там, где версия до правки была заархивирована, она остаётся доступной для чтения по постоянной ссылке с DOI.

Русский текст переписан как полноценный технический перевод, а не калька с японского. Утверждения статьи не менялись.
Первая публикация
Цитирование статьи(DOI: 10.5281/zenodo.21619734)

Статья заархивирована на Zenodo. Ниже приведены DOI, который всегда ведёт к последней версии, и DOI, закреплённый за версией, которую вы читаете.

Го Комура (2026). Минимум знаний перед чтением исходного кода COBOL. KomuraSoft LLC. https://doi.org/10.5281/zenodo.21619734 https://comcomponent.com/ru/blog/2026/03/17/001-cobol-minimum-reading-guide/

DOI (последняя версия)
10.5281/zenodo.21619734
DOI (эта версия)
10.5281/zenodo.21619735

Передача проекта, разбор сбоя, сопровождение пакета вендора. В таких ситуациях однажды на стол может упасть исходный код 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 — скорее не отдельная переменная, а condition-name: имя значения предыдущего элемента
  • REDEFINESдругой взгляд на ту же память. Это не копия
  • Если есть COPY, открытый файл ещё не полный. Без copybook общей картины не видно
  • Если умеете следить за PERFORM, IF, EVALUATE, READ, WRITE, CALL, общий ход обработки уже улавливается
  • Старый исходник — это фиксированный формат, где позиция колонки имеет смысл. Видимые пробелы — не украшение1

Коротко: DIVISION, PIC, USAGE, COMP-3, REDEFINES, OCCURS, 88, COPY, PERFORM. Когда это читается, теряться начинают гораздо реже.

Карта знаний этой статьи

COBOL — это прежде всего язык, который в DATA DIVISION задаёт форму записи (record), и лишь затем язык процедур в PROCEDURE DIVISION; каждый элемент данных указывает форму в предложении PICTURE и внутреннее представление вроде COMP-3 в USAGE. COMP-3 — packed decimal, который упаковывает по две десятичные цифры в байт; если открыть его как текст, это выглядит как бессмысленная последовательность байтов. REDEFINES перечитывает ту же область в другой форме, OCCURS задаёт массив, уровень 88 — условие, которое даёт имя значению предыдущего элемента. Предложение COPY на этапе компиляции подключает внешний copybook, поэтому один открытый исходник не показывает полной картины; поток обработки нужно вести по PERFORM и scope terminator, а внешнюю границу фиксировать через FILE STATUS, EXEC SQL и EXEC CICS.

Карта знаний: чтение COBOLСхема показывает, что COBOL опирается на DATA DIVISION и PROCEDURE DIVISION; как элементы определения данных — PICTURE, USAGE, COMP-3, REDEFINES, OCCURS, уровень 88 и COPY — связаны с элементами управления вроде PERFORM и scope terminator и с внешними границами вроде EBCDIC, FILE STATUS, EXEC SQL и EXEC CICS.используетиспользуетиспользуетиспользуетиспользуетиспользуетиспользуетиспользуетиспользуетиспользуетиспользуетиспользуетиспользуетиспользуетиспользуетиспользуетиспользуетиспользуетиспользуетиспользуетиспользуетCOBOLDATA DIVISIONCOMP-3 (packed decimal)PROCEDURE DIVISIONфраза PICTURE (PIC)фраза USAGECOMP-5фраза REDEFINESфраза OCCURSOCCURS DEPENDING ONуровень 88 (имя условия)оператор COPYcopybookоператор PERFORMscope terminatorоператор MOVEфиксированный формат (reference format)IBM Enterprise COBOLEBCDICфраза FILE STATUSEXEC SQLEXEC CICS

На схеме сплошная линия обозначает отношение, которое выполняется всегда, а пунктирная — условное отношение (условия указаны в пояснении к каждому отношению на странице сведений). Полный список отношений (всего 21, с доказательствами и степенью уверенности) и определения основных понятий собраны на странице сведений карты знаний (на японском). Данные: JSON-LD / Turtle

2. Считайте, что COBOL — прежде всего язык о форме данных

Если читать его рефлексами C# или Java, сначала хочется идти по if, for и вызовам функций. В COBOL быстрее сначала понять, какие записи программа принимает, какие собирает и какими буферами располагает — и только потом идти дальше.

Типичный бизнес-COBOL идёт примерно так.

  1. Считать запись из файла или БД
  2. Положить её в элементы WORKING-STORAGE
  3. Разветвиться по условию
  4. Переложить данные в другую запись
  5. Записать результат

Иначе говоря, раскладка данных обычно встаёт раньше алгоритма.

Типичный поток бизнес-COBOLСчитать запись из файла или БД, положить в элементы 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 прежде всего делится на четыре крупных 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         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'.

Четыре правила чтения.

  • Колонки 1–6 — порядковый номер. В примере это 000100 и подобные значения; на работу программы они не влияют. Бывает и пусто.
  • Колонка 7 — indicator. * — комментарий, - — продолжение. В примере это строка 000900: продолжение строкового литерала с предыдущей строки.
  • Колонки 8–11 — Area A. Отсюда начинают DIVISION, SECTION, имена параграфов, FD, а также номера уровней 01 и 77. В примере с Area A начинаются IDENTIFICATION DIVISION. и 01 WS-ORDER.
  • Колонки 12–72 — Area B. Сюда пишут обычные операторы и младшие уровни вроде 05. В примере с Area B начинается 05 WS-ORDER-ID.

Пробелы здесь — не «форматирование» в современном смысле, а отчасти синтаксис. Преобразовать табуляцию в редакторе, сдвинуть текст влево или небрежно скопировать — и код обычно ломается. Глядя на старый исходник, сначала спросите, fixed format это или free format. Современный автоформаттер на fixed-формате сдвигает границу Area A и Area B, и компиляция перестаёт проходить.

Прежде чем трогать фиксированный форматВ старом исходнике сначала выясняют, fixed format это или free. Автоформатирование или замена табуляции на fixed-формате сдвигает границу Area A и Area B, и компиляция перестаёт проходить.fixed formatfree formatОткрыли старый исходникfixed или free?Позиция колонки — часть синтаксисаАвтоформат и замена табуляции ломают кодОграничения по колонкам слабее

Рис. 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 : верхний уровень одной записи или группы
  • 0249 : уровни под ним
  • 77 : независимый одиночный элемент
  • 88 : condition-name. Даёт имя значению предыдущего элемента3
  • 66 : для RENAMES. Встречается нечасто, но существует

Важно не считать 88 отдельной bool-переменной. Отдельной области WS-OK нет: когда WS-STATUS равен '0', это же значение можно прочитать под именем WS-OK.

Что на самом деле такое уровень 88Уровень 88 — не отдельная bool-переменная, а condition-name: имя значения предыдущего элемента. WS-OK становится истинным, только когда WS-STATUS имеет нужное значение.когда 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 трактуется как «число с двумя десятичными разрядами», но точки в данных нет. Поэтому если читать файл или дамп как «видимую строку символов», почти наверняка ошибётесь.

V — логическая десятичная точкаV в PIC 9(5)V99 — логическая десятичная точка: символа точки в данных нет. Если читать файл или дамп как видимую строку, почти наверняка ошибётесь.PIC 9(5)V99Трактуют как число с 2 десятичными разрядамиСимвола точки в данных нетЧтение как видимой строки ломает смысл

Рис. 6: Десятичная точка есть в определении, в данных её нет.

5.3 USAGE / DISPLAY / COMP / COMP-3

Если PIC — форма, то USAGEв каком представлении элемент хранится. Для чтения хватит следующего минимума.45

Запись Примерный смысл На что смотреть при чтении
DISPLAY Внешняя десятичная форма, видимая как символы На mainframe нередко предполагается EBCDIC6
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.

Все три — «числа», но способ хранения разный.

Одно и то же число, разное хранениеДаже у числа со знаком USAGE меняет хранение: DISPLAY — внешняя десятичная форма, видимая как символы; COMP — двоичное число; COMP-3 — packed decimal, который как текст выглядит «битым».Числовой элемент той же формы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 всего два правила.45

  1. В один байт укладывают по две десятичные цифры
  2. Но в самом правом байте младшая цифра и знак занимают весь байт

Знак кодируется 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 четыре разряда, поэтому из-за правила 2 в начале появляется свободный разряд. Это ведущий 0.

Тем же способом разберём PIC S9(7)V99 COMP-3, который в статье встречается не раз.

  • S9(7)V99 — это 7 разрядов целой части + 2 разряда дробной = 9 разрядов
  • V только отмечает позицию десятичной точки и не занимает ни одного байта
  • 9 разрядов по два на байт дают 4 байта, оставшийся разряд и знак — ещё 1 байт. Итого 5 байт

Если значение +12345.67, девять разрядов — это 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 — управляющий символ; как символы они не отображаются
  • 344, 56V, 7C|

На экране это похоже на «несколько непечатаемых символов, затем 4V|». Последовательности 12345.67 нигде нет.

В этом и есть «похоже на порчу кодировки, но данные целы». Открыли дамп, увидели бессмысленную последовательность — сначала спросите, нет ли у этого элемента USAGE COMP-3.

Как собираются байты COMP-3В packed decimal десятичные цифры укладывают по две в байт, а в последнем байте — младшая цифра и знак; 9-разрядное число занимает 5 байт, и при открытии как ASCII исходной последовательности цифр нигде нет.По две десятичные цифры в байтПоследний байт: младшая цифра и знак9 разрядов — итого 5 байтВ ASCII исходного числа нетПохоже на порчу кодировки, но данные целы

Рис. 9: Два правила упаковки превращают «непонятный дамп» в читаемую раскладку.

Заодно шпаргалка: из числа разрядов в число байтов. Байтов получается так: число девяток разделить на 2, дробную часть отбросить, прибавить 1.

Число девяток в PICTURE Байтов у COMP-3
1 1
2–3 2
4–5 3
6–7 4
8–9 5
10–11 6
12–13 7

Два разных числа разрядов дают одну длину, потому что свободный ведущий разряд появляется, только когда девяток чётное число. Поэтому S9(4) занимает 3 байта (01 23 4C), и S9(5) тоже укладывается в те же 3 байта.

При сверке layout с внешним файлом без этой таблицы смещение уезжает по одному байту.

Ещё одно уточнение: DISPLAY вовсе не обязательно означает строку ASCII. В линейке z/OS по умолчанию предполагается EBCDIC, поэтому даже если цифры видны как символы, байтовые значения могут отличаться от ASCII '0''9'.6

5.4 REDEFINES / OCCURS / COPY / FILLER

Эти четыре конструкции — типичные места, где чтение стопорится.

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

По ощущению это близко к union в языках семейства C. Часто пишут в духе «один и тот же блок из 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, это таблица переменной длины. Тогда может сдвинуться даже позиция последующих элементов: если читать в логике фиксированной длины, легко промахнуться со смещением.8

На что смотреть в OCCURS DEPENDING ONOCCURS — массив; с DEPENDING ON это таблица переменной длины, и позиция последующих элементов может зависеть от значения. Смещения в логике фиксированной длины тогда неверны.нетестьНашли OCCURSесть ли DEPENDING ON?Таблица фиксированного числа элементовТаблица переменной длиныПозиция последующих элементов тоже может сдвинуться

Рис. 11: Три слова DEPENDING ON меняют исходные допущения для расчёта смещений.

COPY

COPY — это include на этапе компиляции. То есть открытый сейчас файл ещё не обязательно полный исходник.9

       COPY CUSTOMER-REC.
       COPY ERROR-MAP.

Совершенно обычно, когда определения записей, общие флаги, host-переменные для SQL и внешние интерфейсы загнаны в copybook.

Если из-за обилия COPY читать тяжело, быстрее проверить, можно ли посмотреть развёрнутый исходник или compiler listing. В IBM Enterprise COBOL есть опция MDECK: она выводит входной исходный код после обработки библиотек.10

Как читать исходник с COPYCOPY — include на этапе компиляции, поэтому открытый файл может быть ещё неполным. Смотрят copybook; если читать тяжело, ищут развёрнутый исходник, compiler listing или вывод опции MDECK.если читать тяжелоНашли COPYОткрытый файл может быть ещё неполнымОткрыть copybook и проверитьИскать развёрнутый исходник или listing

Рис. 12: Число строк, которое вы видите сейчас, — ещё не вся программа.

FILLER

FILLER — элемент без имени. Но «раз на него не ссылаются, значит он ни на что не влияет» — неверно.

Обычные роли:

  • зарезервированная область
  • дыра для совместимости со старой спецификацией
  • выравнивание длины записи
  • запас под REDEFINES

У FILLER просто нет имени, но как число байтов он существует. Забудете — и при сопоставлении с внешним файлом картина поедет по одному байту.

Что будет, если не посчитать FILLERУ FILLER нет имени, но байты он занимает: резерв или выравнивание длины записи. Если не учесть его в расчёте, сопоставление с внешним файлом смещается по одному байту.учлизабылиFILLER — элемент без имениКак число байтов он существуетучли в расчёте layout?Совпадает с внешним файломСмещение едет по одному байту

Рис. 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 два основных вида.

  • Out-of-line PERFORM, который указывает на параграф или SECTION
  • Inline PERFORM ... END-PERFORM, где блок пишут прямо на месте

В более старом коде обычно встречается и указание диапазона вроде PERFORM A-100 THRU A-199. Это удобно, но если в середину добавить параграф, его легко случайно захватить, поэтому при чтении смотрите, где заканчивается диапазон.

Три формы PERFORMУ PERFORM есть out-of-line, который вызывает параграф или SECTION и возвращается, inline с блоком на месте, и в старом коде диапазон через THRU, который легко захватит лишний параграф, если его вставить в середину.Нашли PERFORMOut-of-line: вызов параграфаInline: блок на местеДиапазон через THRUОбязательно смотреть конец диапазона

Рис. 14: От формы PERFORM зависят и точка возврата, и диапазон, который нужно проследить.

6.2 IF / EVALUATE / область действия

Базовый инструмент условного ветвления — IF. Про EVALUATE достаточно думать примерно как про switch/case.

На что смотреть — как заканчивается область действия (scope).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 вызываемой стороны довольно ясно показывают форму передачи данных.

Как идти по CALLCALL уходит в другую программу; LINKAGE SECTION и PROCEDURE DIVISION USING вызываемой стороны показывают форму передачи аргументов.Нашли CALLУход в другую программуСмотреть LINKAGE SECTION вызываемой стороныЧерез USING понять форму передачи

Рис. 16: Смысл вызова читают вместе с точкой приёма на вызываемой стороне.

7. Что лежит снаружи COBOL

Мир COBOL довольно часто не замыкается на одном исходнике.

Снаружи остаются:

  • определения файлов
  • среда выполнения
  • подключение к БД
  • транзакционная среда
  • управление заданиями (job)

Для чтения стоит освоить хотя бы следующее.

Файлы и FILE STATUS

FILE-CONTROL в ENVIRONMENT DIVISION и FILE SECTION / FD в DATA DIVISION читают парой.13

       SELECT IN-FILE ASSIGN TO ...
           FILE STATUS IS WS-FS.

       FD  IN-FILE.
       01  IN-REC.
           05 ...

Если есть FILE STATUS, туда попадает код результата после каждой операции ввода-вывода. Читать файловые сбои или проверку EOF без этого не с чего начинать.14

Определение файла читают паройSELECT в FILE-CONTROL внутри ENVIRONMENT DIVISION и FD в DATA DIVISION читают парой. Если есть FILE STATUS, туда попадает код результата после каждого ввода-вывода — отсюда начинают чтение сбоев и EOF.SELECT в FILE-CONTROLВместе — одно определение файлаFD в FILE SECTIONВ FILE STATUS попадает код результатаТочка входа в чтение сбоев и EOF

Рис. 17: Образ файла записан в двух DIVISION.

EXEC SQL

Если это встречается, перед вами встроенный SQL.

       EXEC SQL
           SELECT ...
       END-EXEC.

Тогда COBOL — «ёмкость для host-переменных», а фактические условия выборки и объекты обновления находятся на стороне SQL. Поэтому кратчайший путь — читать содержимое EXEC SQL как обычный SQL.

EXEC CICS

Если это встречается, вы в транзакционном контексте CICS.15

       EXEC CICS
           RECEIVE MAP(...)
       END-EXEC.

В этот момент это уже не просто чтение пакетной программы. Нужно читать вместе с внешним контекстом: экранами, транзакциями, кодами ответа, COMMAREA и так далее.

JCL и определения запуска

В mainframe batch не редкость, что какой dataset реально назначат и в каком порядке пойдут job’ы, лежит вне исходника COBOL. Если по одному исходному файлу не понять, «где вообще этот файл», обычно дело не в плохом коде, а в том, что вы смотрите ещё не на весь нужный контур.

Мир снаружи исходникаЕсли есть EXEC SQL, фактические условия — на стороне SQL; если EXEC CICS — экраны и транзакции во внешнем контексте; в mainframe batch назначение dataset и поток job лежат в JCL. Мир COBOL разделён и за пределы исходника.EXEC SQLСмотреть и снаружи исходникаEXEC CICSJCL и определения запускаУсловия SQL, экранный контекст, поток job

Рис. 18: Непонятно не потому, что код плохой, а потому, что поле зрения ещё узкое.

Различия между компиляторами

Статья ориентирована на линейку IBM, но на практике встречаются и Micro Focus, и COBOL на Linux / Windows. Скелет общий, поэтому способ чтения не меняется, зато точки, где одна и та же запись даёт разный результат, известны заранее — их лучше знать, чтобы не ошибиться.

Куда смотреть z/OS (IBM Enterprise COBOL) Открытые платформы (Micro Focus, COBOL для Linux / Windows и т. п.)
Кодировка Предполагается EBCDIC6 Предполагается ASCII6
Reference format Традиционный default — fixed; free тоже можно выбрать1 Есть и fixed, и free; что является default, зависит от настроек сборки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. Зафиксировать reference format (fixed / free). Ошибка здесь плюс автоформат в редакторе ломает код.
  3. Зафиксировать кодировку. EBCDIC или ASCII меняет чтение дампа.
  4. Пометить элементы семейства COMP. Расхождения между реализациями почти всегда здесь.
Короткий путь сверить со своей средойСначала открывают определение сборки и смотрят компилятор с опциями, фиксируют reference format (fixed или free), фиксируют кодировку EBCDIC или ASCII и помечают элементы семейства COMP, где расхождения между реализациями проявляются чаще всего.Сначала открыть определение сборкиЗафиксировать reference formatЗафиксировать кодировкуПометить элементы семейства 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 и элементы статуса После этого легче читаются EOF, норма/ошибка, коды типа
  7. Пометить REDEFINES / OCCURS DEPENDING ON / COMP-3 Они обязательно скажутся позже, поэтому заранее отметьте их как опасные места
  8. Для файлов — посмотреть FILE STATUS Это заметно снижает ошибки чтения, связанные с I/O

При таком порядке не нужно сразу вчитываться во весь текст. С COBOL гораздо легче не пытаться понять всё на 100% с самого начала, а сначала закрепить три точки — записи, внешние границы, основной путь — и только потом идти в детали.

Безопасный порядок чтенияПроходят все COPY и смотрят copybook, собирают определения записей уровня 01, читают форму через PIC и USAGE, поиском находят ввод-вывод и внешние границы, затем идут по цепочке PERFORM от начала PROCEDURE DIVISION и помечают опасные места.Пройти все 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. С какого байта от начала начинается HIST-AMOUNT второй строки?
  4. Если в CUST-KBN лежит '1', какое condition-name истинно?
  5. Если открыть этот файл в текстовом редакторе, какие элементы выглядят «битыми»?
  6. Назовите две вещи, которых из этого определения не видно.

Ответы

  1. 74 байта. Разбор такой.

    Элемент Расчёт Байт
    CUST-ID X(8) 8
    CUST-NAME X(20) 20
    CUST-KBN X 1
    CUST-BALANCE COMP-3 на 9 разрядов 5
    CUST-HIST (8 + 4) × 3 36
    FILLER X(4) 4
    Итого   74

    Уровень 88 — condition-name, поэтому байтов не занимает. Посчитать его — частая ошибка. У FILLER нет имени, но 4 байта на месте.

  2. С 30-го байта. Перед ним 8 + 20 + 1 = 29 байт, поэтому начинается со следующего.

  3. С 55-го байта. CUST-HIST начинается с 35-го байта, одна строка — 12 байт, поэтому первая занимает байты 35–46, вторая — 47–58. Первые 8 байт строки — HIST-DATE, значит HIST-AMOUNT начинается с 55-го байта.

  4. CUST-VIP. Отдельной области CUST-VIP нет: когда CUST-KBN равен '1', то же значение можно прочитать под этим именем.

  5. CUST-BALANCE и HIST-AMOUNT. Оба COMP-3, поэтому как текст дают бессмысленную последовательность. HIST-DATEPIC 9(8) в DISPLAY, и в ASCII-среде его можно прочитать как цифры вроде 20260317. В среде EBCDIC цифры могут выглядеть как цифры, но байтовые значения будут другими, чем в ASCII.

  6. Например, такое.

    • Пришло ли само это определение через COPY. Без copybook не видно, та ли это версия, которая реально используется.
    • EBCDIC или ASCII у файла. От этого зависит, как выглядят PIC X и PIC 9 DISPLAY.
    • Бывает ли в CUST-KBN что-то кроме '0' и '1'. Определены только два condition-name; это не гарантия, что других значений не придёт.
    • Атрибуты самого файла (длина записи, фиксированная она или нет, FILE STATUS). Это видно только в ENVIRONMENT DIVISION и FD.

Если ошиблись в вопросах 1 и 3, вернитесь к таблице разрядов и байтов в разделе 5.3. Ошибки чтения COBOL обычно начинаются отсюда.

9. Типичные места, где застревают

В конце — места, на которых новички спотыкаются с очень высокой вероятностью.

Считать REDEFINES «другой переменной»

Нет. Это другой взгляд на ту же область. Меняете одну сторону — меняется и то, как выглядит другая.7

Считать 88 «отдельным bool»

Нет. Это просто имя значения предыдущего элемента. SET WS-OK TO TRUE на деле записывает соответствующее значение в базовый элемент.3

Игнорировать COPY и читать только тело файла

Открытый файл — ещё только половина целого. Совершенно обычно, когда определения полей, общие флаги и host-переменные целиком лежат снаружи.9

Считать MOVE простым присваиванием

MOVE — не просто memcpy. В зависимости от типа приёмника могут выполняться преобразование, выравнивание разрядов, заполнение нулями, усечение, а также редактирование и обратное редактирование.17

Недооценивать влияние .

Точка . в 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 Обработка БД
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 и ввод-вывод, закрепляют внешние границы через FILE STATUS, EXEC SQL и EXEC CICS. В таком масштабе COBOL читается как обычный язык обработки записей.Взять карту через DIVISIONПрочитать форму в DATA DIVISIONИдти по потоку через PERFORM и ввод-выводЗакрепить внешние границыДревняя магия становится языком обработки записей

Рис. 21: Сложность — не в возрасте языка, а в масштабе, с которого начинают смотреть.

12. Источники

Основные источники, на которые ссылается статья. Надстрочные номера в тексте ведут сюда; стрелка в конце каждой записи возвращает к месту ссылки.

Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.

Внутреннее устройство виртуализации Windows (часть 3) — виртуальные машины, которые стартуют за секунды: почему WSL2, Windows Sandbox и контейнеры такие лёгкие

Почему WSL2 и Windows Sandbox стартуют за секунды и остаются лёгкими. Разбираем динамический базовый образ, direct map, динамическое расп...

Эти страницы показывают тему статьи в более широком контексте услуг и решений.

Статья напрямую связана со следующими услугами.

Частые вопросы

Вопросы, которые часто возникают при консультациях по теме статьи.

С чего начинать чтение исходного кода COBOL?
Одного PROCEDURE DIVISION мало: вы увидите от силы половину. COBOL — прежде всего язык описания записей и лишь потом язык логики, поэтому сначала открывают DATA DIVISION. Безопасный порядок такой: пройти все COPY и посмотреть copybook'и, собрать определения записей уровня 01, прочитать форму элементов через PIC и USAGE, найти READ, WRITE, CALL, EXEC SQL и EXEC CICS, чтобы увидеть ввод-вывод и внешние границы, и только потом пройти цепочку PERFORM от начала PROCEDURE DIVISION — один основной путь.
Что означает PIC S9(7)V99 COMP-3?
PIC задаёт форму элемента, USAGE — в каком представлении он хранится. S9(7)V99 — число со знаком: 7 разрядов целой части и 2 разряда дробной, но V — только логическая десятичная точка, символа точки в данных нет. COMP-3 — packed decimal; так часто описывают суммы, налоги, количества и ставки. Как текст это выглядит «битым», и так и должно быть: смотреть дамп в логике CSV или UTF-8 — верный способ ошибиться.
Как понимать уровень 88 и REDEFINES?
88 — это не отдельная bool-переменная, а condition-name: имя, данное значению предыдущего элемента. SET WS-OK TO TRUE на деле записывает соответствующее значение в базовый элемент. REDEFINES — другой взгляд на ту же область памяти, не копия; по ощущению ближе к union в языках семейства C. Меняете одну сторону — меняется и то, как выглядит другая. Так часто различают типы записей в одном буфере.
Что делать, если из-за обилия COPY не видно общей картины?
COPY — это include на этапе компиляции, поэтому открытый файл ещё не обязательно полный исходник. Определения записей, общие флаги, host-переменные для SQL и внешние интерфейсы вполне обычно лежат в copybook. Если читать тяжело, быстрее проверить, есть ли развёрнутый исходник или compiler listing. В IBM Enterprise COBOL для этого есть опция MDECK: она выводит входной исходный код после обработки библиотек.

Об авторе

Страница с профилем автора статьи.

Го Комура

Представитель KomuraSoft LLC

Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог