Процедуры и параграфы в COBOL
Процедуры и параграфы в COBOL
Как читать эту статью
Статья про параграфы, секции и PERFORM как модульность COBOL. Читайте после управления и типов; закрепите в первой программе.
Общая база: функции в коде — вызов, параметры и возврат в высокоуровневых языках. В COBOL вместо них — параграфы и
PERFORM.
Процедуры и параграфы COBOL
Параграф — единица сопровождения в legacy: имя отражает бизнес-шаг, PERFORM заменяет "функции" без стека вызовов C-стиля.
В языке программирования COBOL понятие "функция" в привычном для современных языков смысле — как именованная единица кода, принимающая параметры и возвращающая значение — отсутствует в ранних версиях стандарта. Вместо этого структурной и логической основой модульности выступают параграфы (PARAGRAPH) и секции (SECTION). Эти элементы выполняют роль процедур или подпрограмм, организуя последовательности операторов в логически завершённые блоки, которые можно многократно использовать в рамках программы.
Параграфы как основа повторного использования кода
Параграф в COBOL представляет собой именованный блок инструкций, начинающийся с уникального имени, за которым следует точка. Имя параграфа должно быть уникальным в пределах процедуры (Procedure Division), где он определён. После имени располагается одна или несколько исполняемых инструкций, каждая из которых завершается точкой или входит в состав многострочной конструкции, корректно оформленной согласно синтаксису COBOL.
Пример простого параграфа:
DISPLAY-MESSAGE.
DISPLAY "Добро пожаловать в систему обработки данных"
Этот параграф не принимает аргументов и не возвращает результат, но инкапсулирует конкретное действие — вывод сообщения на экран. Такой подход позволяет избежать дублирования кода и повышает читаемость программы, особенно в контексте больших систем обработки транзакций, где одни и те же операции повторяются многократно.
Вызов параграфа осуществляется с помощью управляющего глагола PERFORM. Этот глагол указывает интерпретатору или компилятору перейти к указанному параграфу, выполнить все содержащиеся в нём инструкции и вернуться к следующему оператору после PERFORM. Таким образом, PERFORM реализует механизм передачи управления, аналогичный вызову подпрограммы.
Пример вызова:
PERFORM DISPLAY-MESSAGE
Такой вызов обеспечивает линейное выполнение логики, заключённой в параграфе, без необходимости копировать её содержимое в нескольких местах программы.
Секции как крупные логические блоки
Секция (SECTION) — это более крупная структурная единица по сравнению с параграфом. Секция может содержать один или несколько параграфов и используется для группировки связанных по смыслу операций. Например, секция может представлять этап обработки заказа, включающий проверку наличия товара, расчёт стоимости и формирование подтверждения.
Определение секции выглядит так:
ORDER-PROCESSING SECTION.
VALIDATE-ITEM.
...
CALCULATE-TOTAL.
...
CONFIRM-ORDER.
...
Вызов всей секции также осуществляется через PERFORM:
PERFORM ORDER-PROCESSING.
В этом случае последовательно выполняются все параграфы, входящие в секцию, в порядке их объявления. Секции позволяют создавать иерархическую структуру программы, разделяя её на функциональные модули высокого уровня.
Передача данных между параграфами
Хотя классический COBOL не предоставляет механизма передачи параметров в параграфы напрямую, как это реализовано в функциях других языков, взаимодействие между блоками кода достигается через общие переменные, объявленные в разделе Data Division. Все параграфы в пределах одной программы имеют доступ к этим данным, что позволяет им обмениваться информацией.
Например, один параграф может записать значение в переменную TOTAL-AMOUNT, а другой — прочитать это значение и использовать его для дальнейших вычислений. Такой подход требует дисциплины при проектировании структуры данных и чёткого документирования ролей каждой переменной, чтобы избежать побочных эффектов и трудноуловимых ошибок.
Для отдельной скомпилированной программы с параметрами используют CALL и LINKAGE SECTION: вызывающая программа передаёт данные через USING, подпрограмма завершается GOBACK (возврат в вызывающую) или EXIT PROGRAM.
PROCEDURE DIVISION.
CALL "VALIDATE-AMOUNT" USING WS-AMOUNT WS-STATUS
...
В подпрограмме VALIDATE-AMOUNT те же поля объявляются в LINKAGE SECTION, а заголовок процедуры — PROCEDURE DIVISION USING WS-AMOUNT WS-STATUS.
| Оператор | Назначение |
|---|---|
GOBACK | Вернуться из подпрограммы к вызывающей программе |
STOP RUN | Завершить всю задачу (обычно в главной программе) |
EXIT PROGRAM | Выйти из текущей программы без остановки всей задачи |
В стандарте COBOL 2002+ добавлены пользовательские функции (FUNCTION-ID) и методы классов; в legacy-коде по-прежнему доминируют параграфы, секции и CALL.
Многократное выполнение через PERFORM
Глагол PERFORM обладает расширенными возможностями, выходящими за рамки простого однократного вызова. Он поддерживает циклическое выполнение параграфа заданное количество раз, выполнение до тех пор, пока выполняется условие, или обработку диапазона параграфов.
Примеры:
- Выполнение 10 раз:
PERFORM PROCESS-RECORD 10 TIMES
- Выполнение до тех пор, пока условие истинно (тело цикла — встроенные операторы или
PERFORMпараграфа):
PERFORM UNTIL EOF-FLAG = 'Y'
READ CUSTOMER-FILE
AT END MOVE 'Y' TO EOF-FLAG
NOT AT END PERFORM VALIDATE-RECORD
END-READ
END-PERFORM
- Выполнение диапазона:
PERFORM START-ROUTINE THRU END-ROUTINE
Эти конструкции делают PERFORM мощным инструментом управления потоком выполнения, сочетающим в себе черты вызова подпрограммы и организации циклов.
Отсутствие возврата значения в классическом COBOL
В ранних версиях COBOL параграфы и секции не возвращают значения. Результат работы такого блока фиксируется через изменение состояния глобальных переменных или файлов. Это соответствует процедурному стилю программирования, где акцент делается на последовательном изменении данных, а не на вычислении и возврате результатов.
Такой подход хорошо согласуется с задачами, для которых COBOL изначально создавался — массовая обработка записей, работа с файлами фиксированной длины, генерация отчётов. В этих сценариях важна не столько композиция функций, сколько надёжность, предсказуемость и чёткая последовательность шагов.
Появление пользовательских функций (COBOL 2002+)
Начиная со стандарта COBOL 2002, язык поддерживает отдельные функции (FUNCTION-ID) с возвращаемым значением. В production legacy-коде они встречаются редко; ниже — упрощённый учебный скелет (детали зависят от компилятора).
IDENTIFICATION DIVISION.
FUNCTION-ID. CALCULATE-TAX.
DATA DIVISION.
LINKAGE SECTION.
01 INPUT-AMOUNT PIC 9(5)V99.
01 TAX-RATE PIC 9(3)V99.
01 RESULT PIC 9(5)V99.
PROCEDURE DIVISION USING INPUT-AMOUNT TAX-RATE RETURNING RESULT.
COMPUTE RESULT = INPUT-AMOUNT * TAX-RATE / 100
GOBACK
END FUNCTION CALCULATE-TAX.
Вызов в выражении:
COMPUTE FINAL-PRICE =
BASE-PRICE + FUNCTION CALCULATE-TAX (BASE-PRICE 20)
Большинство производственных приложений по-прежнему строятся на параграфах, CALL и общих данных в WORKING-STORAGE / LINKAGE.
Вложенность и организация параграфов в иерархию
В COBOL параграфы и секции формируют древовидную структуру программы, где каждый уровень вызова добавляет новую вложенность в логику выполнения. Такая организация особенно важна в крупных приложениях, где обработка данных разбита на множество этапов — чтение входных файлов, валидация записей, трансформация данных, запись результатов, формирование отчётов. Каждый из этих этапов может быть представлен отдельной секцией, внутри которой расположены специализированные параграфы.
Например, секция INPUT-PROCESSING может содержать параграфы OPEN-INPUT-FILE, READ-NEXT-RECORD, CHECK-FOR-END-OF-FILE. Секция OUTPUT-GENERATION — параграфы FORMAT-LINE, WRITE-TO-REPORT, CLOSE-OUTPUT-FILE. Такая декомпозиция позволяет разработчику мыслить на уровне бизнес-операций, а не отдельных машинных инструкций.
Параграфы могут вызывать другие параграфы, создавая цепочки выполнения. Это не рекурсия в классическом понимании (рекурсивные вызовы в COBOL ограничены или требуют явного разрешения), но последовательная передача управления от одного блока к другому. Такой подход обеспечивает чёткое разделение ответственности — один параграф отвечает за чтение, другой — за проверку, третий — за запись.
Управление потоком выполнения через PERFORM
Глагол PERFORM является центральным элементом управления выполнением в процедурной части COBOL-программы. Он не только вызывает параграфы, но и определяет контекст их выполнения. Существует несколько форм записи PERFORM, каждая из которых поддерживает определённый сценарий:
-
Простой вызов:
PERFORM paragraph-name.
Выполняет указанный параграф один раз и возвращается к следующему оператору. -
Выполнение диапазона:
PERFORM paragraph-A THRU paragraph-Z.
Выполняет все параграфы отparagraph-Aдоparagraph-Zвключительно, в порядке их объявления в исходном коде. Эта форма удобна для группировки связанных операций без необходимости оборачивать их в секцию. -
Циклическое выполнение:
PERFORM paragraph-name n TIMES.
Повторяет выполнение указанного параграфа заданное количество раз. Числоnможет быть литералом или переменной. -
Условное выполнение (UNTIL):
PERFORM UNTIL EOF-FLAG = 'Y'
...
END-PERFORM
По умолчанию условие проверяется до тела цикла (WITH TEST BEFORE); вариант WITH TEST AFTER — проверка в конце итерации.
- Условное выполнение (VARYING):
PERFORM VARYING WS-IDX FROM 1 BY 1
UNTIL WS-IDX > 10
...
END-PERFORM
Аналог цикла for в других языках. Переменная-счётчик автоматически инициализируется, изменяется и проверяется на каждом шаге.
Эти конструкции делают PERFORM универсальным инструментом, сочетающим в себе функции вызова подпрограммы, цикла и условного блока. При этом вся логика остаётся линейной и предсказуемой — важное качество для систем, где ошибки могут привести к финансовым потерям или нарушению регуляторных требований.
Отладка и читаемость параграфов
Одним из преимуществ параграфной структуры является её прозрачность для отладки. Поскольку каждый параграф имеет уникальное имя, трассировка выполнения программы сводится к последовательному отслеживанию переходов между этими именами. Многие COBOL-среды разработки и отладчики отображают текущий активный параграф, что упрощает локализацию ошибок.
Кроме того, имена параграфов часто отражают бизнес-смысл операции — VALIDATE-CUSTOMER-ID, CALCULATE-MONTHLY-BALANCE, GENERATE-INVOICE-HEADER. Это делает код самодокументируемым, особенно для аналитиков и специалистов по поддержке, которые могут не быть профессиональными программистами, но обязаны понимать логику обработки.
Совместимость и поддержка унаследованного кода
Большинство действующих COBOL-систем были написаны десятки лет назад и поддерживаются в неизменном виде благодаря своей стабильности. Переход на современные функции (например, FUNCTION-ID) требует перекомпиляции, тестирования и, зачастую, изменения архитектуры данных. Поэтому организации предпочитают сохранять существующую модель параграфов, даже если компилятор поддерживает новые возможности.
Это создаёт ситуацию, в которой "функции" в COBOL — это прагматический паттерн — повторно используемый, именованный блок логики, вызываемый через PERFORM. Такой паттерн доказал свою жизнеспособность в условиях эксплуатации критически важных банковских, страховых и государственных систем, где отказ недопустим.
Что дальше
| Тема | Статья |
|---|---|
| Первая программа | Первая программа на COBOL |
IF, циклы | Управляющие конструкции и операторы COBOL |
| Архитектура batch | Архитектура программ на COBOL |
| COPYBOOK | Справочник по COBOL |
Что попробовать
- Вынесите
DISPLAYв параграф и вызовитеPERFORMдважды. - Соберите секцию с двумя параграфами и
PERFORMпо диапазону. - Сопоставьте имена параграфов с шагами в intro.
Практический шаблон именования параграфов
Чтобы код не превращался в "список непонятных лейблов", удобно называть параграфы как бизнес-шаги процесса:
INIT-RUNREAD-NEXT-RECORDVALIDATE-RECORDAPPLY-BUSINESS-RULESWRITE-RESULTFINISH-RUN
Такое именование делает трассировку и code review проще: по одному только списку PERFORM уже видно, что делает программа.
Мини-скелет "главный поток + рабочие параграфы"
PROCEDURE DIVISION.
MAIN-FLOW.
PERFORM INIT-RUN
PERFORM UNTIL EOF-FLAG = "Y"
PERFORM READ-NEXT-RECORD
IF EOF-FLAG NOT = "Y"
PERFORM VALIDATE-RECORD
PERFORM APPLY-BUSINESS-RULES
PERFORM WRITE-RESULT
END-IF
END-PERFORM
PERFORM FINISH-RUN
STOP RUN.
Это хороший компромисс между "слишком мелко" и "слишком монолитно": MAIN-FLOW читается как сценарий, детали вынесены в отдельные блоки.
Когда использовать PERFORM THRU, а когда избегать
PERFORM A THRU B полезен, когда вы точно контролируете непрерывный диапазон и не будете часто менять порядок параграфов.
В активно изменяемом коде безопаснее вызывать конкретные параграфы по одному (PERFORM A, PERFORM B), чтобы правка в середине файла не сломала диапазон неявно.
Практический совет: для новых учебных и рабочих модулей сначала проектируйте явный список вызовов. THRU оставляйте для стабильно фиксированных блоков в legacy.
Как это связано с архитектурой и тестированием
- архитектурный контекст batch/CICS: Архитектура;
- ветвления и циклы в теле параграфов: Управление;
- формат входных/выходных данных: Типы и Справочник.
Если параграфы короткие и смысловые, то и тестирование проще: можно подготовить вход и проверить результат по этапам, а не разбирать огромный непрозрачный блок в PROCEDURE DIVISION.