Devin.KR

COBOL · 기본

데이터 처리로 배우는 COBOL

완성 프로젝트 - 일일 거래 일괄 처리

계좌 마스터와 거래 파일 대조(매칭), 잔액 갱신·새 마스터 쓰기, 오류 거래 목록, 처리 요약 보고서, 메인프레임(JCL·VSAM·DB2)에서는 무엇이 달라지는가

개발자KR · 원고 갱신

이 장에서 배우는 것

이 책은 작은 신용협동조합의 예금 계좌를 일괄 처리(batch)하는 프로그램을 장마다 키워 왔다. 이 장에서는 지금까지 익힌 데이터 정의, 계산, 편집, 조건과 반복, 순차 파일 읽기와 쓰기를 한 프로그램에 모은다. 하루치 입출금 거래를 계좌 마스터 파일에 반영하고, 새 마스터 파일과 오류 거래 목록, 처리 요약을 만든다. 마지막에는 같은 일을 메인프레임에서 하면 무엇이 달라지는지 짚는다.

  • 마스터 파일과 거래 파일을 계좌번호로 대조(매칭)하는 반복 구조를 만든다.
  • 입금과 출금을 검증하고 잔액을 갱신해 새 마스터 파일에 쓴다.
  • 처리할 수 없는 거래를 사유와 함께 오류 목록으로 모은다.
  • 건수와 합계로 결과가 맞는지 스스로 검증하는 요약 보고서를 만든다.
  • JCL, VSAM, DB2 가 이 구조를 어떻게 바꾸는지 이해한다.

문제 상황

조합의 업무가 끝나면 창구에서 받은 입금과 출금 기록이 하루 분량의 거래 파일로 모인다. 계좌 정보를 담은 파일은 마스터 파일(master file)이라 하고, 하루 동안 쌓인 변동 기록을 담은 파일은 거래 파일(transaction file)이라 한다. 밤사이 프로그램이 거래를 마스터에 반영해서 다음 날 아침에 쓸 새 마스터를 만들어 놓아야 한다.

거래 중에는 처리할 수 없는 것이 섞여 있다. 없는 계좌번호에 대한 입금, 해지된 계좌에 대한 입금, 잔액보다 큰 출금, 유형 글자가 잘못된 거래, 금액이 0 인 거래가 그 예다. 이런 거래를 조용히 버리면 직원이 원인을 찾을 수 없고, 거꾸로 무리하게 반영하면 잔액이 틀어진다. 그래서 프로그램은 처리한 것과 거절한 것을 모두 기록하고, 마지막에 건수와 합계가 서로 맞는지 보여 주어야 한다.

아래 그림은 이 장의 프로그램이 읽고 쓰는 것을 보여 준다. 입력은 두 개이고 출력은 세 개다.

입력 두 개(마스터, 거래)를 MAIN 이 대조해 새 마스터, 오류 목록, 요약 보고서를 만든다.

두 파일을 맞대어 읽는 대조 방식

정렬되어 있다는 전제

마스터 파일의 모든 계좌에 대해 거래 파일을 처음부터 다시 훑는 방법도 있지만, 계좌가 수만 개이면 읽기 횟수가 너무 많아진다. 두 파일이 모두 같은 키(여기서는 계좌번호)의 오름차순으로 정렬되어 있다면 각 파일을 한 번씩만 앞으로 읽으면 된다. 같은 계좌의 거래가 여러 건이면 파일에 들어 있는 순서가 그대로 처리 순서가 된다. 이 방식을 대조(match)라고 부르며, 일괄 처리의 기본 틀이다.

세 가지 경우

프로그램은 마스터 파일의 현재 레코드와 거래 파일의 현재 레코드를 항상 하나씩 들고 있다. 매 반복마다 두 계좌번호를 비교하고, 결과는 세 가지 중 하나다. 거래 키를 T, 마스터 키를 M 이라 하자.

T 와 M 의 비교 결과는 세 가지이며, 각각 오류 처리, 거래 적용, 마스터 쓰기로 이어진다.

T 가 M 보다 작으면 이 거래가 가리키는 계좌는 마스터에 없다. 정렬되어 있으므로 나중에 나타날 수도 없다. T 와 M 이 같으면 거래를 적용한다. T 가 M 보다 크면 현재 마스터 계좌에 대한 거래는 이미 모두 끝났다는 뜻이므로 그 계좌를 새 마스터에 쓰고 다음 마스터를 읽는다. 마스터를 쓰는 시점은 이 경우뿐이라는 점이 중요하다. 같은 계좌의 거래가 여러 건일 수 있어서, 거래를 한 건 적용했다고 바로 쓰면 다음 거래가 반영되지 않는다.

파일 끝 처리

