【COBOL】PERFORM文ループ処理完全ガイド|TIMES・UNTIL・VARYING・TEST AFTER・多重ループ・実践パターン

COBOLのPERFORM文は、繰り返し処理(ループ)と段落呼び出しの両方を担う中核命令です。TIMES・UNTIL・VARYINGの3種類のループ形式に加え、TEST BEFORE/AFTER(前判定/後判定)・VARYING AFTERによる多重ループ・EXIT PERFORMによるループ中断など、業務コードで頻繁に必要となる応用まで習得することが重要です。

この記事ではPERFORM文の各形式を基本から押さえたうえで、ファイル読み込みループ・配列集計・検索処理・バリデーションループといった業務でよく使うパターンを実践コードで解説します。

この記事でわかること

  • PERFORM段落呼び出し・TIMES・UNTIL・VARYINGの構文と使い分け
  • TEST BEFORE(前判定)とTEST AFTER(後判定)の違いと使い分け
  • VARYING … AFTER句による多次元配列の多重ループ
  • EXIT PERFORMでループを途中で抜ける方法
  • ファイル読み込みループ(AT END/NOT AT END)のパターン
  • 配列集計・線形検索・バリデーションの実践ループパターン
  • よくある落とし穴と対策
スポンサーリンク

PERFORM文の全体像

PERFORM文には大きく「段落呼び出し」と「インラインループ」の2つの使い方があります。

種類 構文 用途
段落呼び出し PERFORM 段落名 段落(パラグラフ)を1回呼び出す
THRU付き段落呼び出し PERFORM 段落A THRU 段落B 段落AからBまで連続実行する
回数指定ループ PERFORM … TIMES 指定回数繰り返す
条件ループ PERFORM UNTIL … 条件が真になるまで繰り返す(TEST BEFORE/AFTER)
カウンタ制御ループ PERFORM VARYING … UNTIL … カウンタを増減しながら繰り返す
多重ループ PERFORM VARYING … AFTER … 2変数以上のネストループ

インラインPERFORMとアウトオブラインPERFORMの使い分け

インライン vs アウトオブライン
*=== アウトオブライン: 処理を段落に切り出す ===
*> メリット: 段落名がコメント代わりになり可読性が高い
*>          同じ処理を複数箇所から呼べる(再利用性)
PROCEDURE DIVISION.
    PERFORM VALIDATE-INPUT
    PERFORM CALC-AMOUNT
    PERFORM OUTPUT-RESULT
    STOP RUN.

VALIDATE-INPUT.
    *> 入力チェック処理
    EXIT.

CALC-AMOUNT.
    *> 計算処理
    EXIT.

OUTPUT-RESULT.
    *> 出力処理
    EXIT.

*=== インライン: END-PERFORMで囲む ===
*> メリット: 短いループには簡潔に書ける
*>          変数スコープが明確
PROCEDURE DIVISION.
    PERFORM VARYING WS-IDX FROM 1 BY 1 UNTIL WS-IDX > 10
        ADD WS-SCORE(WS-IDX) TO WS-TOTAL
    END-PERFORM.
使い分けの目安

  • 5行以下の短い処理 → インラインPERFOMでOK
  • 10行超の処理・複数箇所から呼ぶ処理 → アウトオブラインで段落に切り出す
  • 段落名は「何をする段落か」が一目でわかる名前にする(VALIDATE-INPUT、CALC-TAX等)

PERFORM TIMES(回数指定ループ)

指定した回数だけ処理を繰り返します。回数は定数でも変数でも指定できます。ループカウンタは自動管理されないため、必要な場合は別途変数を用意します。

PERFORM TIMES の構文と使用例
WORKING-STORAGE SECTION.
01 WS-REPEAT-COUNT  PIC 9(3)  VALUE 5.
01 WS-LOOP-CNT      PIC 9(3)  VALUE 0.
01 WS-TOTAL         PIC 9(9)  VALUE 0.
01 WS-INPUT-VAL     PIC 9(5)  VALUE 1000.

PROCEDURE DIVISION.
    *> 定数回繰り返す
    PERFORM 3 TIMES
        DISPLAY 'リトライ中...'
    END-PERFORM.

    *> 変数で回数を指定
    PERFORM WS-REPEAT-COUNT TIMES
        ADD 1 TO WS-LOOP-CNT
        ADD WS-INPUT-VAL TO WS-TOTAL
    END-PERFORM.
    DISPLAY '合計: ' WS-TOTAL  *> 5000
