테이블 - OCCURS 와 SEARCH
이 장에서 배우는 것
앞 장에서는 거래 파일을 읽어 머리글, 상세 줄, 합계가 있는 보고서를 만들었다. 그런데 거래 한 건을 처리할 때마다 "이 상품의 이율은 얼마인가", "이 창구의 수수료는 얼마인가"를 알아야 한다. 이런 값을 IF 문으로 일일이 나열하면 프로그램이 금방 길어진다. 이 장에서는 같은 모양의 데이터를 번호 붙은 칸으로 묶는 테이블(table, 다른 언어의 배열)을 배우고, 코드표를 테이블로 만들어 찾아 쓰는 방법을 익힌다.
- OCCURS 로 같은 모양의 항목을 여러 개 정의할 수 있다.
- 첨자(subscript)와 인덱스(index, INDEXED BY)의 차이를 알고 알맞게 고를 수 있다.
- PERFORM VARYING 으로 테이블 전체를 순서대로 훑을 수 있다.
- SEARCH 와 SEARCH ALL 의 동작 차이를 알고, SEARCH ALL 에 필요한 정렬 키 조건을 지킬 수 있다.
- 이율표와 수수료표를 테이블로 만들어 거래 파일의 코드를 값으로 바꿀 수 있다.
문제 상황
신용협동조합의 일일 거래 파일에는 거래마다 계좌번호, 상품 코드 두 글자, 창구 코드 두 글자, 금액이 들어 있다. 상품 코드 FX 는 정기예금이고 이율이 연 3.50 퍼센트다. GN 은 자유입출금이고 0.10 퍼센트다. 창구 코드 AT 는 ATM 이고 수수료가 500원, XB 는 타행 이체이고 1,000원이다.
이 규칙을 IF 문으로 쓰면 상품이 네 개, 창구가 네 개일 때 이미 여덟 갈래다. 이율이 바뀌면 프로그램 로직 한가운데를 고쳐야 한다. 코드와 값의 짝을 자료로 따로 모아 두고, 로직은 "표에서 찾는다"는 한 가지 동작만 하게 만드는 편이 낫다. COBOL 에서 이 자료를 담는 그릇이 테이블이다.
이번 장의 프로그램은 거래 파일을 읽으면서 다음을 한다.
- 상품 코드로 이율표를 찾아 1년 이자를 계산한다.
- 창구 코드로 수수료표를 찾아 수수료를 붙인다.
- 창구별 건수와 수수료 합계를 또 다른 테이블에 쌓아 마지막에 출력한다.
- 표에 없는 코드가 들어와도 멈추지 않고 UNKNOWN 으로 표시한다.
OCCURS 와 테이블 순회
OCCURS 로 같은 항목 반복하기
OCCURS 는 "이 항목을 몇 번 반복한다"를 선언하는 절이다. 다음은 창구별 건수를 담는 칸 네 개의 정의다.
01 STAT-TABLE.
05 STAT-ENTRY OCCURS 4 TIMES.
10 ST-COUNT PIC 9(3) VALUE ZERO.
10 ST-FEE PIC 9(6) VALUE ZERO.
STAT-ENTRY 는 ST-COUNT 와 ST-FEE 를 묶은 그룹이고, 이 그룹이 4번 이어 붙는다. 메모리에는 9바이트(3 + 6)짜리 칸이 네 개 연달아 놓인다. 각 칸을 가리킬 때는 이름 뒤 괄호에 번호를 쓴다. ST-COUNT (2) 는 두 번째 칸의 건수다. 번호는 1부터 시작한다. 0번 칸은 없다.
OCCURS 는 그룹 항목에도, 기본 항목(PIC 가 있는 항목)에도 붙일 수 있다. 다만 01 레벨에는 붙일 수 없다. 그래서 위 예처럼 01 아래 05 레벨에 붙인다.
값이 미리 들어 있는 테이블: REDEFINES
이율표처럼 프로그램이 시작할 때 이미 값이 정해진 표는 어떻게 만들까. OCCURS 항목에는 칸마다 다른 VALUE 를 줄 수 없다. 그래서 먼저 값을 한 줄씩 나열한 평평한 영역을 만들고, 같은 메모리를 REDEFINES 로 다시 해석해 테이블 모양을 씌운다. REDEFINES 는 같은 바이트에 다른 이름과 구조를 붙이는 절이다.
01 RATE-VALUES.
05 FILLER PIC X(13) VALUE "FXFIXED 350".
05 FILLER PIC X(13) VALUE "GNGENERAL 010".
01 RATE-TABLE REDEFINES RATE-VALUES.
05 RATE-ENTRY OCCURS 2 TIMES.
10 RT-CODE PIC X(2).
10 RT-NAME PIC X(8).
10 RT-RATE PIC 9V99.
한 줄의 13바이트가 코드 2, 이름 8, 이율 3으로 나뉜다. 이율 350 은 PIC 9V99 에서 3.50 으로 읽힌다. V 는 소수점 위치를 뜻하는 가상 표시이고 메모리에는 자리를 차지하지 않는다. 아래 그림은 이 장의 이율표가 메모리에서 어떻게 놓이는지 보여 준다.
첨자와 인덱스
테이블의 칸을 고르는 방법은 두 가지다.
| 구분 | 선언 방법 | 값을 바꾸는 법 | 특징 |
|---|---|---|---|
| 첨자 | 일반 숫자 항목(PIC 9(2) 등) | MOVE, ADD, COMPUTE | 읽기 쉽고 아무 테이블에나 쓴다 |
| 인덱스 | OCCURS 절의 INDEXED BY 이름 | SET, PERFORM VARYING | 칸 위치를 직접 가리켜 빠르고 SEARCH 에 필요하다 |
인덱스는 PIC 를 따로 쓰지 않는다. INDEXED BY 에 이름만 적으면 컴파일러가 알아서 만든다. 인덱스에는 MOVE 대신 SET 을 쓴다.
SET FE-IX TO 1
SET WS-POS TO FE-IX
첫 줄은 인덱스를 1번 칸으로 맞춘다. 둘째 줄은 인덱스가 가리키는 칸 번호를 일반 숫자 항목 WS-POS 에 꺼낸다. 이 장의 프로그램은 수수료표를 SEARCH 로 찾은 뒤 이 방법으로 번호를 얻고, 그 번호를 첨자로 써서 통계 테이블의 같은 위치 칸에 건수를 더한다.
PERFORM VARYING 으로 순회하기
앞 장에서 배운 PERFORM 에 VARYING 을 붙이면 번호를 하나씩 올리면서 같은 블록을 되풀이한다.
PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 4
DISPLAY FE-NAME (WS-I)
END-PERFORM
WS-I 가 1에서 시작해 1씩 늘고, 4를 넘으면 멈춘다. UNTIL 조건은 "블록을 실행하기 전에" 검사하므로 WS-I 가 5가 되는 순간 블록 없이 끝난다. 그래서 WS-I 는 칸 수보다 큰 값을 담을 수 있어야 한다. 칸이 4개이고 PIC 9 로 선언해도 5는 담기지만, 칸이 9개이면 10을 담지 못해 영원히 끝나지 않는 반복이 되므로 여유 있는 자릿수를 준다. 인덱스도 같은 방식으로 쓴다. PERFORM VARYING RT-IX FROM 1 BY 1 UNTIL RT-IX > 4 처럼 쓴다.
SEARCH 와 SEARCH ALL
SEARCH: 앞에서부터 하나씩
SEARCH 는 인덱스가 가리키는 칸부터 시작해 WHEN 조건이 참이 될 때까지 한 칸씩 내려간다. 반드시 지킬 점이 둘 있다. 첫째, 테이블에 INDEXED BY 가 있어야 한다. 둘째, SEARCH 를 실행하기 전에 인덱스를 시작 위치로 SET 해야 한다. SEARCH 는 인덱스를 스스로 1로 되돌리지 않는다.
SET FE-IX TO 1
SEARCH FEE-ENTRY
AT END
MOVE ZERO TO WS-FEE
WHEN FE-CODE (FE-IX) = TR-CHAN
MOVE FE-FEE (FE-IX) TO WS-FEE
END-SEARCH
끝까지 찾아도 없으면 AT END 쪽이 실행된다. AT END 를 쓰지 않으면 못 찾았을 때 아무 일도 일어나지 않아 앞 거래에서 얻은 값이 그대로 남는다. 코드가 틀린 거래가 앞 거래의 수수료를 물게 되는 사고가 여기서 난다.
SEARCH ALL: 정렬된 표에서 절반씩
SEARCH ALL 은 이진 탐색(binary search)이다. 표의 가운데 칸을 보고, 찾는 값이 더 앞이면 앞쪽 절반만, 뒤이면 뒤쪽 절반만 남기는 일을 되풀이한다. 칸이 1,000개여도 열 번 안팎의 비교로 끝난다. 대신 조건이 있다.
- OCCURS 절에
ASCENDING KEY IS(오름차순) 또는DESCENDING KEY IS(내림차순)로 정렬 키를 선언한다. - 표의 내용이 실제로 그 키 순서로 놓여 있어야 한다. 컴파일러는 이것을 검사하지 않는다.
- WHEN 에는 키가 같은지 비교하는 조건만 쓴다.
- SET 으로 인덱스를 미리 맞출 필요가 없다. 항상 표 전체를 대상으로 한다.
| 구분 | SEARCH | SEARCH ALL |
|---|---|---|
| 탐색 방식 | 앞에서부터 순서대로 | 가운데를 기준으로 절반씩 좁힘 |
| 표의 정렬 | 필요 없다 | 키 순서로 정렬되어 있어야 한다 |
| 인덱스 준비 | SET 으로 시작 위치를 맞춘다 | 필요 없다 |
| WHEN 조건 | 어떤 조건이든 가능 | 키 = 값 형태만 가능 |
이 장의 이율표는 상품 코드 순(FX, GN, SV, YT)으로 놓여 있어 SEARCH ALL 로 찾는다. 수수료표는 창구 코드를 입력된 순서(TL, AT, IB, XB)대로 두었으므로 SEARCH 로 찾는다. 칸이 네 개뿐이라 속도 차이는 없다. 두 문장을 각각 써 보는 것이 목적이다. 코드표가 수백 줄이 되면 정렬해 두고 SEARCH ALL 을 쓰는 편이 유리하다.
완성 코드
main.cob
IDENTIFICATION DIVISION.
PROGRAM-ID. MAIN.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT TRANS-FILE ASSIGN TO "trans.dat"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD TRANS-FILE.
01 TRANS-REC.
05 TR-ACCT PIC 9(6).
05 TR-PROD PIC X(2).
05 TR-CHAN PIC X(2).
05 TR-AMT PIC 9(7).
WORKING-STORAGE SECTION.
01 WS-EOF PIC X VALUE "N".
88 END-OF-TRANS VALUE "Y".
01 WS-RATE PIC 9V99 VALUE ZERO.
01 WS-INTEREST PIC 9(9) VALUE ZERO.
01 WS-FEE PIC 9(4) VALUE ZERO.
01 WS-POS PIC 9(2) VALUE ZERO.
01 WS-I PIC 9(2) VALUE ZERO.
01 WS-TOT-COUNT PIC 9(3) VALUE ZERO.
01 WS-TOT-FEE PIC 9(6) VALUE ZERO.
01 RATE-VALUES.
05 FILLER PIC X(13) VALUE "FXFIXED 350".
05 FILLER PIC X(13) VALUE "GNGENERAL 010".
05 FILLER PIC X(13) VALUE "SVSAVINGS 280".
05 FILLER PIC X(13) VALUE "YTYOUTH 400".
01 RATE-TABLE REDEFINES RATE-VALUES.
05 RATE-ENTRY OCCURS 4 TIMES
ASCENDING KEY IS RT-CODE
INDEXED BY RT-IX.
10 RT-CODE PIC X(2).
10 RT-NAME PIC X(8).
10 RT-RATE PIC 9V99.
01 FEE-VALUES.
05 FILLER PIC X(14) VALUE "TLTELLER 0000".
05 FILLER PIC X(14) VALUE "ATATM 0500".
05 FILLER PIC X(14) VALUE "IBINTERNET0300".
05 FILLER PIC X(14) VALUE "XBOTHERBNK1000".
01 FEE-TABLE REDEFINES FEE-VALUES.
05 FEE-ENTRY OCCURS 4 TIMES INDEXED BY FE-IX.
10 FE-CODE PIC X(2).
10 FE-NAME PIC X(8).
10 FE-FEE PIC 9(4).
01 STAT-TABLE.
05 STAT-ENTRY OCCURS 4 TIMES.
10 ST-COUNT PIC 9(3) VALUE ZERO.
10 ST-FEE PIC 9(6) VALUE ZERO.
01 RATE-LINE.
05 RL-CODE PIC X(2).
05 FILLER PIC X(2) VALUE SPACES.
05 RL-NAME PIC X(8).
05 FILLER PIC X(2) VALUE SPACES.
05 RL-RATE PIC Z9.99.
01 DETAIL-LINE.
05 DL-ACCT PIC 9(6).
05 FILLER PIC X(2) VALUE SPACES.
05 DL-PROD PIC X(8).
05 FILLER PIC X VALUE SPACE.
05 DL-RATE PIC Z9.99.
05 FILLER PIC X(2) VALUE SPACES.
05 DL-INT PIC Z,ZZZ,ZZ9.
05 FILLER PIC X(2) VALUE SPACES.
05 DL-CHAN PIC X(8).
05 FILLER PIC X VALUE SPACE.
05 DL-FEE PIC Z,ZZ9.
01 SUM-LINE.
05 SL-NAME PIC X(8).
05 FILLER PIC X(2) VALUE SPACES.
05 SL-COUNT PIC ZZ9.
05 FILLER PIC X(2) VALUE SPACES.
05 SL-FEE PIC ZZZ,ZZ9.
PROCEDURE DIVISION.
MAIN-PARA.
PERFORM SHOW-RATES
DISPLAY "DETAIL"
DISPLAY
"ACCT PRODUCT RATE INTEREST CHANNEL FEE"
OPEN INPUT TRANS-FILE
PERFORM UNTIL END-OF-TRANS
READ TRANS-FILE
AT END
SET END-OF-TRANS TO TRUE
NOT AT END
PERFORM PROCESS-ONE
END-READ
END-PERFORM
CLOSE TRANS-FILE
PERFORM SHOW-SUMMARY
STOP RUN.
SHOW-RATES.
DISPLAY "RATE TABLE"
PERFORM VARYING RT-IX FROM 1 BY 1 UNTIL RT-IX > 4
MOVE RT-CODE (RT-IX) TO RL-CODE
MOVE RT-NAME (RT-IX) TO RL-NAME
MOVE RT-RATE (RT-IX) TO RL-RATE
DISPLAY RATE-LINE
END-PERFORM.
PROCESS-ONE.
MOVE TR-ACCT TO DL-ACCT
SEARCH ALL RATE-ENTRY
AT END
MOVE "UNKNOWN " TO DL-PROD
MOVE ZERO TO WS-RATE
WHEN RT-CODE (RT-IX) = TR-PROD
MOVE RT-NAME (RT-IX) TO DL-PROD
MOVE RT-RATE (RT-IX) TO WS-RATE
END-SEARCH
COMPUTE WS-INTEREST = TR-AMT * WS-RATE / 100
MOVE WS-RATE TO DL-RATE
MOVE WS-INTEREST TO DL-INT
MOVE ZERO TO WS-POS
SET FE-IX TO 1
SEARCH FEE-ENTRY
AT END
MOVE "UNKNOWN " TO DL-CHAN
MOVE ZERO TO WS-FEE
WHEN FE-CODE (FE-IX) = TR-CHAN
MOVE FE-NAME (FE-IX) TO DL-CHAN
MOVE FE-FEE (FE-IX) TO WS-FEE
SET WS-POS TO FE-IX
END-SEARCH
IF WS-POS > 0
ADD 1 TO ST-COUNT (WS-POS)
ADD WS-FEE TO ST-FEE (WS-POS)
END-IF
MOVE WS-FEE TO DL-FEE
DISPLAY DETAIL-LINE.
SHOW-SUMMARY.
DISPLAY "CHANNEL SUMMARY"
DISPLAY "CHANNEL CNT FEE"
PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 4
MOVE FE-NAME (WS-I) TO SL-NAME
MOVE ST-COUNT (WS-I) TO SL-COUNT
MOVE ST-FEE (WS-I) TO SL-FEE
ADD ST-COUNT (WS-I) TO WS-TOT-COUNT
ADD ST-FEE (WS-I) TO WS-TOT-FEE
DISPLAY SUM-LINE
END-PERFORM
MOVE "TOTAL" TO SL-NAME
MOVE WS-TOT-COUNT TO SL-COUNT
MOVE WS-TOT-FEE TO SL-FEE
DISPLAY SUM-LINE.
trans.dat
한 줄이 17글자(계좌번호 6, 상품 2, 창구 2, 금액 7)이고 마지막 줄 끝에도 줄바꿈이 있어야 한다. 여섯 번째 줄의 상품 코드 ZZ 는 이율표에 없는 코드다.
100234FXTL1000000
100871GNAT0050000
101305SVIB0300000
101442YTXB2000000
101977GNAT0030000
102050ZZTL0100000
102118FXIB0500000
102260YTAT0070000
줄별 해설
자료 영역
RATE-VALUES 는 상품 네 개를 13바이트 문자열로 나열한 영역이다. 앞 두 글자가 코드, 다음 여덟 글자가 이름(남는 자리는 공백), 마지막 세 글자가 이율이다. FILLER 는 이름이 필요 없는 칸에 쓰는 자리 채움 항목이다. RATE-TABLE 이 이 영역을 REDEFINES 로 덮어 RT-CODE, RT-NAME, RT-RATE 라는 이름으로 접근하게 한다. ASCENDING KEY IS RT-CODE 는 "코드 오름차순으로 놓았다"는 약속이다. FX, GN, SV, YT 는 알파벳순이므로 약속이 맞다.
FEE-TABLE 은 같은 방식이지만 KEY 절이 없고 INDEXED BY FE-IX 만 있다. 수수료가 0 인 창구(TL)도 표에 넣어 두었다. 수수료가 없는 창구도 "표에 있다"고 말할 수 있어야 못 찾은 경우와 구별되기 때문이다.
STAT-TABLE 은 수수료표와 같은 순서의 칸 네 개로, 창구별 건수와 수수료 합계를 쌓는다. 1번 칸이 TL, 2번 칸이 AT 하는 식으로 두 표의 번호가 서로 대응한다. 그래서 수수료표에서 찾은 위치를 그대로 통계 테이블에 쓸 수 있다.
편집 항목 RL-RATE, DL-RATE 는 Z9.99 로, 이율을 "3.50" 모양으로 찍는다. DL-INT 와 DL-FEE, SL-FEE 는 쉼표가 들어간 금액 모양이다. 편집 항목은 앞서 출력 편집을 다룬 장에서 배운 방식 그대로다.
MAIN-PARA
먼저 SHOW-RATES 로 이율표를 찍고, 상세 부분의 제목 줄을 찍는다. 제목 줄 문자열은 72열을 넘지 않도록 DISPLAY 다음 줄로 내려 썼다. 그다음 파일을 열어 끝까지 읽는다. 읽은 레코드는 PROCESS-ONE 이 한 건씩 처리하고, 마지막에 SHOW-SUMMARY 가 창구별 합계를 찍는다. SET END-OF-TRANS TO TRUE 는 조건 이름에 해당하는 값("Y")을 WS-EOF 에 넣는다.
SHOW-RATES
인덱스 RT-IX 를 1에서 4까지 올리며 이율표의 각 칸을 RATE-LINE 으로 옮겨 찍는다. PERFORM VARYING 이 인덱스를 직접 다룰 수 있다는 점을 보여 준다.
PROCESS-ONE
첫 SEARCH ALL 은 거래의 상품 코드 TR-PROD 를 이율표에서 찾는다. 찾으면 이름과 이율을 꺼내고, 못 찾으면 이름을 UNKNOWN 으로, 이율을 0 으로 둔다. 이어서 TR-AMT * WS-RATE / 100 으로 1년 이자를 구한다. 이율이 3.50 이면 금액에 3.50 을 곱해 100 으로 나누는 셈이다. 이 장의 데이터는 나누어떨어지게 맞춰 놓았다.
수수료 쪽은 먼저 WS-POS 를 0 으로 비우고 인덱스를 1로 SET 한 뒤 SEARCH 를 한다. 찾으면 수수료를 꺼내고 SET WS-POS TO FE-IX 로 칸 번호를 보관한다. 못 찾으면 WS-POS 가 0 으로 남아 있다. 바로 아래 IF WS-POS > 0 이 그 경우를 걸러, 통계 테이블의 0번 칸이라는 존재하지 않는 칸에 더하는 일을 막는다. 여섯 번째 거래는 상품 코드만 틀렸고 창구는 TL 이므로 통계에는 들어간다.
SHOW-SUMMARY
WS-I 를 1에서 4까지 올리며 수수료표의 이름과 통계 테이블의 건수·수수료를 한 줄로 찍고, 같은 반복 안에서 총합계도 더한다. 반복이 끝나면 이름 칸에 TOTAL 을 넣어 합계 줄을 찍는다.
실행 결과
main.cob 와 trans.dat 를 같은 폴더에 두고 실행한다.
$ cobc -x -Wall main.cob
$ ./main
RATE TABLE
FX FIXED 3.50
GN GENERAL 0.10
SV SAVINGS 2.80
YT YOUTH 4.00
DETAIL
ACCT PRODUCT RATE INTEREST CHANNEL FEE
100234 FIXED 3.50 35,000 TELLER 0
100871 GENERAL 0.10 50 ATM 500
101305 SAVINGS 2.80 8,400 INTERNET 300
101442 YOUTH 4.00 80,000 OTHERBNK 1,000
101977 GENERAL 0.10 30 ATM 500
102050 UNKNOWN 0.00 0 TELLER 0
102118 FIXED 3.50 17,500 INTERNET 300
102260 YOUTH 4.00 2,800 ATM 500
CHANNEL SUMMARY
CHANNEL CNT FEE
TELLER 2 0
ATM 3 1,500
INTERNET 2 600
OTHERBNK 1 1,000
TOTAL 8 3,100
이자는 금액 × 이율 ÷ 100 이다. 첫 줄은 1,000,000 × 3.50 ÷ 100 = 35,000 이다. 여섯 번째 줄은 상품 코드를 못 찾아 UNKNOWN 과 이율 0.00 이 나왔고, 창구 TL 은 찾았으므로 통계에는 TELLER 건수로 잡혔다.
실무에서 자주 틀리는 것
1. SEARCH 전에 인덱스를 SET 하지 않는다
두 번째 거래부터 앞 거래에서 찾은 위치에서 탐색이 시작된다. 새 코드가 그보다 앞 칸에 있으면 못 찾는다.
* 틀린 코드
SEARCH FEE-ENTRY
AT END MOVE ZERO TO WS-FEE
WHEN FE-CODE (FE-IX) = TR-CHAN
MOVE FE-FEE (FE-IX) TO WS-FEE
END-SEARCH
* 고친 코드
SET FE-IX TO 1
SEARCH FEE-ENTRY
AT END MOVE ZERO TO WS-FEE
WHEN FE-CODE (FE-IX) = TR-CHAN
MOVE FE-FEE (FE-IX) TO WS-FEE
END-SEARCH
2. 정렬되지 않은 표에 SEARCH ALL 을 쓴다
컴파일러는 표가 정렬되어 있는지 검사하지 않는다. 순서가 틀린 표에서는 표에 분명히 있는 코드를 못 찾는 일이 생긴다. 어떤 코드에서 실패하는지도 표 내용에 달려 있어 재현하기 어렵다.
* 틀린 코드: 입력 순서대로 둔 표에 SEARCH ALL
05 FEE-ENTRY OCCURS 4 TIMES
ASCENDING KEY IS FE-CODE
INDEXED BY FE-IX.
* VALUE 순서가 TL, AT, IB, XB 이다
* 고친 코드: 값을 키 순서(AT, IB, TL, XB)로 나열한다
05 FILLER PIC X(14) VALUE "ATATM 0500".
05 FILLER PIC X(14) VALUE "IBINTERNET0300".
05 FILLER PIC X(14) VALUE "TLTELLER 0000".
05 FILLER PIC X(14) VALUE "XBOTHERBNK1000".
코드를 추가할 때는 맨 끝이 아니라 키 순서에 맞는 자리에 끼워 넣는다.
3. AT END 를 생략한다
못 찾은 거래가 앞 거래의 값을 그대로 물려받는다. 오류 없이 틀린 금액이 나오므로 가장 찾기 어렵다.
* 틀린 코드
SEARCH ALL RATE-ENTRY
WHEN RT-CODE (RT-IX) = TR-PROD
MOVE RT-RATE (RT-IX) TO WS-RATE
END-SEARCH
* 고친 코드
SEARCH ALL RATE-ENTRY
AT END
MOVE ZERO TO WS-RATE
WHEN RT-CODE (RT-IX) = TR-PROD
MOVE RT-RATE (RT-IX) TO WS-RATE
END-SEARCH
4. 칸 수를 넘은 번호로 접근한다
OCCURS 4 TIMES 인 표에 ST-COUNT (5) 를 쓰면 표 바깥의 메모리를 읽거나 덮어쓴다. 기본 설정에서는 오류 없이 지나가 엉뚱한 변수가 바뀐다. 못 찾은 경우의 번호 0 도 같은 위험이다.
* 틀린 코드: 못 찾아도 번호 0 으로 더한다
ADD 1 TO ST-COUNT (WS-POS)
* 고친 코드
IF WS-POS > 0
ADD 1 TO ST-COUNT (WS-POS)
END-IF
개발 중에는 cobc -x -debug main.cob 로 컴파일하면 범위를 벗어난 접근이 실행 시점에 오류로 드러난다. 다만 검사 때문에 느려지므로 완성본에서는 쓰지 않는 경우가 많다.
한눈에 보기
| 하고 싶은 일 | 쓰는 문법 | 주의할 점 |
|---|---|---|
| 항목 반복 정의 | OCCURS n TIMES | 01 레벨에는 쓰지 못한다 |
| 값이 든 표 만들기 | FILLER VALUE + REDEFINES | 줄마다 글자 수를 정확히 맞춘다 |
| 칸 고르기 | 이름 (번호) | 번호는 1부터 칸 수까지 |
| 인덱스 선언·변경 | INDEXED BY, SET | MOVE 가 아니라 SET 을 쓴다 |
| 표 전체 순회 | PERFORM VARYING ... UNTIL | 끝 조건에서 번호가 칸 수 + 1 이 된다 |
| 순서대로 찾기 | SET 인덱스 TO 1 후 SEARCH | AT END 로 못 찾은 경우를 처리한다 |
| 정렬된 표에서 찾기 | ASCENDING KEY + SEARCH ALL | 표가 정말 정렬되어 있어야 한다 |
연습 문제
- 이율표에 상품 코드 ET(이름 EVENT, 이율 1.20)를 추가하려 한다. 어느 위치에 줄을 넣어야 하고, 바꿔야 할 곳은 어디인가. 이 장의 이율표가 SEARCH ALL 로 찾는다는 점을 고려해 답하라.
- 수수료표에서 수수료가 0 보다 큰 창구가 몇 개인지 세는 코드를 PERFORM VARYING 으로 작성하라. 세는 변수는 WS-CNT 로 한다.
- 수수료표를 SEARCH ALL 로 찾게 바꾸려 한다. KEY 절을 어떻게 쓰고, 표의 줄은 어떤 순서로 놓아야 하는가.
- 거래 파일에 창구 코드가 MB 인 거래가 들어 있다. 수수료표에 MB 가 없을 때 이 장의 프로그램은 상세 줄과 창구별 합계에서 각각 어떻게 동작하는가.
정답과 해설
- ET 는 알파벳순으로 FX 보다 앞이므로 RATE-VALUES 의 맨 첫 줄에
VALUE "ETEVENT 120"을 넣는다(코드 2 + 이름 8 + 이율 3 = 13글자). 그리고OCCURS 4 TIMES를OCCURS 5 TIMES로 고치고, SHOW-RATES 의UNTIL RT-IX > 4도 5 로 고친다. 맨 끝에 넣으면 키 순서가 깨져 SEARCH ALL 이 일부 코드를 놓칠 수 있다. - 다음과 같이 쓴다. 기대하는 결과는 3 이다(AT, IB, XB).
MOVE ZERO TO WS-CNT PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 4 IF FE-FEE (WS-I) > 0 ADD 1 TO WS-CNT END-IF END-PERFORM - OCCURS 절에
ASCENDING KEY IS FE-CODE를 추가한다. 줄은 AT, IB, TL, XB 순서로 놓는다. 그러면 통계 테이블의 칸 번호 대응도 이 순서로 바뀐다는 점에 주의한다. 요약 줄의 출력 순서도 같이 바뀐다. - SEARCH 가 끝까지 가도 못 찾으므로 AT END 가 실행되어 상세 줄의 창구 칸에는 UNKNOWN, 수수료에는 0 이 찍힌다. WS-POS 가 0 이므로 IF 문에서 걸려 어느 창구의 합계에도 들어가지 않는다. 그 결과 상세 줄 수와 TOTAL 의 건수가 어긋난다. 못 찾은 거래의 건수를 따로 세는 변수를 두면 이 어긋남을 확인할 수 있다.