Devin.KR

제어 단절 보고서 - 지점별 소계

개발자KR 조회 0

이 장에서 배우는 것

앞 장까지는 레코드 하나를 읽어 처리하고 한 줄을 출력하는 프로그램을 만들었다. 실무 보고서는 대개 "지점별로 묶어서 합계를 보여 달라"는 요구로 이어진다. 이 장에서는 같은 키를 가진 레코드가 이어지는 동안 값을 모으다가, 키가 바뀌는 순간 소계를 내는 방식을 익힌다. 이 방식을 제어 단절(control break)이라고 부른다. 여기서 키(key)는 레코드를 묶는 기준이 되는 항목이고, 이 장의 예제에서는 지점 코드다.

  • 제어 단절이 입력이 정렬되어 있어야 성립하는 이유를 설명할 수 있다.
  • 이전 키를 저장해 두고 현재 키와 비교해서 키 변화를 감지할 수 있다.
  • 첫 레코드와 마지막 레코드에서 소계가 빠지거나 헛도는 문제를 막을 수 있다.
  • 지점별 소계를 총계에 더하고 소계 칸을 비우는 순서를 지킬 수 있다.
  • SORT 문으로 입력 파일을 미리 정렬할 수 있다.

문제 상황

신용협동조합의 전산 담당자가 하루치 입출금 거래 파일을 받았다. 파일에는 서울, 부산, 대구 지점에서 들어온 거래가 도착한 순서대로 섞여 있다. 지점장은 "지점별로 거래를 모아서 입금 합계, 출금 합계, 순증감액을 보고 싶다. 마지막에는 전체 합계도 있어야 한다"고 요청한다.

가장 먼저 떠오르는 방법은 지점마다 파일을 한 번씩 읽는 것이다. 지점이 세 개면 세 번, 서른 개면 서른 번 읽어야 하므로 거래가 많아질수록 느려진다. 배치 처리에서는 파일을 한 번만 읽고 끝내는 방식이 표준이다. 그러려면 같은 지점의 거래가 연달아 나와야 하고, 지점이 바뀌는 순간에 소계를 찍어야 한다. 이 장의 프로그램은 이 두 조건을 순서대로 만족시킨다. 먼저 SORT 문으로 거래 파일을 지점 순으로 정렬하고, 정렬된 파일을 한 번 읽으면서 키 변화를 감시한다.

정렬된 입력과 키 변화 감지

왜 정렬이 먼저인가

프로그램은 레코드를 한 건씩 읽을 뿐 앞으로 무엇이 올지 알지 못한다. "지금 읽은 레코드의 지점이 방금 전 레코드의 지점과 다르다"는 사실로만 한 묶음이 끝났다고 판단할 수 있다. 같은 지점의 거래가 떨어져 있으면, 그 지점은 소계가 여러 번 나온다. 서울 거래가 세 곳에 흩어져 있다면 서울 소계 줄이 세 개 생기고, 그중 어느 것도 서울 전체의 합계가 아니다. 그래서 제어 단절 보고서의 입력은 키 순서로 정렬되어 있어야 한다.

정렬된 입력에서는 키가 바뀌는 자리마다 소계를 한 번씩 출력하고, 파일 끝에서 마지막 소계와 총계를 출력한다.

이전 키와 현재 키 비교

키 변화를 감지하는 방법은 단순하다. 작업 영역에 "이전 레코드의 키"를 담아 둘 칸을 하나 두고, 새 레코드를 읽을 때마다 현재 키와 비교한다. 같으면 같은 묶음이 계속되는 것이고, 다르면 이전 묶음이 끝난 것이다. 아래 표는 반복문 안에서 마주치는 네 가지 상황을 정리한 것이다.