注意: TIMES指定値が0以下の場合
PERFORM 0 TIMESはループ本体を実行しません(0回ループ)。負の値を指定した場合の動作はコンパイラ依存ですが、ほとんどの実装で実行されません。ループ回数を変数で渡す場合は、事前に値が正か確認しておきましょう。

PERFORM UNTIL(条件ループ)とTEST BEFORE/TEST AFTER

PERFORM UNTILは「条件が真になるまで繰り返す」ループです。条件の判定タイミングをTEST BEFORE(前判定・デフォルト)TEST AFTER(後判定)かで選べます。

種類 動作 0回実行の可否 向いているケース
TEST BEFORE(省略時デフォルト) ループ本体実行前に条件を評価する 条件が最初から真の場合、0回実行 ファイル読み込み・通常の繰り返し
TEST AFTER ループ本体実行後に条件を評価する 必ず最低1回は実行される 初回は必ず処理したいケース(メニュー表示・再入力要求)
TEST BEFORE(前判定)の動作
*> TEST BEFORE: 最初に条件を評価する(デフォルト)
WORKING-STORAGE SECTION.
01 WS-CNT    PIC 9(3) VALUE 0.
01 WS-LIMIT  PIC 9(3) VALUE 0.    *> 最初から0

PROCEDURE DIVISION.
    *> WS-LIMITが0なので、条件 WS-CNT >= WS-LIMIT は最初からTRUE
    PERFORM WITH TEST BEFORE UNTIL WS-CNT >= WS-LIMIT
        ADD 1 TO WS-CNT      *> 一度も実行されない
    END-PERFORM
    DISPLAY WS-CNT.   *> 0(本体が0回実行)
TEST AFTER(後判定)の動作
*> TEST AFTER: 本体を実行してから条件を評価する(do-while相当)
WORKING-STORAGE SECTION.
01 WS-CNT    PIC 9(3) VALUE 0.
01 WS-LIMIT  PIC 9(3) VALUE 0.    *> 最初から0

PROCEDURE DIVISION.
    *> TEST AFTER: 最初に必ず1回実行してから条件を評価
    PERFORM WITH TEST AFTER UNTIL WS-CNT >= WS-LIMIT
        ADD 1 TO WS-CNT      *> 1回実行される
    END-PERFORM
    DISPLAY WS-CNT.   *> 1(本体が1回実行)
TEST AFTERの実用例:再入力要求
WORKING-STORAGE SECTION.
01 WS-USER-INPUT   PIC X(1).
01 WS-RETRY-COUNT  PIC 9(2) VALUE 0.

PROCEDURE DIVISION.
    *> 正しい入力が来るまで繰り返す(最低1回は必ず入力させる)
    PERFORM WITH TEST AFTER UNTIL
            (WS-USER-INPUT = 'Y' OR WS-USER-INPUT = 'N')
            OR WS-RETRY-COUNT >= 3

        DISPLAY '処理を続けますか?(Y/N): '
        ACCEPT WS-USER-INPUT
        INSPECT WS-USER-INPUT CONVERTING 'yn' TO 'YN'  *> 小文字対応
        ADD 1 TO WS-RETRY-COUNT

    END-PERFORM

    IF WS-RETRY-COUNT >= 3 AND
       WS-USER-INPUT NOT = 'Y' AND WS-USER-INPUT NOT = 'N'
        DISPLAY '入力回数超過。処理を中断します'
        MOVE 8 TO RETURN-CODE
        GOBACK
    END-IF

    IF WS-USER-INPUT = 'Y'
        PERFORM MAIN-PROCESS
    ELSE
        DISPLAY '処理をキャンセルしました'
    END-IF.

PERFORM VARYING(カウンタ制御ループ)

PERFORM VARYINGはカウンタ変数を自動的に増減させながら繰り返すループです。配列(OCCURS句)の走査に最もよく使われます。

PERFORM VARYING の基本
WORKING-STORAGE SECTION.
01 WS-IDX         PIC 9(3).
01 WS-SCORE-TBL.
   05 WS-SCORE    PIC 9(3) OCCURS 10 TIMES.
01 WS-TOTAL       PIC 9(6) VALUE 0.
01 WS-MAX-SCORE   PIC 9(3) VALUE 0.

