예외 상황 관리, 1부
저자: Steven Feuerstein
최선의 관행으로 PL/SQL 예외 상황을 처리해 보십시오.
자신의 프로그램을 완벽하게 검증하기 위해 시간을 투자하는 프로그래머들은 그리 많지 않습니다. 또한 이들 대다수는 고객 유지나 인보이스 생성 같은 애플리케이션의 기본적인 기능을 구현하고 이를 위해 코드를 작성하는 일 자체가 충분히 힘든 작업이라는 사실을 알고 있습니다.
프로그래머들은 종종 모든 환경에서 최상의 조건을 가정하는 애플리케이션을 작성합니다. 즉, 자신의 프로그램에는 버그가 없으며, 사용자들은 올바른 데이타를 올바른 방식으로 입력할 것이고, 모든 하드웨어와 소프트웨어를 포함한 시스템은 항상 완벽한 상태일 것이라고 가정합니다.
하지만 아무리 노력을 한다 해도 애플리케이션에는 버그가 또 다시 발견되기 마련이며, 화면을 찌그러지게 만드는 키누름만을 골라서 누르는 사용자가 있기 마련입니다. 선택은 명백합니다. "시간을 투자해 제대로 디버깅하여 완벽하게 만들던가 아니면 흥분한 사용자들의 서비스 요청에 급한 불을 끄러 나가는 전쟁을 끊임없이 치르던가" 이 두 가지 중 하나를 선택해야 합니다.
하지만 다행히도 PL/SQL은 프로그램의 오류를 잡아서 처리하는 강력하고 유연한 방법을 제공합니다. PL/SQL 언어를 사용하면 오류로부터 사용자와 데이타베이스를 완전히 보호해 주는 애플리케이션을 만드는 일이 전적으로 가능해 지며, 제대로만 하면 작성할 코드의 양을 최소화 시킬 수도 있습니다.
전체 2부로 구성된 첫 번째 문서에서는, PL/SQL 기반 애플리케이션에서 고품질의 종합적 오류 처리를 위해 따라야 할 최고의 관행 몇 가지를 살펴보겠습니다. 이 문서는 오류 처리를 위한 최고 관행을 포괄적으로 다루고 이와 함께 예외 상황과 관련된 최고 관행에 초점을 맞추고 있습니다. 후속 문서에서는 "예외 상황들을 어떻게 처리할 수 있는지" 그 방법에 대해 다룰 예정입니다.
시작하기 전에 지침 사항을 설정하십시오.
애플리케이션 코드 작성을 시작하기 전에, 먼저 오류 처리에 대한 종합적 전략과 구체적 코딩 지침 사항을 결정해야 합니다. 모든 애플리케이션에서 다 통할 수 있는 단일 전략이란 존재하지 않으며, 자신의 특정 시스템에 무엇이 가장 적합할 지는 사용자가 결정해야 하기 때문입니다. 예를 들어 저장된 모든 프로시저에 예외 상황 섹션을 두어 오류가 발생한 블록에서 가장 근접한 곳에서 오류가 포착되고 처리되도록 결정할 수 있는 한편, 예외 상황 섹션이 가장 바깥 블록에만 있도록 결정할 수도 있습니다.
일단 포괄적인 접근방법을 설정하고 나면, 팀의 모든 개발자들이 같은 방법으로 오류 처리를 작성하도록 할 필요가 있습니다. 오류 발생과 처리 및 로그인 오류에 대해 일관된 접근방식을 따르지 않는다면 사용자들이 엄청나게 혼란스러워 할 것이고, 애플리케이션 지원 부서 사람들은 무엇이 잘못되었는지를 알아내느라 골머리를 앓게 될 것입니다.
애플리케이션 전체에서 일관성 있는 오류 처리 방식을 구현하려면 적어도 아래의 요소를 갖춘 표준화된 패키지를 만들어 이용하는 것이 가장 좋습니다.
- RAISE_APPLICATION_ERROR 및 애플리케이션 오류 번호의 복잡성을 감춰주는 발생 프로그램(raise program)
- 오류 로그 작성 및 오류 처리 등, 대부분의 예외 상황 처리 작업을 수행할 수 있는 프로시저
- 주어진 오류 번호에 대해 오류 메시지 텍스트를 되돌려 보내는 기능
다음은 위의 요건에 맞는 간단한 오류 처리 패키지의 사양입니다(errpkg.pkg file에 위치해 있음).
CREATE OR REPLACE PACKAGE errpkg
IS
PROCEDURE raise (
err_in IN INTEGER := SQLCODE,
msg_in IN VARCHAR2 := NULL);
PROCEDURE report_and_stop (
err_in IN INTEGER := SQLCODE,
msg_in IN VARCHAR2 := NULL);
PROCEDURE report_and_go (
err_in IN INTEGER := SQLCODE,
msg_in IN VARCHAR2 := NULL);
FUNCTION errtext (
err_in IN INTEGER := SQLCODE)
RETURN VARCHAR2;
END errpkg;
이 오류 처리 패키지(errpkg.pkg)의 프로시저 및 기능을 실행하는 방법과, 이들이 프로그램 코드에 미치는 영향을 살펴보겠습니다.
예외 상황 발생
예외 상황은 발생되어야만 처리될 수 있습니다. 따라서 예외 발생에 대한 몇 가지 최상의 관행을 살펴보는 것으로 시작하겠습니다. 다음의 몇 가지 최상의 관행을 통해, 예외 상황을 발생시켜야 할 상황을 판단하는 방법, 어떤 예외 상황 정보를 전달해야 할지를 결정하는 방법, 그리고 예외 상황을 발생시키는 최상의 방법에 대해 설명하겠습니다.
사전 조건을 검증하십시오.
프로그램을 작성할 때 마다 프로그래머는 전제 조건을 설정하지만, 프로그램 사용자들이 이러한 전제 조건을 알아야 할 필요는 없습니다. 전제 조건들이 위반되지 않도록 "수비적 코딩"을 하지 않으면, 프로그램이 갑자기 작동되지 않을 수도 있습니다.
명제(assertion) 루틴을 사용하여, 선언적 방식으로 전제 조건들을 검증하기 쉽도록 만드십시오. 애플리케이션 전체에 걸쳐 표준화되어 있는 이러한 루틴은 잡다한 일들을 깔끔하게 처리해 줍니다. 이 루틴들은 조건이 맞지 않을 때는 무엇을 해야 하며, 문제점은 어떻게 보고하며, 프로그램을 중지시킬지 여부와 그 방법을 알려줍니다.
목록 1에는 어떤 조건이 TRUE인지를 점검하는 간단한 명제(assertion) 프로그램이 있습니다. 조건이 FALSE 또는 NULL이면, 프로시저는 메시지를 화면에 표시한 후 동적 PL/SQL을 사용하여 예외 상황을 선택적으로 발생시킵니다.
목록 1의 2~6번째 라인에는, 재사용이 가능한 코드의 핵심적 특징인 상당한 유연성이 있는 매개 변수 목록을 만들었습니다. 먼저 명제를 정할(assert) 조건을 제공하되, 조건(부울 표현식)의 결과가 TRUE이면 명제 프로그램은 아무 일도 하지 않도록 했습니다. 표현식이 FALSE 또는 NULL일 경우에는, 명제 프로시저가 다른 매개 변수를 사용하여 오류를 표시하도록 했습니다. 그 후, 예외 상황이 발생되기를 원한다는 것을 지정했습니다(raise_exception_in이 기본 값인TRUE일 때). 이렇게 하면 프로시저는 4번째 매개 변수(5번째와 6번째 행)에 명명한 것을 발생시키기 위해 고유의 동적 SQL을 사용하게 됩니다(18번째와 19번째 행).
명제 프로시저를 사용함으로써, 업무 논리에 따라 진행하기 전에 모든 입력이 유효한 지를 선언적인 방식으로 확인할 수 있게 되었습니다.
예를 들면 다음과 같습니다.
BEGIN
assert (isbn_in IS NOT NULL,
'The ISBN must be provided.');
assert (page_count_in < 2000,
'Readers don't like big, fat books!');
실행 가능한 섹션을 다수의 "가상" 섹션으로 나누어주는 블록 템플릿을 다음과 같이 설정할 수 있습니다.
CREATE OR REPLACE PROCEDURE <name>
IS
<declarations>
PROCEDURE initialize IS
BEGIN
<any startup code>
END;
PROCEDURE assert IS
BEGIN
<sequence of assertions>
END;
PROCEDURE cleanup IS
BEGIN
<any clean-up code>
END;
BEGIN
initialize;
assert;
<body of code>
cleanup;
END <name>;
여기에 표시된 명제 프로시저에 대한 프로토타입은 assert.pro 및 assert.pkg 파일에 들어 있습니다.
기본 모델을 사용하십시오.
여러분이 (PL/SQL 또는 Java와는 달리) 정교한 오류 처리 아키텍처가 없는 프로그래밍 언어로 작업하고 있다면, 상태 코드 및 메시지를 되돌려 보내는 모든 프로그램에 OUT 매개변수를 추가하는 일에 익숙해져 있을 것입니다.
하지만 PL/SQL 코드를 호출하는 호스트 환경에 그러한 정보를 전달할 필요가 없다면, 그렇게 하지 마십시오. PL/SQL 프로그램이 다른 PL/SQL 블록으로부터 호출되거나 이들 블록과 통신할 경우, 여러분은 기본 모델에 의지해야 합니다. 즉, 블록에 대한 예외 상황만을 위한 분리된 섹션을 만들어서, 예외 상황을 발생시키고 처리하십시오.
다음은 피해야 할 코드 사례입니다.
BEGIN
overdue.analyze_status (
title_in,
start_date_in,
report_info_out,
error_code,
error_msg);
IF error_code != 0
THEN
errpkg.log (...);
GOTO end_of_program;
END IF;
overdue.send_report (
report_info_out,
error_code,
error_msg);
IF error_code != 0
THEN
err.log (...);
GOTO end_of_program;
END IF;
...
<end_of_program>
NULL;
END;
여기서 각각의 부속 프로그램 호출 상태를 점검해야만 하는 것에 주목해 주십시오. 또, 실패에 대처하기 위해 평소 GOTOs와 레이블을 사용하게 된 것과, 성공의 표시로 "0"과 같은 하드 코드 값을 너무 자주 사용하는 것에도 주목해 주십시오. 여기서 만일 성공 표시자를 변경한다면 어떻게 될까요?
"전통적인" PL/SQL 논리를 사용한다면, 위의 실행 가능한 섹션은 다음과 같이 될 것입니다.
BEGIN
overdue.analyze_status (
title_in,
start_date_in,
report_info_out);
overdue.send_report (report_info_out);
EXCEPTION
WHEN overdue.invalid_date
THEN
errpkg.report_and_go (msg_in => start_date_in);
WHEN OTHERS
THEN
errpkg.report_and_stop;
END;
PL/SQL의 기본 예외 상황 처리 모델에 맞출 경우, 실행 가능한 섹션은 명확하고 단순하며 쉬워집니다. 여러분은 프로그램을 호출한 뒤에 상태를 점검할 필요가 없으며, 발생되는 위기 상황을 포착하여 대처하려면 단지 예외 상황 섹션을 포함시키기만 하면 됩니다.
RAISE-APPLICATION ERROR 캡슐화
NO_DATA_FOUND와 같은 "시스템" 예외 상황을 발생시키려면, RAISE를 사용하십시오. 그리고 특정 애플리케이션 오류를 발생시킬 경우에는 RAISE_APPLICATION_ERROR를 사용하면 됩니다. 단 후자를 선택할 경우에는 오류 번호와 메시지를 제공해야 하는데, 이로 인해 불필요한 하드-코딩을 초래할 수 있습니다.
더 나은 접근 방식은 오류 번호를 자동으로 점검하고 오류 발생을 위한 올바른 방법을 결정해 줄, 미리 정의된 발생 프로시저를 사용하는 것입니다. 이러한 프로시저의 예는 errpkg.pkg 파일에 있으며, 아래와 같습니다.
위와 같이 쓰는 대신,
RAISE_APPLICATION_ERROR (
-20734,
'Employee must be 18 years old.');
다음과 같이 써야 합니다.
errpkg.raise (errnums.en_emp_too_young);
두 번째 실행에서 오류 번호나 메시지를 하드-코딩하지 않은 것에 유의하십시오. 그 대신, 미리 정의된 일련의 오류 번호가 들어있는 errnums 패키지를 열어서 상황에 맞는 것을 하나 찾은 후, 명명된 상수로 오류를 참조하였습니다. 즉, errnums 패키지 지정은 다음과 같을 수 있습니다. (이는 errnums.pkg에서 찾아볼 수 있습니다.)
CREATE OR REPLACE PACKAGE errnums
IS
exc_bal_too_low EXCEPTION;
en_bal_too_low CONSTANT INTEGER := -20100;
PRAGMA EXCEPTION_INIT (exc_bal_too_low, -20100);
exc_emp_too_young EXCEPTION;
en_emp_too_young CONSTANT INTEGER := -20200;
PRAGMA EXCEPTION_INIT (exc_emp_too_young, -20200);
END errnums;
목록 2에는 errpkg.raise 프로시저의 수행 방법이 들어있습니다. 이 부분은 프로그램 중 가장 흥미 있는 요소에 대한 설명입니다: Line 2.에는 오류 메시지, 기본 SQLCODE 및 오버라이드 오류 메시지가 있으며, 제공된 것이 하나도 없을 경우에는 메시지 표에 저장되어 있는 기본 메시지를 사용하였습니다. Line 5부터 7에는 RAISE_APPLICATION_ERROR에 적용되는 오류 메시지의 범위가 제공되어 있습니다. 제공된 오류 메시지가 그 범위 안에 들 경우, 공급된 오류 번호와 메시지를 사용하여 내장 프로시저를 호출하였습니다.
메시지가 null일 경우에는 errpkg.errtext 기능을 사용하였습니다. Line 9부터 11에서는 특정한 애플리케이션 오류 번호에 대한 양수들을 처리했습니다. 양수 오류 메시지 번호를 처리함으로써(Oracle이 사용하고 있는 유일한 두 양수 오류 번호인 1과 100은 피하고 있음.), 일부는 Oracle에서 사용하기도 하는, -20,999와 -20,000 사이의 오류 번호에 얽매일 필요가 없습니다. 또한 Line 14부터 18에서는 로컬 예외 상황을 선언하는 PL/SQL 블록을 만들고 프라그마 EXCEPTION_INT를 사용하여 예외 상황을 공급된 오류 번호와 연결한 다음, 그 예외 상황을 발생시켰습니다.
이 errpkg.raise 프로시저를 사용하면, 개발자들은 (RAISE 또는 RAISE_APPLICATION_ERROR를 사용하여) 예외 상황을 어떻게 발생시켜야 할지 그 방법에 대한 호출을 만들 필요가 없어집니다. 개발자가 (상수 이름으로 식별 가능한) 해당 오류 번호만 전달하면, 힘든 일은 발생(RAISE) 엔진이 모두 처리합니다.
RAISE는 오류에만 사용
프로그램에서 정상적인 프로세스를 취소시키기 위해 RAISE를 사용해서는 절대로 안되며, 그렇게 하려면 해당되는 WHEN 처리기로 가야 합니다. 오류가 발생할 경우에만 예외 상황을 발생시켜야 하며, 프로그램 흐름을 제어하기 위해 발생시키면 안됩니다.
목록 3의 기능은 문제점을 보여줍니다. 이 기능은 그룹의 전체 표를 검색하여 일치하는 것이 있으면 즉시 종료됩니다. exit_function 예외 상황은 입력 제목이 NULL인 경우에 기능을 중지시키기 위해 사용되며, 또한 기능의 마지막 행으로도 사용됩니다.
목록 3에서는 기능의 마지막까지 다뤘으며, 하나의 예외 상황을 발생시키는 것으로 끝냈습니다. 이것은 구조가 빈약한 코드이며, 이해하기도 어렵고 유지보수 하기도 힘듭니다. 이러한 오류 처리의 예 즉, 선언된 예외 상황의 이름이 "exit function"이라는 조치를 설명하는 경우에 주의를 기울이십시오. 예외 상황의 이름은 "null name" 또는 "invalid date"와 같이 오류 상황을 나타내야만 합니다.
목록 4를 보면 더 좋은 접근 방법을 알 수 있습니다.
발생과 처리
이제까지 어떻게 하면 예외 상황 발생에 대해 조직적이고 강력한 방식으로 접근할 수 있는지를 설명했으며, 이를 요약하면 다음과 같습니다. 첫째, 애플리케이션 팀이 일관성 있게 오류를 관리하는 데 필요한 툴이 모두 들어 있는, 범용 예외 상황 처리 패키지를 작성하십시오. 둘째, 예외 상황 발생에 대한 명확한 지침 사항을 제시하고, 개발자들이 개인적인 추측을 못하도록 일반적인 발생 프로그램을 제공하십시오.
다음 기사에서는, 발생된 예외 상황을 처리하는 최상의 관행에 대해 살펴보도록 하겠습니다.
Steven Feuerstein (steven@stevenfeuerstein.com)은 PL/SQL 언어의 권위자입니다. Quest Software의 수석 기술 고문이기도 한 Feuerstein 씨는 PL/SQL과 관련하여 Oracle PL/SQL Best Practices 및 Oracle PL/SQL Programming을 포함한 9권의 책(모두 O'Reilly & Associates에서 출판)을 저술했습니다.
본 기사의 원문은 http://otn.oracle.com/oramag/oracle/03-may/o33plsql.html에서 확인하실 수 있습니다.
원문 : http://otn.oracle.co.kr/tech/column/2003/ |