키 비교 결과에 따라 해야 할 일
상황판정해야 할 일예제에서
첫 레코드비교할 이전 키가 없다소계 없이 키만 저장하고 지점 머리글을 출력한다맨 위의 BUS 거래
같은 키현재 키와 이전 키가 같다상세 줄을 출력하고 금액을 누적한다BUS 의 두 번째 거래
키 변화현재 키와 이전 키가 다르다이전 묶음의 소계를 출력하고 합산 후 초기화한 뒤 새 묶음을 시작한다BUS 에서 DAE 로 넘어갈 때
파일 끝읽을 레코드가 없다마지막 묶음의 소계와 전체 총계를 출력한다마지막 SEL 거래 뒤

여기서 중요한 점은 소계를 출력하는 시점이다. 키 변화는 새 레코드를 읽은 뒤에야 알 수 있다. 이때 작업 영역의 레코드 칸에는 이미 다음 지점의 데이터가 들어 있다. 소계 줄에 지점 코드를 찍고 싶다면 레코드의 키가 아니라 저장해 둔 이전 키를 써야 한다. 또 이전 키는 소계를 출력한 다음에 새 키로 바꾸어야 한다.

첫 레코드와 마지막 레코드

반복문 안에서 키 변화만 검사하면 양 끝에서 문제가 생긴다. 먼저 첫 레코드가 있다. 이전 키 칸이 공백인 상태로 반복문에 들어가면 첫 레코드의 지점은 공백과 다르므로, 존재하지 않는 묶음의 소계가 먼저 출력된다. 이를 막으려고 반복문에 들어가기 전에 레코드를 한 건 미리 읽고, 그 키를 이전 키에 넣어 둔다. 이 "미리 읽기"를 프라이밍 읽기(priming read)라고 부른다.

마지막 레코드는 반대 문제다. 마지막 묶음 뒤에는 다른 키를 가진 레코드가 없으므로 키 변화가 일어나지 않는다. 파일 끝에서 반복문이 끝나면, 마지막 묶음의 소계는 아직 출력되지 않은 채 남아 있다. 그래서 반복문이 끝난 직후에 소계를 한 번 더 출력해야 한다. 입력이 비어 있을 수도 있으므로, 처리한 거래가 한 건이라도 있을 때만 출력한다.

총계는 소계를 모아서 만든다

총계를 만드는 방법은 두 가지다. 거래 한 건마다 소계와 총계에 모두 더하는 방법이 있고, 소계를 출력하는 순간 소계 값을 총계에 더하는 방법이 있다. 이 장은 뒤쪽을 택한다. 덧셈 횟수가 지점 수만큼으로 줄고, 소계를 비우는 자리와 총계에 더하는 자리가 한곳에 모여서 빠뜨리기 어렵다. 순서는 항상 출력, 총계에 합산, 소계 초기화다.

반복문은 키가 바뀔 때만 소계 처리 경로를 지나고, 파일 끝에서 마지막 소계와 총계를 출력한다.

SORT 문으로 미리 정렬하기

COBOL 은 정렬을 언어 안에 갖고 있다. SORT 문은 입력 파일을 읽어 지정한 키 순서로 정렬하고, 결과를 다른 파일에 쓴다. 정렬하는 동안 레코드를 임시로 담아 두는 작업 파일은 FD 가 아니라 SD(sort description)로 선언한다. SD 아래에 레코드 형식을 적고, 정렬 키는 이 레코드 안의 항목 이름으로 지정한다.

이 장에서 쓰는 SORT 문의 절
절역할예제에서
SORT 작업파일정렬 작업에 쓰는 SD 파일을 지정한다SORT-WORK
ON ASCENDING KEY오름차순 정렬 키를 왼쪽부터 우선순위 순으로 나열한다지점, 계좌, 구분, 금액
USING정렬할 입력 파일을 지정한다. 파일을 열고 닫는 일은 SORT 가 맡는다TRANS-FILE
GIVING정렬 결과를 쓸 출력 파일을 지정한다SORTED-FILE

