Devin.KR

테이블 - OCCURS 와 SEARCH

개발자KR 조회 0

이 장에서 배우는 것

앞 장에서는 거래 파일을 읽어 머리글, 상세 줄, 합계가 있는 보고서를 만들었다. 그런데 거래 한 건을 처리할 때마다 "이 상품의 이율은 얼마인가", "이 창구의 수수료는 얼마인가"를 알아야 한다. 이런 값을 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 는 소수점 위치를 뜻하는 가상 표시이고 메모리에는 자리를 차지하지 않는다. 아래 그림은 이 장의 이율표가 메모리에서 어떻게 놓이는지 보여 준다.

이율표는 13바이트짜리 항목 네 개가 연달아 놓이고 각 항목은 코드 2, 이름 8, 이율 3바이트로 나뉜다.

첨자와 인덱스

테이블의 칸을 고르는 방법은 두 가지다.

첨자와 인덱스의 차이
구분선언 방법값을 바꾸는 법특징
첨자일반 숫자 항목(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 비교
구분SEARCHSEARCH ALL
탐색 방식앞에서부터 순서대로가운데를 기준으로 절반씩 좁힘
표의 정렬필요 없다키 순서로 정렬되어 있어야 한다
인덱스 준비SET 으로 시작 위치를 맞춘다필요 없다
WHEN 조건어떤 조건이든 가능키 = 값 형태만 가능
정렬된 8칸에서 F 를 찾을 때 SEARCH 는 6번, SEARCH ALL 은 2번 비교한다.

이 장의 이율표는 상품 코드 순(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 TIMES01 레벨에는 쓰지 못한다
값이 든 표 만들기FILLER VALUE + REDEFINES줄마다 글자 수를 정확히 맞춘다
칸 고르기이름 (번호)번호는 1부터 칸 수까지
인덱스 선언·변경INDEXED BY, SETMOVE 가 아니라 SET 을 쓴다
표 전체 순회PERFORM VARYING ... UNTIL끝 조건에서 번호가 칸 수 + 1 이 된다
순서대로 찾기SET 인덱스 TO 1 후 SEARCHAT END 로 못 찾은 경우를 처리한다
정렬된 표에서 찾기ASCENDING KEY + SEARCH ALL표가 정말 정렬되어 있어야 한다

연습 문제

  1. 이율표에 상품 코드 ET(이름 EVENT, 이율 1.20)를 추가하려 한다. 어느 위치에 줄을 넣어야 하고, 바꿔야 할 곳은 어디인가. 이 장의 이율표가 SEARCH ALL 로 찾는다는 점을 고려해 답하라.
  2. 수수료표에서 수수료가 0 보다 큰 창구가 몇 개인지 세는 코드를 PERFORM VARYING 으로 작성하라. 세는 변수는 WS-CNT 로 한다.
  3. 수수료표를 SEARCH ALL 로 찾게 바꾸려 한다. KEY 절을 어떻게 쓰고, 표의 줄은 어떤 순서로 놓아야 하는가.
  4. 거래 파일에 창구 코드가 MB 인 거래가 들어 있다. 수수료표에 MB 가 없을 때 이 장의 프로그램은 상세 줄과 창구별 합계에서 각각 어떻게 동작하는가.

정답과 해설

  1. 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 이 일부 코드를 놓칠 수 있다.
  2. 다음과 같이 쓴다. 기대하는 결과는 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
    
  3. OCCURS 절에 ASCENDING KEY IS FE-CODE 를 추가한다. 줄은 AT, IB, TL, XB 순서로 놓는다. 그러면 통계 테이블의 칸 번호 대응도 이 순서로 바뀐다는 점에 주의한다. 요약 줄의 출력 순서도 같이 바뀐다.
  4. SEARCH 가 끝까지 가도 못 찾으므로 AT END 가 실행되어 상세 줄의 창구 칸에는 UNKNOWN, 수수료에는 0 이 찍힌다. WS-POS 가 0 이므로 IF 문에서 걸려 어느 창구의 합계에도 들어가지 않는다. 그 결과 상세 줄 수와 TOTAL 의 건수가 어긋난다. 못 찾은 거래의 건수를 따로 세는 변수를 두면 이 어긋남을 확인할 수 있다.

댓글 0

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

댓글을 남기려면 로그인이 필요합니다.