PERFORM для вызова именованных блоков

В COBOL нет функций в привычном смысле — их роль выполняют именованные участки кода, которые вызываются через тот же оператор PERFORM.

Параграф — именованный блок кода в COBOL-программе, а PERFORM ИМЯ-ПАРАГРАФА — способ выполнить этот блок из другого места программы и вернуться обратно, как вызов функции в других языках.

Почему в COBOL нет привычных функций

В языках вроде Python или JavaScript код разбивают на функции: def calculate_bonus(): или function calculateBonus() { ... }. COBOL появился раньше, чем такой синтаксис стал стандартом, и организует код иначе — через именованные параграфы. Параграф — это просто участок кода с именем-меткой, после которого до следующего такого имени идут строки программы. Никаких скобок, никакого специального ключевого слова для «объявления функции» — только имя и двоеточие с логикой ниже.

Чтобы выполнить параграф из другого места программы (а не просто дать программе дойти до него по порядку), используется знакомый нам PERFORM — та же конструкция, что и для циклов, но без VARYING и UNTIL.

Синтаксис: объявление и вызов параграфа

Пример: программа считает премию сотруднику и отдельно печатает чек — с выносом каждого действия в свой параграф.

       PROCEDURE DIVISION.
       MAIN-LOGIC.
           PERFORM CALCULATE-BONUS
           PERFORM PRINT-RECEIPT
           STOP RUN.

       CALCULATE-BONUS.
           COMPUTE WS-BONUS = WS-SALARY * 0.1
           DISPLAY "Премия рассчитана: " WS-BONUS.

       PRINT-RECEIPT.
           DISPLAY "=== Чек ==="
           DISPLAY "Оклад: " WS-SALARY
           DISPLAY "Премия: " WS-BONUS.

Вывод:

Премия рассчитана: 500.00
=== Чек ===
Оклад: 5000.00
Премия: 500.00

Разбор по частям:

  • MAIN-LOGIC, CALCULATE-BONUS, PRINT-RECEIPT — это имена параграфов. Их придумывает программист, обычно заглавными буквами через дефис.
  • PERFORM CALCULATE-BONUS внутри MAIN-LOGIC — вызов: выполнение переходит к строкам параграфа CALCULATE-BONUS, а после того как этот параграф закончится, управление автоматически вернётся к следующей строке после вызова — то есть к PERFORM PRINT-RECEIPT.
  • STOP RUN завершает программу целиком. Он стоит внутри MAIN-LOGIC, а не в конце файла — потому что после выполнения этой команды сама структура «главный параграф вызывает остальные» становится наглядной: вся логика программы видна одним взглядом в MAIN-LOGIC, а детали спрятаны в отдельных параграфах.

Важное отличие от функций в других языках: параграф не принимает аргументы и не возвращает значение через return. Вместо этого все параграфы работают с одними и теми же переменными раздела WORKING-STORAGE — они как бы общие «глобальные» поля для всей программы. CALCULATE-BONUS записывает результат в WS-BONUS, а PRINT-RECEIPT потом просто читает это же поле — никакой явной передачи данных между параграфами не происходит, они просто разделяют общую память.

PERFORM THRU: вызов нескольких параграфов подряд

Иногда логику разбивают не на один параграф, а на несколько идущих подряд — и хотят вызвать их все одной командой. Для этого есть форма PERFORM ПЕРВЫЙ-ПАРАГРАФ THRU ПОСЛЕДНИЙ-ПАРАГРАФ:

       PERFORM STEP-ONE THRU STEP-THREE.

       STEP-ONE.
           DISPLAY "Шаг 1".

       STEP-TWO.
           DISPLAY "Шаг 2".

       STEP-THREE.
           DISPLAY "Шаг 3".

Вывод:

Шаг 1
Шаг 2
Шаг 3

THRU (сокращение от through, «насквозь») означает «выполни всё от параграфа STEP-ONE до параграфа STEP-THREE включительно, по порядку их расположения в файле» — даже если между ними есть параграфы без явного упоминания, вроде STEP-TWO здесь. Это старый, но всё ещё встречающийся в реальном коде банковских систем приём — особенно там, где логика писалась ещё до широкого использования отдельных подпрограмм.

Мини-пример: обработка списка через параграф внутри цикла

PERFORM для вызова параграфа отлично сочетается с PERFORM-циклом из предыдущего урока — параграф можно вызывать многократно:

       PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 3
           PERFORM VALIDATE-ITEM
       END-PERFORM.

       VALIDATE-ITEM.
           IF WS-I > 0
               DISPLAY "Элемент " WS-I " корректен"
           END-IF.

Вывод:

Элемент 1 корректен
Элемент 2 корректен
Элемент 3 корректен

Здесь на каждом шаге цикла вызывается один и тот же параграф VALIDATE-ITEM, а он читает текущее значение общей переменной WS-I — снова через общую память, без передачи параметров.

Частые ошибки

  • Ждать, что параграф принимает параметры и возвращает значение, как функция. В COBOL так не работает — только чтение и запись общих переменных WORKING-STORAGE. Хотя в более новых стандартах COBOL появилась возможность оформлять по-настоящему отдельные подпрограммы с параметрами (через отдельный раздел PROCEDURE DIVISION USING), это отдельная и более сложная тема, а не поведение обычного параграфа с PERFORM.
  • Забыть, что выполнение «проваливается» в следующий параграф без PERFORM. Если просто дать программе дойти до конца одного параграфа без вызова, а не вернуться к вызывающему коду, она автоматически продолжит выполнение со следующего параграфа по порядку в файле — это может привести к неожиданному повторному выполнению кода, если забыть об этой особенности.
  • Перепутать порядок параграфов при использовании THRU. THRU ориентируется на физический порядок расположения параграфов в файле, а не на смысловую логику — если случайно переставить параграфы местами, THRU затронет совсем не те блоки кода.

Итоги

  • Параграф — именованный участок кода, замена функции в классическом COBOL: без параметров и return, с общими переменными WORKING-STORAGE.
  • PERFORM ИМЯ-ПАРАГРАФА выполняет параграф и возвращает управление обратно, как вызов функции.
  • PERFORM ПЕРВЫЙ THRU ПОСЛЕДНИЙ вызывает несколько параграфов подряд по их физическому порядку в файле.
  • Параграфы можно вызывать многократно, в том числе внутри циклов PERFORM VARYING/UNTIL.
Проверьте себя
1. Как параграф в COBOL получает данные для работы, если он не принимает параметры, как обычная функция?
AЧерез общие переменные раздела WORKING-STORAGE, доступные всем параграфам программы
BЧерез специальный аргумент после имени параграфа в вызове PERFORM
CЧерез возвращаемое значение предыдущего параграфа
DПараграфы в COBOL вообще не могут работать с данными
2. Что делает PERFORM STEP-ONE THRU STEP-THREE?
AВыполняет все параграфы от STEP-ONE до STEP-THREE включительно, в порядке их расположения в файле
BВыполняет только параграфы STEP-ONE и STEP-THREE, пропуская всё между ними
CСоздаёт цикл, который повторяется от 1 до 3 раз
DВызывает параграф STEP-THREE три раза подряд