정렬 키를 지점 코드 하나만 쓰면 같은 지점 안의 거래 순서는 정해지지 않을 수 있다. 소계는 순서와 상관없이 같지만, 상세 줄의 순서가 실행마다 달라지면 보고서를 비교하기 어렵다. 그래서 예제는 지점, 계좌 번호, 구분, 금액 순으로 키를 이어 붙여 결과 순서를 고정했다. 키가 완전히 같은 레코드끼리의 원래 순서까지 지켜야 한다면 SORT 문에 WITH DUPLICATES IN ORDER 절을 쓴다. 정렬 전후에 레코드를 걸러내거나 고치고 싶다면 USING, GIVING 대신 INPUT PROCEDURE, OUTPUT PROCEDURE 를 쓰는 방법도 있다. 절의 정확한 문법은 GnuCOBOL 프로그래머 가이드의 SORT 항목에서 확인할 수 있다.

완성 코드

trans.dat

거래 한 건은 17바이트다. 1~3째 자리는 지점 코드, 4~9째는 계좌 번호, 10째는 구분(D 는 입금, W 는 출금), 11~17째는 금액이다. 일부러 지점이 뒤섞인 순서로 적었다.

SEL100002D0300000
BUS200001D0120000
SEL100001D0500000
DAE300001W0070000
BUS200002W0045000
SEL100001W0150000
BUS200001D0030000
DAE300002D0250000
SEL100002W0100000