PROCEDURE DIVISION.
    *> 配列の合計・最大値を求める
    PERFORM VARYING WS-IDX FROM 1 BY 1 UNTIL WS-IDX > 10
        ADD WS-SCORE(WS-IDX) TO WS-TOTAL
        IF WS-SCORE(WS-IDX) > WS-MAX-SCORE
            MOVE WS-SCORE(WS-IDX) TO WS-MAX-SCORE
        END-IF
    END-PERFORM

    DISPLAY '合計: '    WS-TOTAL
    DISPLAY '最大値: '  WS-MAX-SCORE

FROM・BY・UNTILの組み合わせパターン

FROM/BY/UNTILの各パターン
*> 順方向ループ(1から始まり+1ずつ)
PERFORM VARYING WS-IDX FROM 1 BY 1 UNTIL WS-IDX > WS-MAX
    処理
END-PERFORM.

*> 逆順ループ(末尾から-1ずつ)
PERFORM VARYING WS-IDX FROM WS-MAX BY -1 UNTIL WS-IDX < 1
    処理
END-PERFORM.

*> 偶数インデックスのみ(+2ずつ)
PERFORM VARYING WS-IDX FROM 2 BY 2 UNTIL WS-IDX > WS-MAX
    処理
END-PERFORM.

*> 変数でステップを制御
WORKING-STORAGE SECTION.
01 WS-STEP    PIC 9(3) VALUE 10.   *> 10件ずつ処理

PROCEDURE DIVISION.
PERFORM VARYING WS-IDX FROM 1 BY WS-STEP UNTIL WS-IDX > WS-TOTAL-COUNT
    *> WS-IDX, WS-IDX+1, ..., WS-IDX+9 の10件をまとめて処理
    PERFORM BULK-PROCESS
END-PERFORM.

VARYING … AFTER による多重ループ

PERFORM VARYINGにAFTER句を追加すると、複数のカウンタを持つ多重ループ(二重・三重ループ)を1つのPERFORM文で記述できます。二次元配列や帳票の行列処理に使います。

VARYING … AFTER の構文
PERFORM VARYING 外側カウンタ FROM 開始値 BY 増分 UNTIL 終了条件
         AFTER  内側カウンタ FROM 開始値 BY 増分 UNTIL 終了条件
    処理
END-PERFORM.
二次元配列の初期化(VARYING … AFTER)
WORKING-STORAGE SECTION.
01 WS-MATRIX.
   05 WS-ROW    OCCURS 5 TIMES.
      10 WS-COL PIC 9(5) OCCURS 5 TIMES.
01 WS-ROW-IDX  PIC 9(2).
01 WS-COL-IDX  PIC 9(2).
01 WS-COUNTER  PIC 9(4) VALUE 1.

PROCEDURE DIVISION.
    *> 二次元配列に連番を埋める(1〜25)
    PERFORM VARYING WS-ROW-IDX FROM 1 BY 1 UNTIL WS-ROW-IDX > 5
             AFTER  WS-COL-IDX FROM 1 BY 1 UNTIL WS-COL-IDX > 5
        MOVE WS-COUNTER TO WS-COL(WS-ROW-IDX WS-COL-IDX)
        ADD 1 TO WS-COUNTER
    END-PERFORM

    *> 結果確認(5×5の行列表示)
    PERFORM VARYING WS-ROW-IDX FROM 1 BY 1 UNTIL WS-ROW-IDX > 5
         AFTER  WS-COL-IDX FROM 1 BY 1 UNTIL WS-COL-IDX > 5
        IF WS-COL-IDX = 5
            DISPLAY WS-COL(WS-ROW-IDX WS-COL-IDX)
        ELSE
            DISPLAY WS-COL(WS-ROW-IDX WS-COL-IDX) ' ' WITH NO ADVANCING
        END-IF
    END-PERFORM.
VARYING … AFTERの評価順序に注意
外側変数(VARYING)が1回増分されるとき、内側変数(AFTER)はFROMの値にリセットされます。つまり外側1周ごとに内側は最初から最後まで走ります(通常の二重ループと同じ動作)。3重ループが必要な場合はAFTERをもう1つ追加できます。
三重ループの例(年×月×日集計)
WORKING-STORAGE SECTION.
01 WS-YEAR-IDX   PIC 9(2).    *> 1〜3(3年分)
01 WS-MONTH-IDX  PIC 9(2).    *> 1〜12
01 WS-DAY-IDX    PIC 9(2).    *> 1〜31
01 WS-SALES-TBL.
   05 WS-YEAR-TBL OCCURS 3 TIMES.
      10 WS-MONTH-TBL OCCURS 12 TIMES.
         15 WS-DAY-SALES PIC 9(9)V99 OCCURS 31 TIMES.
