수정 이력(6건, 최종 수정 2026년 09월 03일)
이 글에 적용한 변경 사항의 기록입니다. 보관해 둔 수정 전 버전은 DOI가 부여된 고정 URL에서 읽을 수 있습니다.
- permalink·저자 표기·지식 맵 래퍼·깨진 내부 링크 등 CI가 지적한 표시용 수정을 반영했습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
- 일본어 원문의 완역으로 다시 번역했습니다. 기존 한국어판은 원문의 일부만 옮긴 축약본이라 절, 표, Mermaid 그림, 그림 설명, FAQ가 빠져 있었습니다. 일본어 원문에 맞춰 이들을 모두 복원했고, 기술적인 주장은 일본어판과 같습니다. 수정 전 버전 보기 (DOI: 10.5281/zenodo.21635181)
- 데이터 정의 읽는 법이나 처리 흐름, 외부 경계를 잡는 법을 그림으로도 따라갈 수 있도록, Mermaid 그림을 21점 추가했습니다(본문 500〜750자당 1그림 규약에 맞춘 것입니다). 본문 문장은 바꾸지 않았습니다.
- 글 맨 앞에 「이 글의 지식 맵」 절을 추가했습니다. 본문에서 다루는 개념과 그 관계를 요약·그림·상세 페이지 링크로 정리한 것입니다. 본문의 주장은 바꾸지 않았습니다.
- 외부 리뷰(1283건) 대응으로 본문을 갱신했습니다. 개별 변경 내용은 아래 이력을 참조하십시오.
- COMP-3가 실제로 어떤 바이트 열이 되는지를, IBM에 실린 예와 도출을 붙여 추가했습니다(ASCII로 읽으면 원래 수치가 어디에도 나오지 않는다는 점도 보였습니다). 고정 형식의 열 구조 그림을 80열 눈금이 있는 것으로 바꾸고, 독해 연습을 6문항 추가했으며, 컴파일러 차이(문자 코드, `COMP-4`와 `COMP-5`의 자리 수 다루기 차이)를 덧붙였습니다.
- 최초 공개
이 글을 인용하기(DOI: 10.5281/zenodo.21635180)
이 글은 Zenodo에 보관되어 있습니다. 항상 최신 버전으로 연결되는 DOI와 지금 보고 있는 버전에 고정된 DOI를 아래에 함께 제시합니다.
Go Komura (2026). 「COBOL 소스를 읽기 전에 알아 둘 최소 지식」. 합동회사 코무라소프트. https://doi.org/10.5281/zenodo.21635180 https://comcomponent.com/ko/blog/cobol-minimum-reading-guide/
- DOI(최신 버전)
- 10.5281/zenodo.21635180
- DOI(이 버전)
- 10.5281/zenodo.22217461
인계, 장애 대응, 벤더 패키지 보수. 그런 상황에서 어느 날 갑자기 COBOL 소스가 날아오는 경우가 있습니다.
- 파일 이름은
.cbl이나.cpy - 변수 이름은 전부 대문자
01,05,77,88이 나열된다PIC S9(7)V99 COMP-3처럼, 주문과 회계 소프트웨어의 중간 같은 표기가 나온다- 게다가
COPY투성이라 연 파일만으로는 전체가 보이지 않는다
이 지점에서 대개 손이 멈춥니다.
다만, 읽기 위한 지도는 그리 크지 않습니다. COBOL에는 컴파일러나 제품마다 차이가 있지만, 기존 업무 시스템을 읽을 때 먼저 잡아야 할 골격은 꽤 공통입니다. 이 글에서는 IBM 계열이나 전형적인 업무 COBOL을 염두에 두고, 갑자기 소스를 읽게 된 사람을 위한 최소 세트를 정리합니다.
flowchart TB
accTitle: 손이 멈추는 이유와 지도의 크기
accDescr: 전부 대문자인 변수 이름이나 레벨 번호, PIC S9(7)V99 COMP-3 같은 표기, COPY 투성이라 전체가 보이지 않아 손이 멈추지만, 기존 업무 시스템을 읽기 위해 먼저 잡아야 할 골격은 꽤 공통이고 지도는 그리 크지 않음을 나타내는 그림.
iv1["익숙하지 않은 표기 때문에 손이 멈춘다"] --> iv2["다만 골격은 컴파일러를 넘어 공통이다"]
iv2 --> iv3["최소 세트의 지도만 먼저 가진다"]
iv1 -.-> iv4["COPY 투성이라 전체가 보이지 않는다"]
그림1: 주문처럼 보이는 표기의 정체는 몇 개의 공통 개념으로 모아집니다.
1. 먼저 결론(한 마디로)
먼저 꽤 거칠지만, 실무에서 도움이 되는 말로 하면 이렇습니다.
- COBOL은 로직의 언어이기 전에, 상당히 강하게 레코드 정의의 언어입니다
PROCEDURE DIVISION만 읽어서는 절반밖에 파악하지 못합니다. 먼저DATA DIVISION을 봅니다PIC는 항목의 형태,USAGE는 어떤 표현으로 가질지입니다COMP-3는 packed decimal입니다. 금액이나 건수의 세계에서 자주 나옵니다88은 별도 변수라기보다, 직전 항목의 값에 이름을 붙인 조건명입니다REDEFINES는 같은 메모리를 다른 형태로 보는 장치입니다. 복사가 아닙니다COPY가 있다면, 지금 연 소스는 아직 미완성입니다. copybook을 보지 않으면 전체가 보이지 않습니다PERFORM,IF,EVALUATE,READ,WRITE,CALL을 따라가면, 대체적인 흐름은 잡을 수 있습니다- 오래된 소스는 열 위치에 의미가 있는 고정 형식입니다. 겉모습의 공백이 단순한 장식이 아닙니다1
요컨대, DIVISION, PIC, USAGE, COMP-3, REDEFINES, OCCURS, 88, COPY, PERFORM. 이 부근이 읽히면 길을 잃을 확률이 꽤 내려갑니다.
이 글의 지식 맵
COBOL은 PROCEDURE DIVISION에 의한 처리 절차의 언어이기 전에 DATA DIVISION에서 레코드의 형태를 정의하는 언어이며, 각 데이터 항목은 PICTURE로 형을, USAGE로 COMP-3 같은 내부 표현을 지정합니다. COMP-3는 10진 숫자를 2자리씩 1바이트에 채워 넣는 packed decimal로, 텍스트로 열면 의미를 알 수 없는 바이트열로 보입니다. REDEFINES는 같은 영역을 다른 형태로 보고, OCCURS는 배열을, 88 레벨은 직전 항목의 값에 이름을 붙인 조건을 나타냅니다. COPY 문은 외부의 copybook을 컴파일 시점에 include하므로, 열려 있는 소스만으로는 전체 모습이 보이지 않으며, PERFORM 문과 scope terminator로 처리의 흐름을 따라가고 FILE STATUS나 EXEC SQL, EXEC CICS로 외부 경계를 파악할 필요가 있습니다.
flowchart LR
accTitle: COBOL 읽는 법
accDescr: COBOL이 DATA DIVISION과 PROCEDURE DIVISION을 기둥으로 하고, PICTURE·USAGE·COMP-3·REDEFINES·OCCURS·88 레벨·COPY 문 같은 데이터 정의 요소와, PERFORM 문이나 scope terminator 같은 제어 요소, EBCDIC이나 FILE STATUS·EXEC SQL·EXEC CICS 같은 외부 경계의 요소가 어떻게 관계되는지를 보여주는 그림
cobol["COBOL"]
cobol_data_division["DATA DIVISION"]
comp_3["COMP-3(packed decimal)"]
cobol_procedure_division["PROCEDURE DIVISION"]
cobol_picture_clause["PICTURE 절(PIC)"]
cobol_usage_clause["USAGE 절"]
comp_5["COMP-5"]
cobol_redefines["REDEFINES 절"]
cobol_occurs["OCCURS 절"]
cobol_occurs_depending_on["OCCURS DEPENDING ON"]
cobol_88_level["88레벨(조건명)"]
cobol_copy["COPY문"]
copybook["copybook"]
cobol_perform["PERFORM 문"]
cobol_scope_terminator["범위 종료자(scope terminator)"]
cobol_move["MOVE 문"]
cobol_fixed_format["고정 형식(참조 형식)"]
ibm_enterprise_cobol["IBM Enterprise COBOL"]
ebcdic["EBCDIC"]
cobol_file_status["FILE STATUS절"]
exec_sql["EXEC SQL"]
exec_cics["EXEC CICS"]
cobol -->|"이용한다"| cobol_data_division
cobol -->|"이용한다"| cobol_procedure_division
cobol_data_division -->|"이용한다"| cobol_picture_clause
cobol_data_division -->|"이용한다"| cobol_usage_clause
cobol_usage_clause -->|"이용한다"| comp_3
cobol_usage_clause -->|"이용한다"| comp_5
cobol_data_division -->|"이용한다"| cobol_redefines
cobol_data_division -->|"이용한다"| cobol_occurs
cobol_occurs_depending_on -->|"이용한다"| cobol_occurs
cobol_data_division -->|"이용한다"| cobol_88_level
cobol -.->|"이용한다"| cobol_copy
cobol_copy -->|"이용한다"| copybook
cobol_procedure_division -->|"이용한다"| cobol_perform
cobol_procedure_division -->|"이용한다"| cobol_scope_terminator
cobol_procedure_division -->|"이용한다"| cobol_move
cobol -.->|"이용한다"| cobol_fixed_format
ibm_enterprise_cobol -->|"이용한다"| ebcdic
ibm_enterprise_cobol -.->|"이용한다"| cobol_fixed_format
cobol -.->|"이용한다"| cobol_file_status
cobol -.->|"이용한다"| exec_sql
cobol -.->|"이용한다"| exec_cics
그림의 실선은 항상 성립하는 관계, 점선은 조건이 붙는 관계입니다(성립 조건은 상세 페이지의 관계별 설명에 적혀 있습니다). 관계 전체 목록(총 21건, 근거와 확신도 포함)과 주요 개념의 정의는 지식 맵 상세 페이지에 정리되어 있습니다(일본어). 데이터: JSON-LD / Turtle
2. COBOL은 먼저 「데이터의 형태」의 언어라고 본다
C#이나 Java의 감각으로 읽으면, 처음에는 if나 for나 함수 호출을 따라가고 싶어집니다.
하지만 COBOL은 그쪽으로 가기 전에 「이 프로그램은 어떤 레코드를 받고, 어떤 레코드를 만들며, 어떤 버퍼를 가지고 있는가」를 잡는 편이 빠릅니다.
전형적인 업무 COBOL은 대체로 다음 흐름입니다.
- 파일이나 DB에서 레코드를 읽는다
WORKING-STORAGE상의 항목으로 넣는다- 조건 분기한다
- 다른 레코드로 다시 채운다
- 써 낸다
즉, 알고리즘보다 레이아웃이 먼저 서기 쉽습니다.
flowchart TB
accTitle: 전형적인 업무 COBOL의 흐름
accDescr: 파일이나 DB에서 레코드를 읽고, WORKING-STORAGE 상의 항목으로 넣고, 조건 분기하고, 다른 레코드로 다시 채워 써 낸다는, 전형적인 업무 COBOL의 흐름을 나타내는 그림.
f1["레코드를 읽는다"] --> f2["WORKING-STORAGE로 넣는다"]
f2 --> f3["조건 분기한다"]
f3 --> f4["다른 레코드로 다시 채운다"]
f4 --> f5["써 낸다"]
그림2: 주인공은 레코드의 흐름이고, 알고리즘은 그 사이에 끼어듭니다.
예를 들어 이런 골격입니다.
IDENTIFICATION DIVISION.
PROGRAM-ID. SAMPLE01.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT SALES-FILE ASSIGN TO ...
DATA DIVISION.
FILE SECTION.
FD SALES-FILE.
01 SALES-REC.
05 SALE-ID PIC 9(8).
05 SALE-AMOUNT PIC S9(7)V99 COMP-3.
WORKING-STORAGE SECTION.
01 WS-EOF PIC X VALUE 'N'.
88 EOF VALUE 'Y'.
PROCEDURE DIVISION.
PERFORM UNTIL EOF
READ SALES-FILE
AT END
SET EOF TO TRUE
NOT AT END
PERFORM PROCESS-SALE
END-READ
END-PERFORM
STOP RUN.
이 코드를 읽을 때, 먼저 봐야 할 것은 PERFORM보다 앞선 SALE-AMOUNT의 형이나 EOF의 의미입니다.
COBOL은 그 순서로 읽으면 갑자기 조용해집니다.
3. 4개의 DIVISION을 먼저 본다
COBOL 소스는 먼저 크게 4개의 DIVISION으로 나뉩니다.
| DIVISION | 먼저 볼 것 |
|---|---|
IDENTIFICATION DIVISION |
프로그램 이름, 오래된 주석, 유래 |
ENVIRONMENT DIVISION |
파일, 외부 자원, 입출력의 전제 |
DATA DIVISION |
레코드 정의, 작업 영역, 인수 |
PROCEDURE DIVISION |
실제 처리 절차 |
특히 중요한 것은 이 부근입니다.
FILE SECTION입출력 파일의 레코드 정의가 있다WORKING-STORAGE SECTION평소 쓰는 변수, 플래그, 카운터, 작업 버퍼가 있다LOCAL-STORAGE SECTION호출마다 초기화되는 영역이 있는 경우가 있다LINKAGE SECTION밖에서 넘겨받는 인수나, 서브프로그램의 입구가 있는 경우가 있다
LINKAGE SECTION과 PROCEDURE DIVISION USING ...이 보이면, 그 프로그램은 단독으로 완결되지 않고, 밖에서 데이터를 받아 동작할 가능성이 높습니다.
flowchart TB
accTitle: LINKAGE SECTION이 가리키는 것
accDescr: LINKAGE SECTION과 PROCEDURE DIVISION USING이 보이면, 그 프로그램은 단독으로 완결되지 않고 밖에서 데이터를 받아 동작할 가능성이 높음을 나타내는 그림.
lk1["LINKAGE SECTION이 있다"] --> lk3["밖에서 데이터를 받아 동작할 가능성"]
lk2["PROCEDURE DIVISION USING이 있다"] --> lk3
lk3 -.-> lk4["단독 완결 프로그램이 아니다"]
그림3: 입구 정의가 보이면, 호출 측이 있다는 전제로 읽습니다.
4. 고정 형식의 겉모습에 겁먹지 않는다
오래된 COBOL에서는 소스 1행의 열 위치 그 자체에 의미가 있습니다. 이를 모른 채로 보면, 「왜 왼쪽에 이상한 여백이 있는지」를 영원히 알 수 없습니다. 1
고정 형식에서는 대략 이렇습니다.
- 1 - 6열: 일련번호
- 7열: indicator
- 8 - 11열: Area A
- 12 - 72열: Area B
7열은 특히 중요합니다.
*또는/: 주석 행-: 연속 행D: debugging line*>: 중간에도 쓸 수 있는 주석
열과 내용의 대응을 눈금과 함께 보면 이렇습니다. 1행이 10의 자리, 2행이 1의 자리 눈금입니다.
1 2 3 4 5 6 7 8
12345678901234567890123456789012345678901234567890123456789012345678901234567890
SSSSSSIAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB........
S= 일련번호 (1 - 6열)I= indicator (7열)A= Area A (8 - 11열)B= Area B (12 - 72열).= 73열 이후. 컴파일러에 따라서는 식별란으로 쓰이지만, 프로그램의 의미에는 관여하지 않습니다
실제 소스에 대입하면 이렇습니다.
000100* 이 행은 7열이 *이므로 주석
000200 IDENTIFICATION DIVISION.
000300 PROGRAM-ID. SAMPLE01.
000400 DATA DIVISION.
000500 WORKING-STORAGE SECTION.
000600 01 WS-ORDER.
000700 05 WS-ORDER-ID PIC 9(8).
000800 05 WS-LONG-NAME PIC X(30) VALUE 'ABCDEFGHIJKLMNOPQRST
000900- 'UVWXYZ0123'.
읽는 포인트는 네 가지입니다.
- 1 - 6열은 일련번호입니다. 위 예에서
000100처럼 붙어 있는 것이 그것이며, 프로그램 동작과는 관계가 없습니다. 공백인 경우도 있습니다. - 7열이 indicator입니다.
*이면 주석,-이면 연속 행. 위 예의000900행이 그것이며, 앞 행 문자열 리터럴의 이어짐입니다. - 8 - 11열이 Area A입니다.
DIVISION,SECTION, 단락 이름,FD, 그리고01과77의 레벨 번호는 여기서 시작합니다. 위 예의IDENTIFICATION DIVISION.과01 WS-ORDER.가 Area A 시작입니다. - 12 - 72열이 Area B입니다. 보통의 문과
05같은 하위 레벨은 여기에 씁니다. 위 예의05 WS-ORDER-ID가 Area B 시작입니다.
여기의 공백은 현대적 의미의 「정렬」이 아니라, 부분적으로 구문입니다. 에디터에서 탭 변환을 하거나, 왼쪽으로 붙이거나, 대충 복사해 붙이면 그대로 깨집니다. 오래된 소스를 볼 때는 먼저 그 파일이 fixed format인지 free format인지를 의심하십시오. fixed 형식 소스에 현대적인 자동 정렬을 걸면 Area A와 Area B의 경계가 무너져 컴파일이 통과하지 않습니다.
flowchart TB
accTitle: 고정 형식 소스를 만지기 전에
accDescr: 오래된 소스에서는 먼저 fixed format인지 free format인지를 확인하고, fixed 형식 소스에 현대적인 자동 정렬이나 탭 변환을 걸면 Area A와 Area B의 경계가 무너져 컴파일이 통과하지 않음을 나타내는 그림.
fx1["오래된 소스를 열었다"] --> fx2{"fixed인가 free인가"}
fx2 -->|"fixed 형식"| fx3["열 위치 그 자체가 구문"]
fx3 -.-> fx4["자동 정렬이나 탭 변환으로 깨진다"]
fx2 -->|"free 형식"| fx5["열 제약은 느슨하다"]
그림4: 정렬보다 먼저, 그 파일의 형식을 확정합니다.
5. DATA DIVISION의 최소한
5.1 레벨 번호
COBOL의 데이터 정의는 들여쓰기가 아니라 레벨 번호로 계층을 만듭니다. 2
01 WS-ORDER.
05 WS-ORDER-ID PIC 9(8).
05 WS-AMOUNT PIC S9(7)V99 COMP-3.
05 WS-STATUS PIC X.
88 WS-OK VALUE '0'.
88 WS-ERROR VALUE '9'.
77 WS-COUNT PIC 9(4).
최소한 이것만 기억해 두면 충분합니다.
01: 한 묶음의 최상위 레코드, 그룹02-49: 그 아래 계층77: 독립된 단일 항목88: condition-name. 직전 항목의 값에 이름을 붙인다366:RENAMES용. 만날 확률은 높지 않지만 존재는 한다
중요한 것은 88을 bool의 별도 변수라고 생각하지 않는 것입니다.
WS-OK라는 영역이 따로 있는 것이 아니라, WS-STATUS가 '0'일 때 WS-OK라는 이름으로 읽을 수 있다는 느낌입니다.
flowchart TB
accTitle: 88 레벨의 정체
accDescr: 88 레벨은 독립된 bool 변수가 아니라 직전 항목의 값에 이름을 붙인 조건명이며, WS-STATUS가 특정 값일 때 WS-OK라는 이름으로 읽을 수 있을 뿐임을 나타내는 그림.
cn1["기저 항목 WS-STATUS"] --> cn2{"값은 무엇인가"}
cn2 -->|"0일 때"| cn3["WS-OK라는 이름으로 참이 된다"]
cn2 -->|"9일 때"| cn4["WS-ERROR라는 이름으로 참이 된다"]
cn3 -.-> cn5["다른 영역은 존재하지 않는다"]
그림5: 88은 변수가 아니라, 값에 붙인 읽기 쉬운 별명입니다.
또 하나 중요한 것은, 계층을 정하는 것은 공백이 아니라 레벨 번호라는 점입니다.
겉모습의 들여쓰기는 참고가 되지만, 최종적으로 믿어야 할 것은 01 / 05 / 10 / 88 쪽입니다. 2
5.2 PICTURE
PIC는 그 항목의 형태를 나타냅니다.
가장 자주 보는 것은 이 부근입니다.
| 표기 | 대략의 의미 |
|---|---|
X |
문자 |
9 |
숫자 |
S |
부호 있음 |
V |
소수점은 논리상으로만 가진다 |
X(10) |
10문자 |
9(5) |
5자리 수치 |
S9(7)V99 |
부호 있음, 정수 7자리 + 소수 2자리 |
예를 들어,
PIC X(10)→ 10문자PIC 9(5)V99→ 5자리 정수 + 2자리 소수PIC S9(7)V99→ 부호 있는 7자리 정수 + 2자리 소수
입니다.
여기서 특히 중요한 것은 V입니다.
V는 실제 . 문자를 갖지 않습니다.
PIC 9(5)V99는 「소수점 2자리의 수」로 다루어지지만, 데이터에 점 문자가 들어 있는 것은 아닙니다.
그래서 파일이나 덤프를 「겉모습의 문자열」로 해석하면 대개 실패합니다.
flowchart TB
accTitle: V는 논리상의 소수점
accDescr: PIC 9(5)V99의 V는 논리상의 소수점이며 실제 점 문자를 갖지 않고, 데이터에 점이 들어 있는 것은 아니므로 파일이나 덤프를 겉모습의 문자열로 해석하면 실패한다는 것을 나타내는 그림.
vd1["PIC 9(5)V99"] --> vd2["소수점 2자리의 수로 다룬다"]
vd2 --> vd3["데이터에 점 문자는 들어가지 않는다"]
vd3 -.-> vd4["겉모습의 문자열로 해석하면 실패한다"]
그림6: 소수점은 정의 안에만 있고, 데이터 안에는 없습니다.
5.3 USAGE / DISPLAY / COMP / COMP-3
PIC가 형태라면, USAGE는 어떤 표현으로 유지할지입니다.
최소한 다음만 잡으면 꽤 읽을 수 있습니다. 45
| 표기 | 대략의 의미 | 읽을 때의 주의 |
|---|---|---|
DISPLAY |
문자로 보이는 외부 십진 | mainframe에서는 EBCDIC 전제인 경우가 있다6 |
COMP / BINARY |
2진수 | 겉모습의 자리 수와 내부 표현은 별개 |
COMP-3 / PACKED-DECIMAL |
packed decimal | 문자로 읽으면 깨져 보인다 |
예를 들어,
01 WS-AMOUNT-DISP PIC S9(7)V99.
01 WS-AMOUNT-BIN PIC S9(7) COMP.
01 WS-AMOUNT-PACK PIC S9(7)V99 COMP-3.
이 셋은 전부 「수치」이지만, 안의 가지는 방식이 다릅니다.
flowchart TB
accTitle: 같은 수치라도 가지는 방식이 다르다
accDescr: 같은 부호 있는 수치라도 DISPLAY는 문자로 보이는 외부 십진, COMP는 2진수, COMP-3는 packed decimal로, USAGE에 따라 안의 가지는 방식이 다름을 나타내는 그림.
us1["같은 형태의 수치 항목"] --> us2["DISPLAY〔문자로 보인다〕"]
us1 --> us3["COMP〔2진수〕"]
us1 --> us4["COMP-3〔packed decimal〕"]
us4 -.-> us5["문자로 읽으면 깨져 보인다"]
그림7: PIC가 같아도 USAGE가 바뀌면 바이트 열은 다른 것이 됩니다.
실무에서 가장 도움이 되는 것은 COMP-3를 본 순간의 반응입니다.
- 그것은 packed decimal
- 아마 금액, 세액, 건수, 레이트 계열
- 텍스트로 보면 깨져 보이는 것이 정상
- CSV나 UTF-8 감각으로 보면 사고가 난다
는 이해를 갖고 있으면, 덤프나 바이너리 파일의 보이는 방식 때문에 쓸데없이 당황하지 않아도 됩니다.
flowchart TB
accTitle: COMP-3를 본 순간의 반응
accDescr: COMP-3를 보면 packed decimal이며 아마도 금액이나 세액이나 건수나 레이트 계열 항목이고, 텍스트로 보면 깨져 보이는 것이 정상이므로 CSV나 UTF-8 감각으로 보지 않는다는 반응을 몸에 익히는 것을 나타내는 그림.
cp1["COMP-3를 찾았다"] --> cp2["packed decimal임을 안다"]
cp2 --> cp3["금액이나 건수 항목을 의심한다"]
cp3 --> cp4["텍스트로 깨져 보여도 당황하지 않는다"]
그림8: 이 반사만으로, 덤프 앞에서 당황하는 횟수가 줄어듭니다.
COMP-3가 실제로 어떤 바이트 열이 되는지
여기는 한 번만 손으로 따라가면, 이후의 보이는 방식이 바뀝니다.
packed decimal의 규칙은 두 가지뿐입니다. 45
- 1바이트에 십진 숫자를 2자리씩 넣는다
- 다만 가장 오른쪽 바이트만, 하위 1자리와 부호로 1바이트를 쓴다
부호는 4비트 값으로 나타나며, C가 양, D가 음, F가 부호 없음입니다.
IBM 매뉴얼에 실려 있는 예로 확인합니다. 4
| 정의 | 값 | 바이트 열 |
|---|---|---|
PIC S9(4) PACKED-DECIMAL |
+1234 |
01 23 4C |
PIC S9(4) PACKED-DECIMAL |
-1234 |
01 23 4D |
PIC 9(4) PACKED-DECIMAL |
1234 |
01 23 4F |
1234는 4자리이므로, 규칙 2 때문에 1자리만큼의 빈 칸이 앞에 생깁니다. 그것이 앞의 0입니다.
같은 요령으로, 이 글에 여러 번 나온 PIC S9(7)V99 COMP-3를 따라가 봅니다.
S9(7)V99는 정수 7자리 + 소수 2자리 = 9자리V는 소수점 위치만 나타내므로, 바이트를 하나도 소비하지 않습니다- 9자리를 2자리씩 넣으면 4바이트, 나머지 1자리와 부호로 1바이트. 합계 5바이트
값이 +12345.67이면, 9자리에 맞추면 001234567이므로 이렇게 됩니다.
값 : +12345.67
9자리 표현 : 0 0 1 2 3 4 5 6 7 과 부호
바이트 : 00 12 34 56 7C
^ 부호 C = 양
음수 -12345.67이면, 마지막이 7D가 될 뿐입니다.
바이트 : 00 12 34 56 7D
이것을 텍스트로 열면 어떻게 보이는가가 본론입니다. 00 12 34 56 7C를 억지로 1바이트 1문자로 ASCII에 대응시키면,
00은 NUL,12는 제어 문자라 애초에 문자로 표시할 수 없습니다34는4,56은V,7C는|
가 됩니다. 즉 화면상은 「표시할 수 없는 문자가 몇 개 이어진 뒤에 4V|」처럼 보입니다. 12345.67이라는 나열은 어디에도 나타나지 않습니다.
이것이 「문자처럼 깨져 보이지만 깨진 것이 아닌」 정체입니다. 덤프를 열어 의미 없는 나열을 만나면, 먼저 그 항목의 USAGE가 COMP-3가 아닌지를 의심하십시오.
flowchart TB
accTitle: COMP-3 바이트 열이 만들어지는 방식
accDescr: packed decimal에서는 십진 숫자를 1바이트에 2자리씩 넣고 가장 오른쪽 바이트만 하위 1자리와 부호로 쓰므로, 9자리 수치는 5바이트가 되고 그것을 ASCII로 열어도 원래 수치의 나열은 어디에도 나타나지 않음을 나타내는 그림.
pk1["십진 숫자를 2자리씩 1바이트로"] --> pk2["마지막 바이트는 하위 1자리와 부호"]
pk2 --> pk3["9자리면 합계 5바이트"]
pk3 --> pk4["ASCII로 열면 원래 수치는 나타나지 않는다"]
pk4 -.-> pk5["깨진 문자처럼 보이지만 깨지지 않았다"]
그림9: 넣는 방식의 규칙 두 가지를 알면, 의미 없는 덤프가 읽을 수 있는 열로 바뀝니다.
덧붙여, 자리 수에서 바이트 수를 내는 한눈에 보는 표도 둡니다. 바이트 수는 「9의 수를 2로 나누어 소수를 버리고, 1을 더한다」로 구합니다.
| PICTURE의 9의 수 | COMP-3의 바이트 수 |
|---|---|
| 1 | 1 |
| 2 - 3 | 2 |
| 4 - 5 | 3 |
| 6 - 7 | 4 |
| 8 - 9 | 5 |
| 10 - 11 | 6 |
| 12 - 13 | 7 |
같은 바이트 수에 자리 수가 둘 나란히 있는 것은, 9의 수가 짝수일 때만 선두에 빈 1자리가 생기기 때문입니다. S9(4)가 01 23 4C의 3바이트가 되고, S9(5)도 같은 3바이트에 들어가는 것은 그 때문입니다.
외부 파일과의 레이아웃 맞춤에서는, 이 표가 없으면 1바이트씩 어긋나 갑니다.
하나 더 보충하면, DISPLAY라고 해서 반드시 ASCII 문자열인 것은 아닙니다.
z/OS 계열에서는 EBCDIC이 전제가 되므로, 숫자가 문자로 보여도 바이트 값은 ASCII의 '0' - '9'와 다를 수 있습니다. 6
5.4 REDEFINES / OCCURS / COPY / FILLER
이 넷은 읽을 때 막히는 지점입니다.
REDEFINES
REDEFINES는 같은 영역을 다른 형태로 보는 장치입니다. 복사가 아닙니다. 7
01 REC-BUF.
05 REC-TYPE PIC X.
05 REC-DATA PIC X(99).
01 HEADER-REC REDEFINES REC-BUF.
05 HDR-TYPE PIC X.
05 HDR-DATE PIC 9(8).
05 FILLER PIC X(91).
이것은 C 계열에서 말하는 union적인 감각에 가깝습니다.
「하나의 100바이트를 다른 레코드 종류로 구분한다」 같은 쓰기 방식에서 자주 나옵니다.
flowchart TB
accTitle: REDEFINES는 같은 영역의 다른 해석
accDescr: REDEFINES는 복사가 아니라 같은 메모리 영역을 다른 형태로 보는 장치이며, 하나의 버퍼를 범용 레코드로도 헤더 레코드로도 읽을 수 있고, 한쪽을 쓰면 다른 쪽의 보이는 방식도 바뀜을 나타내는 그림.
rd1["하나의 메모리 영역"] --> rd2["REC-BUF로 본다"]
rd1 --> rd3["HEADER-REC로 본다"]
rd2 -.-> rd4["한쪽을 쓰면 양쪽의 보이는 방식이 바뀐다"]
rd3 -.-> rd4
그림10: 정의가 둘이어도, 실체의 바이트 열은 하나뿐입니다.
OCCURS
OCCURS는 배열입니다. COBOL에서는 table이라고 부르는 경우가 많습니다.
05 WS-ITEM OCCURS 12 TIMES.
10 WS-PRICE PIC 9(5).
여기에 OCCURS DEPENDING ON이 나오면 가변 길이 테이블입니다.
이 경우 후속 항목의 위치까지 영향을 받을 수 있으므로, 고정 길이 감각으로 따라가면 발을 헛디딥니다. 8
flowchart TB
accTitle: OCCURS DEPENDING ON의 주의
accDescr: OCCURS는 배열이며, OCCURS DEPENDING ON이 붙으면 가변 길이 테이블이 되어 후속 항목의 위치까지 값에 따라 움직일 수 있으므로, 고정 길이 감각으로 오프셋을 따라가면 헛디딘다는 것을 나타내는 그림.
oc1["OCCURS를 찾았다"] --> oc2{"DEPENDING ON이 붙어 있는가"}
oc2 -->|"붙어 있지 않다"| oc3["고정 횟수의 테이블"]
oc2 -->|"붙어 있다"| oc4["가변 길이 테이블"]
oc4 -.-> oc5["후속 항목의 위치도 움직일 수 있다"]
그림11: DEPENDING ON 세 단어로, 오프셋 계산의 전제가 바뀝니다.
COPY
COPY는 compile time의 include입니다.
즉, 지금 연 소스는 아직 완성형이 아닐 수 있습니다. 9
COPY CUSTOMER-REC.
COPY ERROR-MAP.
레코드 정의, 공통 플래그, SQL용 host variable, 외부 인터페이스가 copybook에 들어가 있는 것은 꽤 흔합니다.
COPY가 많아서 읽기 어려울 때는 전개 후 소스나 compiler listing을 볼 수 없는지를 확인하는 편이 빠릅니다. IBM Enterprise COBOL에는 MDECK라는, 라이브러리 처리 후 입력 소스를 출력하기 위한 option도 있습니다. 10
flowchart TB
accTitle: COPY가 있는 소스의 읽는 법
accDescr: COPY는 컴파일 시의 include이며 연 소스는 완성형이 아닐 수 있으므로, copybook을 확인하거나 읽기 어려울 때는 전개 후 소스나 compiler listing, MDECK 옵션의 출력을 찾는 것을 나타내는 그림.
cy1["COPY를 찾았다"] --> cy2["연 소스는 미완성일 수 있다"]
cy2 --> cy3["copybook을 열어 확인"]
cy2 -.->|"읽기 어려울 때"| cy4["전개 후 소스나 listing을 찾는다"]
그림12: 지금 보이는 행 수가 그 프로그램의 전부는 아닐 수 있습니다.
FILLER
FILLER는 이름 없는 항목입니다.
다만 「참조하지 않으니 무의미」한 것은 아닙니다.
- 예약 영역
- 구 사양과의 호환용 구멍
- 레코드 길이 맞춤
REDEFINES용 여백
으로서 실제로 역할을 합니다.
FILLER는 이름이 없을 뿐, 바이트 수로서는 존재합니다. 이것을 잊으면 외부 파일과의 매핑에서 1바이트씩 세계가 어긋나 갑니다.
flowchart TB
accTitle: FILLER를 세는 것을 잊으면 일어나는 일
accDescr: FILLER는 이름이 없을 뿐 바이트 수로서는 존재하고, 예약 영역이나 레코드 길이 맞춤으로 효력이 있으므로, 세는 것을 잊으면 외부 파일과의 매핑이 1바이트씩 어긋난다는 것을 나타내는 그림.
fl1["FILLER는 이름 없는 항목"] --> fl2["바이트 수로서는 존재한다"]
fl2 --> fl3{"레이아웃 계산에 넣었는가"}
fl3 -->|"넣었다"| fl4["외부 파일과 일치한다"]
fl3 -->|"잊었다"| fl5["1바이트씩 어긋나 간다"]
그림13: 참조되지 않는 항목에도, 길이라는 역할이 있습니다.
6. PROCEDURE DIVISION의 최소한
DATA DIVISION이 지도라면, PROCEDURE DIVISION은 이동 경로입니다.
6.1 PERFORM
PERFORM은 COBOL의 기본적인 제어 이동입니다.
거칠게 말하면 처리를 호출하고 돌아온다입니다. 11
자주 보는 형태는 다음입니다.
PERFORM INIT-PROC
PERFORM UNTIL EOF
PERFORM READ-PROC
IF NOT EOF
PERFORM EDIT-PROC
PERFORM WRITE-PROC
END-IF
END-PERFORM
PERFORM에는 크게 두 계통이 있습니다.
- 단락이나 절을 지정하는 out-of-line
PERFORM - 그 자리에 블록을 쓰는 inline
PERFORM ... END-PERFORM
나아가 오래된 코드에서는 PERFORM A-100 THRU A-199처럼 범위 지정도 평범하게 나옵니다.
이것은 편리하지만, 단락을 중간에 넣으면 범위에 의도치 않게 포함되는 사고가 나기 쉬우므로, 읽을 때는 범위의 끝을 제대로 봅니다.
flowchart TB
accTitle: PERFORM의 세 가지 형태
accDescr: PERFORM에는 단락이나 절을 지정해 호출하고 돌아오는 out-of-line과 그 자리에 블록을 쓰는 inline이 있고, 오래된 코드에는 THRU에 의한 범위 지정도 있어 범위 지정은 단락을 중간에 넣으면 범위에 의도치 않게 포함되는 사고가 나기 쉬움을 나타내는 그림.
pf1["PERFORM을 찾았다"] --> pf2["단락을 호출하는 out-of-line"]
pf1 --> pf3["그 자리에 쓰는 inline"]
pf1 --> pf4["THRU의 범위 지정"]
pf4 -.-> pf5["범위의 끝을 반드시 확인한다"]
그림14: 어떤 형태의 PERFORM인지에 따라, 따라가야 할 복귀처와 범위가 바뀝니다.
6.2 IF / EVALUATE / 스코프
조건 분기는 IF가 기본입니다.
EVALUATE는 switch/case적인 것이라고 생각하면 대체로 맞습니다.
주의해야 할 것은 스코프가 끝나는 방식입니다. 12
END-IFEND-PERFORMEND-READ
같은 명시적 종결이 있는 코드는 그래도 읽기 쉽습니다.
문제는 오래된 코드입니다. COBOL에서는 .이 암시적 scope terminator로 동작하며, 아직 닫히지 않은 문을 한꺼번에 끝냅니다. 12
즉, 마침표 하나면,
- 어디까지가
IF인지 - 어디까지가
PERFORM인지 - 어디서 다음 sentence로 넘어가는지
가 바뀝니다.
나아가 NEXT SENTENCE는 CONTINUE와 같지 않습니다.
NEXT SENTENCE는 다음 마침표 뒤로 진행하므로, 후속 .의 위치에 따라 점프처가 바뀝니다. 12
오래된 COBOL을 읽을 때는 행 끝이 아니라 마침표를 본다 정도로 딱 맞습니다.
flowchart TB
accTitle: 마침표의 무게
accDescr: END-IF 같은 명시적 종결이 있는 코드는 읽기 쉽지만, 오래된 코드에서는 마침표가 암시적 scope terminator로 동작해 아직 닫히지 않은 문을 한꺼번에 끝내므로, 마침표 하나로 IF나 PERFORM의 범위가 바뀜을 나타내는 그림.
sc1{"명시적 종결은 있는가"}
sc1 -->|"END-IF 등이 있다"| sc2["범위를 읽기 쉽다"]
sc1 -->|"없는 오래된 코드"| sc3["마침표가 암시적 종결"]
sc3 --> sc4["하나 위치로 범위가 바뀐다"]
sc4 -.-> sc5["행 끝이 아니라 마침표를 본다"]
그림15: 오래된 코드의 제어 흐름은 마침표 위치가 쥐고 있습니다.
6.3 READ / WRITE / CALL
업무 COBOL에서 자주 나오는 것은 이 부근입니다.
READWRITEREWRITESTARTCALL
특히 READ ... AT END ...는 왕도입니다.
READ IN-FILE
AT END
SET EOF TO TRUE
NOT AT END
PERFORM PROCESS-REC
END-READ
CALL 'SUBPGM' USING ...이 있으면 다른 프로그램으로 점프합니다.
그때는 호출되는 쪽의 LINKAGE SECTION과 PROCEDURE DIVISION USING을 보면, 주고받는 형태가 꽤 보입니다.
flowchart TB
accTitle: CALL을 찾았을 때의 따라가는 법
accDescr: CALL이 있으면 다른 프로그램으로 점프하므로, 호출되는 쪽의 LINKAGE SECTION과 PROCEDURE DIVISION USING을 보면 인수의 주고받는 형태가 보임을 나타내는 그림.
ca1["CALL을 찾았다"] --> ca2["다른 프로그램으로 점프한다"]
ca2 --> ca3["호출되는 쪽의 LINKAGE SECTION을 본다"]
ca3 --> ca4["USING으로 주고받는 형태를 잡는다"]
그림16: 호출의 의미는, 호출되는 쪽의 입구와 세트로 읽습니다.
7. COBOL의 바깥에 있는 것
COBOL은 소스만으로 세계가 완결되지 않는 경우가 꽤 있습니다.
- 파일 정의
- 실행 환경
- DB 연결
- 트랜잭션 환경
- job 제어
가 바깥에 나뉘어 있기 때문입니다.
최소한 다음은 잡으면 읽기 쉬워집니다.
파일과 FILE STATUS
ENVIRONMENT DIVISION의 FILE-CONTROL과, DATA DIVISION의 FILE SECTION / FD는 세트로 읽습니다. 13
SELECT IN-FILE ASSIGN TO ...
FILE STATUS IS WS-FS.
FD IN-FILE.
01 IN-REC.
05 ...
FILE STATUS가 있으면 각 I/O 후의 결과 코드가 들어갑니다.
파일 계열의 장애나 EOF 판정을 읽을 때는, 이것을 보지 않으면 시작되지 않습니다. 14
flowchart TB
accTitle: 파일 정의는 세트로 읽는다
accDescr: ENVIRONMENT DIVISION의 FILE-CONTROL에 있는 SELECT와 DATA DIVISION의 FD는 세트로 읽고, FILE STATUS가 있으면 각 입출력 후 결과 코드가 들어가므로, 파일 계열의 장애나 EOF 판정은 여기서부터 읽는다는 것을 나타내는 그림.
fs1["FILE-CONTROL의 SELECT"] --> fs3["세트로 하나의 파일 정의"]
fs2["FILE SECTION의 FD"] --> fs3
fs3 --> fs4["FILE STATUS에 결과 코드가 들어간다"]
fs4 -.-> fs5["장애와 EOF 판정 독해의 출발점"]
그림17: 파일의 모습은 두 DIVISION에 나뉘어 쓰여 있습니다.
EXEC SQL
이것이 나오면 임베디드 SQL입니다.
EXEC SQL
SELECT ...
END-EXEC.
이 경우 COBOL은 「호스트 변수의 그릇」이고, 실제 취득 조건이나 갱신 대상은 SQL 쪽에 있습니다.
그래서 EXEC SQL의 내용을 보통 SQL로 읽는 것이 지름길입니다.
EXEC CICS
이것이 나오면 CICS의 트랜잭션 맥락입니다. 15
EXEC CICS
RECEIVE MAP(...)
END-EXEC.
이 순간, 단순한 배치 독해가 아니게 됩니다. 화면, 트랜잭션, 응답 코드, COMMAREA 등, 외부 맥락을 포함해 읽을 필요가 있습니다.
JCL이나 실행 정의
mainframe batch에서는 실제로 어떤 데이터셋이 할당되는지나 어떤 순서로 job이 흐르는지가 COBOL 소스 바깥에 있는 것도 드물지 않습니다. 소스만 보고 「이 파일은 어디에 있는지」를 모를 때는, 코드가 나쁜 것이 아니라 보고 있는 범위가 아직 부족한 것뿐인 경우가 흔합니다.
flowchart TB
accTitle: 소스 바깥에 있는 세계
accDescr: EXEC SQL이 나오면 실제 취득 조건은 SQL 쪽에, EXEC CICS가 나오면 화면이나 트랜잭션 등의 외부 맥락에, mainframe batch에서는 데이터셋 할당이나 job의 흐름이 JCL에, COBOL 소스 바깥에 세계가 나뉘어 있음을 나타내는 그림.
ex1["EXEC SQL"] --> ex7["소스 바깥도 본다"]
ex3["EXEC CICS"] --> ex7
ex5["JCL이나 실행 정의"] --> ex7
ex7 -.-> ex2["SQL의 조건·화면 맥락·job의 흐름"]
그림18: 모르는 것은 코드 탓이 아니라, 보고 있는 범위가 좁을 뿐인 경우가 있습니다.
컴파일러의 차이
이 글은 IBM 계열을 염두에 두고 쓰였지만, 실무에서는 Micro Focus나 Linux / Windows 위의 COBOL을 만나는 일도 있습니다. 골격은 공통이므로 읽는 법은 바뀌지 않지만, 「같은 쓰기 방식인데 결과가 다른」 포인트는 정해져 있으므로, 먼저 파악해 두면 사고가 나지 않습니다.
| 볼 곳 | z/OS 계열(IBM Enterprise COBOL) | 오픈 계열(Micro Focus, Linux / Windows 판 등) |
|---|---|---|
| 문자 코드 | EBCDIC이 전제6 | ASCII가 전제6 |
| 참조 형식 | fixed 형식이 전통적인 기본값. free 형식도 고를 수 있다1 | fixed와 free를 모두 가지며, 기본값이 어느 쪽인지는 빌드 설정 나름1 |
| copybook을 찾는 법 | 라이브러리 지정 | 컴파일러 옵션으로 검색 경로 지정 |
| 방언 | — | 「어느 컴파일러에 맞출지」를 바꾸는 옵션이 있다 |
특히 바로 효과가 있는 것이 문자 코드입니다. 같은 PIC X(10)이라도, z/OS에서 쓴 파일을 그대로 Windows 쪽에서 읽으면 숫자조차 다른 바이트 값이 됩니다. 「옮겨 왔더니 전부 깨졌다」의 상당수는 이것이며, COMP-3 이야기와는 다른 문제입니다.
하나 더, 수치의 내부 표현에도 차이가 나기 쉬운 곳이 있습니다. IBM Enterprise COBOL에서는 BINARY / COMP-4는 PICTURE에 쓴 자리 수에서 잘림이 일어나는 반면, COMP-5는 2 / 4 / 8바이트라는 네이티브 2진수의 용량까지 값을 가지며, 잘림도 바이너리 크기 쪽에서 일어납니다. 16 즉 PIC S9(4) COMP와 PIC S9(4) COMP-5는 같아 보여도 들어가는 값의 상한이 다릅니다. 다른 시스템과 2진수 그대로 값을 주고받는 코드에서 COMP-5가 나오면, 일부러 그렇게 써 있는 것으로 생각하고 읽으십시오.
자기 환경과 대조할 때의 최단 절차는 이렇습니다.
- 빌드 정의(makefile, JCL, 프로젝트 설정)를 먼저 연다. 어느 컴파일러의, 어느 옵션으로 컴파일되고 있는지가 소스보다 먼저 보입니다.
- 참조 형식(fixed / free)을 확정한다. 여기를 틀린 채로 에디터에서 정렬하면 깨집니다.
- 문자 코드를 확정한다. EBCDIC인지 ASCII인지에 따라 덤프 읽는 법이 바뀝니다.
COMP계열 항목에 표시를 붙인다. 컴파일러 차이가 나는 곳은 거의 여기입니다.
flowchart TB
accTitle: 자기 환경과 대조하는 최단 절차
accDescr: 빌드 정의를 먼저 열어 어느 컴파일러와 옵션인지 확인하고, 참조 형식의 fixed인지 free인지를 확정하고, 문자 코드가 EBCDIC인지 ASCII인지를 확정하고, 컴파일러 차이가 나기 쉬운 COMP 계열 항목에 표시를 붙인다는 절차를 나타내는 그림.
ck1["빌드 정의를 먼저 연다"] --> ck2["참조 형식을 확정한다"]
ck2 --> ck3["문자 코드를 확정한다"]
ck3 --> ck4["COMP 계열 항목에 표시를 붙인다"]
그림19: 소스를 읽기 전의 네 단계로, 컴파일러 차이의 사고를 막을 수 있습니다.
8. 최소한의 읽기 순서
갑자기 COBOL을 읽게 되었을 때는, 다음 순서가 안전합니다.
COPY를 전부 훑는다 copybook을 열 수 있으면 연다. 안 되면 listing이나 전개 후 소스를 찾는다01레벨의 레코드 정의를 집어낸다FILE SECTION,WORKING-STORAGE,LINKAGE SECTION의 최상위를 목록화한다PIC와USAGE를 읽는다 금액, 날짜, 건수, 코드, 플래그를 식별한다READ/WRITE/REWRITE/CALL/EXEC SQL/EXEC CICS를 검색한다 입출력과 외부 경계를 먼저 잡는다- 처음의 주 경로만 따라간다
PROCEDURE DIVISION의 선두부터PERFORM연쇄를 따라간다 88과 status 항목을 본다 EOF, 정상/이상, 종류 코드의 의미가 읽기 쉬워진다REDEFINES/OCCURS DEPENDING ON/COMP-3에 표시를 붙인다 나중에 반드시 쓰이므로, 먼저 위험물로 표시해 둔다- 파일이면
FILE STATUS를 본다 I/O 오류 계열의 오독을 꽤 줄일 수 있다
이 순서면 갑자기 전문을 정독하지 않아도 됩니다. COBOL은 처음부터 100% 이해하려 하기보다, 레코드, 외부 경계, 주 경로의 세 점을 잡은 뒤 세부로 가는 편이 훨씬 편합니다.
flowchart TB
accTitle: 안전한 읽기 순서
accDescr: COPY를 훑어 copybook을 확인하고, 01 레벨의 레코드 정의를 목록화하고, PIC와 USAGE로 항목의 형태를 읽고, 입출력과 외부 경계를 검색으로 잡은 다음, PROCEDURE DIVISION 선두의 PERFORM 연쇄로 주 경로를 따라가며 위험물에 표시를 붙인다는 읽기 순서를 나타내는 그림.
ro1["COPY를 전부 훑는다"] --> ro2["01 레벨을 목록화한다"]
ro2 --> ro3["PIC와 USAGE로 형태를 읽는다"]
ro3 --> ro4["입출력과 외부 경계를 검색한다"]
ro4 --> ro5["주 경로의 PERFORM 연쇄를 따라간다"]
ro5 -.-> ro6["위험물에 표시를 붙여 둔다"]
그림20: 전문 정독이 아니라, 이 순서로 바깥부터 채워 갑니다.
8.1 연습: 이 레코드 정의를 읽어 보기
읽는 법 설명만으로는 자리 잡지 않으므로, 작은 레코드를 하나 둡니다. 먼저 스스로 답을 낸 다음 아래 해답을 보십시오.
01 CUST-REC.
05 CUST-ID PIC X(8).
05 CUST-NAME PIC X(20).
05 CUST-KBN PIC X.
88 CUST-NORMAL VALUE '0'.
88 CUST-VIP VALUE '1'.
05 CUST-BALANCE PIC S9(7)V99 COMP-3.
05 CUST-HIST OCCURS 3 TIMES.
10 HIST-DATE PIC 9(8).
10 HIST-AMOUNT PIC S9(5)V99 COMP-3.
05 FILLER PIC X(4).
문항
CUST-REC는 전체로 몇 바이트입니까.CUST-BALANCE는, 레코드 선두부터 세어 몇 바이트째부터 시작합니까.- 2건째의
HIST-AMOUNT는, 선두부터 몇 바이트째부터 시작합니까. CUST-KBN에'1'이 들어 있을 때, 참이 되는 조건명은 무엇입니까.- 이 파일을 텍스트 에디터로 열었을 때, 깨져 보이는 것은 어느 항목입니까.
- 이 정의만으로는 알 수 없는 것을 두 가지 들어 주십시오.
해답
-
74바이트입니다. 내역은 이렇습니다.
항목 계산 바이트 수 CUST-IDX(8)8 CUST-NAMEX(20)20 CUST-KBNX1 CUST-BALANCE9자리의 COMP-3 5 CUST-HIST(8 + 4)× 3회36 FILLERX(4)4 합계 74 88레벨은 조건명이므로 바이트를 소비하지 않습니다. 여기를 세는 것이 흔한 실수입니다.FILLER는 이름이 없을 뿐, 4바이트는 분명히 존재합니다. -
30바이트째부터입니다. 앞에
8 + 20 + 1 = 29바이트가 있으므로, 그다음부터 시작합니다. -
55바이트째부터입니다.
CUST-HIST가 시작하는 것은 35바이트째이고, 1건이 12바이트이므로, 1건째는 35 - 46바이트, 2건째는 47 - 58바이트. 그중 선두 8바이트가HIST-DATE이므로,HIST-AMOUNT는 55바이트째부터입니다. -
CUST-VIP입니다.CUST-VIP라는 영역이 따로 있는 것이 아니라,CUST-KBN이'1'일 때 그 이름으로 읽을 수 있을 뿐입니다. -
CUST-BALANCE와HIST-AMOUNT입니다. 둘 다COMP-3이므로, 문자로 읽으면 의미 없는 나열이 됩니다.HIST-DATE는PIC 9(8)의DISPLAY이므로, ASCII 환경이면20260317처럼 숫자로 읽을 수 있습니다. 다만 EBCDIC 환경에서는 숫자로 보여도 바이트 값은 ASCII와 다릅니다. -
예를 들어 다음과 같은 것입니다.
- 이 정의 자체가
COPY로 들어와 있는지. copybook 쪽을 보지 않으면, 실제로 쓰이는 판인지 알 수 없습니다. - 파일의 문자 코드가 EBCDIC인지 ASCII인지.
PIC X와PIC 9 DISPLAY의 보이는 방식이 바뀝니다. CUST-KBN에'0'과'1'이외가 들어오는 운용이 있는지. 조건명은 둘만 정의되어 있지만, 그 외의 값이 오지 않는다는 보증은 되지 않습니다.- 파일 자체의 속성(레코드 길이, 가변 길이 여부,
FILE STATUS). 이것은ENVIRONMENT DIVISION과FD를 보지 않으면 알 수 없습니다.
- 이 정의 자체가
문항 1과 3을 틀렸다면, 5.3의 자리 수와 바이트 수 표로 돌아가십시오. COBOL의 오독은 대개 여기서 시작됩니다.
9. 자주 막히는 지점
마지막으로, 초보자가 꽤 높은 확률로 걸리는 곳을 정리합니다.
REDEFINES를 「다른 변수」라고 생각한다
아닙니다. 같은 영역을 다른 형태로 읽고 있습니다. 한쪽을 바꾸면, 다른 쪽의 보이는 방식도 바뀝니다. 7
88을 「독립된 bool」이라고 생각한다
아닙니다.
직전 항목의 값에 이름이 붙어 있을 뿐입니다. SET WS-OK TO TRUE는 실제로는 기저 항목에 대응 값을 넣습니다. 3
COPY를 무시하고 본문만 읽는다
연 파일은 아직 전체의 절반입니다. 필드 정의, 공통 플래그, host variable이 한꺼번에 바깥에 있는 것은 흔합니다. 9
MOVE를 단순 대입이라고 생각한다
MOVE는 단순한 memcpy가 아닙니다.
받는 쪽의 형에 따라 변환, 자리 맞춤, 제로 채움, 잘라 내기, 편집·역편집이 들어갈 수 있습니다. 17
.의 영향을 가볍게 본다
COBOL의 .은 상상보다 무겁습니다.
명시 종결이 없는 오래된 코드에서는, 이 마침표가 어디까지 닫히는지를 잘못 보면 제어 흐름을 오독합니다. 12
packed decimal이나 EBCDIC을 「깨진 문자」라고 생각한다
깨져 있다고 단정할 수는 없습니다. 처음부터 문자열이 아니거나, ASCII가 아닐 뿐인 경우가 꽤 있습니다. 46
OCCURS DEPENDING ON의 뒤를 고정 위치라고 생각한다
가변 길이 테이블의 후속 항목은 값에 따라 위치가 움직일 수 있습니다. 고정 길이라는 전제로 읽으면 오프셋 계산이 전부 어긋납니다. 8
10. 바로 보는 표
| 찾은 말 | 먼저 생각할 것 |
|---|---|
01 |
레코드나 그룹의 최상위. 여기서부터 전체 모습을 잡는다 |
88 |
플래그나 상태 코드의 의미 이름. 분기를 읽는 열쇠 |
PIC X(...) |
문자 항목 |
PIC 9(...) / S9(...)V... |
수치 항목. 자리 수와 소수 위치를 확인 |
COMP |
binary |
COMP-3 |
packed decimal. 금액·건수일 가능성이 높다 |
REDEFINES |
같은 영역을 다른 해석으로 보고 있다 |
OCCURS |
배열·table |
OCCURS DEPENDING ON |
가변 길이. 후속 위치에도 주의 |
FILLER |
이름은 없지만 길이는 있다 |
COPY |
copybook을 보지 않으면 완성형이 보이지 않는다 |
PERFORM |
주 경로의 골격 |
READ / WRITE / REWRITE |
파일 I/O |
EXEC SQL |
DB 처리 |
EXEC CICS |
트랜잭션 처리 |
FILE STATUS |
I/O의 결과 코드 |
11. 정리
COBOL은 오래되어서 어려운 것이 아닙니다. 데이터 정의, 외부 파일, 실행 맥락이 밀접하게 묶여 있어 처음 입구가 보이기 어려울 뿐입니다.
읽기 위한 최소 세트를 다시 정리하면 이렇습니다.
DIVISION으로 지도를 잡는다DATA DIVISION을 먼저 읽는다PIC와USAGE로 항목의 형태를 읽는다COMP-3,REDEFINES,OCCURS,88,COPY에 표시를 붙인다PERFORM,READ,WRITE,CALL을 따라간다FILE STATUS,EXEC SQL,EXEC CICS로 외부 경계를 잡는다.의 효력을 가볍게 보지 않는다
여기가 보이면 COBOL은 「수수께끼의 고대 마법」에서 「레코드 처리의 언어」로 바뀝니다. 레거시 기술은 이름이 오래되어 무서운 것이 아니라, 처음에 보는 축척을 잘못 잡으면 갑자기 알기 어려워질 뿐입니다. 지도의 축척이 맞으면 의외로 평범하게 읽을 수 있습니다.
flowchart TB
accTitle: 축척을 맞춰 읽는다
accDescr: DIVISION으로 지도를 잡고, DATA DIVISION을 먼저 읽어 항목의 형태를 잡고, PERFORM이나 READ 등으로 흐름을 따라가며, FILE STATUS나 EXEC SQL이나 EXEC CICS로 외부 경계를 잡는 순서로 축척을 맞추면 COBOL은 레코드 처리의 언어로서 평범하게 읽을 수 있음을 나타내는 그림.
sm1["DIVISION으로 지도를 잡는다"] --> sm2["DATA DIVISION으로 형태를 읽는다"]
sm2 --> sm3["PERFORM과 입출력으로 흐름을 따라간다"]
sm3 --> sm4["외부 경계를 잡는다"]
sm4 -.-> sm5["수수께끼의 고대 마법이 레코드 처리의 언어가 된다"]
그림21: 어려움의 정체는 언어가 오래되었다는 점이 아니라, 처음 축척을 고르는 방식입니다.
12. 참고 자료
본문의 주요 참조처입니다. 본문의 위첨자 번호가 그대로 이 목록으로의 링크가 되어 있고, 각 항목 끝의 화살표에서 원래 위치로 돌아갈 수 있습니다.
-
IBM, “Reference format” / IBM, “Area A or Area B” / Micro Focus, “Fixed Format” ↩ ↩2 ↩3 ↩4
-
IBM, “Level-numbers” ↩ ↩2
-
IBM, “Format 2: condition-name value” ↩ ↩2
-
IBM, “Examples: numeric data and internal representation” ↩ ↩2 ↩3 ↩4
-
IBM, “PACKED-DECIMAL (COMP-3)” ↩ ↩2
-
IBM, “The EBCDIC character set” / IBM, “Handling differences in ASCII SBCS and EBCDIC SBCS characters” ↩ ↩2 ↩3 ↩4 ↩5
-
IBM, “REDEFINES 절” ↩ ↩2
-
IBM, “OCCURS DEPENDING ON clause” ↩ ↩2
-
IBM, “PERFORM statement” / IBM, “Procedure division structure” ↩
-
IBM, “Scope terminators” / IBM, “Coding a choice of actions” ↩ ↩2 ↩3 ↩4
-
IBM, “파일 구조의 상세 기술” ↩
-
IBM, “FILE STATUS clause” / IBM, “Using file status keys” ↩
-
IBM, “CICS에서 실행하는 COBOL 프로그램 코딩” ↩
-
IBM, “Computational items” ↩
-
IBM, “Elementary move rules” ↩
관련 기사
같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.
MSMQ는 언제까지 쓸 수 있는가 ── 「deprecated조차 아닌」 레거시 큐의 마이그레이션 판단
MSMQ는 공식 deprecated 목록에 없는 한편, System.Messaging은 .NET Framework에만 있어 .NET으로의 이전을 가로막습니다. 폐지 소문과 실제 현재 위치를 사실로 정리하고, 계속 쓸지·옮길지의 판단 기준과 이전 ...
QR 코드를 읽은 값을 그대로 쓰면 안 됩니다 ── 오류 정정이 통과해도 값은 보장되지 않습니다
QR 코드의 오류 정정은, 정정이 통과하면 값이 맞다고 보장하는 장치가 아닙니다. 샘플 이미지와 decoder 2종으로 측정한 결과에서, 오염이 어디에 걸리느냐에 따라 다른 값으로 읽힐 수 있는 이유와 업무 시스템 쪽에 필요한 검증을 설명합니다.
PowerShell로 REST API와 연동하기 ── Invoke-RestMethod의 실무
PowerShell에서 사내 API나 SaaS REST API를 호출하는 실무를 정리합니다. 인증 헤더 전달 방법, 일본어 JSON 문자 깨짐 대책, 4xx/5xx 오류 처리, 429 재시도, 페이징, 프록시와 TLS의 함정까지 설명합니다.
업무 시스템의 코드 설계 ── 상품 코드·고객 코드를 정하는 법과 check digit
상품 코드·고객 코드 등 업무 시스템의 코드 체계를 정하는 실무 가이드. 유의미 코드와 무의미 일련번호 판단표, JAN·Luhn 등의 check digit 산식과 C# 구현, Excel의 선행 0 소실 대책, 자릿수 초과와 이행까지 정리합니다.
DLL・COM 인터페이스의 하위 호환성 ── 어떤 변경이 호출 측을 깨뜨리는지의 판단표
DLL이나 COM 컴포넌트의 어떤 변경이 호출 측을 깨뜨리는지. 바이너리 호환・소스 호환・동작 호환의 3계층을 정리하고, 변경 내용별 판단표, COM 인터페이스 불변의 철칙, semver 운용까지를 실무 가이드로 정리합니다.
관련 토픽
이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.
Windows 기술 토픽
Windows 개발, 장애 조사, 기존 자산 활용에 관한 KomuraSoft LLC 기사를 모은 토픽 허브입니다.
ActiveX 이관
COM / ActiveX / OCX 자산을 유지할지, 감쌀지, 교체할지의 단계적 판단을 정리한 토픽 페이지입니다.
이 주제와 연결되는 서비스
이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.
기술 상담 & 설계 리뷰
기존 COBOL 자산을 어떻게 읽는지, 개수를 어디서부터 할지, 외부 경계 파악, 이행 전 전망 정리까지 포함해, 기술 상담·설계 리뷰와 잘 맞는 주제입니다.
장애 조사 & 원인 분석
인수인계 직후의 장애 대응이나, COBOL 자산의 어디서 불일치가 일어나는지를 추적하는 작업은 결함 조사·원인 분석으로 진행하기 쉽습니다.
자주 묻는 질문
이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.
- COBOL 소스는 어디서부터 읽기 시작하면 되나요?
- PROCEDURE DIVISION만 읽어서는 절반밖에 파악하지 못합니다. COBOL은 로직의 언어이기 전에 레코드 정의를 상당히 강하게 다루는 언어이므로, 먼저 DATA DIVISION을 봅니다. 안전한 읽기 순서는 COPY를 전부 훑어 copybook을 확인하고, 01 레벨의 레코드 정의를 목록화하고, PIC와 USAGE로 항목의 형태를 읽고, READ·WRITE·CALL·EXEC SQL·EXEC CICS를 검색해 입출력과 외부 경계를 잡은 다음, PROCEDURE DIVISION 선두의 PERFORM 연쇄로 주 경로만 따라가는 흐름입니다.
- PIC S9(7)V99 COMP-3는 무슨 뜻인가요?
- PIC는 항목의 형태, USAGE는 어떤 표현으로 유지할지를 나타냅니다. S9(7)V99는 부호 있는 정수 7자리+소수 2자리 숫자이지만, V는 논리상의 소수점이며 데이터에 점 문자가 들어 있는 것은 아닙니다. COMP-3는 packed decimal이며, 금액·세액·건수·레이트 계열 항목에서 자주 나옵니다. 텍스트로 보면 깨져 보이는 것이 정상이므로, CSV나 UTF-8 감각으로 덤프를 보면 사고가 납니다.
- COBOL의 88 레벨이나 REDEFINES는 어떻게 이해하면 되나요?
- 88은 독립된 bool 변수가 아니라, 직전 항목의 값에 이름을 붙인 조건명(condition-name)입니다. SET WS-OK TO TRUE는 실제로는 기저 항목에 대응하는 값을 넣습니다. REDEFINES는 같은 메모리 영역을 다른 형태로 보는 장치이며, 복사가 아니라 C 계열의 union에 가까운 감각입니다. 한쪽을 바꾸면 다른 쪽의 보이는 방식도 바뀌므로, 하나의 영역을 레코드 종류마다 구분하는 쓰기 방식에서 자주 나옵니다.
- COPY 문이 많아서 전체가 보이지 않을 때는 어떻게 하면 되나요?
- COPY는 컴파일 시의 include이므로, 지금 연 소스는 아직 완성형이 아닐 수 있습니다. 레코드 정의, 공통 플래그, SQL용 host variable, 외부 인터페이스가 copybook에 들어가 있는 것은 꽤 흔합니다. 읽기 어려울 때는 전개 후 소스나 compiler listing을 볼 수 있는지 확인하는 편이 빠르고, IBM Enterprise COBOL에는 라이브러리 처리 후 입력 소스를 출력하는 MDECK 옵션도 있습니다.