한쪽 파일이 먼저 끝나는 경우가 있다. 이 장의 프로그램은 파일 끝에 도달하면 해당 키를 999999 로 바꿔 놓는다. 이 값은 모든 실제 계좌번호보다 크다고 약속한 끝 표시다. 거래가 먼저 끝나면 T 가 항상 더 크므로 남은 마스터가 모두 새 파일로 복사되고, 마스터가 먼저 끝나면 남은 거래는 모두 "계좌 없음"이 된다. 반복은 두 키가 모두 끝 표시일 때 끝난다. 별도의 예외 처리 없이 세 가지 경우만으로 파일 끝까지 해결된다.

거래 검증과 통제 합계

거절 사유

계좌가 맞더라도 거래를 바로 반영하면 안 된다. 아래 순서대로 검사하고, 처음 걸린 사유 하나만 기록한다.

거래 검증 순서와 거절 사유
사유조건처리이 장의 예
NO ACCOUNT거래 키가 마스터에 없음오류 목록100250, 100999
BAD TYPE유형이 D 도 W 도 아님오류 목록100303 의 X
BAD AMOUNT금액이 0오류 목록100505 의 0원 입금
CLOSED ACCOUNT계좌 상태가 C오류 목록100404
INSUFFICIENT FUNDS출금액이 잔액보다 큼오류 목록100202 출금 30,000

잔액 필드는 부호가 없는 9 자리 숫자(PIC 9(9))다. 입금 결과가 9 자리를 넘으면 값이 잘리므로, ADD 에 ON SIZE ERROR 를 붙여 "BALANCE OVERFLOW" 로 거절한다. 출금은 뺄셈 전에 잔액과 먼저 비교한다.

통제 합계로 자기 검증하기

일괄 처리 프로그램은 사람이 지켜보지 않는 시간에 돈을 다룬다. 그래서 결과가 맞는지 프로그램이 스스로 확인하게 만든다. 읽기 시작할 때 마스터 잔액의 합(기초 합계)을 구하고, 적용한 입금 합계와 출금 합계를 따로 모은다. 새 마스터를 쓸 때는 잔액 합(기말 합계)을 따로 구한다. 이때 기초 합계 + 입금 합계 − 출금 합계가 기말 합계와 같아야 한다. 이렇게 별도 경로로 구한 합계를 통제 합계(control total)라고 한다. 두 값이 다르면 프로그램이 어딘가에서 레코드를 빠뜨렸거나 이중으로 반영했다는 신호다.

완성 코드

세 파일을 같은 폴더에 두고 컴파일한다. 코드의 화면 출력 문구는 고정 형식 72열 제한과 글자 폭 때문에 영문으로 썼다. 실무에서는 레코드 배치를 카피북(.cpy)으로 빼서 쓰지만, 이 장에서는 한눈에 보도록 한 파일에 모았다.

