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ì sao trong COBOL có 01 trước tên biến? Hiểu đúng về Level Number và cấu trúc dữ liệu phân cấp

Vì sao trong COBOL có 01 trước tên biến?

Khi đọc code COBOL, bạn sẽ thường thấy cấu trúc như sau:

01 CUSTOMER-SRC.
   05 CUST-ID      PIC 9(5).
   05 CUST-NAME    PIC X(30).

01 CUSTOMER-DST.
   05 CUST-ID      PIC 9(5).
   05 CUST-NAME    PIC X(30).

Rất nhiều người mới học COBOL thắc mắc:

Tại sao lại có 01 trước CUSTOMER-SRCCUSTOMER-DST?
Nó có ý nghĩa gì? Có giống kiểu dữ liệu không?

Trong bài viết này, chúng ta sẽ phân tích rõ bản chất của 01, cách COBOL tổ chức dữ liệu, và tại sao điều này cực kỳ quan trọng trong hệ thống legacy.


1️⃣ 01 là gì trong COBOL?

01Level Number – mức cấp trong cấu trúc dữ liệu phân cấp của COBOL.

COBOL tổ chức dữ liệu theo dạng cây (hierarchical structure), không giống như kiểu struct/class hiện đại.

Ví dụ các level thường gặp:

Level Ý nghĩa
01 Record cấp cao nhất (root)
05 Field con của 01
10 Field con của 05
77 Biến đơn lẻ (không thuộc group)
88 Condition name (biến điều kiện)

Trong bài 002 về biến và kiểu dữ liệu, bạn đã thấy level number dùng để khai báo group item và field con. Ở đây, ta zoom kỹ hơn vào vai trò của 01.


2️⃣ Cấu trúc bộ nhớ của ví dụ CUSTOMER-SRCCUSTOMER-DST

Đoạn code:

01 CUSTOMER-SRC.
   05 CUST-ID      PIC 9(5).
   05 CUST-NAME    PIC X(30).

01 CUSTOMER-DST.
   05 CUST-ID      PIC 9(5).
   05 CUST-NAME    PIC X(30).

Tạo ra 2 record độc lập trong bộ nhớ:

CUSTOMER-SRC
 ├── CUST-ID     (5 bytes)
 └── CUST-NAME   (30 bytes)

CUSTOMER-DST
 ├── CUST-ID     (5 bytes)
 └── CUST-NAME   (30 bytes)

Mỗi record có kích thước:

  • CUST-ID : 5 ký tự numeric → 5 bytes
  • CUST-NAME: 30 ký tự alphanumeric → 30 bytes

Tổng cộng: 35 bytes cho mỗi record.

Hai vùng nhớ này hoàn toàn tách biệt. 01 chính là root của mỗi vùng dữ liệu.


3️⃣ Vì sao phải là 01 chứ không phải 05 hay 10?

Trong COBOL:

  • 01level root – bắt đầu một cấu trúc dữ liệu độc lập
  • Các level khác (05, 10, 15…) phải nằm bên trong một level lớn hơn

Nếu bạn viết:

05 CUSTOMER-SRC.

→ Compile sẽ lỗi.

05 không thể đứng độc lập. Nó phải thuộc một 01 (hoặc một level thấp hơn đã khai báo trước đó).

Nói cách khác:

  • 01 ~ định nghĩa ra một "record"/"struct" hoàn chỉnh
  • 05, 10, 15 ~ các field con bên trong record đó

Đây là lý do hầu hết record trong FILE SECTION hay WORKING-STORAGE đều bắt đầu bằng 01.


4️⃣ Tại sao hay thấy hai record giống nhau: SRC và DST?

Pattern phổ biến trong batch COBOL:

Record Vai trò
CUSTOMER-SRC Record nguồn (input)
CUSTOMER-DST Record đích (output)

Ví dụ thực tế:

  • Đọc file vào CUSTOMER-SRC
  • Validate / chuẩn hóa dữ liệu
  • Ghi ra CUSTOMER-DST (có thể layout giống hoặc hơi khác, tùy hệ thống)

Kiểu thiết kế này giúp tách dữ liệu đầu vàodữ liệu đã xử lý rõ ràng, tránh ghi đè lung tung.