main.cob

      * CONTROL BREAK REPORT BY BRANCH
       IDENTIFICATION DIVISION.
       PROGRAM-ID. MAIN.

       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT TRANS-FILE ASSIGN TO "trans.dat"
               ORGANIZATION IS LINE SEQUENTIAL.
           SELECT SORT-WORK ASSIGN TO "sortwork.tmp".
           SELECT SORTED-FILE ASSIGN TO "sorted.dat"
               ORGANIZATION IS LINE SEQUENTIAL.

       DATA DIVISION.
       FILE SECTION.
       FD  TRANS-FILE.
       01  TRANS-REC               PIC X(17).

       SD  SORT-WORK.
       01  SORT-REC.
           05  SR-BRANCH           PIC X(3).
           05  SR-ACCT             PIC 9(6).
           05  SR-TYPE             PIC X(1).
           05  SR-AMOUNT           PIC 9(7).

       FD  SORTED-FILE.
       01  IN-REC.
           05  IN-BRANCH           PIC X(3).
           05  IN-ACCT             PIC 9(6).
           05  IN-TYPE             PIC X(1).
           05  IN-AMOUNT           PIC 9(7).

       WORKING-STORAGE SECTION.
       01  WS-EOF-FLAG             PIC X VALUE "N".
           88  END-OF-INPUT        VALUE "Y".
       01  WS-PREV-BRANCH          PIC X(3) VALUE SPACES.
       01  WS-NET                  PIC S9(9) VALUE 0.
       01  WS-BRANCH-TOTALS.
           05  WS-BR-COUNT         PIC 9(4) VALUE 0.
           05  WS-BR-DEPOSIT       PIC 9(9) VALUE 0.
           05  WS-BR-WITHDRAW      PIC 9(9) VALUE 0.
       01  WS-GRAND-TOTALS.
           05  WS-GR-COUNT         PIC 9(4) VALUE 0.
           05  WS-GR-DEPOSIT       PIC 9(9) VALUE 0.
           05  WS-GR-WITHDRAW      PIC 9(9) VALUE 0.
       01  WS-DASHES               PIC X(31) VALUE ALL "-".
       01  WS-DETAIL-LINE.
           05  FILLER              PIC X(2) VALUE SPACES.
           05  DL-ACCT             PIC 9(6).
           05  FILLER              PIC X(2) VALUE SPACES.
           05  DL-TYPE-NAME        PIC X(8).
           05  FILLER              PIC X(2) VALUE SPACES.
           05  DL-AMOUNT           PIC ZZZ,ZZZ,ZZ9.
       01  WS-SUB-LINE.
           05  SL-LABEL            PIC X(20).
           05  SL-AMOUNT           PIC ZZZ,ZZZ,ZZ9.
       01  WS-NET-LINE.
           05  NL-LABEL            PIC X(19).
           05  NL-AMOUNT           PIC -ZZZ,ZZZ,ZZ9.

       PROCEDURE DIVISION.
       MAIN-PARA.
           SORT SORT-WORK
               ON ASCENDING KEY SR-BRANCH SR-ACCT SR-TYPE SR-AMOUNT
               USING TRANS-FILE
               GIVING SORTED-FILE
           OPEN INPUT SORTED-FILE
           DISPLAY "DAILY TRANSACTIONS BY BRANCH"
           DISPLAY WS-DASHES
           PERFORM READ-NEXT
           IF NOT END-OF-INPUT
               MOVE IN-BRANCH TO WS-PREV-BRANCH
               DISPLAY "BRANCH: " IN-BRANCH
           END-IF
           PERFORM UNTIL END-OF-INPUT
               IF IN-BRANCH NOT = WS-PREV-BRANCH
                   PERFORM BRANCH-BREAK
                   MOVE IN-BRANCH TO WS-PREV-BRANCH
                   DISPLAY "BRANCH: " IN-BRANCH
               END-IF
               PERFORM PROCESS-DETAIL
               PERFORM READ-NEXT
           END-PERFORM
           IF WS-BR-COUNT > 0
               PERFORM BRANCH-BREAK
           END-IF
           PERFORM PRINT-GRAND
           CLOSE SORTED-FILE
           STOP RUN.

       READ-NEXT.
           READ SORTED-FILE
               AT END SET END-OF-INPUT TO TRUE
           END-READ.

       PROCESS-DETAIL.
           IF IN-TYPE = "D"
               MOVE "DEPOSIT" TO DL-TYPE-NAME
               ADD IN-AMOUNT TO WS-BR-DEPOSIT
           ELSE
               MOVE "WITHDRAW" TO DL-TYPE-NAME
               ADD IN-AMOUNT TO WS-BR-WITHDRAW
           END-IF
           ADD 1 TO WS-BR-COUNT
           MOVE IN-ACCT TO DL-ACCT
           MOVE IN-AMOUNT TO DL-AMOUNT
           DISPLAY WS-DETAIL-LINE.

       BRANCH-BREAK.
           DISPLAY "  End of " WS-PREV-BRANCH
           MOVE "  Subtotal count" TO SL-LABEL
           MOVE WS-BR-COUNT TO SL-AMOUNT
           DISPLAY WS-SUB-LINE
           MOVE "  Subtotal deposit" TO SL-LABEL
           MOVE WS-BR-DEPOSIT TO SL-AMOUNT
           DISPLAY WS-SUB-LINE
           MOVE "  Subtotal withdraw" TO SL-LABEL
           MOVE WS-BR-WITHDRAW TO SL-AMOUNT
           DISPLAY WS-SUB-LINE
           COMPUTE WS-NET = WS-BR-DEPOSIT - WS-BR-WITHDRAW
           MOVE "  Subtotal net" TO NL-LABEL
           MOVE WS-NET TO NL-AMOUNT
           DISPLAY WS-NET-LINE
           DISPLAY WS-DASHES
           ADD WS-BR-COUNT TO WS-GR-COUNT
           ADD WS-BR-DEPOSIT TO WS-GR-DEPOSIT
           ADD WS-BR-WITHDRAW TO WS-GR-WITHDRAW
           INITIALIZE WS-BRANCH-TOTALS.

       PRINT-GRAND.
           MOVE "TOTAL count" TO SL-LABEL
           MOVE WS-GR-COUNT TO SL-AMOUNT
           DISPLAY WS-SUB-LINE
           MOVE "TOTAL deposit" TO SL-LABEL
           MOVE WS-GR-DEPOSIT TO SL-AMOUNT
           DISPLAY WS-SUB-LINE
           MOVE "TOTAL withdraw" TO SL-LABEL
           MOVE WS-GR-WITHDRAW TO SL-AMOUNT
           DISPLAY WS-SUB-LINE
           COMPUTE WS-NET = WS-GR-DEPOSIT - WS-GR-WITHDRAW
           MOVE "TOTAL net" TO NL-LABEL
           MOVE WS-NET TO NL-AMOUNT
           DISPLAY WS-NET-LINE.