main.cob

       IDENTIFICATION DIVISION.
       PROGRAM-ID. MAIN.
      * 일일 거래 일괄 처리
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT OLD-MASTER ASSIGN TO "master.dat"
               ORGANIZATION IS LINE SEQUENTIAL
               FILE STATUS IS WS-FS-OLD.
           SELECT TRANS-FILE ASSIGN TO "trans.dat"
               ORGANIZATION IS LINE SEQUENTIAL
               FILE STATUS IS WS-FS-TRN.
           SELECT NEW-MASTER ASSIGN TO "newmast.dat"
               ORGANIZATION IS LINE SEQUENTIAL
               FILE STATUS IS WS-FS-NEW.

       DATA DIVISION.
       FILE SECTION.
       FD  OLD-MASTER.
       01  OLD-REC                 PIC X(26).
       FD  TRANS-FILE.
       01  TRN-REC                 PIC X(16).
       FD  NEW-MASTER.
       01  NEW-REC                 PIC X(26).

       WORKING-STORAGE SECTION.
       01  WS-FS-OLD               PIC XX.
       01  WS-FS-TRN               PIC XX.
       01  WS-FS-NEW               PIC XX.
       01  HIGH-KEY                PIC 9(6) VALUE 999999.
       01  WS-MAST.
           05  WM-ACCT             PIC 9(6).
           05  WM-NAME             PIC X(10).
           05  WM-STATUS           PIC X.
               88  WM-CLOSED       VALUE "C".
           05  WM-BAL              PIC 9(9).
       01  WS-TRN.
           05  WT-ACCT             PIC 9(6).
           05  WT-TYPE             PIC X.
               88  WT-DEPOSIT      VALUE "D".
               88  WT-WITHDRAW     VALUE "W".
           05  WT-AMT              PIC 9(9).
       01  WS-COUNTS.
           05  CT-MAST-IN          PIC 9(4) VALUE 0.
           05  CT-TRN-IN           PIC 9(4) VALUE 0.
           05  CT-NEW              PIC 9(4) VALUE 0.
           05  CT-DEP              PIC 9(4) VALUE 0.
           05  CT-WDR              PIC 9(4) VALUE 0.
           05  CT-REJ              PIC 9(4) VALUE 0.
       01  WS-TOTALS.
           05  TT-OPEN             PIC 9(11) VALUE 0.
           05  TT-DEP              PIC 9(11) VALUE 0.
           05  TT-WDR              PIC 9(11) VALUE 0.
           05  TT-CLOSE            PIC 9(11) VALUE 0.
           05  TT-EXPECT           PIC 9(11) VALUE 0.
       01  WS-REASON               PIC X(20).
       01  ED-CNT                  PIC ZZZ9.
       01  ED-AMT                  PIC ZZZ,ZZZ,ZZ9.
       01  ED-TOT                  PIC ZZ,ZZZ,ZZZ,ZZ9.

       PROCEDURE DIVISION.
       0000-MAIN.
           PERFORM 1000-OPEN-FILES
           PERFORM 2000-READ-MASTER
           PERFORM 2100-READ-TRANS
           DISPLAY "REJECTED TRANSACTIONS"
           DISPLAY "  NO  ACCT    T       AMOUNT  REASON"
           PERFORM 3000-MATCH-STEP
               UNTIL WM-ACCT = HIGH-KEY AND WT-ACCT = HIGH-KEY
           CLOSE OLD-MASTER TRANS-FILE NEW-MASTER
           PERFORM 8000-SUMMARY
           PERFORM 9000-LIST-NEW-MASTER
           STOP RUN.

       1000-OPEN-FILES.
           OPEN INPUT OLD-MASTER TRANS-FILE
                OUTPUT NEW-MASTER
           IF WS-FS-OLD NOT = "00" OR WS-FS-TRN NOT = "00"
              OR WS-FS-NEW NOT = "00"
               DISPLAY "OPEN FAILED: " WS-FS-OLD " " WS-FS-TRN
                   " " WS-FS-NEW
               MOVE 8 TO RETURN-CODE
               STOP RUN
           END-IF.

       2000-READ-MASTER.
           READ OLD-MASTER INTO WS-MAST
               AT END
                   MOVE HIGH-KEY TO WM-ACCT
               NOT AT END
                   ADD 1 TO CT-MAST-IN
                   ADD WM-BAL TO TT-OPEN
           END-READ.

       2100-READ-TRANS.
           READ TRANS-FILE INTO WS-TRN
               AT END
                   MOVE HIGH-KEY TO WT-ACCT
               NOT AT END
                   ADD 1 TO CT-TRN-IN
           END-READ.

       3000-MATCH-STEP.
           EVALUATE TRUE
      * 거래 키가 작다: 짝 없는 거래
               WHEN WT-ACCT < WM-ACCT
                   MOVE "NO ACCOUNT" TO WS-REASON
                   PERFORM 4500-REJECT
                   PERFORM 2100-READ-TRANS
      * 키가 같다: 거래를 적용한다
               WHEN WT-ACCT = WM-ACCT
                   PERFORM 4000-APPLY
                   PERFORM 2100-READ-TRANS
      * 마스터 키가 작다: 마스터 완료
               WHEN OTHER
                   PERFORM 5000-WRITE-NEW
                   PERFORM 2000-READ-MASTER
           END-EVALUATE.

       4000-APPLY.
           EVALUATE TRUE
               WHEN NOT WT-DEPOSIT AND NOT WT-WITHDRAW
                   MOVE "BAD TYPE" TO WS-REASON
                   PERFORM 4500-REJECT
               WHEN WT-AMT = 0
                   MOVE "BAD AMOUNT" TO WS-REASON
                   PERFORM 4500-REJECT
               WHEN WM-CLOSED
                   MOVE "CLOSED ACCOUNT" TO WS-REASON
                   PERFORM 4500-REJECT
               WHEN WT-DEPOSIT
                   ADD WT-AMT TO WM-BAL
                       ON SIZE ERROR
                           MOVE "BALANCE OVERFLOW" TO WS-REASON
                           PERFORM 4500-REJECT
                       NOT ON SIZE ERROR
                           ADD 1 TO CT-DEP
                           ADD WT-AMT TO TT-DEP
                   END-ADD
               WHEN WT-AMT > WM-BAL
                   MOVE "INSUFFICIENT FUNDS" TO WS-REASON
                   PERFORM 4500-REJECT
               WHEN OTHER
                   SUBTRACT WT-AMT FROM WM-BAL
                   ADD 1 TO CT-WDR
                   ADD WT-AMT TO TT-WDR
           END-EVALUATE.

       4500-REJECT.
           ADD 1 TO CT-REJ
           MOVE CT-TRN-IN TO ED-CNT
           MOVE WT-AMT TO ED-AMT
           DISPLAY ED-CNT "  " WT-ACCT "  " WT-TYPE "  "
               ED-AMT "  " FUNCTION TRIM(WS-REASON TRAILING).

       5000-WRITE-NEW.
           WRITE NEW-REC FROM WS-MAST
           ADD 1 TO CT-NEW
           ADD WM-BAL TO TT-CLOSE.

       8000-SUMMARY.
           COMPUTE TT-EXPECT = TT-OPEN + TT-DEP - TT-WDR
           DISPLAY "DAILY BATCH SUMMARY"
           DISPLAY "-------------------"
           MOVE CT-MAST-IN TO ED-CNT
           DISPLAY "MASTER RECORDS READ   : " ED-CNT
           MOVE CT-TRN-IN TO ED-CNT
           DISPLAY "TRANSACTIONS READ     : " ED-CNT
           MOVE CT-NEW TO ED-CNT
           DISPLAY "NEW MASTER RECORDS    : " ED-CNT
           MOVE CT-DEP TO ED-CNT
           MOVE TT-DEP TO ED-TOT
           DISPLAY "DEPOSITS APPLIED      : " ED-CNT "  " ED-TOT
           MOVE CT-WDR TO ED-CNT
           MOVE TT-WDR TO ED-TOT
           DISPLAY "WITHDRAWALS APPLIED   : " ED-CNT "  " ED-TOT
           MOVE CT-REJ TO ED-CNT
           DISPLAY "REJECTED              : " ED-CNT
           MOVE TT-OPEN TO ED-TOT
           DISPLAY "OPENING BALANCE TOTAL : " ED-TOT
           MOVE TT-CLOSE TO ED-TOT
           DISPLAY "CLOSING BALANCE TOTAL : " ED-TOT
           IF TT-CLOSE = TT-EXPECT
               DISPLAY "CONTROL CHECK         : OK"
           ELSE
               DISPLAY "CONTROL CHECK         : MISMATCH"
               MOVE 12 TO RETURN-CODE
           END-IF.

       9000-LIST-NEW-MASTER.
           DISPLAY "NEW MASTER FILE"
           OPEN INPUT NEW-MASTER
           PERFORM UNTIL WS-FS-NEW NOT = "00"
               READ NEW-MASTER INTO WS-MAST
                   AT END
                       CONTINUE
                   NOT AT END
                       MOVE WM-BAL TO ED-AMT
                       DISPLAY WM-ACCT "  " WM-NAME "  "
                           WM-STATUS "  " ED-AMT
               END-READ
           END-PERFORM
           CLOSE NEW-MASTER.