01 WS-TOTAL      PIC 9(12)V99 VALUE 0.

PROCEDURE DIVISION.
    PERFORM VARYING WS-YEAR-IDX  FROM 1 BY 1 UNTIL WS-YEAR-IDX  > 3
             AFTER  WS-MONTH-IDX FROM 1 BY 1 UNTIL WS-MONTH-IDX > 12
             AFTER  WS-DAY-IDX   FROM 1 BY 1 UNTIL WS-DAY-IDX   > 31
        ADD WS-DAY-SALES(WS-YEAR-IDX WS-MONTH-IDX WS-DAY-IDX)
            TO WS-TOTAL
    END-PERFORM
    DISPLAY '3年間の総売上: ' WS-TOTAL.

EXIT PERFORMでループを途中で抜ける

ループ処理中に特定の条件が成立したとき、残りの繰り返しをスキップしてループを終了させるにはEXIT PERFORMを使います。他の言語のbreakに相当します。

EXIT PERFORMの基本(配列の線形検索)
WORKING-STORAGE SECTION.
01 WS-ITEM-TABLE.
   05 WS-ITEM-CODE PIC X(6) OCCURS 100 TIMES.
01 WS-SEARCH-CODE  PIC X(6).
01 WS-FOUND-IDX    PIC 9(3) VALUE 0.
01 WS-IDX          PIC 9(3).

PROCEDURE DIVISION.
    MOVE 'ITM042' TO WS-SEARCH-CODE
    MOVE 0 TO WS-FOUND-IDX

    PERFORM VARYING WS-IDX FROM 1 BY 1 UNTIL WS-IDX > 100
        IF WS-ITEM-CODE(WS-IDX) = WS-SEARCH-CODE
            MOVE WS-IDX TO WS-FOUND-IDX
            EXIT PERFORM       *> 発見次第ループを即時終了
        END-IF
    END-PERFORM

    IF WS-FOUND-IDX > 0
        DISPLAY '見つかりました: 位置=' WS-FOUND-IDX
    ELSE
        DISPLAY '見つかりませんでした'
    END-IF.
EXIT PERFORM CYCLEで当回のみスキップ(continue相当)
*> EXIT PERFORM CYCLE: 当回の残り処理をスキップして次の繰り返しへ進む(continue相当)
WORKING-STORAGE SECTION.
01 WS-IDX         PIC 9(3).
01 WS-DATA-TBL.
   05 WS-DATA     PIC S9(7)V99 OCCURS 50 TIMES.
01 WS-POSITIVE-SUM PIC 9(9)V99 VALUE 0.

PROCEDURE DIVISION.
    PERFORM VARYING WS-IDX FROM 1 BY 1 UNTIL WS-IDX > 50
        *> 負の値はスキップ(正の値だけ合計)
        IF WS-DATA(WS-IDX) <= 0
            EXIT PERFORM CYCLE   *> 次の繰り返しへ
        END-IF
        ADD WS-DATA(WS-IDX) TO WS-POSITIVE-SUM
    END-PERFORM
    DISPLAY '正の値の合計: ' WS-POSITIVE-SUM.

PERFORM THRU(段落範囲の実行)

PERFORM THRU は指定した段落から別の段落まで連続して実行します。複数の段落をひとまとまりの処理として呼び出すときに使います。

PERFORM THRU の使い方
PROCEDURE DIVISION.
MAIN-PROC.
    PERFORM OPEN-FILES THRU READ-FIRST-RECORD
    PERFORM PROCESS-LOOP UNTIL WS-EOF
    PERFORM CLOSE-FILES
    STOP RUN.

OPEN-FILES.
    OPEN INPUT TRANS-FILE
    OPEN OUTPUT REPORT-FILE.

INIT-COUNTERS.
    MOVE ZEROS TO WS-COUNT WS-TOTAL.

READ-FIRST-RECORD.
    READ TRANS-FILE
        AT END SET WS-EOF TO TRUE
    END-READ.
