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
Шаг 3THRU (сокращение от 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.