master.dat

레코드 길이는 26 바이트다. 계좌번호 6, 이름 10, 상태 1(A 정상, C 해지), 잔액 9 자리 순으로 붙어 있고 구분 문자는 없다. 이름은 10 칸을 채우도록 뒤에 공백을 붙였다.

100101KIM MINSU A000150000
100202LEE JIWON A000020000
100303PARK SUJINA000500000
100404CHOI HANA C000000000
100505JUNG DOYUNA001200000

trans.dat

레코드 길이는 16 바이트다. 계좌번호 6, 유형 1(D 입금, W 출금), 금액 9 자리다.

100101D000050000
100101W000030000
100202W000030000
100250D000010000
100303W000200000
100303X000010000
100404D000070000
100505D000000000
100505D000300000
100505W000500000
100999D000005000

줄별 해설

파일 선언

SELECT 세 개는 프로그램 안의 파일 이름(OLD-MASTER 등)과 디스크의 실제 파일(master.dat 등)을 잇는다. LINE SEQUENTIAL 은 한 줄이 한 레코드인 텍스트 형태 파일이다. FILE STATUS 에 지정한 두 글자 변수에는 입출력이 끝날 때마다 결과 코드가 담기며, "00" 은 정상이다. FD 아래의 레코드 이름은 단순히 크기만 정해 두고, 실제 필드 분해는 작업 영역(WORKING-STORAGE)의 WS-MAST 와 WS-TRN 이 맡는다. READ 와 WRITE 에 INTO, FROM 을 붙여서 두 영역 사이를 옮긴다.

끝 표시와 필드 정의

HIGH-KEY 는 파일 끝을 뜻하는 999999 다. 실제 계좌번호는 이보다 작아야 한다. WM-CLOSED, WT-DEPOSIT, WT-WITHDRAW 는 조건 이름(88 레벨)이어서 IF 와 EVALUATE 에서 값 비교 대신 이름으로 읽힌다. 금액과 잔액은 부호 없는 숫자이므로 음수가 될 수 없다는 점을 코드가 보장해 주지 않고, 출금 전 비교로 직접 막아야 한다. 합계 필드는 11 자리로 넉넉히 잡아 하루치 합이 넘치지 않게 했다.