*> PERFORM OPEN-FILES THRU READ-FIRST-RECORD で
*> OPEN-FILES → INIT-COUNTERS → READ-FIRST-RECORD の順に実行

PROCESS-LOOP.
    PERFORM PROCESS-RECORD
    READ TRANS-FILE
        AT END SET WS-EOF TO TRUE
    END-READ.

CLOSE-FILES.
    CLOSE TRANS-FILE REPORT-FILE.
PERFORM THRUの注意点
PERFORM A THRU B では、A段落から始まりB段落の最後のEXIT.まで、その間にあるすべての段落が順に実行されます。間に意図しない段落が挟まっていた場合も実行されるため、段落の並び順を把握しておく必要があります。大規模なプログラムでは意図しない段落の実行が起きやすいため、THRUの範囲は最小限にしてEXIT.で明示的に終端を示すことを推奨します。

業務でよく使う実践パターン

ファイル読み込みループ(最も基本的なバッチパターン)

COBOLバッチ処理の基本形です。AT END/NOT AT ENDを使ったファイル全件読み込みパターンです。

ファイル全件読み込みと集計
IDENTIFICATION DIVISION.
PROGRAM-ID. FILE-AGG.

ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
    SELECT SALES-FILE ASSIGN TO 'SALES.DAT'
           ORGANIZATION IS LINE SEQUENTIAL
           FILE STATUS IS WS-FILE-STATUS.

DATA DIVISION.
FILE SECTION.
FD  SALES-FILE.
01  SALES-RECORD.
    05 SR-DATE       PIC X(8).
    05 SR-DEPT-CODE  PIC X(4).
    05 SR-AMOUNT     PIC 9(9)V99.

WORKING-STORAGE SECTION.
01 WS-FILE-STATUS PIC X(2).
01 WS-EOF-FLG     PIC X(1) VALUE 'N'.
   88 WS-EOF      VALUE 'Y'.
01 WS-TOTAL-AMT   PIC 9(12)V99 VALUE 0.
01 WS-REC-COUNT   PIC 9(7)    VALUE 0.

PROCEDURE DIVISION.
MAIN.
    OPEN INPUT SALES-FILE
    IF WS-FILE-STATUS NOT = '00'
        DISPLAY 'ファイルOPENエラー: ' WS-FILE-STATUS
        MOVE 8 TO RETURN-CODE
        STOP RUN
    END-IF

    *> 先読み(プライミングリード)
    READ SALES-FILE
        AT END SET WS-EOF TO TRUE
    END-READ

    PERFORM UNTIL WS-EOF
        ADD 1          TO WS-REC-COUNT
        ADD SR-AMOUNT  TO WS-TOTAL-AMT
        PERFORM PROCESS-RECORD

        READ SALES-FILE
            AT END SET WS-EOF TO TRUE
        END-READ
    END-PERFORM

    CLOSE SALES-FILE
    DISPLAY '処理件数: ' WS-REC-COUNT
    DISPLAY '合計金額: ' WS-TOTAL-AMT
    STOP RUN.

PROCESS-RECORD.
    *> レコードごとの処理(省略)
    EXIT.

配列への累積集計(部門別集計)

部門コードで分類して配列に集計
WORKING-STORAGE SECTION.
01 WS-DEPT-COUNT      PIC 9(2) VALUE 5.
01 WS-DEPT-TABLE.
   05 WS-DEPT-ENTRY   OCCURS 5 TIMES INDEXED BY WS-DEPT-IDX.
      10 WS-DEPT-CODE PIC X(4).
      10 WS-DEPT-SALES PIC 9(12)V99 VALUE 0.
      10 WS-DEPT-CNT  PIC 9(7)     VALUE 0.
01 WS-SEARCH-IDX      PIC 9(2).
01 WS-FOUND-FLG       PIC X(1).
   88 DEPT-FOUND      VALUE 'Y'.

