Site logo
Tác giả
  • avatar Nguyễn Đức Xinh
    Name
    Nguyễn Đức Xinh
    Twitter
Ngày xuất bản
Ngày xuất bản

Vòng lặp và PERFORM trong COBOL – Hiểu rõ loop pattern trong batch program

Tại sao phải hiểu PERFORM và vòng lặp trong COBOL?

Ở các bài trước bạn đã có:

  • Bài 002: hiểu DATA DIVISION, PIC, level number, group item
  • Bài 004: nắm toán tử & biểu thức (MOVE, ADD, COMPUTE, ...)
  • Bài 005: làm chủ cấu trúc điều kiện (IF, EVALUATE)

Để đọc được một batch program thực tế như RZZBSQLN1.PCO, bạn còn thiếu một mảnh ghép rất quan trọng:

Vòng lặp và cách COBOL điều khiển flow bằng PERFORM.

Trong Java/C#/Python, bạn quen với for, while, do-while. Trong COBOL, hầu hết control flow đều xoay quanh:

  • PERFORM paragraph – gọi một đoạn code như gọi hàm
  • PERFORM paragraph THRU paragraph-EXIT – gọi một block nhiều paragraph liên tiếp
  • PERFORM ... UNTIL ... – vòng lặp kiểu do { ... } while (...)
  • PERFORM VARYING ... FROM ... BY ... UNTIL ... – gần giống for tăng biến đếm

Bài này sẽ giúp bạn:

  • Đọc hiểu main loop của một batch COBOL
  • Hiểu được kiểu code như: PERFORM MAIN-RTN UNTIL INP1_END = 1.
  • Mapping PERFORM VARYING sang for trong Java/Python
  • Áp dụng thực tế với ví dụ và bài tập

PERFORM paragraph – Gọi một đoạn code như hàm

Cú pháp cơ bản

PERFORM paragraph-name.

Trong đó paragraph-name là một nhãn trong PROCEDURE DIVISION:

       PROCEDURE DIVISION.
       MAIN-RTN.
           PERFORM INIT-RTN
           PERFORM PROCESS-RTN
           PERFORM FINISH-RTN
           STOP RUN.

       INIT-RTN.
           DISPLAY "INIT".
           EXIT.

       PROCESS-RTN.
           DISPLAY "PROCESS".
           EXIT.

       FINISH-RTN.
           DISPLAY "FINISH".
           EXIT.

Mapping sang Java:

void main() {
    initRtn();
    processRtn();
    finishRtn();
}

Trong các chương trình enterprise như RZZBSQLN1, cấu trúc rất giống:

  • MAIN-RTN đóng vai trò main logic
  • Các *-RTN khác là subroutine được PERFORM

Quy tắc quan trọng

  • PERFORM paragraph. chạy từ đầu paragraph cho tới
    • Gặp EXIT. của paragraph đó, hoặc
    • Gặp STOP RUN., hoặc
    • Rơi vào paragraph khác mà không có EXIT rõ ràng (pattern legacy, khó đọc – sẽ nói thêm ở bài kiến trúc).

Best practice hiện đại: mỗi paragraph nên kết thúc bằng EXIT. hoặc GOBACK. để flow dễ follow.


PERFORM ... THRU ... – Gọi cả một block nhiều paragraph

Trong code legacy, bạn sẽ thấy rất nhiều đoạn kiểu:

PERFORM INIT-RTN THRU INIT-EXT.

Ý nghĩa:

Chạy từ paragraph INIT-RTN cho đến paragraph INIT-EXT, bao gồm cả hai.

Ví dụ đơn giản:

       PROCEDURE DIVISION.

       MAIN-RTN.
           PERFORM INIT-RTN THRU INIT-EXT
           PERFORM WORK-RTN THRU WORK-EXT
           STOP RUN.

       INIT-RTN.
           DISPLAY "INIT-1".

       INIT-RTN-2.
           DISPLAY "INIT-2".

       INIT-EXT.
           DISPLAY "INIT-END".
           EXIT.

       WORK-RTN.
           DISPLAY "WORK".

       WORK-EXT.
           DISPLAY "WORK-END".
           EXIT.

Khi PERFORM INIT-RTN THRU INIT-EXT:

  1. Bắt đầu tại INIT-RTN
  2. Chạy tiếp INIT-RTN-2
  3. Dừng sau khi kết thúc INIT-EXT (ở đây có EXIT.)

Khi mapping sang Java, bạn có thể tưởng tượng:

void mainRtn() {
    initBlock();
    workBlock();
}

void initBlock() {
    System.out.println("INIT-1");
    System.out.println("INIT-2");
    System.out.println("INIT-END");
}

void workBlock() {
    System.out.println("WORK");
    System.out.println("WORK-END");
}