줄별 해설

파일 선언

SELECT 문이 세 개다. TRANS-FILE 은 정렬 전 입력이고, SORTED-FILE 은 정렬 결과가 담기는 파일이다. 두 파일은 줄 단위로 읽고 쓰는 LINE SEQUENTIAL 이다. SORT-WORK 는 정렬 작업용이어서 ORGANIZATION 절이 없다. 파일 이름 sortwork.tmp 는 형식상 적어 둔 것이고, 실제 작업 공간은 정렬 기능이 알아서 관리한다.

FD TRANS-FILE 의 레코드는 17바이트짜리 문자 하나로만 선언했다. 이 파일은 SORT 가 통째로 읽어 가기 때문에 프로그램이 항목별로 들여다볼 일이 없다. 정렬 키가 되는 항목은 SD 의 SORT-REC 에 있고, 항목 배치가 입력 레코드(1~3째 지점, 4~9째 계좌, 10째 구분, 11~17째 금액)와 정확히 겹쳐야 한다. 정렬된 결과를 읽는 FD SORTED-FILE 의 IN-REC 도 같은 배치로 선언했다. 세 레코드의 길이는 모두 17바이트다.

작업 영역

WS-EOF-FLAG 와 조건 이름 END-OF-INPUT 은 파일 끝 표시다. WS-PREV-BRANCH 가 앞에서 말한 이전 키 칸이다. 소계용 WS-BRANCH-TOTALS 와 총계용 WS-GRAND-TOTALS 는 같은 모양이다. 건수, 입금 합계, 출금 합계를 담는다. 소계를 한 번에 비우려고 세 칸을 한 그룹으로 묶었다. WS-NET 은 입금에서 출금을 뺀 값이므로 음수가 될 수 있어서 부호 있는 S9(9) 로 선언했다.

출력 줄은 세 가지다. WS-DETAIL-LINE 은 상세 줄, WS-SUB-LINE 은 이름표 하나와 금액 하나로 된 합계 줄, WS-NET-LINE 은 부호가 붙을 수 있는 순증감액 줄이다. 금액 칸의 ZZZ,ZZZ,ZZ9 는 앞쪽의 0을 공백으로 바꾸고 세 자리마다 쉼표를 넣는 편집 형식이다. NL-AMOUNT 의 맨 앞 하이픈은 음수일 때만 마이너스 기호가 되고, 양수이면 공백이 된다. 세 줄 모두 오른쪽 끝이 31칸에 맞도록 너비를 정했다.

MAIN-PARA

SORT 문이 가장 먼저 실행된다. 이 한 문장이 TRANS-FILE 을 열어 읽고, 네 개 키로 정렬한 뒤, SORTED-FILE 을 만들어 닫는다. 그래서 프로그램에는 TRANS-FILE 의 OPEN 이나 CLOSE 가 없다. 이어서 정렬 결과를 OPEN INPUT 으로 연다.

READ-NEXT 를 한 번 실행하는 것이 프라이밍 읽기다. 입력이 비어 있지 않으면 첫 레코드의 지점을 이전 키에 넣고 지점 머리글을 출력한다. 입력이 비어 있으면 END-OF-INPUT 이 이미 켜져서 반복문이 한 번도 돌지 않는다.