PROCEDURE DIVISION.
    *> テーブルの初期化
    PERFORM VARYING WS-SEARCH-IDX FROM 1 BY 1
                    UNTIL WS-SEARCH-IDX > WS-DEPT-COUNT
        MOVE SPACES TO WS-DEPT-CODE(WS-SEARCH-IDX)
        MOVE ZEROS  TO WS-DEPT-SALES(WS-SEARCH-IDX)
        MOVE ZEROS  TO WS-DEPT-CNT(WS-SEARCH-IDX)
    END-PERFORM

    *> 売上データ読み込みループ(省略: SR-DEPT-CODE, SR-AMOUNTが読み込まれている想定)
    PERFORM UNTIL WS-EOF
        *> 部門コードを検索
        MOVE 'N' TO WS-FOUND-FLG
        PERFORM VARYING WS-SEARCH-IDX FROM 1 BY 1
                        UNTIL WS-SEARCH-IDX > WS-DEPT-COUNT
            IF WS-DEPT-CODE(WS-SEARCH-IDX) = SR-DEPT-CODE
                MOVE 'Y' TO WS-FOUND-FLG
                ADD SR-AMOUNT TO WS-DEPT-SALES(WS-SEARCH-IDX)
                ADD 1         TO WS-DEPT-CNT(WS-SEARCH-IDX)
                EXIT PERFORM
            END-IF
        END-PERFORM

        *> 次レコード読み込み(省略)
    END-PERFORM

    *> 結果出力
    PERFORM VARYING WS-SEARCH-IDX FROM 1 BY 1
                    UNTIL WS-SEARCH-IDX > WS-DEPT-COUNT
        DISPLAY WS-DEPT-CODE(WS-SEARCH-IDX)
                ': '   WS-DEPT-CNT(WS-SEARCH-IDX)   '件'
                '  '   WS-DEPT-SALES(WS-SEARCH-IDX) '円'
    END-PERFORM.

リトライループ(API呼び出し・タイムアウト対策)

最大3回リトライするパターン
WORKING-STORAGE SECTION.
01 WS-RETRY-MAX    PIC 9(1) VALUE 3.
01 WS-RETRY-CNT    PIC 9(1) VALUE 0.
01 WS-API-STATUS   PIC X(2).
   88 API-SUCCESS  VALUE '00'.
   88 API-TIMEOUT  VALUE '08'.

PROCEDURE DIVISION.
    PERFORM WITH TEST AFTER
            UNTIL API-SUCCESS OR WS-RETRY-CNT >= WS-RETRY-MAX

        PERFORM CALL-EXTERNAL-API
        ADD 1 TO WS-RETRY-CNT

        IF NOT API-SUCCESS AND WS-RETRY-CNT < WS-RETRY-MAX
            DISPLAY 'リトライ ' WS-RETRY-CNT '回目...'
            *> 実際の処理では CALL 'SLEEP' などを挿入
        END-IF

    END-PERFORM

    IF NOT API-SUCCESS
        DISPLAY 'API呼び出し失敗(' WS-RETRY-MAX '回リトライ済)'
        MOVE 12 TO RETURN-CODE
        GOBACK
    END-IF
    DISPLAY 'API呼び出し成功'.

ページング出力(帳票の改ページ)

ページ制御付きの帳票出力ループ
WORKING-STORAGE SECTION.
01 WS-PAGE-NO      PIC 9(4) VALUE 1.
01 WS-LINE-NO      PIC 9(2) VALUE 0.
01 WS-LINES-PER-PAGE PIC 9(2) VALUE 50.
01 WS-IDX          PIC 9(7) VALUE 0.

PROCEDURE DIVISION.
    PERFORM PRINT-HEADER
    MOVE 0 TO WS-LINE-NO

    PERFORM UNTIL WS-EOF
        *> ページ満杯になったら改ページ
        IF WS-LINE-NO >= WS-LINES-PER-PAGE
            WRITE REPORT-RECORD FROM WS-PAGE-BREAK-LINE
            ADD 1 TO WS-PAGE-NO
            PERFORM PRINT-HEADER
            MOVE 0 TO WS-LINE-NO
        END-IF

        WRITE REPORT-RECORD FROM SALES-RECORD
        ADD 1 TO WS-LINE-NO

        READ SALES-FILE
            AT END SET WS-EOF TO TRUE
        END-READ
    END-PERFORM.

PRINT-HEADER.
    MOVE WS-PAGE-NO TO WS-HDR-PAGE
    WRITE REPORT-RECORD FROM WS-HEADER-LINE
    MOVE 0 TO WS-LINE-NO.
    EXIT.

よくある落とし穴と対策