Trong RZZBSQLN1, pattern này xuất hiện rất nhiều:

  • PERFORM INP1-OPEN-RTN THRU INP1-OPEN-EXT
  • PERFORM SQL-EXEC-RTN THRU SQL-EXEC-EXT

Đây chính là cách họ module hóa logic I/O, SQL, logging theo block.


PERFORM ... UNTIL – Vòng lặp kiểu while/do-while

Cú pháp cơ bản

PERFORM paragraph-name
    UNTIL condition.

Hoặc dạng inline (in-line perform):

PERFORM
    statement-1
    statement-2
    ...
UNTIL condition.

Trong batch enterprise, form thường gặp là:

PERFORM MAIN-RTN UNTIL INP1_END = 1.

Ý nghĩa (pseudo-code Java):

while (inp1End != 1) {
    mainRtn();
}

Hoặc nếu coi INP1_END được set khi hết file:

do {
    mainRtn();
} while (inp1End != 1);

Ví dụ: đọc file đến hết

       WORKING-STORAGE SECTION.
       01  WS-END-FLAG      PIC 9 VALUE 0.
       01  WS-REC-COUNT     PIC 9(5) VALUE 0.

       PROCEDURE DIVISION.
           PERFORM OPEN-FILE-RTN
           PERFORM READ-LOOP-RTN UNTIL WS-END-FLAG = 1
           PERFORM CLOSE-FILE-RTN
           STOP RUN.

       OPEN-FILE-RTN.
           OPEN INPUT INFILE
           IF WS-FILE-STS NOT = "00"
               DISPLAY "FILE OPEN ERROR"
               MOVE 1 TO WS-END-FLAG
           END-IF
           EXIT.

       READ-LOOP-RTN.
           READ INFILE
               AT END
                   MOVE 1 TO WS-END-FLAG
               NOT AT END
                   ADD 1 TO WS-REC-COUNT
                   DISPLAY "READ REC " WS-REC-COUNT
           END-READ
           EXIT.

       CLOSE-FILE-RTN.
           CLOSE INFILE
           EXIT.

Mapping mindset:

  • PERFORM READ-LOOP-RTN UNTIL WS-END-FLAG = 1 ~ while (wsEndFlag != 1) { readLoopRtn(); }
  • Flag WS-END-FLAG được set bên trong paragraph READ-LOOP-RTN khi gặp AT END.

Pattern này rất giống những gì RZZBSQLN1 làm với:

  • INP1_END – cờ EOF của file input
  • INP1-INPUT-RTN – đoạn đọc file từng record

PERFORM VARYING – Vòng lặp for với biến đếm

Đây là form gần nhất với for (i = start; i <= end; i += step) trong ngôn ngữ hiện đại.

Cú pháp tổng quát

PERFORM VARYING index FROM start BY step
        UNTIL condition
    paragraph-or-statements
END-PERFORM.

Có hai style:

  1. PERFORM paragraph-name VARYING ... – gọi paragraph trong loop
  2. PERFORM VARYING ... in-line – viết trực tiếp statements trong block

Ví dụ 1 – loop đơn giản với paragraph

       WORKING-STORAGE SECTION.
       01  IDX        PIC 9(2) VALUE 0.

       PROCEDURE DIVISION.
           PERFORM PRINT-HELLO
               VARYING IDX FROM 1 BY 1
               UNTIL IDX > 5
           STOP RUN.

       PRINT-HELLO.
           DISPLAY "HELLO #" IDX
           EXIT.

Mapping Java:

for (int idx = 1; idx <= 5; idx++) {
    printHello(idx);
}

Ví dụ 2 – in-line PERFORM VARYING

       WORKING-STORAGE SECTION.
       01  IDX        PIC 9(2) VALUE 0.

       PROCEDURE DIVISION.
           PERFORM VARYING IDX FROM 1 BY 1
                   UNTIL IDX > 3
               DISPLAY "IDX=" IDX
           END-PERFORM
           STOP RUN.

Mapping Python:

for idx in range(1, 4):
    print("IDX=", idx)

Liên hệ với RZZBSQLN1

Trong chương trình thực tế, bạn sẽ gặp kiểu logic:

PERFORM VARYING WK_BCMO_IDX FROM 1 BY 1
        UNTIL WK_BCMO_IDX > WK_BCMO_MAX
    ... xử lý phần tử thứ WK_BCMO_IDX trong table WK_PARM_TBL ...
END-PERFORM.

Mapping mental model:

for (int i = 1; i <= wkBcmoMax; i++) {
    // xử lý WK_PARM_TBL(i)
}

Ở đây, WK_PARM_TBL là table được khai báo với OCCURS (sẽ học chi tiết ở bài 008). WK_BCMO_IDX là index để truy cập từng phần tử trong table đó.


So sánh các kiểu PERFORM trong batch program