0000-MAIN

파일을 열고 마스터와 거래를 각각 한 건씩 미리 읽어 둔다. 대조 반복은 두 키가 모두 HIGH-KEY 일 때 끝난다. 반복이 끝나면 파일을 닫고 요약을 찍은 뒤, 쓴 새 마스터를 다시 열어 내용을 보여 준다. 반복 본체는 3000-MATCH-STEP 한 단락이고, 한 번 실행할 때마다 마스터 또는 거래 중 하나를 반드시 한 건 앞으로 읽는다. 그래서 반복은 반드시 끝난다.

1000-OPEN-FILES, 2000·2100 읽기 단락

열기에 실패하면 상태 코드를 보여 주고 RETURN-CODE 8 로 끝낸다. 운영 환경에서는 이 값을 스케줄러가 읽어 다음 단계를 멈춘다. 읽기 단락은 AT END 에서 키를 HIGH-KEY 로 바꾸고, 정상적으로 읽었을 때만 건수를 센다. 마스터를 읽을 때 기초 합계(TT-OPEN)도 함께 더한다. 이 합계는 갱신 이전의 잔액으로 만들어지므로, 나중에 계산한 기말 합계와 독립적인 검증 기준이 된다.

3000-MATCH-STEP

EVALUATE TRUE 의 세 갈래가 앞에서 본 세 가지 경우다. 첫째 갈래는 이유를 적고 4500-REJECT 를 부른 뒤 거래를 읽는다. 둘째 갈래는 4000-APPLY 를 부른 뒤 거래를 읽는다. 셋째 갈래는 마스터를 새 파일에 쓰고 마스터를 읽는다. WHEN OTHER 에 해당하는 경우는 마스터 키가 거래 키보다 작은 때뿐이다. 앞의 두 조건이 이미 나머지 경우를 걸러 내기 때문이다.

4000-APPLY

WHEN 은 위에서부터 차례로 검사해 처음 참인 것 하나만 실행한다. 그래서 순서가 곧 검증 우선순위다. 유형 검사, 금액 검사, 해지 검사를 먼저 하고, 입금은 ON SIZE ERROR 로 자릿수 넘침을 막으며, 출금은 잔액 비교 후에 SUBTRACT 를 실행한다. 거절 경로에서는 잔액을 건드리지 않고, 적용 경로에서만 건수와 합계를 올린다. WM-BAL 은 작업 영역의 현재 마스터 레코드이므로, 같은 계좌의 다음 거래가 갱신된 잔액을 이어서 쓴다.

4500-REJECT

거절 건수를 올리고, 지금까지 읽은 거래 수(곧 현재 거래의 파일 안 순번)를 ZZZ9 로 편집해 보여 준다. 금액은 ZZZ,ZZZ,ZZ9 로 편집하며 0 은 마지막 자리에서 "0" 으로 남는다. 사유 문자열은 20 칸에 공백이 채워져 있어 FUNCTION TRIM 으로 뒤쪽 공백을 잘라 낸다. 그러지 않으면 모든 줄 끝에 공백이 붙는다.

5000-WRITE-NEW

작업 영역의 마스터 레코드를 새 마스터 파일에 쓰고, 쓴 건수와 기말 합계를 올린다. 기말 합계는 새 마스터에 실제로 쓴 레코드에서 모은 것이므로 파일의 내용과 직접 연결된다.

8000-SUMMARY, 9000-LIST-NEW-MASTER

COMPUTE 로 기대 기말 합계(기초 + 입금 − 출금)를 구해 실제 기말 합계와 비교한다. 같으면 OK, 다르면 MISMATCH 와 함께 RETURN-CODE 12 를 남긴다. 마지막 단락은 새 마스터를 입력으로 다시 열어 한 줄씩 보여 준다. 결과를 눈으로 확인하는 용도이며, 파일이 실제로 쓰였는지도 함께 증명한다. 읽기가 끝나면 상태 코드가 "10" 으로 바뀌어 PERFORM UNTIL 이 끝난다.

실행 결과

$ cobc -x -Wall main.cob
$ ./main
REJECTED TRANSACTIONS
  NO  ACCT    T       AMOUNT  REASON
   3  100202  W       30,000  INSUFFICIENT FUNDS
   4  100250  D       10,000  NO ACCOUNT
   6  100303  X       10,000  BAD TYPE
   7  100404  D       70,000  CLOSED ACCOUNT
   8  100505  D            0  BAD AMOUNT
  11  100999  D        5,000  NO ACCOUNT
