ABAP

RAISE EXCEPTION 3가지 패턴 — TYPE·MESSAGE·RESUMABLE #shorts #SAP #ABAP

▶ YouTube에서 보기

개요 및 핵심 포인트

ABAP의 RAISE EXCEPTION 명령은 단순히 오류를 던지는 것 이상의 의미를 가집니다. TYPE으로 어떤 예외 클래스를 발생시킬지, MESSAGE로 사용자에게 보여줄 메시지를 어떻게 첨부할지, RESUMABLE로 호출자가 실행을 이어갈 수 있도록 허용할지를 결합하여 결정해야 합니다. 세 가지 패턴을 제대로 이해하면 트랜잭션 로직, RAP 동작 구현, BAdI 구현체 어디에서도 일관된 오류 처리 코드를 작성할 수 있습니다.

학습 체크리스트

  • cx_rootcx_static_check / cx_dynamic_check / cx_no_check 계층 구분
  • RAISE EXCEPTION TYPE ... EXPORTING ... 문법으로 속성 주입
  • RAISE EXCEPTION TYPE ... MESSAGE ID ... TYPE ... NUMBER ... WITH ... T100 연결
  • RAISE RESUMABLE EXCEPTIONCATCH BEFORE UNWIND ... RESUME 흐름
  • PREVIOUS를 이용한 예외 체인(wrapping) 구현

사전에 알아두면 좋은 배경

클래식 ABAP의 RAISE / MESSAGE ... RAISING은 함수 모듈의 EXCEPTIONS 인터페이스에 의존하는 옛 방식입니다. 반면 클래스 기반 예외(class-based exceptions)는 cx_root를 루트로 하는 객체 인스턴스를 생성·전파합니다. TRY ... CATCH ... ENDTRY 블록, RAISING 절, 상속 기반 다형성을 이용한 다단계 처리가 가능해야 본 문서의 패턴이 의미를 가지므로 객체 지향 ABAP 기본 문법은 미리 익혀두는 것이 좋습니다.

실습 환경 및 버전

  • ABAP 언어 버전: 7.50 이상 (RESUMABLE은 7.0부터 지원되나 인라인 선언 등은 7.40 SP08+ 필요)
  • 플랫폼: SAP S/4HANA 2020 이상 또는 SAP BTP ABAP Environment
  • 개발 도구: ABAP Development Tools(ADT) for Eclipse 권장
  • 예외 클래스 생성: SE24 또는 ADT의 New ABAP Class 마법사 → With Message Class 옵션
  • 메시지 클래스: SE91에서 T100 메시지 등록(예: ZTUT_MSG)
주의: SAP BTP ABAP Environment(Steampunk)에서는 일부 옛 메시지 호출이 제한되므로 클래스 기반 예외 + MESSAGE 절 사용이 일반적으로 권장됩니다.

핵심 개념과 동작 원리

예외 클래스 계층 비유

ABAP의 예외 계층은 회사 보고 체계에 비유할 수 있습니다. cx_root는 모든 예외의 CEO이며, 그 아래에 세 가지 부서가 있습니다.

  • cx_static_check: 컴파일러가 RAISING 선언을 강제로 검사하는 "정적 결재" 부서. 호출자는 반드시 처리하거나 다시 선언해야 합니다.
  • cx_dynamic_check: 런타임에만 검증되는 "동적 결재" 부서. 보통 비즈니스 로직에서 가장 많이 사용됩니다.
  • cx_no_check: 어디서나 자유롭게 던질 수 있는 "프리패스" 부서. 시스템 예외가 여기에 해당합니다.

RAISE EXCEPTION의 동작 흐름

RAISE EXCEPTION TYPE cx_my_error는 다음 순서로 동작합니다.

  1. 지정된 예외 클래스의 인스턴스를 자동으로 CREATE OBJECT
  2. EXPORTING에 적힌 값이 인스턴스 속성에 주입
  3. MESSAGE 절이 있으면 T100 메시지 ID/번호/변수가 인스턴스에 저장
  4. 호출 스택을 거슬러 올라가며 매칭되는 CATCH를 탐색
  5. RESUMABLE이면 스택을 즉시 풀지 않고 BEFORE UNWIND 핸들러를 우선 시도

get_text vs MESSAGE