5️⃣ Điều quan trọng: Group MOVE (MOVE cả record 01)

Trong đoạn code kinh điển:

MOVE CUSTOMER-SRC TO CUSTOMER-DST.

Đây không phải là copy từng field bằng tay.

Trong COBOL:

MOVE group-to-group = copy toàn bộ memory block theo layout đã khai báo.

Có thể hình dung tương đương với C:

memcpy(dst, src, sizeof(src));

Điều kiện để group MOVE an toàn:

  • Hai structure phải có cùng layout (hoặc ít nhất là tương thích)
  • Tổng kích thước (số byte) phải khớp hoặc bạn hiểu rõ mình đang chấp nhận truncate/garbage

Trong ví dụ này:

  • CUSTOMER-SRC = 35 bytes
  • CUSTOMER-DST = 35 bytes

MOVE CUSTOMER-SRC TO CUSTOMER-DSThợp lệ và an toàn.

Đây là một trong những pattern rất mạnh (và cũng rất nguy hiểm nếu dùng sai) trong COBOL: copy cả record chỉ với một lệnh MOVE.


6️⃣ Tại sao phải viết OF CUSTOMER-SRC khi gán từng field?

Xem đoạn code đầy đủ hơn:

01 CUSTOMER-SRC.
   05 CUST-ID      PIC 9(5).
   05 CUST-NAME    PIC X(30).

01 CUSTOMER-DST.
   05 CUST-ID      PIC 9(5).
   05 CUST-NAME    PIC X(30).

PROCEDURE DIVISION.
    MOVE 12345           TO CUST-ID OF CUSTOMER-SRC.
    MOVE "Nguyen Van A" TO CUST-NAME OF CUSTOMER-SRC.

    MOVE CUSTOMER-SRC    TO CUSTOMER-DST.
    STOP RUN.

Ở đây có hai biến tên CUST-ID:

  • CUST-ID trong CUSTOMER-SRC
  • CUST-ID trong CUSTOMER-DST

Nếu bạn chỉ viết:

MOVE 12345 TO CUST-ID.

Compiler sẽ báo lỗi (ambiguous reference) vì không biết bạn muốn gán vào field nào.

Do đó, bạn phải fully-qualify:

  • CUST-ID OF CUSTOMER-SRC
  • hoặc CUST-ID OF CUSTOMER-DST

Pattern này rất quan trọng khi bạn có nhiều record với layout giống nhau trong cùng một chương trình.


7️⃣ So sánh với ngôn ngữ hiện đại (Java, PL/SQL)

Java

class Customer {
    int custId;
    String custName;
}

Customer src = new Customer();
Customer dst = new Customer();

src.custId = 12345;
src.custName = "Nguyen Van A";

// Copy toàn bộ object (giả sử có hàm copy)
copyCustomer(dst, src);

Ở đây:

  • Customer ~ layout của record (giống phần sau 01 trong COBOL)
  • src / dst ~ hai biến instance khác nhau

PL/SQL

TYPE customer_rec IS RECORD (
   cust_id   NUMBER(5),
   cust_name VARCHAR2(30)
);

src customer_rec;
dst customer_rec;

Trong mental model này:

  • COBOL 01 CUSTOMER-SRC ~ một instance của customer_rec
  • COBOL 01 CUSTOMER-DST ~ một instance khác của cùng layout

Khác biệt là COBOL diễn tả trực tiếp layout bằng PIC + level number, thay vì dùng TYPE/class riêng.


8️⃣ Điểm nguy hiểm của group MOVE khi layout thay đổi

Giả sử bạn chỉnh sửa record destination:

01 CUSTOMER-DST.
   05 CUST-ID      PIC 9(5).
   05 CUST-NAME    PIC X(30).
   05 CUST-STATUS  PIC X(1).

Tức là:

  • CUSTOMER-SRC = 35 bytes
  • CUSTOMER-DST = 36 bytes

Câu lệnh:

MOVE CUSTOMER-SRC TO CUSTOMER-DST.