PERFORM UNTIL 반복의 몸통은 세 단계다. 먼저 현재 키와 이전 키를 비교한다. 다르면 BRANCH-BREAK 로 이전 묶음을 마무리하고, 그다음에 이전 키를 새 키로 갱신하고 새 머리글을 출력한다. 갱신이 소계 출력 뒤에 오는 순서를 눈여겨보라. 그다음 PROCESS-DETAIL 로 현재 레코드를 처리하고, 마지막에 다음 레코드를 읽는다. 읽기가 반복문 맨 끝에 있기 때문에, 파일 끝에서 UNTIL 조건이 참이 되면 처리되지 않은 레코드는 남지 않는다.

반복문 뒤의 IF WS-BR-COUNT > 0 이 마지막 묶음 처리다. 지금 모으는 소계 건수가 0보다 크다는 것은 아직 출력하지 못한 묶음이 있다는 뜻이다. 거래가 한 건도 없으면 건수가 0이어서 소계가 출력되지 않는다. 이후 PRINT-GRAND 가 총계를 찍고 파일을 닫는다.

PROCESS-DETAIL 과 BRANCH-BREAK

PROCESS-DETAIL 은 구분이 D 이면 입금 합계에, 아니면 출금 합계에 금액을 더하고 건수를 하나 올린 뒤 상세 줄을 출력한다. BRANCH-BREAK 는 네 줄의 소계를 출력하고 구분선을 찍는다. 이어서 소계 세 칸을 총계에 더한 다음 INITIALIZE 로 소계 그룹을 0으로 되돌린다. "출력, 합산, 초기화" 순서가 한 문단 안에 있으므로, 소계가 필요한 곳에서는 이 문단 하나만 실행하면 된다. 소계 제목 줄의 WS-PREV-BRANCH 는 이 시점에 아직 이전 지점의 코드를 갖고 있다.

실행 결과

세 파일을 같은 폴더에 두고 컴파일한 뒤 실행한다. 실행하면 같은 폴더에 정렬 결과 sorted.dat 가 생긴다.

$ cobc -x -Wall main.cob
$ ./main
DAILY TRANSACTIONS BY BRANCH
-------------------------------
BRANCH: BUS
  200001  DEPOSIT        30,000
  200001  DEPOSIT       120,000
  200002  WITHDRAW       45,000
  End of BUS
  Subtotal count              3
  Subtotal deposit      150,000
  Subtotal withdraw      45,000
  Subtotal net          105,000
-------------------------------
BRANCH: DAE
  300001  WITHDRAW       70,000
  300002  DEPOSIT       250,000
  End of DAE
  Subtotal count              2
  Subtotal deposit      250,000
  Subtotal withdraw      70,000
  Subtotal net          180,000
-------------------------------
BRANCH: SEL
  100001  DEPOSIT       500,000
  100001  WITHDRAW      150,000
  100002  DEPOSIT       300,000
  100002  WITHDRAW      100,000
  End of SEL
  Subtotal count              4
  Subtotal deposit      800,000
  Subtotal withdraw     250,000
  Subtotal net          550,000
-------------------------------
TOTAL count                   9
TOTAL deposit         1,200,000
TOTAL withdraw          365,000
TOTAL net               835,000

지점은 문자 순서(BUS, DAE, SEL)로 나온다. 세 소계의 건수를 더하면 9, 입금은 150,000+250,000+800,000=1,200,000, 출금은 45,000+70,000+250,000=365,000이므로 총계와 일치한다. 총계가 소계의 합과 맞는지 확인하는 것은 제어 단절 프로그램을 검증하는 가장 쉬운 방법이다.

실무에서 자주 틀리는 것

정렬하지 않은 파일을 그대로 읽는다

SORT 를 빼고 trans.dat 를 바로 읽으면 프로그램은 오류 없이 돌아가지만 보고서가 틀린다.

           OPEN INPUT TRANS-FILE
           READ TRANS-FILE
           ...