Bảng sau giúp bạn tóm tắt nhanh các pattern PERFORM thường gặp:

Pattern COBOL Java/C#/Python tương đương Use case điển hình
Gọi 1 đoạn code PERFORM INIT-RTN. Gọi hàm: initRtn(); Khởi tạo, kết thúc, setup đơn giản
Gọi 1 block nhiều paragraph PERFORM INIT-RTN THRU INIT-EXT. Gọi hàm chứa cả block Group INIT, WORK, FINISH cho cùng mục đích
Vòng lặp theo flag PERFORM MAIN-RTN UNTIL END-FLAG = 1. while (!endFlag) { mainRtn(); } Đọc file đến EOF, loop main của batch
Vòng lặp theo index PERFORM VARYING I FROM 1 BY 1 ... for (i = 1; i <= n; i++) Duyệt table OCCURS
PERFORM in-line PERFORM ... UNTIL ... END-PERFORM while/for với block inline Logic ngắn, không cần paragraph riêng

Khi đọc một chương trình COBOL dài:

  1. Tìm tất cả câu PERFORM quan trọng
  2. Map nó sang main, loop, for, while trong đầu bạn
  3. Sau đó mới dive vào chi tiết IF, COMPUTE bên trong

Ví dụ tổng hợp: main loop xử lý file và table tham số

Ví dụ dưới đây mô phỏng một batch program đơn giản:

  • Đọc từng record từ file input
  • Mỗi record chứa một "lệnh" (command) và một giá trị
  • Lưu các lệnh hợp lệ vào table (tối đa 10 dòng)
  • In ra số lệnh đã lưu

Khai báo DATA DIVISION (rút gọn)

       IDENTIFICATION DIVISION.
       PROGRAM-ID. CMD-BATCH.

       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT CMD-FILE ASSIGN TO "cmd.txt"
               ORGANIZATION IS LINE SEQUENTIAL
               FILE STATUS IS WS-FILE-STS.

       DATA DIVISION.
       FILE SECTION.
       FD  CMD-FILE.
       01  CMD-REC.
           05 CMD-CODE   PIC X(5).
           05 CMD-VALUE  PIC 9(5).

       WORKING-STORAGE SECTION.
       01  WS-FILE-STS      PIC X(2) VALUE "00".
       01  WS-END-FLAG      PIC 9    VALUE 0.
       01  WS-CMD-COUNT     PIC 9(3) VALUE 0.

       01  MAX-CMD          PIC 9(3) VALUE 10.
       01  IDX              PIC 9(3).

       01  CMD-TABLE.
           05 CMD-ITEM OCCURS 10 TIMES.
              10 T-CMD-CODE   PIC X(5).
              10 T-CMD-VALUE  PIC 9(5).

PROCEDURE DIVISION với PERFORM

       PROCEDURE DIVISION.

       MAIN-RTN.
           PERFORM INIT-RTN THRU INIT-EXT
           PERFORM READ-LOOP-RTN UNTIL WS-END-FLAG = 1
           PERFORM FINISH-RTN THRU FINISH-EXT
           STOP RUN.

       INIT-RTN.
           OPEN INPUT CMD-FILE
           IF WS-FILE-STS NOT = "00"
               DISPLAY "FILE OPEN ERROR: " WS-FILE-STS
               MOVE 1 TO WS-END-FLAG
           END-IF.

       INIT-EXT.
           EXIT.

       READ-LOOP-RTN.
           READ CMD-FILE
               AT END
                   MOVE 1 TO WS-END-FLAG
               NOT AT END
                   PERFORM PROCESS-REC-RTN THRU PROCESS-REC-EXT
           END-READ
           EXIT.

       PROCESS-REC-RTN.
           IF WS-CMD-COUNT < MAX-CMD
               ADD 1 TO WS-CMD-COUNT
               MOVE CMD-CODE  TO T-CMD-CODE (WS-CMD-COUNT)
               MOVE CMD-VALUE TO T-CMD-VALUE(WS-CMD-COUNT)
           ELSE
               DISPLAY "TABLE FULL, IGNORE MORE CMD"
           END-IF.

       PROCESS-REC-EXT.
           EXIT.

       FINISH-RTN.
           DISPLAY "TOTAL CMD: " WS-CMD-COUNT

           PERFORM VARYING IDX FROM 1 BY 1
                   UNTIL IDX > WS-CMD-COUNT
               DISPLAY "CMD #" IDX
               DISPLAY "  CODE : " T-CMD-CODE (IDX)
               DISPLAY "  VALUE: " T-CMD-VALUE(IDX)
           END-PERFORM

           CLOSE CMD-FILE.

       FINISH-EXT.
           EXIT.