DAILY BATCH SUMMARY
-------------------
MASTER RECORDS READ   :    5
TRANSACTIONS READ     :   11
NEW MASTER RECORDS    :    5
DEPOSITS APPLIED      :    2         350,000
WITHDRAWALS APPLIED   :    3         730,000
REJECTED              :    6
OPENING BALANCE TOTAL :      1,870,000
CLOSING BALANCE TOTAL :      1,490,000
CONTROL CHECK         : OK
NEW MASTER FILE
100101  KIM MINSU   A      170,000
100202  LEE JIWON   A       20,000
100303  PARK SUJIN  A      300,000
100404  CHOI HANA   C            0
100505  JUNG DOYUN  A    1,000,000

결과를 손으로 따라가 보자. 거래 11건 중 5건이 적용되고 6건이 거절되어 합이 11 이다. 100101 은 150,000 에서 50,000 을 입금하고 30,000 을 출금해 170,000 이 된다. 100505 는 1,200,000 에서 300,000 을 입금하고 500,000 을 출금해 1,000,000 이 된다. 기초 합계 1,870,000 에 입금 350,000 을 더하고 출금 730,000 을 빼면 1,490,000 으로, 새 마스터의 잔액 합과 같다.

실무에서 자주 틀리는 것

한쪽 파일이 끝나면 반복을 멈춘다

거래 파일만 기준으로 반복을 돌리면 마지막 거래 뒤에 남은 마스터가 새 파일에 복사되지 않는다.

      * 틀린 코드
           PERFORM 3000-MATCH-STEP
               UNTIL WT-ACCT = HIGH-KEY

이 데이터에서는 마지막 거래(100999)가 끝난 시점에 100505 까지 모두 쓰인 상태라 우연히 맞아 보이지만, 마지막 거래가 100202 에 대한 것이었다면 100303 이후 세 계좌가 새 마스터에서 사라진다. 두 키가 모두 끝 표시일 때 끝내야 한다.

      * 고친 코드
           PERFORM 3000-MATCH-STEP
               UNTIL WM-ACCT = HIGH-KEY AND WT-ACCT = HIGH-KEY

거래 하나를 적용한 직후 마스터를 쓴다

적용할 때마다 마스터를 쓰고 다음 마스터를 읽으면, 같은 계좌의 두 번째 거래는 다음 계좌와 비교된다.

      * 틀린 코드
               WHEN WT-ACCT = WM-ACCT
                   PERFORM 4000-APPLY
                   PERFORM 5000-WRITE-NEW
                   PERFORM 2000-READ-MASTER
                   PERFORM 2100-READ-TRANS

100101 의 입금 뒤에 마스터를 넘겨 버리면 같은 계좌의 출금 30,000 은 "계좌 없음"으로 거절된다. 마스터를 쓰는 곳은 마스터 키가 거래 키보다 작은 갈래 하나로 한정한다.

      * 고친 코드
               WHEN WT-ACCT = WM-ACCT
                   PERFORM 4000-APPLY
                   PERFORM 2100-READ-TRANS

출금 전에 잔액을 비교하지 않는다

부호 없는 필드에 음수 결과를 저장하면 부호가 사라지고 절댓값이 남는다.

      * 틀린 코드
               WHEN WT-WITHDRAW
                   SUBTRACT WT-AMT FROM WM-BAL

100202 의 잔액 20,000 에서 30,000 을 빼면 −10,000 이어야 하지만 필드에는 10,000 이 저장된다. 오류가 나지 않아 눈치채기 어렵다. 먼저 비교하고, 부족하면 거절한다.

      * 고친 코드
               WHEN WT-AMT > WM-BAL
                   MOVE "INSUFFICIENT FUNDS" TO WS-REASON
                   PERFORM 4500-REJECT
               WHEN OTHER
                   SUBTRACT WT-AMT FROM WM-BAL

정렬되지 않은 거래 파일을 그대로 받는다

대조 방식은 입력이 정렬되어 있다고 믿고 동작한다. 거래 파일의 순서가 어긋나면 뒤에 나온 작은 키의 거래는 마스터가 이미 지나간 뒤라 "계좌 없음"으로 거절되고, 원인을 찾기도 어렵다. 읽을 때 직전 키와 비교해 순서가 거꾸로 되면 즉시 멈추게 하는 것이 안전하다.

      * 틀린 코드: 순서를 검사하지 않는다
           READ TRANS-FILE INTO WS-TRN

      * 고친 코드: 직전 키를 기억해 비교한다
           IF WT-ACCT < WS-PREV-KEY
               DISPLAY "TRANS OUT OF ORDER"
               MOVE 12 TO RETURN-CODE
               STOP RUN
           END-IF
           MOVE WT-ACCT TO WS-PREV-KEY

