Минимум знаний перед чтением исходного кода COBOL
· Обновлено: · Го Комура · COBOL, легаси, бизнес-системы, сопровождение, Mainframe
История изменений (1 обновлений, последнее 30 Aug 2026)
Журнал изменений этой статьи. Там, где версия до правки была заархивирована, она остаётся доступной для чтения по постоянной ссылке с DOI.
- Русский текст переписан как полноценный технический перевод, а не калька с японского. Утверждения статьи не менялись.
- Первая публикация
Цитирование статьи(DOI (зарегистрированный архив): 10.5281/zenodo.21619734)
Приведённые ниже DOI относятся к ранее зарегистрированным архивным версиям, которые могут отличаться от текущего текста. Для ссылки на текущий текст используйте URL этой страницы.
Го Комура (2026). Минимум знаний перед чтением исходного кода COBOL. KomuraSoft LLC. 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, разберём минимальный набор для тех, кому внезапно пришлось читать такой исходный код.
flowchart TB
accTitle: Почему стопорятся и насколько велика карта
accDescr: Непривычные имена заглавными, номера уровней, запись вроде PIC S9(7)V99 COMP-3 и обилие COPY, из-за которого не видно целого, заставляют остановиться. Но скелет для чтения существующей бизнес-системы довольно общий, и карта не такая уж большая.
iv1["Непривычная запись — и стопор"] --> iv2["Но скелет общий для разных компиляторов"]
iv2 --> iv3["Сначала держат только минимальную карту"]
iv1 -.-> iv4["Сплошной 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. Когда это читается, теряться начинают гораздо реже.
На схеме сплошная линия обозначает отношение, которое выполняется всегда, а пунктирная — условное отношение (условия указаны в пояснении к каждому отношению на странице сведений). Полный список отношений (всего 21, с доказательствами и степенью уверенности) и определения основных понятий собраны на странице сведений карты знаний (на японском). Данные: JSON-LD / Turtle
2. Считайте, что COBOL — прежде всего язык о форме данных
Если читать его рефлексами C# или Java, сначала хочется идти по if, for и вызовам функций.
В COBOL быстрее сначала понять, какие записи программа принимает, какие собирает и какими буферами располагает — и только потом идти дальше.
Типичный бизнес-COBOL идёт примерно так.
- Считать запись из файла или БД
- Положить её в элементы
WORKING-STORAGE - Разветвиться по условию
- Переложить данные в другую запись
- Записать результат
Иначе говоря, раскладка данных обычно встаёт раньше алгоритма.
flowchart TB
accTitle: Типичный поток бизнес-COBOL
accDescr: Считать запись из файла или БД, положить в элементы WORKING-STORAGE, разветвиться по условию, переложить в другую запись и записать результат — типичный поток бизнес-COBOL.
f1["Считать запись"] --> f2["Положить в WORKING-STORAGE"]
f2 --> f3["Разветвиться по условию"]
f3 --> f4["Переложить в другую запись"]
f4 --> f5["Записать результат"]
Рис. 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 ..., велика вероятность, что программа не самодостаточна: она работает с данными, которые приходят снаружи.
flowchart TB
accTitle: На что указывает LINKAGE SECTION
accDescr: Если видны LINKAGE SECTION и PROCEDURE DIVISION USING, программа, скорее всего, не самодостаточна и работает с данными, которые приходят снаружи.
lk1["Есть LINKAGE SECTION"] --> lk3["Вероятно, данные приходят снаружи"]
lk2["Есть PROCEDURE DIVISION USING"] --> lk3
lk3 -.-> lk4["Это не самодостаточная программа"]
Рис. 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, и компиляция перестаёт проходить.
flowchart TB
accTitle: Прежде чем трогать фиксированный формат
accDescr: В старом исходнике сначала выясняют, fixed format это или free. Автоформатирование или замена табуляции на fixed-формате сдвигает границу Area A и Area B, и компиляция перестаёт проходить.
fx1["Открыли старый исходник"] --> fx2{"fixed или free?"}
fx2 -->|"fixed format"| fx3["Позиция колонки — часть синтаксиса"]
fx3 -.-> fx4["Автоформат и замена табуляции ломают код"]
fx2 -->|"free format"| fx5["Ограничения по колонкам слабее"]
Рис. 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. Даёт имя значению предыдущего элемента366: дляRENAMES. Встречается нечасто, но существует
Важно не считать 88 отдельной bool-переменной.
Отдельной области WS-OK нет: когда WS-STATUS равен '0', это же значение можно прочитать под именем WS-OK.
flowchart TB
accTitle: Что на самом деле такое уровень 88
accDescr: Уровень 88 — не отдельная bool-переменная, а condition-name: имя значения предыдущего элемента. WS-OK становится истинным, только когда WS-STATUS имеет нужное значение.
cn1["Базовый элемент WS-STATUS"] --> cn2{"какое значение?"}
cn2 -->|"когда 0"| cn3["Истинно под именем WS-OK"]
cn2 -->|"когда 9"| cn4["Истинно под именем WS-ERROR"]
cn3 -.-> cn5["Отдельной области нет"]
Рис. 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 трактуется как «число с двумя десятичными разрядами», но точки в данных нет.
Поэтому если читать файл или дамп как «видимую строку символов», почти наверняка ошибётесь.
flowchart TB
accTitle: V — логическая десятичная точка
accDescr: V в PIC 9(5)V99 — логическая десятичная точка: символа точки в данных нет. Если читать файл или дамп как видимую строку, почти наверняка ошибётесь.
vd1["PIC 9(5)V99"] --> vd2["Трактуют как число с 2 десятичными разрядами"]
vd2 --> vd3["Символа точки в данных нет"]
vd3 -.-> vd4["Чтение как видимой строки ломает смысл"]
Рис. 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.
Все три — «числа», но способ хранения разный.
flowchart TB
accTitle: Одно и то же число, разное хранение
accDescr: Даже у числа со знаком USAGE меняет хранение: DISPLAY — внешняя десятичная форма, видимая как символы; COMP — двоичное число; COMP-3 — packed decimal, который как текст выглядит «битым».
us1["Числовой элемент той же формы"] --> us2["DISPLAY (видно как символы)"]
us1 --> us3["COMP (двоичное число)"]
us1 --> us4["COMP-3 (packed decimal)"]
us4 -.-> us5["Как текст выглядит «битым»"]
Рис. 7: Даже при том же PIC другой USAGE даёт другую последовательность байтов.
На практике сильнее всего выручает рефлекс в момент, когда видите COMP-3.
- Это packed decimal
- Скорее всего сумма, налог, количество или ставка
- Как текст выглядит «битым» — так и должно быть
- Смотреть в логике CSV или UTF-8 — верный способ ошибиться
С таким пониманием дампы и двоичные файлы гораздо реже вызывают лишнюю панику.
flowchart TB
accTitle: Рефлекс в момент, когда видите COMP-3
accDescr: Увидев COMP-3, понимают, что это packed decimal, скорее всего сумма, налог, количество или ставка, и что «битый» вид как текст ожидаем, поэтому дамп не смотрят в логике CSV или UTF-8.
cp1["Нашли COMP-3"] --> cp2["Поняли: packed decimal"]
cp2 --> cp3["Подозревают сумму или количество"]
cp3 --> cp4["«Битый» текст не пугает"]
Рис. 8: Одного этого рефлекса достаточно, чтобы реже паниковать перед дампом.
Какая последовательность байтов получается у COMP-3
Это стоит один раз пройти руками — дальше дампы читаются иначе.
У packed decimal всего два правила.45
- В один байт укладывают по две десятичные цифры
- Но в самом правом байте младшая цифра и знак занимают весь байт
Знак кодируется 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— управляющий символ; как символы они не отображаются34—4,56—V,7C—|
На экране это похоже на «несколько непечатаемых символов, затем 4V|». Последовательности 12345.67 нигде нет.
В этом и есть «похоже на порчу кодировки, но данные целы». Открыли дамп, увидели бессмысленную последовательность — сначала спросите, нет ли у этого элемента USAGE COMP-3.
flowchart TB
accTitle: Как собираются байты COMP-3
accDescr: В packed decimal десятичные цифры укладывают по две в байт, а в последнем байте — младшая цифра и знак; 9-разрядное число занимает 5 байт, и при открытии как ASCII исходной последовательности цифр нигде нет.
pk1["По две десятичные цифры в байт"] --> pk2["Последний байт: младшая цифра и знак"]
pk2 --> pk3["9 разрядов — итого 5 байт"]
pk3 --> pk4["В ASCII исходного числа нет"]
pk4 -.-> pk5["Похоже на порчу кодировки, но данные целы"]
Рис. 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 байт различать как разные типы записи».
flowchart TB
accTitle: REDEFINES — другая интерпретация той же области
accDescr: REDEFINES — не копия, а другой взгляд на ту же область памяти: один буфер читают и как общую запись, и как заголовок; запись с одной стороны меняет вид с другой.
rd1["Одна область памяти"] --> rd2["Смотреть как REC-BUF"]
rd1 --> rd3["Смотреть как HEADER-REC"]
rd2 -.-> rd4["Запись с одной стороны меняет вид обеих"]
rd3 -.-> rd4
Рис. 10: Определений два, последовательность байтов — одна.
OCCURS
OCCURS — это массив. В COBOL его чаще называют table.
05 WS-ITEM OCCURS 12 TIMES.
10 WS-PRICE PIC 9(5).
Если дальше встречается OCCURS DEPENDING ON, это таблица переменной длины.
Тогда может сдвинуться даже позиция последующих элементов: если читать в логике фиксированной длины, легко промахнуться со смещением.8
flowchart TB
accTitle: На что смотреть в OCCURS DEPENDING ON
accDescr: OCCURS — массив; с DEPENDING ON это таблица переменной длины, и позиция последующих элементов может зависеть от значения. Смещения в логике фиксированной длины тогда неверны.
oc1["Нашли OCCURS"] --> oc2{"есть ли DEPENDING ON?"}
oc2 -->|"нет"| oc3["Таблица фиксированного числа элементов"]
oc2 -->|"есть"| oc4["Таблица переменной длины"]
oc4 -.-> oc5["Позиция последующих элементов тоже может сдвинуться"]
Рис. 11: Три слова DEPENDING ON меняют исходные допущения для расчёта смещений.
COPY
COPY — это include на этапе компиляции.
То есть открытый сейчас файл ещё не обязательно полный исходник.9
COPY CUSTOMER-REC.
COPY ERROR-MAP.
Совершенно обычно, когда определения записей, общие флаги, host-переменные для SQL и внешние интерфейсы загнаны в copybook.
Если из-за обилия COPY читать тяжело, быстрее проверить, можно ли посмотреть развёрнутый исходник или compiler listing. В IBM Enterprise COBOL есть опция MDECK: она выводит входной исходный код после обработки библиотек.10
flowchart TB
accTitle: Как читать исходник с COPY
accDescr: COPY — include на этапе компиляции, поэтому открытый файл может быть ещё неполным. Смотрят copybook; если читать тяжело, ищут развёрнутый исходник, compiler listing или вывод опции MDECK.
cy1["Нашли COPY"] --> cy2["Открытый файл может быть ещё неполным"]
cy2 --> cy3["Открыть copybook и проверить"]
cy2 -.->|"если читать тяжело"| cy4["Искать развёрнутый исходник или listing"]
Рис. 12: Число строк, которое вы видите сейчас, — ещё не вся программа.
FILLER
FILLER — элемент без имени.
Но «раз на него не ссылаются, значит он ни на что не влияет» — неверно.
Обычные роли:
- зарезервированная область
- дыра для совместимости со старой спецификацией
- выравнивание длины записи
- запас под
REDEFINES
У FILLER просто нет имени, но как число байтов он существует. Забудете — и при сопоставлении с внешним файлом картина поедет по одному байту.
flowchart TB
accTitle: Что будет, если не посчитать FILLER
accDescr: У FILLER нет имени, но байты он занимает: резерв или выравнивание длины записи. Если не учесть его в расчёте, сопоставление с внешним файлом смещается по одному байту.
fl1["FILLER — элемент без имени"] --> fl2["Как число байтов он существует"]
fl2 --> fl3{"учли в расчёте layout?"}
fl3 -->|"учли"| fl4["Совпадает с внешним файлом"]
fl3 -->|"забыли"| fl5["Смещение едет по одному байту"]
Рис. 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.
Это удобно, но если в середину добавить параграф, его легко случайно захватить, поэтому при чтении смотрите, где заканчивается диапазон.
flowchart TB
accTitle: Три формы PERFORM
accDescr: У PERFORM есть out-of-line, который вызывает параграф или SECTION и возвращается, inline с блоком на месте, и в старом коде диапазон через THRU, который легко захватит лишний параграф, если его вставить в середину.
pf1["Нашли PERFORM"] --> pf2["Out-of-line: вызов параграфа"]
pf1 --> pf3["Inline: блок на месте"]
pf1 --> pf4["Диапазон через THRU"]
pf4 -.-> pf5["Обязательно смотреть конец диапазона"]
Рис. 14: От формы PERFORM зависят и точка возврата, и диапазон, который нужно проследить.
6.2 IF / EVALUATE / область действия
Базовый инструмент условного ветвления — IF.
Про EVALUATE достаточно думать примерно как про switch/case.
На что смотреть — как заканчивается область действия (scope).12
Код с явными терминаторами вроде
END-IFEND-PERFORMEND-READ
читать ещё сравнительно легко.
Проблема — в старом коде. В COBOL точка . работает как неявный scope terminator и разом закрывает все ещё не закрытые операторы.12
То есть от одной точки зависит:
- докуда простирается
IF - докуда простирается
PERFORM - где начинается следующий sentence
К тому же NEXT SENTENCE — это не то же самое, что CONTINUE.
NEXT SENTENCE переходит за следующую точку, поэтому пункт назначения зависит от того, где стоит очередная ..12
При чтении старого COBOL правильный настрой — смотреть на точки, а не на концы строк.
flowchart TB
accTitle: Вес точки
accDescr: Код с явными терминаторами вроде END-IF читать легче, а в старом коде точка работает как неявный scope terminator и разом закрывает незакрытые операторы, так что одна позиция точки меняет диапазон IF и PERFORM.
sc1{"есть ли явный терминатор?"}
sc1 -->|"есть END-IF и подобные"| sc2["Диапазон читается легко"]
sc1 -->|"нет, старый код"| sc3["Точка — неявный терминатор"]
sc3 --> sc4["Одна позиция меняет диапазон"]
sc4 -.-> sc5["Смотреть на точки, не на концы строк"]
Рис. 15: Поток управления в старом коде держит позиция точки.
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 вызываемой стороны довольно ясно показывают форму передачи данных.
flowchart TB
accTitle: Как идти по CALL
accDescr: CALL уходит в другую программу; LINKAGE SECTION и PROCEDURE DIVISION USING вызываемой стороны показывают форму передачи аргументов.
ca1["Нашли CALL"] --> ca2["Уход в другую программу"]
ca2 --> ca3["Смотреть LINKAGE SECTION вызываемой стороны"]
ca3 --> ca4["Через 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
flowchart TB
accTitle: Определение файла читают парой
accDescr: SELECT в FILE-CONTROL внутри ENVIRONMENT DIVISION и FD в DATA DIVISION читают парой. Если есть FILE STATUS, туда попадает код результата после каждого ввода-вывода — отсюда начинают чтение сбоев и EOF.
fs1["SELECT в FILE-CONTROL"] --> fs3["Вместе — одно определение файла"]
fs2["FD в FILE SECTION"] --> fs3
fs3 --> fs4["В FILE STATUS попадает код результата"]
fs4 -.-> fs5["Точка входа в чтение сбоев и 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. Если по одному исходному файлу не понять, «где вообще этот файл», обычно дело не в плохом коде, а в том, что вы смотрите ещё не на весь нужный контур.
flowchart TB
accTitle: Мир снаружи исходника
accDescr: Если есть EXEC SQL, фактические условия — на стороне SQL; если EXEC CICS — экраны и транзакции во внешнем контексте; в mainframe batch назначение dataset и поток job лежат в JCL. Мир COBOL разделён и за пределы исходника.
ex1["EXEC SQL"] --> ex7["Смотреть и снаружи исходника"]
ex3["EXEC CICS"] --> ex7
ex5["JCL и определения запуска"] --> ex7
ex7 -.-> ex2["Условия 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, читайте это как сознательную запись.
Самый короткий путь сверить со своей средой такой.
- Сначала открыть определение сборки (makefile, JCL, настройки проекта). Какой компилятор и с какими опциями — это видно раньше, чем из исходника.
- Зафиксировать reference format (fixed / free). Ошибка здесь плюс автоформат в редакторе ломает код.
- Зафиксировать кодировку. EBCDIC или ASCII меняет чтение дампа.
- Пометить элементы семейства
COMP. Расхождения между реализациями почти всегда здесь.
flowchart TB
accTitle: Короткий путь сверить со своей средой
accDescr: Сначала открывают определение сборки и смотрят компилятор с опциями, фиксируют reference format (fixed или free), фиксируют кодировку EBCDIC или ASCII и помечают элементы семейства COMP, где расхождения между реализациями проявляются чаще всего.
ck1["Сначала открыть определение сборки"] --> ck2["Зафиксировать reference format"]
ck2 --> ck3["Зафиксировать кодировку"]
ck3 --> ck4["Пометить элементы семейства COMP"]
Рис. 19: Четыре шага до чтения исходника закрывают типичные аварии из-за различий компиляторов.
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и элементы статуса После этого легче читаются EOF, норма/ошибка, коды типа - Пометить
REDEFINES/OCCURS DEPENDING ON/COMP-3Они обязательно скажутся позже, поэтому заранее отметьте их как опасные места - Для файлов — посмотреть
FILE STATUSЭто заметно снижает ошибки чтения, связанные с I/O
При таком порядке не нужно сразу вчитываться во весь текст. С COBOL гораздо легче не пытаться понять всё на 100% с самого начала, а сначала закрепить три точки — записи, внешние границы, основной путь — и только потом идти в детали.
flowchart TB
accTitle: Безопасный порядок чтения
accDescr: Проходят все COPY и смотрят copybook, собирают определения записей уровня 01, читают форму через PIC и USAGE, поиском находят ввод-вывод и внешние границы, затем идут по цепочке PERFORM от начала PROCEDURE DIVISION и помечают опасные места.
ro1["Пройти все COPY"] --> ro2["Собрать уровень 01"]
ro2 --> ro3["Прочитать форму через PIC и USAGE"]
ro3 --> ro4["Найти ввод-вывод и внешние границы"]
ro4 --> ro5["Пройти цепочку PERFORM основного пути"]
ro5 -.-> ro6["Пометить опасные места"]
Рис. 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).
Вопросы
- Сколько байтов занимает
CUST-RECцеликом? - С какого байта от начала записи начинается
CUST-BALANCE? - С какого байта от начала начинается
HIST-AMOUNTвторой строки? - Если в
CUST-KBNлежит'1', какое condition-name истинно? - Если открыть этот файл в текстовом редакторе, какие элементы выглядят «битыми»?
- Назовите две вещи, которых из этого определения не видно.
Ответы
-
74 байта. Разбор такой.
Элемент Расчёт Байт CUST-IDX(8)8 CUST-NAMEX(20)20 CUST-KBNX1 CUST-BALANCECOMP-3 на 9 разрядов 5 CUST-HIST(8 + 4)× 336 FILLERX(4)4 Итого 74 Уровень
88— condition-name, поэтому байтов не занимает. Посчитать его — частая ошибка. УFILLERнет имени, но 4 байта на месте. -
С 30-го байта. Перед ним
8 + 20 + 1 = 29байт, поэтому начинается со следующего. -
С 55-го байта.
CUST-HISTначинается с 35-го байта, одна строка — 12 байт, поэтому первая занимает байты 35–46, вторая — 47–58. Первые 8 байт строки —HIST-DATE, значитHIST-AMOUNTначинается с 55-го байта. -
CUST-VIP. Отдельной областиCUST-VIPнет: когдаCUST-KBNравен'1', то же значение можно прочитать под этим именем. -
CUST-BALANCEиHIST-AMOUNT. ОбаCOMP-3, поэтому как текст дают бессмысленную последовательность.HIST-DATE—PIC 9(8)вDISPLAY, и в ASCII-среде его можно прочитать как цифры вроде20260317. В среде EBCDIC цифры могут выглядеть как цифры, но байтовые значения будут другими, чем в ASCII. -
Например, такое.
- Пришло ли само это определение через
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 перестаёт быть «загадочной древней магией» и становится «языком обработки записей». Легаси пугает не потому, что название старое, а потому что если с самого начала выбрать не тот масштаб, всё сразу становится непонятным. Если масштаб карты совпадает, читается на удивление обычно.
flowchart TB
accTitle: Читать в подходящем масштабе
accDescr: Берут карту через DIVISION, сначала читают форму в DATA DIVISION, идут по потоку через PERFORM и ввод-вывод, закрепляют внешние границы через FILE STATUS, EXEC SQL и EXEC CICS. В таком масштабе COBOL читается как обычный язык обработки записей.
sm1["Взять карту через DIVISION"] --> sm2["Прочитать форму в DATA DIVISION"]
sm2 --> sm3["Идти по потоку через PERFORM и ввод-вывод"]
sm3 --> sm4["Закрепить внешние границы"]
sm4 -.-> sm5["Древняя магия становится языком обработки записей"]
Рис. 21: Сложность — не в возрасте языка, а в масштабе, с которого начинают смотреть.
12. Источники
Основные источники, на которые ссылается статья. Надстрочные номера в тексте ведут сюда; стрелка в конце каждой записи возвращает к месту ссылки.
-
IBM, “Reference format” / IBM, “Area A or Area B” / Micro Focus, “Fixed Format” ↩ ↩2 ↩3 ↩4
-
IBM, “Level-numbers” ↩ ↩2
-
IBM, “Format 2: condition-name value” ↩ ↩2
-
IBM, “Examples: numeric data and internal representation” ↩ ↩2 ↩3 ↩4
-
IBM, “PACKED-DECIMAL (COMP-3)” ↩ ↩2
-
IBM, “The EBCDIC character set” / IBM, “Handling differences in ASCII SBCS and EBCDIC SBCS characters” ↩ ↩2 ↩3 ↩4 ↩5
-
IBM, “предложение REDEFINES” ↩ ↩2
-
IBM, “OCCURS DEPENDING ON clause” ↩ ↩2
-
IBM, “оператор COPY” ↩ ↩2
-
IBM, “PERFORM statement” / IBM, “Procedure division structure” ↩
-
IBM, “Scope terminators” / IBM, “Coding a choice of actions” ↩ ↩2 ↩3 ↩4
-
IBM, “FILE STATUS clause” / IBM, “Using file status keys” ↩
-
IBM, “Написание программ COBOL для выполнения под управлением CICS” ↩
-
IBM, “Computational items” ↩
-
IBM, “Elementary move rules” ↩
Похожие статьи
Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.
Создаём инструмент анализа PowerShell на самом PowerShell — читаем скрипты через AST, а не регулярными выражениями
Показываем настоящий AST PowerShell и разбираем, в какой объект превращается каждая строка кода. Проходим от ScriptBlockAst к CommandAst ...
Как ярлык Windows находит перемещённый файл? — Местоположение файла и его идентичность — разные вещи
Почему ярлык по-прежнему открывает перемещённый файл? Windows ищет цель не только по сохранённому пути, но и по идентификаторам отслежива...
Нужно ли по-прежнему «безопасно извлекать» USB-накопитель? — взгляд через быстрое удаление и кэш записи
Копирование закончилось — можно ли сразу вынуть USB-накопитель? Разбираем кэш записи и разницу между «быстрым удалением» и «повышенной пр...
Почему звук прерывается при низкой загрузке CPU? — взгляд от буфера и сроков
Звук прерывается щелчками, хотя загрузка CPU низкая. Объясняем причину через буфер, в котором звук накапливается немного вперёд, и срок п...
Почему RDP тормозит при быстром канале? — разбираем ввод, отрисовку и сеть по отдельности
Спидтест показывает высокую скорость, а ввод и прокрутка в удалённом рабочем столе запаздывают. Разбираем причины — от кругового обхода и...
Связанные темы
Эти страницы показывают тему статьи в более широком контексте услуг и решений.
Технические темы Windows
Раздел о разработке Windows, расследовании сбоев и использовании существующих активов.
Услуги по этой теме
Статья напрямую связана со следующими услугами.
Технические консультации и ревью дизайна
Как разбирать существующий COBOL, с чего начинать доработку, как увидеть внешние границы и как оценить картину до миграции — тема хорошо стыкуется с технической консультацией и ревью архитектуры.
Расследование ошибок и причин
Разбор сбоя сразу после передачи проекта и поиск, где в COBOL расходятся данные, удобно вести как расследование ошибки и анализ причины.
Частые вопросы
Вопросы, которые часто возникают при консультациях по теме статьи.
- С чего начинать чтение исходного кода 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, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.