예외 메시지는 두 가지 경로로 표시됩니다. oref->get_text( )는 OTR(Online Text Repository) 텍스트 또는 첨부된 T100 텍스트를 반환합니다. MESSAGE oref TYPE 'E'는 예외에 저장된 T100 정보를 ABAP 메시지 시스템으로 직접 출력합니다. 두 방식은 동일한 데이터를 다른 채널로 흘려보내는 것입니다.

실전 코드

1단계 — TYPE 옵션: 가장 기본적인 예외 발생

먼저 SE24 또는 ADT에서 ZCX_DIVISION_ERROR 예외 클래스를 만듭니다. 슈퍼클래스는 cx_static_check로 지정하고, 속성으로 divisor TYPE i를 추가합니다. 그 후 다음 코드를 실행합니다.

CLASS zcl_calc DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    METHODS divide
      IMPORTING iv_num1       TYPE i
                iv_num2       TYPE i
      RETURNING VALUE(rv_res) TYPE i
      RAISING   zcx_division_error.
ENDCLASS.

CLASS zcl_calc IMPLEMENTATION.
  METHOD divide.
    IF iv_num2 = 0.
      RAISE EXCEPTION TYPE zcx_division_error
        EXPORTING
          divisor = iv_num2.
    ENDIF.
    rv_res = iv_num1 / iv_num2.
  ENDMETHOD.
ENDCLASS.

핵심은 EXPORTING divisor = iv_num2 부분입니다. ABAP 런타임이 자동으로 인스턴스를 만들어 속성에 값을 채워주므로, 호출자는 CATCH에서 lo_ex->divisor로 어떤 값이 문제였는지 정확히 알 수 있습니다.

2단계 — MESSAGE 절: T100 메시지 첨부와 로깅

실무에서는 단순한 속성 전달을 넘어 사용자에게 다국어 메시지를 보여주어야 합니다. 먼저 SE91에서 메시지 클래스 ZTUT_MSG의 001번에 "&1 항목 처리 중 오류: &2"를 등록한 뒤 아래처럼 사용합니다.

METHOD post_invoice.
  TRY.
      validate_amount( iv_amount ).
    CATCH zcx_amount_invalid INTO DATA(lo_prev).
      RAISE EXCEPTION TYPE zcx_invoice_error
        EXPORTING
          textid   = zcx_invoice_error=>invalid_amount
          previous = lo_prev
        MESSAGE ID 'ZTUT_MSG' TYPE 'E' NUMBER '001'
          WITH 'INVOICE' lo_prev->get_text( ).
  ENDTRY.
ENDMETHOD.

" 호출자 측
TRY.
    lo_service->post_invoice( iv_amount = -100 ).
  CATCH zcx_invoice_error INTO DATA(lo_err).
    " 화면 메시지 출력
    MESSAGE lo_err TYPE 'I' DISPLAY LIKE 'E'.
    " Application Log 기록
    cl_bali_message_setter=>create(
      severity = if_bali_constants=>c_severity_error
      text     = lo_err->get_text( ) ).
ENDTRY.

MESSAGE 절은 호출자가 get_text( )를 호출하거나 MESSAGE lo_err TYPE 'E'를 사용할 때 SE91에 등록된 텍스트를 자동으로 조립해줍니다. WITH 뒤의 값들은 메시지 placeholder &1~&4에 순서대로 매핑됩니다.

3단계 — RESUMABLE: 실행 재개가 가능한 예외

일괄 처리에서 일부 레코드가 실패해도 전체를 중단하지 않고 계속 진행해야 할 때 RESUMABLE이 유용합니다. 예외 클래스를 만들 때 Exception can be resumed 옵션이 활성화되어야 하며, 호출 쪽에서는 RAISING RESUMABLE(...)로 시그니처를 선언합니다.

METHODS process_row
  IMPORTING is_row TYPE zsales_row
  RAISING   RESUMABLE(zcx_row_warning).

METHOD process_row.
  IF is_row-amount < 0.
    RAISE RESUMABLE EXCEPTION TYPE zcx_row_warning
      EXPORTING row_id = is_row-id.
  ENDIF.
  " 정상 처리 로직 (RESUME 후에도 여기까지 실행됨)
  insert_row( is_row ).
ENDMETHOD.

" 배치 호출자
LOOP AT lt_rows INTO DATA(ls_row).
  TRY.
      lo_service->process_row( is_row = ls_row ).
    CATCH BEFORE UNWIND zcx_row_warning INTO DATA(lo_warn).
      log_warning( lo_warn->row_id ).
      RESUME.   "  process_row 내부 RAISE 다음 줄부터 재개
  ENDTRY.