입력 순서가 SEL, BUS, SEL, DAE, BUS, SEL, BUS, DAE, SEL 이므로 지점이 바뀔 때마다 소계가 찍힌다. 결과적으로 SEL 소계가 세 번, BUS 소계가 두 번 나오고 어느 것도 전체 합계가 아니다. 컴파일러와 실행기는 이를 알려 주지 않는다. 고친 코드는 이 장의 방식 그대로, 읽기 전에 SORT 를 실행하고 그 결과 파일을 읽는 것이다.

           SORT SORT-WORK
               ON ASCENDING KEY SR-BRANCH SR-ACCT SR-TYPE SR-AMOUNT
               USING TRANS-FILE
               GIVING SORTED-FILE
           OPEN INPUT SORTED-FILE

마지막 묶음의 소계를 빠뜨린다

반복문 안에서만 소계를 출력하면 마지막 지점이 사라진다.

           PERFORM UNTIL END-OF-INPUT
               ...
           END-PERFORM
           PERFORM PRINT-GRAND

마지막 SEL 묶음 뒤에는 키가 바뀌는 레코드가 없다. 그 결과 SEL 소계가 출력되지 않고, 총계에도 SEL 의 금액이 들어가지 않는다. 총계가 보고서에 보이는 소계들의 합과 맞지 않으면 이 오류를 의심하라. 고치려면 반복문이 끝난 직후에 한 번 더 소계를 처리한다.

           END-PERFORM
           IF WS-BR-COUNT > 0
               PERFORM BRANCH-BREAK
           END-IF
           PERFORM PRINT-GRAND

첫 레코드에서 빈 소계가 나온다

프라이밍 읽기 없이 이전 키를 공백으로 둔 채 반복문에 들어가는 경우다.

           MOVE SPACES TO WS-PREV-BRANCH
           PERFORM UNTIL END-OF-INPUT
               READ SORTED-FILE
                   AT END SET END-OF-INPUT TO TRUE
               END-READ
               IF IN-BRANCH NOT = WS-PREV-BRANCH
                   PERFORM BRANCH-BREAK

첫 레코드의 지점은 공백이 아니므로 키 변화로 판정되어, 지점 이름이 빈 건수 0짜리 소계가 맨 앞에 출력된다. 또 이 모양은 파일 끝에서 마지막 READ 가 실패한 뒤에도 몸통이 한 번 더 실행되는 문제를 안고 있다. 고친 코드는 반복문 앞에서 한 건을 읽고 이전 키에 저장하는 것이다.

           PERFORM READ-NEXT
           IF NOT END-OF-INPUT
               MOVE IN-BRANCH TO WS-PREV-BRANCH
               DISPLAY "BRANCH: " IN-BRANCH
           END-IF

소계 칸을 비우지 않는다

BRANCH-BREAK 에서 총계에 더하기만 하고 초기화를 빠뜨린 경우다.

           ADD WS-BR-COUNT TO WS-GR-COUNT
           ADD WS-BR-DEPOSIT TO WS-GR-DEPOSIT
           ADD WS-BR-WITHDRAW TO WS-GR-WITHDRAW
      *    (INITIALIZE 가 없다)

이러면 BUS 의 건수 3이 그대로 남아 DAE 소계는 5건, SEL 소계는 9건으로 출력된다. 총계도 이중으로 더해져서 부풀려진다. 첫 번째 소계는 맞게 나오기 때문에 눈에 잘 띄지 않는다. 합산 뒤에 소계 그룹을 비우는 문장을 같은 문단에 두면 이 실수를 막을 수 있다.

           ADD WS-BR-WITHDRAW TO WS-GR-WITHDRAW
           INITIALIZE WS-BRANCH-TOTALS.

한눈에 보기