같은 계좌의 거래는 연달아 나올 수 있으므로 같은 값은 허용하고 작아질 때만 오류로 본다. WS-PREV-KEY 는 PIC 9(6) 으로 선언하고 0 으로 시작한다.

메인프레임에서는 무엇이 달라지는가

이 장의 구조(마스터 + 거래 → 새 마스터 + 보고서)는 메인프레임에서도 그대로 쓰인다. 달라지는 것은 프로그램을 둘러싼 환경이다. JCL(Job Control Language)은 작업을 설명하는 별도 언어로, 실행할 프로그램과 사용할 파일을 지정한다. VSAM 은 메인프레임의 파일 저장 방식 중 하나이고, DB2 는 IBM 의 관계형 데이터베이스다. 아래 표는 이 장의 방식과 비교한 것이다.

이 장의 방식과 메인프레임 방식의 차이
항목이 장 (GnuCOBOL)메인프레임
파일 연결ASSIGN TO "master.dat" 로 실제 파일 이름을 직접 지정프로그램에는 논리 이름만 두고 JCL 의 DD 문이 실제 데이터 세트를 연결
실행 단위셸에서 한 프로그램을 실행JCL 이 정렬, 대조, 보고서 단계를 이어 실행하고 단계별 반환 코드로 분기
마스터 저장순차 파일을 읽고 새 파일을 씀VSAM KSDS 같은 색인 파일에서 키로 찾아 REWRITE 로 제자리 갱신 가능
데이터 저장소파일DB2 테이블에 EXEC SQL 로 접근하고 COMMIT 으로 확정
숫자 형식화면 표시형 숫자금액에 압축 십진수(COMP-3)를 흔히 사용, 문자 코드는 EBCDIC

JCL

메인프레임 프로그램은 파일의 실제 이름을 소스에 쓰지 않는 것이 보통이다. 소스에는 SELECT OLD-MASTER ASSIGN TO OLDMAST 처럼 논리 이름만 두고, JCL 의 //OLDMAST DD 문이 그 이름에 실제 데이터 세트를 붙인다. 같은 프로그램을 시험용 파일과 운영 파일에 소스 수정 없이 쓸 수 있다는 장점이 있다. 새 마스터를 만드는 단계는 매일 새 세대의 파일을 만들도록 구성하는 경우가 많고, 이전 세대가 남아 있어 문제가 생기면 전날 마스터로 되돌아가 다시 처리할 수 있다.

VSAM

색인 파일에서는 키로 레코드를 바로 찾아 읽고 같은 자리에 다시 쓸 수 있다(ORGANIZATION IS INDEXED, REWRITE). 이 경우 거래 한 건마다 해당 계좌만 찾아 갱신하면 되어서 새 마스터 파일을 통째로 만들 필요가 없다. 거래가 계좌 전체에 비해 적을 때 유리하다. 반대로 거래가 많거나 모든 계좌를 훑는 처리는 이 장처럼 정렬된 순차 대조가 여전히 빠르다. GnuCOBOL 도 색인 파일을 지원하지만 저장 형식은 VSAM 과 다르다.

DB2

데이터베이스를 쓰면 마스터는 테이블이 되고, 프로그램에는 SELECT 와 UPDATE 같은 SQL 문이 들어간다. 읽기와 갱신은 DB2 가 처리하므로 파일 상태 코드 대신 SQLCODE 를 확인한다. 가장 큰 차이는 확정과 되돌림이다. 여러 건을 갱신하다 오류가 나면 ROLLBACK 으로 마지막 확정 시점까지 되돌릴 수 있고, 일괄 처리에서는 일정 건수마다 COMMIT 하면서 중단 지점을 기록해 두었다가 그 뒤부터 다시 시작하는 방식이 흔하다. 이 장에서 쓴 통제 합계는 어떤 저장 방식에서도 결과를 확인하는 수단으로 그대로 쓸 수 있다. 자세한 문법은 IBM 의 COBOL 공식 문서와 DB2 문서를 확인한다. 이 책의 예제 환경은 GnuCOBOL 이다.

한눈에 보기

이 장에서 쓴 핵심 기법과 쓰임
기법코드에서의 모습목적
정렬된 두 파일 대조EVALUATE TRUE 로 T < M, T = M, 그 외를 구분각 파일을 한 번씩만 읽는다
끝 표시 키AT END 에서 키를 999999 로 바꿈파일이 먼저 끝나도 같은 구조로 처리
검증 순서WHEN 을 위에서부터 검사, 사유 하나 기록잘못된 거래를 반영하지 않고 원인 보존
ON SIZE ERRORADD 에 붙여 자릿수 넘침 감지잔액이 조용히 잘리는 것을 방지
통제 합계기초 + 입금 − 출금 = 기말 확인레코드 누락과 이중 반영 발견
RETURN-CODE오류 시 8 또는 12 설정다음 단계 실행 여부를 바깥에서 판단

