개요 및 이 글에서 다룰 내용
ABAP에서 조건 분기를 표현할 때 가장 익숙한 도구는 IF ... ELSEIF ... ENDIF입니다. 그러나 단순히 변수 하나에 값을 채우기 위해 5~6줄의 IF 블록을 반복해서 작성하다 보면 코드는 빠르게 비대해지고, 한눈에 의도를 파악하기 어려워집니다. ABAP 7.40 SP08부터 도입된 COND 표현식은 이러한 중첩 분기 문제를 한 줄(또는 표현식 한 덩어리)로 압축할 수 있도록 설계된 인라인 조건식입니다. 이 글에서는 COND가 등장한 배경, 동작 원리, 그리고 판매 주문 상태와 재고 분류 같은 실무 시나리오에 적용하는 방법을 단계적으로 다룹니다.
- 중첩 IF 코드의 가독성 문제 진단
- COND 기본 문법과 타입 추론(
#) 동작 이해 - SalesOrder/Inventory 분류 예제로 실무 적용
- SWITCH와의 명확한 차이 구분
- 남용 시 발생하는 안티패턴 회피
먼저 알고 있어야 할 내용
이 글은 ABAP의 기본 데이터 타입(TYPES, DATA), 내부 테이블, 그리고 IF/CASE 문법을 사용해 본 경험이 있다는 전제로 작성되었습니다. 또한 ABAP 7.40 이상의 인라인 선언(DATA(...))과 생성자 표현식(VALUE, NEW)을 한 번이라도 접해봤다면 더 깊이 있게 따라올 수 있습니다.
환경과 준비물
COND 표현식은 ABAP 7.40 SP08에서 처음 도입되었고, 7.50 이후 표현식 호환성이 크게 개선되었습니다. 이 글의 코드는 다음 환경에서 동작하는 것을 기준으로 합니다.
- SAP NetWeaver AS ABAP 7.50 이상 또는 ABAP Platform 2022 이상
- SAP S/4HANA On-Premise 2021 / Cloud Edition (Steampunk) 동일하게 지원
- 개발 도구: ABAP Development Tools(ADT) for Eclipse 권장, SE80 가능
- 테스트:
CL_ABAP_UNIT_ASSERT기반 단위 테스트 클래스
참고로 RAP(RESTful ABAP Programming) 기반의 동작 구현, CDS Behavior Definition의 mapping 로직에서도 동일한 표현식을 사용할 수 있습니다. 다만 일부 구버전 7.40 SP02 이하에서는 컴파일러가 표현식을 인식하지 못하므로 이전 시스템에서는 사용을 피해야 합니다.
중첩 IF의 문제와 COND의 핵심 개념
조건 분기가 늘어날수록 IF 블록은 들여쓰기 계단(stair-step)이 됩니다. 다음은 판매 주문의 금액 등급을 분류하는 전형적인 코드입니다.
DATA lv_grade TYPE string.
IF gs_order-net_amount >= 1000000.
lv_grade = 'PLATINUM'.
ELSEIF gs_order-net_amount >= 500000.
lv_grade = 'GOLD'.
ELSEIF gs_order-net_amount >= 100000.
lv_grade = 'SILVER'.
ELSE.
lv_grade = 'BRONZE'.
ENDIF.
이 코드는 동작에는 문제가 없지만 다음과 같은 한계가 있습니다. 첫째, 변수 lv_grade를 여러 번 다시 할당하므로 "결과를 한 번에 결정한다"는 의도를 코드 구조로는 표현하지 못합니다. 둘째, 분기 가지가 늘어날수록 위아래로 스크롤해야 흐름을 파악할 수 있습니다. 셋째, 함수형 호출의 인자로 그대로 끼워 넣을 수 없습니다.
COND 표현식은 이러한 "여러 조건 중 하나의 값을 선택"하는 패턴을 표현식 하나로 묶어줍니다. 비유하자면 IF 블록이 여러 개의 작은 우체통에 같은 편지를 다시 쓰는 행위라면, COND는 분류 컨베이어 위에서 조건에 맞는 라벨만 한 번 붙여 통과시키는 것과 같습니다.
"기본 구조
result = COND data_type(
WHEN condition1 THEN value1
WHEN condition2 THEN value2
ELSE value_default ).
여기서 data_type은 결과 타입이며 #로 두면 컨텍스트에서 추론합니다. 각 WHEN은 위에서 아래로 순차 평가되며, 처음 참이 된 분기의 THEN 값이 반환됩니다. 이를 short-circuit evaluation이라 부르며, 뒤쪽 조건들은 평가되지 않으므로 비싼 함수 호출을 뒤로 미루는 최적화에도 활용할 수 있습니다.
주의할 점은 COND는 표현식이라는 것입니다. 즉 값으로 평가되어야 하며, 명령문(statement)을 실행할 수는 없습니다. 따라서 분기 안에서 DB 업데이트 같은 사이드 이펙트를 일으키는 코드는 들어갈 수 없고, 오직 "어떤 값을 선택할 것인가"에만 집중하도록 강제됩니다. 이 제약이 오히려 가독성을 보장합니다.
1단계 예제 — 기본 문법 익히기
앞서 본 주문 금액 등급 분류 코드를 COND로 재작성합니다.
DATA(lv_grade) = COND string(
WHEN gs_order-net_amount >= 1000000 THEN 'PLATINUM'
WHEN gs_order-net_amount >= 500000 THEN 'GOLD'
WHEN gs_order-net_amount >= 100000 THEN 'SILVER'
ELSE 'BRONZE' ).
WRITE: / 'Grade:', lv_grade.
코드 라인 수는 절반 가까이 줄었고, "이 변수의 값은 단 한 번, 조건에 따라 결정된다"는 의도가 한눈에 보입니다.
"왼쪽 변수가 이미 정의되어 있을 때
lv_grade = COND #(
WHEN gs_order-net_amount >= 1000000 THEN 'PLATINUM'
WHEN gs_order-net_amount >= 500000 THEN 'GOLD'
ELSE 'BRONZE' ).
이 표현식의 또 다른 강점은 함수형 호출의 인자에 직접 끼워 넣을 수 있다는 점입니다.
cl_demo_output=>display(
COND string( WHEN gs_order-net_amount IS INITIAL THEN 'No Order'
ELSE |Amount: { gs_order-net_amount }| ) ).
2단계 예제 — 판매 주문 상태와 로깅을 포함한 실무 시나리오
실제 업무에서는 단순 분류뿐 아니라 여러 필드를 조합한 복합 조건이 필요합니다. 다음은 판매 주문 헤더의 상태 코드와 결제 상태, 출하 일자를 함께 보고 사용자 친화적 상태 라벨을 만드는 예제입니다.
TYPES: BEGIN OF ty_sales_order,
order_id TYPE c LENGTH 10,
status_code TYPE c LENGTH 1,
payment_flag TYPE abap_bool,
delivery_date TYPE d,
END OF ty_sales_order.
DATA(gs_order) = VALUE ty_sales_order(
order_id = '4500001234'
status_code = 'B'
payment_flag = abap_true
delivery_date = sy-datum + 3 ).
DATA(lv_display_status) = COND string(
WHEN gs_order-status_code = 'D'
THEN 'CLOSED'
WHEN gs_order-status_code = 'C' AND gs_order-payment_flag = abap_true
THEN 'SHIPPED & PAID'
WHEN gs_order-status_code = 'C' AND gs_order-payment_flag = abap_false
THEN 'SHIPPED - AWAITING PAYMENT'
WHEN gs_order-status_code = 'B' AND gs_order-delivery_date < sy-datum
THEN 'CONFIRMED - DELIVERY OVERDUE'
WHEN gs_order-status_code = 'B'
THEN 'CONFIRMED'
WHEN gs_order-status_code = 'A'
THEN 'CREATED'
ELSE THROW cx_sy_conversion_no_number( ) ).
여기서 두 가지 특징에 주목할 만합니다. 첫째, ELSE 분기에서 THROW를 사용해 예외를 발생시킬 수 있습니다. 예상치 못한 상태 코드가 들어왔을 때 무음으로 빈 문자열을 반환하는 대신, 즉시 예외를 던져 호출자가 알 수 있게 만드는 것이 안전합니다. 둘째, 조건 순서가 곧 우선순위입니다.
DATA(lv_severity) = COND symsgty(
WHEN gs_order-delivery_date < sy-datum THEN 'E'
WHEN gs_order-delivery_date = sy-datum THEN 'W'
WHEN gs_order-delivery_date BETWEEN sy-datum AND sy-datum + 7 THEN 'I'
ELSE 'S' ).
MESSAGE ID 'ZSD_ORDER' TYPE lv_severity NUMBER '001'
WITH gs_order-order_id gs_order-delivery_date.
3단계 예제 — 재고 분류 서비스와 단위 테스트
프로덕션 코드에서는 표현식을 클래스 메서드로 캡슐화하고 단위 테스트로 보호하는 것이 일반적입니다. 다음은 재고 수준을 분류하는 도메인 메서드입니다.
CLASS zcl_inventory_classifier DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
TYPES ty_stock_level TYPE c LENGTH 12.
CLASS-METHODS classify
IMPORTING iv_on_hand TYPE i
iv_reorder_point TYPE i
iv_safety_stock TYPE i
RETURNING VALUE(rv_level) TYPE ty_stock_level.
ENDCLASS.
CLASS zcl_inventory_classifier IMPLEMENTATION.
METHOD classify.
rv_level = COND ty_stock_level(
WHEN iv_on_hand <= 0 THEN 'OUT_OF_STOCK'
WHEN iv_on_hand < iv_safety_stock THEN 'CRITICAL'
WHEN iv_on_hand < iv_reorder_point THEN 'REORDER'
WHEN iv_on_hand < iv_reorder_point * 2 THEN 'NORMAL'
ELSE 'SURPLUS' ).
ENDMETHOD.
ENDCLASS.
CLASS ltcl_classifier DEFINITION FINAL FOR TESTING
DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
METHODS: out_of_stock FOR TESTING, critical FOR TESTING,
reorder FOR TESTING, surplus FOR TESTING.
ENDCLASS.
CLASS ltcl_classifier IMPLEMENTATION.
METHOD out_of_stock.
cl_abap_unit_assert=>assert_equals(
exp = 'OUT_OF_STOCK'
act = zcl_inventory_classifier=>classify( iv_on_hand = 0
iv_reorder_point = 50
iv_safety_stock = 10 ) ).
ENDMETHOD.
METHOD surplus.
cl_abap_unit_assert=>assert_equals(
exp = 'SURPLUS'
act = zcl_inventory_classifier=>classify( iv_on_hand = 500
iv_reorder_point = 50
iv_safety_stock = 10 ) ).
ENDMETHOD.
ENDCLASS.
성능 관점에서 보면 COND의 각 WHEN 절은 순차 평가되므로, 가장 자주 참이 되는 조건을 위쪽에 배치하는 것이 평균 평가 비용을 줄이는 일반적인 권장 방법입니다. 또한 조건 내에서 비싼 함수 호출(예: DB 조회)을 직접 부르는 대신, 메서드 인자로 미리 계산된 값을 받아 분기만 수행하도록 분리하면 캐시 친화성과 테스트 용이성이 동시에 개선됩니다.
흔한 실수와 트러블슈팅
Q1. "Type cannot be determined" 컴파일 오류가 납니다.
A. COND #(...)처럼 타입을 추론하도록 작성했는데 좌변에도 인라인 DATA(...)를 썼다면, 컴파일러는 양쪽 모두에서 타입 단서를 찾지 못해 실패합니다. COND string(...) 또는 COND ty_stock_level(...)처럼 명시적 타입을 지정하거나, 좌변을 미리 선언된 변수로 두어야 합니다.
Q2. ELSE를 생략했더니 런타임에 빈 값이 나옵니다.
A. 모든 WHEN이 거짓이면 COND는 결과 타입의 초기값을 반환합니다. 비즈니스 규칙상 "그 외 모든 경우"가 명확히 정의되어야 한다면 ELSE를 반드시 작성하거나, ELSE THROW로 명시적 실패를 선택하세요.
Q3. SWITCH와 무엇이 다른가요?
A. SWITCH는 하나의 피연산자에 대한 값 매칭(operand = value) 전용이고, COND는 임의의 boolean 조건을 평가합니다. 단일 변수의 값으로만 분기한다면 SWITCH가 더 의도를 잘 드러내고, 범위 비교나 복합 조건이 섞이면 COND가 적합합니다.
한 가지 더, COND를 무리하게 중첩하면 오히려 IF 블록보다 읽기 어려워집니다. WHEN 분기가 7~8개를 넘어가거나 한 분기 안에 또 다른 COND가 들어간다면, Strategy 패턴이나 결정 테이블(BRFplus)로 분리하는 편이 유지보수 측면에서 권장됩니다.
이어서 살펴볼 만한 주제
COND를 익혔다면 자연스럽게 SWITCH, VALUE, REDUCE, FILTER, FOR 같은 다른 표현식들로 시야를 넓힐 수 있습니다. 특히 내부 테이블을 다룰 때 VALUE와 COND를 결합하면 조건부 행 추가 같은 패턴을 한 표현식으로 작성할 수 있습니다. RAP의 Determination/Validation 구현에서도 동일한 표현식이 빈번히 사용되므로, S/4HANA 확장 개발을 목표로 한다면 함께 익혀두는 것이 좋습니다.
댓글 0
아직 댓글이 없습니다.