제어 단절 보고서의 구성 요소
요소방법기억할 점
정렬SORT ... ON ASCENDING KEY ... USING ... GIVING ...입력이 키 순서가 아니면 소계가 중복된다
이전 키작업 영역에 한 칸을 두고 새 키 처리 뒤에 갱신한다소계 출력이 먼저이고 갱신이 나중이다
첫 레코드반복문 앞의 프라이밍 읽기이전 키를 첫 레코드로 채운다
키 변화현재 키와 이전 키 비교출력, 합산, 초기화의 순서를 한 문단에 둔다
마지막 레코드반복문 뒤에서 소계를 한 번 더 처리미처리 건수가 0이면 건너뛴다
총계소계를 출력할 때 총계에 더한다총계는 소계 합과 반드시 맞아야 한다

이 장의 BRANCH-BREAK 처럼 한 가지 일을 맡은 문단은 다른 프로그램에서도 쓰고 싶어진다. 레코드 형식 선언도 여러 프로그램이 똑같이 반복해서 적게 된다. 다음 장에서는 이런 코드를 서브프로그램과 카피북으로 나누는 방법을 다룬다.

연습 문제

  1. SORT 문의 첫 키 SR-BRANCH 앞의 ASCENDING 을 DESCENDING 으로 바꾸면 보고서에서 지점은 어떤 순서로 나오는가. 소계와 총계의 값은 달라지는가.
  2. trans.dat 가 내용 없는 빈 파일이면 이 프로그램은 무엇을 출력하는가. 어느 문장 때문에 그렇게 되는지 설명하라.
  3. 지점 소계에 입금 건수와 출금 건수를 따로 보여 주려면 작업 영역과 문단을 어떻게 고쳐야 하는가. 고칠 위치를 모두 적어라.
  4. 지점별로 가장 큰 거래 금액을 소계 뒤에 한 줄 더 출력하려 한다. 최대값을 담을 칸을 어디에서 갱신하고, 어디에서 초기화해야 하는가.

정답과 해설

  1. 지점은 SEL, DAE, BUS 순으로 나온다. 다른 세 키(계좌, 구분, 금액)는 오름차순이 그대로여서 지점 안의 순서는 변하지 않는다. 각 지점의 소계와 총계의 값은 그대로다. 정렬은 묶음의 순서만 바꾸고 묶음 안의 내용은 바꾸지 않기 때문이다.
  2. 머리 두 줄과 총계 네 줄만 출력된다. 프라이밍 읽기에서 곧바로 파일 끝이 되어 END-OF-INPUT 이 켜지므로 첫 IF 의 몸통과 PERFORM UNTIL 반복이 실행되지 않는다. 반복문 뒤의 IF WS-BR-COUNT > 0 은 건수가 0이라 거짓이어서 소계가 출력되지 않는다. 총계의 건수, 입금, 출금은 0으로, 순증감액도 0으로 출력된다.
  3. WS-BRANCH-TOTALS 에 입금 건수와 출금 건수 칸 두 개를 추가한다. PROCESS-DETAIL 의 D 쪽과 ELSE 쪽에서 각각 하나씩 올린다. BRANCH-BREAK 에서 두 줄을 출력하고 총계 칸에 더하며, INITIALIZE 가 새 칸도 비우게 한다. 총계 그룹과 PRINT-GRAND 도 같은 방식으로 고친다.
  4. WS-BRANCH-TOTALS 그룹 안에 최대값 칸을 하나 넣는다. PROCESS-DETAIL 에서 IF IN-AMOUNT > 최대값 칸 이면 그 칸에 IN-AMOUNT 를 옮겨서 갱신한다. 초기화는 BRANCH-BREAK 끝의 INITIALIZE 가 맡는다. 최대값 칸이 그룹 안에 있으면 소계와 함께 0으로 돌아가므로 다음 지점의 값이 섞이지 않는다. 출력은 소계를 비우기 전에 해야 한다.

댓글 0

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

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