落とし穴 問題 対策
無限ループ UNTIL条件の変数がループ内で更新されないため条件が永久にFALSEのまま ループ内で必ずUNTIL条件に影響する変数を更新する。またはループカウンタに上限を設ける
OFF-BY-ONE(1ずれ) UNTIL WS-IDX > 10 とすべきところを = 10 にして最後の要素を処理しない 「>最大値」か「>= 最大値+1」で終了条件を書く。配列の先頭は1(0ではない)
VARYINGカウンタのループ後の値 PERFORM VARYING WS-IDX FROM 1 BY 1 UNTIL WS-IDX > 10 が終了した後、WS-IDXの値は11(終了条件を満たした値) ループ後にWS-IDXを使う場合は、ループ内で最後に処理した値を別変数に退避しておく
TEST AFTERの意図しない1回実行 TEST AFTERを使うと条件が最初から真でも1回は実行される 事前に条件が真になる可能性があるループにはTEST BEFORE(デフォルト)を使う
ファイルループでのプライミングリード忘れ PERFORM UNTIL WS-EOFの前にREADを1回書かないと、最初のレコードが2回処理されることがある ループ開始前に先読み(プライミングリード)を必ず行う

よくある質問

QPERFORM VARYINGでBY句に負の値を指定できますか?
Aはい、BY -1のように負のステップを指定することで逆順ループが実現できます。例えばPERFORM VARYING WS-IDX FROM WS-MAX BY -1 UNTIL WS-IDX < 1とすると配列の末尾から先頭に向かって処理できます。配列の逆順表示や後ろから検索する場合に使います。
QインラインPERFORMとアウトオブラインPERFORMの使い分けの基準は?
A処理が5行程度で再利用しない場合はインラインPERFOM(END-PERFORM)が簡潔です。処理が長い・複数箇所から呼ぶ・名前で意図を説明したい場合はアウトオブライン(段落呼び出し)が適しています。一般的に段落名が「ドキュメントの役割」を果たすため、重要な処理の塊はアウトオブラインにする方が保守性が高まります。
QPERFORM UNTILとPERFOM WHILEの違いは何ですか?
ACOBOLにはPERFORM WHILEという構文はありません。すべてUNTILです。他言語のwhile(条件が真の間ループ)と逆で、COBOLのUNTILは「条件が真になるまでループ」です。while (x < 10) は PERFORM UNTIL x >= 10 と書きます。UNTILの条件は「終了条件」であることを忘れないようにしましょう。
QPERFORM VARYINGのループでINDEX型とPIC 9型のどちらを使うべきですか?
AOCCURS句と一緒にSEARCH文を使う場合はINDEXED BY句で定義したINDEX型が必要です。一方、PERFORM VARYINGでは通常のPIC 9変数が使えます。PERFORM VARYINGのカウンタにはPIC 9(3)程度の変数を使うのが一般的で、配列サイズが大きい場合はPIC 9(5)などに桁数を合わせましょう。
Qループの途中で処理をスキップする方法は?
AEXIT PERFORM CYCLEを使います。他言語のcontinueに相当し、当回の残りの処理をスキップして次の繰り返しの先頭に戻ります。IF条件と組み合わせて「特定の条件のレコードだけ処理をスキップ」するパターンでよく使います。

まとめ

PERFORM文のループ形式と使い分けを整理します。

形式 説明
PERFORM TIMES 固定回数ループ。カウンタ不要でシンプルに書ける
PERFORM UNTIL(TEST BEFORE) ゼロ回実行あり。ファイル読み込みなど条件が最初から成立する可能性があるループ
PERFORM UNTIL(TEST AFTER) 最低1回は必ず実行。入力要求・リトライなど
PERFORM VARYING 配列処理の基本。FROM/BY/UNTILでカウンタを自動制御
PERFORM VARYING … AFTER 多次元配列の多重ループを1文で記述できる
EXIT PERFORM ループを途中で終了(break相当)
EXIT PERFORM CYCLE 当回のループをスキップして次へ(continue相当)
PERFORM THRU 複数の段落をまとめて実行。範囲の管理に注意

TEST BEFORE/AFTERの選択とVARYING AFTERの多重ループは実務でよく使うにもかかわらず見落とされがちな機能です。配列処理や複数ファイルの同時処理の設計時に活用してみましょう。

ループで扱う配列の定義についてはOCCURS句の使い方を、条件判定についてはIF文の完全ガイドも合わせてご参照ください。