SAP 운영 중 가장 피해야 할 상황은 숏 덤프(Short Dump) — 사용자 화면에 빨갛게 ABAP RUNTIME ERROR 가 뜨면서 트랜잭션이 끊기는 사고입니다. 중복 키 INSERT, 0 나누기, 숫자 오버플로우, 0건 결과를 첫 행으로 접근, 형변환 실패 등 — 사소한 케이스에서도 덤프는 너무 쉽게 발생합니다.
해결은 ABAP 표준 예외 처리 구문 TRY ... CATCH ... ENDTRY. 예외가 발생할 수 있는 코드를 TRY 블록 안에 두고, 발생할 수 있는 예외 클래스를 CATCH 로 잡아 처리하면 덤프 대신 메시지·로그·재시도 같은 정상 흐름으로 빠질 수 있습니다. 추가로 get_source_position( ) 으로 에러가 정확히 어느 프로그램·인클루드·줄에서 났는지까지 끌어낼 수 있어 디버깅에도 강력합니다.
이 글은 TRY/CATCH/ENDTRY 의 기본 문법 + cx_root 만능 catch + get_text 메시지 추출 + get_source_position 위치 추적 + 함수형 EXCEPTIONS 와의 결합 까지 한 번에 정리한 메모입니다.
핵심 원리
덤프가 발생하는 흐름과 TRY/CATCH 로 가로채는 흐름 비교.
| 시점 | TRY 없이 그냥 실행 | TRY/CATCH 로 감쌌을 때 |
|---|---|---|
| 예외 발생 | 즉시 ABAP RUNTIME ERROR — 덤프 | CATCH 블록으로 점프 — 정상 흐름 유지 |
| 사용자 화면 | 덤프 페이지 — 트랜잭션 끊김 | 개발자가 만든 메시지 출력 + 정상 종료 |
| 데이터 상태 | 부분 처리 가능성 (불일치 위험) | 롤백/보상 로직 직접 수행 가능 |
| 로그·추적 | ST22 덤프 로그에서 사후 확인 | 실시간 메시지·위치 추적 가능 (get_source_position) |
핵심 트레이드오프: 모든 코드를 TRY 로 감싸는 건 과함. 외부 시스템 호출·동적 SQL·형변환·DB INSERT 처럼 실패 가능성이 명확한 지점만 감싸는 게 표준입니다. 가능성도 없는 단순 대입을 TRY 로 감싸면 성능 손해이자 코드 가독성 저해.
1단계 — TRY/CATCH/ENDTRY 기본 + cx_root
가장 단순한 형태. 예외가 어떤 종류든 다 잡고 싶으면 모든 예외의 부모 클래스 cx_root 로 catch.
SELECT * INTO TABLE @DATA(lt_sflight)
FROM sflight
WHERE carrid = 'AA'.
TRY.
INSERT sflight FROM TABLE lt_sflight. " 중복키 → 덤프 위험
CATCH cx_root INTO DATA(oref).
WRITE: / oref->get_text( ). " 덤프 대신 메시지만 출력
ENDTRY.
핵심 포인트:
cx_root는 모든 예외의 최상위 부모 — 종류 불문 catchINTO DATA(oref)로 예외 객체 참조 받아 메서드 호출 가능ENDTRY.까지 반드시 닫기
2단계 — 예외 클래스 계층 + 자주 쓰는 종류
ABAP 예외 클래스는 트리 구조입니다. 자주 만나는 클래스 정리.
| 클래스 | 발생 케이스 |
|---|---|
cx_root |
모든 예외 — 최상위 부모 (catch-all) |
cx_sy_zerodivide |
0 으로 나누기 |
cx_sy_conversion_overflow |
숫자 오버플로우 (값이 변수 크기 초과) |
cx_sy_conversion_no_number |
문자 → 숫자 변환 실패 ('ABC' → integer) |
cx_sy_itab_line_not_found |
READ TABLE 결과 없음 (특히 inline) |
cx_sy_arithmetic_overflow |
사칙연산 결과가 범위 초과 |
cx_sy_open_sql_db |
Open SQL 실행 오류 (중복키·제약위반 등) |
cx_sql_exception |
ADBC(Native SQL) 실행 오류 |
cx_sy_no_handler |
잡지 못한 예외(함수형 EXCEPTIONS 미처리 등) 메타-catch |
여러 CATCH 를 나열해서 종류별 다르게 처리할 수도 있습니다.
TRY.
DATA(lv_result) = lv_a / lv_b.
INSERT zsome_table FROM ls_data.
CATCH cx_sy_zerodivide INTO DATA(ox_div).
MESSAGE '0 으로 나눌 수 없습니다' TYPE 'E'.
CATCH cx_sy_open_sql_db INTO DATA(ox_db).
MESSAGE |DB 오류: { ox_db->get_text( ) }| TYPE 'E'.
CATCH cx_root INTO DATA(ox_etc).
MESSAGE |기타 예외: { ox_etc->get_text( ) }| TYPE 'E'.
ENDTRY.
CATCH 는 위에서부터 매칭 — 더 구체적인 클래스를 먼저 두고, cx_root 같은 catch-all 은 맨 아래.
3단계 — get_text( ) 로 메시지 추출
예외 객체에서 사람이 읽을 수 있는 메시지를 꺼내는 표준 메서드.
TRY.
" 위험한 코드
CATCH cx_root INTO DATA(oref).
DATA(lv_msg) = oref->get_text( ). " "Short text" 추출
MESSAGE lv_msg TYPE 'I'.
" 더 자세한 정보가 필요하면
DATA(lv_long) = oref->get_longtext( ). " "Long text" 추출 (있을 때만)
ENDTRY.
get_text( ) 는 예외 클래스에 등록된 Text(메시지 클래스 연결) 를 사용자 언어로 반환합니다. 운영 로그·MESSAGE 출력에 그대로 쓰면 됩니다.
4단계 — get_source_position( ) 으로 위치 추적
복잡한 시스템에서 "어디서 터졌는지" 추적할 때 강력합니다.
TRY.
DATA(lv_waers) = 'KRW'.
DATA(lv_amount) = 10000000000. " 의도적 오버플로우
CALL FUNCTION 'CURRENCY_AMOUNT_SAP_TO_DISPLAY'
EXPORTING
currency = lv_waers
amount_internal = lv_amount
IMPORTING
amount_display = lv_amount
EXCEPTIONS
internal_error = 1
OTHERS = 2.
CATCH cx_sy_no_handler INTO DATA(hid).
IF hid->classname = 'CX_SY_CONVERSION_OVERFLOW'.
WRITE: hid->classname.
DATA(lv_text) = hid->get_text( ).
WRITE: lv_text.
" ★ 프로그램명/인클루드명/소스 라인 추출
hid->get_source_position(
IMPORTING
program_name = DATA(program_name)
include_name = DATA(include_name)
source_line = DATA(source_line)
).
DATA(lv_pos) = |{ program_name }/{ include_name } @ { source_line }|.
WRITE: / '예외 위치:', lv_pos.
ENDIF.
ENDTRY.
핵심 포인트:
get_source_position은cx_root의 표준 메서드 — 모든 예외 객체에 있음- 운영 로그 테이블에
program_name·include_name·source_line을 같이 저장해두면 ST22 안 봐도 어디서 터졌는지 즉시 확인 hid->classname으로 실제 예외 클래스명도 확인 가능
5단계 — 함수형 EXCEPTIONS 와 cx_sy_no_handler
레거시 SAP 표준 함수(CALL FUNCTION) 는 클래스 기반이 아닌 숫자 코드 기반 예외(EXCEPTIONS) 를 씁니다.
CALL FUNCTION 'CONVERT_DATE_TO_INTERNAL'
EXPORTING
date_external = '2026/02/29'
IMPORTING
date_internal = lv_date
EXCEPTIONS
date_external_is_invalid = 1
OTHERS = 2.
IF sy-subrc <> 0.
" sy-subrc 로 분기
ENDIF.
이 패턴은 sy-subrc 로 처리하는 게 정석. 단, 함수 내부에서 처리 안 된 클래스형 예외가 올라올 수 있는데, 그게 바로 cx_sy_no_handler:
TRY.
CALL FUNCTION 'CURRENCY_AMOUNT_SAP_TO_DISPLAY'
EXPORTING
currency = 'KRW'
amount_internal = 99999999999999.
" EXCEPTIONS 절 없음 → 함수 내부 예외가 그대로 올라옴
CATCH cx_sy_no_handler INTO DATA(hid).
" 함수 안에서 발생한 예외(예: cx_sy_conversion_overflow) 의 메타 wrapper
DATA(actual_class) = hid->classname. " 실제 클래스명
WRITE: / '함수 내부 예외:', actual_class.
ENDTRY.
cx_sy_no_handler 의 classname 속성에 진짜 발생한 예외 클래스 이름이 들어옵니다. 함수형 코드에서 덤프 방지 + 실제 원인 추적을 같이 잡고 싶을 때 이 패턴.
흔히 빠뜨리는 함정
ENDTRY 누락
TRY 만 쓰고 ENDTRY 안 쓰면 구문 에러. SE80 의 들여쓰기로 보면 단번에 보임 — 항상 짝 맞춰 작성.
CATCH 순서 잘못 — 자식이 부모 아래에
CATCH cx_root 를 맨 위에 두고 그 아래에 CATCH cx_sy_zerodivide 를 두면 후자에 절대 도달하지 못함. 구체적인 자식 클래스 먼저, catch-all 부모는 맨 아래.
CATCH 안 한 클래스형 예외 → cx_sy_no_handler 덤프
호출한 함수/메서드가 RAISING cx_xxx 로 클래스형 예외를 던지는데 호출 측에서 안 잡으면 cx_sy_no_handler 로 변환되어 그대로 덤프. 메서드 시그니처의 RAISING 절을 확인 하고 CATCH.
cx_root 만 남발 → 진짜 원인 가려짐
모든 걸 cx_root 로만 받으면 어떤 예외인지 구분 불가. 최소한 classname 이라도 로그에 남길 것.
TRY 안에서 발생한 SQL 의 부분 INSERT 가 살아남음
INSERT FROM TABLE 중 한 행에서 오류 나도 그 전까지의 행은 DB 캐시에 남아있을 수 있음(ACCEPTING DUPLICATE KEYS 없으면). CATCH 안에서 ROLLBACK WORK 명시.
TRY/CATCH 가 성능 비용 = 0 이라는 착각
예외가 발생하지 않을 때 는 거의 무비용. 하지만 발생 시점 의 스택 언와인딩은 무겁습니다. 정상 흐름 분기에 예외를 쓰면(예: 결과 없음을 cx_sy_itab_line_not_found 로 처리) 안 좋습니다 — sy-subrc / IS NOT INITIAL 등 분기로 처리.
get_text( ) 만 보고 끝내기
운영 트러블슈팅은 classname + get_text( ) + get_source_position( ) 3종 세트로 로그를 남겨야 빠릅니다. 메시지만 남기면 어디서 터졌는지 모름.
CATCH 안에서 또 예외 발생
CATCH 블록 내부에서 다시 예외가 나면 cx_sy_no_handler 덤프. CATCH 안에서는 단순한 로그·메시지만, 추가 작업이 필요하면 별도 TRY 로 한 번 더 감싸기.
전체 코드 — 복사용 통합본
위 단계를 하나의 ABAP 프로그램으로 합친 통합본입니다. SE38 에 그대로 복사해 활성화하면 동작합니다.
*&---------------------------------------------------------------------*
*& TRY / CATCH / ENDTRY 덤프 방지 패턴 모음
*&---------------------------------------------------------------------*
REPORT zexample_try_catch NO STANDARD PAGE HEADING.
PARAMETERS: p_mode TYPE c LENGTH 1 DEFAULT '1'.
" 1 = cx_root 만능 catch
" 2 = 클래스별 분기 catch
" 3 = get_source_position 위치 추적
" 4 = cx_sy_no_handler 함수형 메타 catch
START-OF-SELECTION.
CASE p_mode.
* ---------------------------------------------------------
* 1) 만능 catch — cx_root + get_text( )
* ---------------------------------------------------------
WHEN '1'.
SELECT * INTO TABLE @DATA(lt_sflight)
FROM sflight
WHERE carrid = 'AA'.
TRY.
* 이미 존재하는 키 → DUPLICATE KEY 예외
INSERT sflight FROM TABLE lt_sflight.
CATCH cx_root INTO DATA(oref).
WRITE: / 'cx_root:', oref->get_text( ).
* 부분 반영 방지
ROLLBACK WORK.
ENDTRY.
* ---------------------------------------------------------
* 2) 클래스별 분기 — 구체적인 클래스 먼저
* ---------------------------------------------------------
WHEN '2'.
DATA: lv_a TYPE i VALUE 10,
lv_b TYPE i VALUE 0.
TRY.
DATA(lv_div) = lv_a / lv_b.
WRITE: / lv_div.
CATCH cx_sy_zerodivide INTO DATA(ox_div).
WRITE: / '★ 0으로 나누기:', ox_div->get_text( ).
CATCH cx_sy_arithmetic_overflow INTO DATA(ox_over).
WRITE: / '★ 산술 오버플로:', ox_over->get_text( ).
CATCH cx_root INTO DATA(ox_etc).
WRITE: / '★ 기타:', ox_etc->classname, ox_etc->get_text( ).
ENDTRY.
* ---------------------------------------------------------
* 3) get_source_position — 에러 위치 추적
* ---------------------------------------------------------
WHEN '3'.
TRY.
DATA: lv_str TYPE string VALUE 'NOT_A_NUMBER',
lv_n TYPE i.
lv_n = lv_str.
CATCH cx_root INTO DATA(ox_pos).
ox_pos->get_source_position(
IMPORTING
program_name = DATA(gv_prog)
include_name = DATA(gv_incl)
source_line = DATA(gv_line)
).
WRITE: / 'class :', ox_pos->classname,
/ 'text :', ox_pos->get_text( ),
/ 'where :', |{ gv_prog } / { gv_incl } @ line { gv_line }|.
ENDTRY.
* ---------------------------------------------------------
* 4) cx_sy_no_handler — 함수형 메타 catch
* ---------------------------------------------------------
WHEN '4'.
TRY.
DATA: lv_waers TYPE waers VALUE 'KRW',
lv_amount TYPE p LENGTH 16 DECIMALS 2 VALUE '10000000000000000'.
CALL FUNCTION 'CURRENCY_AMOUNT_SAP_TO_DISPLAY'
EXPORTING
currency = lv_waers
amount_internal = lv_amount
IMPORTING
amount_display = lv_amount.
* EXCEPTIONS 절 생략 → 함수 내부 예외가 클래스형으로 올라옴
CATCH cx_sy_no_handler INTO DATA(hid).
WRITE: / 'wrapper :', hid->classname,
/ 'actual :', hid->classname, " 실제 raised 된 클래스
/ 'text :', hid->get_text( ).
ENDTRY.
WHEN OTHERS.
WRITE: / 'p_mode 는 1/2/3/4 중 하나'.
ENDCASE.
* ※ 참고:
* - CATCH 순서: 구체적 자식 → catch-all cx_root 는 맨 아래
* - DB 작업 실패 시 CATCH 안에서 ROLLBACK WORK 명시 권장
* - 운영 로그는 classname + get_text + get_source_position 3종 세트
* - 정상 분기 처리에 예외 남용 X (성능 비용)
* - CATCH 안에서 추가 작업은 별도 TRY 로 감쌀 것
요약
| 단계 | 처리 | 핵심 |
|---|---|---|
| 1 | 기본 구조 | TRY ... CATCH cx_root INTO oref ... ENDTRY |
| 2 | 클래스 계층 | 자식(cx_sy_zerodivide 등) 먼저 → cx_root 는 맨 아래 |
| 3 | 메시지 추출 | oref->get_text( ) / get_longtext( ) / classname |
| 4 | 위치 추적 | get_source_position 으로 program/include/line 추출 |
| 5 | 함수형 결합 | cx_sy_no_handler 메타 catch 로 함수 내부 클래스 예외 추적 |
ABAP 덤프 방지의 표준은 TRY ... CATCH ... ENDTRY. 모든 예외는 cx_root 의 자식이므로 catch-all 만능 처리가 가능하고, 정밀한 분기가 필요하면 cx_sy_zerodivide·cx_sy_conversion_overflow 같은 자식 클래스를 따로 잡습니다. 운영 트러블슈팅이 빠르려면 메시지(get_text) 외에 classname 과 get_source_position 까지 로그에 남기는 게 표준 — 어디서·왜·무엇이 라는 3종 정보가 한 줄에 다 들어옵니다.
Disclaimer — 이 포스트는 실무 정리 노트를 바탕으로 AI 보조로 정리되었습니다. TRY·CATCH·ENDTRY·cx_root·cx_sy_no_handler 는 SAP NetWeaver 표준 ABAP 객체지향 예외 처리 문법으로 시스템 버전 의존 없이 동작합니다. get_source_position( ) 은 모든 cx_root 후손 클래스에서 호출 가능합니다. 다만 DB 작업·외부 연동 트랜잭션 처리 시에는 CATCH 블록에서 ROLLBACK WORK 명시·재시도 로직·운영 로그 저장 정책을 함께 설계해야 데이터 무결성이 유지됩니다.