- Tác giả

- Name
- Nguyễn Đức Xinh
- 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àmPERFORM paragraph THRU paragraph-EXIT– gọi một block nhiều paragraph liên tiếpPERFORM ... UNTIL ...– vòng lặp kiểudo { ... } while (...)PERFORM VARYING ... FROM ... BY ... UNTIL ...– gần giốngfortă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 VARYINGsangfortrong 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
*-RTNkhác là subroutine đượcPERFORM
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ó
EXITrõ ràng (pattern legacy, khó đọc – sẽ nói thêm ở bài kiến trúc).
- Gặp
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-RTNcho đến paragraphINIT-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:
- Bắt đầu tại
INIT-RTN - Chạy tiếp
INIT-RTN-2 - 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-EXTPERFORM 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 paragraphREAD-LOOP-RTNkhi gặpAT END.
Pattern này rất giống những gì RZZBSQLN1 làm với:
INP1_END– cờ EOF của file inputINP1-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:
PERFORM paragraph-name VARYING ...– gọi paragraph trong loopPERFORM 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:
- Tìm tất cả câu
PERFORMquan trọng - Map nó sang
main,loop,for,whiletrong đầu bạn - Sau đó mới dive vào chi tiết
IF,COMPUTEbê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-RTNlà main flow của chương trìnhPERFORM READ-LOOP-RTN UNTIL WS-END-FLAG = 1- chính là main loop đọc file đến hết
PROCESS-REC-RTNchỉ xử lý một record, được gọi cho mỗi lầnREADthành côngPERFORM 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++)
- là loop duyệt table kết quả – giống hệt
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-Ilên 1 - Cộng
WS-IvàoWS-SUM
- Tăng
- 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
MOVEthủ 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-RTNgọi:PERFORM OPEN-RTN THRU OPEN-EXTPERFORM READ-RTN UNTIL END-FLAG = 1PERFORM CLOSE-RTN THRU CLOSE-EXT
READ-RTNthực hiện:READfile- Nếu
AT END→ setEND-FLAG = 1 NOT AT END→ tăng counter vàDISPLAYnộ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
PERFORMquan trọng trong COBOL - Mapping được
PERFORM,PERFORM THRU,PERFORM VARYINGsangwhile/fortrong Java/C#/Python - Thấy rõ hơn cách một batch program như
RZZBSQLN1tổ 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
RZZBSQLN1lưu SQL vàoWK_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.