ENDLOOP.

CATCH BEFORE UNWIND가 핵심입니다. 일반 CATCH는 스택을 풀어버려 RAISE 위치로 돌아갈 수 없지만, BEFORE UNWIND는 스택을 보존해 RESUME으로 RAISE 명령 바로 다음 줄로 점프할 수 있게 합니다. 단, 같은 TRY 블록에서 BEFORE UNWIND와 일반 CATCH를 함께 쓸 때 우선순위 규칙을 반드시 숙지해야 합니다.

실전 패턴 — 예외 래핑(PREVIOUS 체인)

저수준 예외를 그대로 상위 계층까지 노출하면 추상화가 깨집니다. 일반적으로 권장되는 방법은 PREVIOUS 파라미터로 원래 예외를 감싸 새 예외를 던지는 것입니다.

METHOD load_customer.
  TRY.
      SELECT SINGLE * FROM kna1 INTO @DATA(ls_kna1)
        WHERE kunnr = @iv_kunnr.
      IF sy-subrc <> 0.
        RAISE EXCEPTION TYPE cx_sy_itab_line_not_found.
      ENDIF.
    CATCH cx_sy_itab_line_not_found INTO DATA(lo_root).
      RAISE EXCEPTION TYPE zcx_customer_not_found
        EXPORTING
          kunnr    = iv_kunnr
          previous = lo_root.    " 원본 보존
  ENDTRY.
ENDMETHOD.

호출자는 lo_err->previous를 따라가며 원인을 추적할 수 있고, get_text( ) 호출 시 자동으로 체인 정보를 활용할 수 있습니다. 이는 분산 트랜잭션이나 RAP 동작에서 비즈니스 예외로 변환할 때 표준적으로 사용되는 기법입니다.

흔한 실수와 트러블슈팅

FAQ 1. "RAISE RESUMABLE EXCEPTION ... is not allowed here" 컴파일 오류

예외 클래스에 resumable 옵션이 켜져 있어도, 메서드 시그니처가 RAISING zcx_xxx(일반)으로 선언되어 있으면 컴파일되지 않습니다. 반드시 RAISING RESUMABLE(zcx_xxx)로 적어야 하며, 호출 체인 전체가 동일하게 선언되어야 RESUME이 동작합니다.

FAQ 2. get_text( )가 메시지 클래스 ID만 반환

MESSAGE 절을 사용한 예외가 빈 텍스트 또는 이상한 코드만 반환한다면, 예외 클래스 속성에 if_t100_dyn_msg 또는 if_t100_message 인터페이스가 누락된 경우가 많습니다. 7.50+ 에서는 클래스 생성 마법사의 With Message Class를 체크하면 자동으로 msgid/msgno/msgv1~4 속성과 인터페이스가 추가됩니다.

FAQ 3. CATCH BEFORE UNWIND 후 RESUME을 호출하지 않으면?

RESUME을 호출하지 않고 CATCH 블록이 끝나면 일반 CATCH처럼 스택이 풀리고, 컨트롤이 ENDTRY 다음으로 이동합니다. 의도적으로 "경고는 무시하고 계속"이 아니라 "한 번 더 검사하고 진행/중단을 결정"하는 경우에는 명시적으로 RESUME 또는 다시 RAISE를 호출하는 것이 권장됩니다.

기타 디버깅 팁

  • ADT 디버거의 Exceptions 뷰에서 previous 체인을 펼쳐볼 수 있습니다.
  • cl_demo_output=>display( lo_err )로 예외 객체 전체를 빠르게 시각화할 수 있습니다.
  • 운영 환경에서는 ST22 short dump가 아니라 Application Log(SLG1)에 기록되도록 catch-and-log 패턴을 사용하는 것이 일반적입니다.

다음 단계 / 관련 주제

  • RAP Managed BehaviorFAILED / REPORTED 구조와 예외 매핑
  • cl_bali_* 클래스 기반 신규 Application Log API (BAL 후속)
  • ABAP Unitcl_abap_unit_assert=>fail과 예외 검증 패턴
  • Clean ABAP 가이드의 "Throw class-based exceptions" 규칙 학습
  • OData/RAP 서비스에서 /IWBEP/CX_MGW_BUSI_EXCEPTION으로의 변환 전략

참고 자료

댓글 0

아직 댓글이 없습니다.