Điểm cần chú ý:

  • MAIN-RTNmain flow của chương trình
  • PERFORM READ-LOOP-RTN UNTIL WS-END-FLAG = 1
    • chính là main loop đọc file đến hết
  • PROCESS-REC-RTN chỉ xử lý một record, được gọi cho mỗi lần READ thành công
  • PERFORM VARYING IDX FROM 1 BY 1 UNTIL IDX > WS-CMD-COUNT
    • là loop duyệt table kết quả – giống hệt for (i = 1; i <= wsCmdCount; i++)

Nếu bạn đã xem RZZBSQLN1.PCO, sẽ thấy pattern rất tương tự:

  • INP1-OPEN-RTN / INP1-INPUT-RTN / INP1-CLOSE-RTN
  • Table SQL WK_SQL_TBL OCCURS 50
  • Loop bằng WK_SQL_IDX để duyệt SQL đã đọc

Bài tập thực hành

Bài 1 – PERFORM UNTIL với biến đếm

Yêu cầu:

  • Khai báo WS-I (9(2)), WS-SUM (9(4))
  • Dùng PERFORM CALC-RTN UNTIL WS-I > 10
  • Mỗi lần chạy CALC-RTN:
    • Tăng WS-I lên 1
    • Cộng WS-I vào WS-SUM
  • Sau khi kết thúc, hiển thị tổng từ 1 đến 10

Gợi ý: Đây là cách viết for (i = 1; i <= 10; i++) sum += i; bằng PERFORM ... UNTIL.

Bài 2 – PERFORM VARYING cho table OCCURS

Yêu cầu:

  • Khai báo table điểm thi:

    01 SCORE-TBL.
       05 SCORE-ITEM OCCURS 5 TIMES.
          10 SCORE-VALUE PIC 9(3).
    
  • Gán 5 điểm khác nhau (có thể dùng MOVE thủ công)

  • Dùng PERFORM VARYING IDX FROM 1 BY 1 UNTIL IDX > 5 để:

    • Hiển thị từng điểm
    • Tính tổng điểm
  • Cuối cùng, tính trung bình (dùng COMPUTE) và hiển thị

Bài 3 – Mô phỏng main loop đọc file

Viết skeleton chương trình batch với cấu trúc:

  • MAIN-RTN gọi:
    • PERFORM OPEN-RTN THRU OPEN-EXT
    • PERFORM READ-RTN UNTIL END-FLAG = 1
    • PERFORM CLOSE-RTN THRU CLOSE-EXT
  • READ-RTN thực hiện:
    • READ file
    • Nếu AT END → set END-FLAG = 1
    • NOT AT END → tăng counter và DISPLAY nội dung record

Chưa cần compile được, chỉ cần đúng cấu trúc PERFORM và phân chia paragraph hợp lý.

Bài 4 – Dịch PERFORM trong RZZBSQLN1 sang pseudo-code

Chọn một đoạn code có PERFORM trong RZZBSQLN1.PCO, ví dụ:

PERFORM SQL-EXEC-RTN THRU SQL-EXEC-EXT
    VARYING WK_SQL_IDX FROM 1 BY 1
    UNTIL WK_SQL_IDX > WK_SQL_MAX.

Hãy viết lại bằng pseudo-code Java hoặc Python để chắc chắn bạn hiểu:

  • Loop bắt đầu từ đâu?
  • Kết thúc khi nào?
  • Mỗi vòng lặp đang làm gì với WK_SQL_IDX?

Ví dụ Java-style:

for (int wkSqlIdx = 1; wkSqlIdx <= wkSqlMax; wkSqlIdx++) {
    sqlExecRtn(); // bên trong dùng wkSqlIdx để lấy dòng SQL tương ứng
}

Kết luận

Sau bài này, bạn đã:

  • Hiểu được các pattern PERFORM quan trọng trong COBOL
  • Mapping được PERFORM, PERFORM THRU, PERFORM VARYING sang while/for trong Java/C#/Python
  • Thấy rõ hơn cách một batch program như RZZBSQLN1 tổ chức main loop đọc file, loop xử lý table

Kết hợp với:

  • Bài 002: cấu trúc dữ liệu, PIC, OCCURS
  • Bài 004: toán tử & biểu thức
  • Bài 005: IF & EVALUATE

…bạn đã gần đủ “bộ kỹ năng” để đọc trôi chảy logic của một chương trình COBOL enterprise.

Ở các bài tiếp theo, chúng ta sẽ đi sâu vào:

  • FILE-CONTROL & FILE SECTION – cách định nghĩa file, record layout, READ ... AT END ...
  • WORKING-STORAGE nâng cao với OCCURS & REDEFINES – hiểu rõ table và cách RZZBSQLN1 lưu SQL vào WK_SQL_TBL

Từ đó, việc đọc, debug và thậm chí viết mới một batch COBOL sẽ trở nên tự tin hơn nhiều.