Vẫn compile bình thường, nhưng:

  • 35 bytes đầu của CUSTOMER-DST sẽ được overwrite bởi CUSTOMER-SRC
  • Byte cuối cùng (CUST-STATUS) có thể giữ nguyên giá trị cũ hoặc garbage (tùy initial value)

Nếu struct chênh lệch theo hướng ngược lại (source dài hơn destination), dữ liệu có thể bị truncate.

Vì COBOL không type-safe như Java/C#, các lỗi kiểu này khó trace trong hệ thống lớn, đặc biệt khi data chạy qua nhiều batch job.

Best practice khi làm việc với legacy:

  • Cực kỳ cẩn thận khi thay đổi layout của các record 01
  • Tìm tất cả chỗ đang MOVE group-to-group giữa các record này trước khi sửa

9️⃣ Tóm tắt nhanh về vai trò của 01

01 trong COBOL:

  • level number cao nhất (root) của một cấu trúc dữ liệu
  • Tạo ra một record độc lập trong bộ nhớ
  • Cho phép sử dụng group MOVE để copy cả block dữ liệu một lần
  • Là nền tảng để tổ chức dữ liệu phân cấp (01 → 05 → 10 ...)

Không có 01 → không có root structure → không tạo được record hoàn chỉnh.

Khi đọc code COBOL, mỗi lần thấy 01 bạn nên nghĩ ngay:

"À, đây là một record/struct – một đơn vị dữ liệu lớn".


🔟 Khi nào cần hiểu sâu về 01 và level number?

Bạn nên nắm rất rõ về 01 và level number nếu đang làm các công việc sau:

  • Migrate COBOL sang PL/SQL / Java / dịch vụ microservice
  • Refactor hệ thống legacy để tách module, chuẩn hóa data
  • Debug batch program xử lý dữ liệu file/phát sinh lỗi mapping
  • Làm data mapping giữa COBOL copybook và schema trong database hoặc message (JSON/AVRO)

Nhiều tool ETL, data integration, hoặc generator sẽ đọc COBOL copybook (dựa trên 01, 05, 10…) rồi sinh ra:

  • Bảng database
  • Struct trong Java/Scala (Spark)
  • Schema trong Kafka/Avro

Nếu bạn không hiểu level number, rất khó kiểm soát được việc mapping có đúng hay không.


So sánh level number COBOL với struct/class hiện đại

Khái niệm COBOL Java / C# / PL/SQL
Định nghĩa layout dữ liệu 01 CUSTOMER ... 05 FIELD ... class Customer { ... }
Instance dữ liệu Mỗi 01 là một instance Mỗi biến new Customer()
Field con Level 05, 10, 15 Thuộc tính trong class
Biến đơn lẻ không thuộc group Level 77 Biến global/local riêng lẻ
Biến điều kiện (flag logic) Level 88 Enum/const/boolean helper

Bức tranh tổng thể:

  • Level number là cách COBOL mô tả cây dữ liệu
  • 01 chính là gốc (root) của cây đó
  • Các kỹ thuật như group MOVE, REDEFINES, OCCURS… đều dựa trên cách bố trí bộ nhớ theo level number

Kết luận

01 không chỉ là một con số đứng trước tên biến trong COBOL.

Nó đại diện cho:

Cách COBOL tổ chức bộ nhớ, cách dữ liệu được copy, và cách toàn bộ hệ thống batch vận hành.

Hiểu được 01 là bước đầu tiên để:

  • Đọc và hiểu các FILE SECTION / WORKING-STORAGE phức tạp
  • Thiết kế data mapping chính xác khi migrate hệ thống
  • Tự tin chỉnh sửa, refactor hoặc mở rộng chương trình COBOL mà không làm hỏng data layout

Nếu bạn đang trong quá trình đưa hệ thống COBOL sang kiến trúc hiện đại, các chủ đề tiếp theo nên đào sâu là:

  • Sự khác biệt chi tiết giữa 01, 77, và 88
  • Cách convert COBOL structure (copybook) sang PL/SQL RECORD, Java class, JSON schema
  • Cách parse level structure để build tool tự động chuyển đổi từ COBOL sang format khác

Bạn muốn đào sâu theo hướng nào, hãy note lại – chúng ta có thể biến nó thành bài tiếp theo trong series về COBOL data structure.