연습 문제

  1. trans.dat 의 세 번째 줄(100202W000030000) 바로 뒤에 100202D000010000 한 줄을 추가한다. 실행하면 100202 의 새 잔액, 입금 건수, 읽은 거래 수, 기말 합계는 각각 얼마인가.
  2. 한 번에 출금할 수 있는 금액을 1,000,000 원으로 제한하려 한다. 초과하면 "OVER LIMIT" 으로 거절하도록 4000-APPLY 를 고쳐 보라. 어느 위치에 넣어야 하는지도 설명한다.
  3. 본문의 "정렬되지 않은 거래 파일" 대비책을 2100-READ-TRANS 에 넣어 보라. WORKING-STORAGE 에 추가할 항목과 읽기 단락의 수정본을 쓴다.
  4. VSAM 색인 파일을 쓰면 새 마스터 파일을 따로 만들 필요가 없어지는 이유를 두세 문장으로 설명하라. 그래도 순차 대조가 쓰이는 경우는 언제인가.

정답과 해설

  1. 추가한 거래는 계좌 100202 에 대한 세 번째 줄 뒤에서 처리된다. 앞의 출금 30,000 은 잔액 20,000 보다 커서 여전히 거절되고, 새 입금 10,000 이 적용되어 100202 의 잔액은 30,000 이 된다. 입금 건수는 3, 읽은 거래 수는 12 다. 입금 합계가 360,000 이 되므로 기말 합계는 1,870,000 + 360,000 − 730,000 = 1,500,000 이다. 거절 건수는 그대로 6 이고, 출금은 이전 순서에서 이미 거절되었으므로 다시 평가되지 않는다. 같은 계좌의 거래는 파일에 들어 있는 순서대로 처리된다는 점을 확인하는 문제다.
  2. WHEN WM-CLOSED 아래, 입금 WHEN 아래 어디든 출금 판단 앞이면 된다. 예를 들면 다음과 같다.
                   WHEN WT-WITHDRAW AND WT-AMT > 1000000
                       MOVE "OVER LIMIT" TO WS-REASON
                       PERFORM 4500-REJECT
    
    이 WHEN 은 잔액 부족 검사(WHEN WT-AMT > WM-BAL)보다 앞에 두어야 한다. 뒤에 두면 한도를 넘는 출금이 먼저 잔액 부족으로 걸려 사유가 달라진다. 또 WHEN WT-DEPOSIT 뒤에 넣어도 입금은 해당 WHEN 에서 이미 처리되므로 동작은 같지만, 의미가 분명하도록 WT-WITHDRAW 조건을 함께 쓴다.
  3. WORKING-STORAGE 에 01 WS-PREV-KEY PIC 9(6) VALUE 0. 을 추가한다. 읽기 단락은 다음과 같이 고친다.
           2100-READ-TRANS.
               READ TRANS-FILE INTO WS-TRN
                   AT END
                       MOVE HIGH-KEY TO WT-ACCT
                   NOT AT END
                       ADD 1 TO CT-TRN-IN
                       IF WT-ACCT < WS-PREV-KEY
                           DISPLAY "TRANS OUT OF ORDER"
                           MOVE 12 TO RETURN-CODE
                           STOP RUN
                       END-IF
                       MOVE WT-ACCT TO WS-PREV-KEY
               END-READ.
    
    검사는 NOT AT END 안에서 해야 한다. 파일 끝에서 WT-ACCT 에 999999 가 들어가면 순서와 상관없는 값이 비교되기 때문이다. 같은 키는 허용하므로 비교는 < 만 쓴다.
  4. 색인 파일은 키로 레코드를 직접 찾아 같은 위치에 다시 쓸 수 있으므로, 변경된 계좌만 갱신하면 되고 나머지 계좌를 복사할 필요가 없다. 반면 거래가 대부분의 계좌에 걸쳐 있거나 모든 계좌를 한 번씩 훑는 처리(월말 이자 계산 등)에서는 건마다 키로 찾는 것보다 정렬된 두 파일을 한 번씩 읽는 순차 대조가 빠르다. 또 갱신 전 마스터가 그대로 남기 때문에 문제가 생기면 전날 상태에서 다시 시작하기 쉽다.
오탈자·오류 제보 비공개로 접수되어 원고 수정에 반영됩니다

이메일 등 개인정보는 받지 않습니다. 답변이 필요한 질문은 아래 댓글을 이용해 주세요.

READER FEEDBACK

질문·의견

내용에 관한 질문이나 더 나은 설명을 위한 의견을 남겨 주세요. 오탈자는 위의 제보 양식이 더 빨리 반영됩니다. 이 댓글은 원래 게시글과 같은 자리에 쌓입니다.

댓글 0

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

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