ABAP

IF 중첩 없이 한 줄로 조건 처리하기 #shorts #SAP #ABAP

IF 중첩의 늪 - 코드가 무너지는 순간

ABAP으로 비즈니스 로직을 작성하다 보면 어느 순간 IF/ELSEIF가 5단, 6단까지 중첩되는 코드를 마주하게 됩니다. 주문 상태, 고객 등급, 결제 방식 같은 도메인 분기는 한두 줄로 끝나지 않습니다. 이 글에서는 ABAP 7.4 SP08 이상에서 도입된 COND 표현식을 사용해 중첩 IF를 한 줄짜리 선언적 코드로 바꾸는 방법을 다룹니다.

이 글을 끝까지 따라가면 다음을 할 수 있게 됩니다:

  • 중첩 IF가 발생하는 패턴을 식별하고 COND로 평탄화
  • CONDSWITCH의 사용 기준 구분
  • COND + 인라인 선언으로 임시 변수 제거
  • 주문 상태(SalesOrder Status) 같은 실무 분기를 한 줄로 표현
  • 타입 호환성 함정 (CONV 필요 시점) 회피

COND를 쓰기 전에 알아두면 좋은 것

ABAP의 기본 흐름 제어(IF/CASE), 인라인 선언 DATA(...), 그리고 메서드 반환값에 대한 이해가 있다면 충분합니다. RAP나 CDS 지식까지는 필요하지 않습니다. 다만 ABAP 7.40 이전 문법(EXPORTING/RECEIVING)에 익숙하다면, 이 글의 표현식 기반 스타일이 처음에는 낯설게 느껴질 수 있습니다.

실행 환경과 ABAP 릴리스 확인

COND 표현식은 ABAP 7.40 SP08에서 처음 도입되어 7.50, 7.54, ABAP Cloud(스티어 모드)에서 점차 강화되었습니다. 이 글의 코드는 다음 환경에서 동작합니다.

  • ABAP 7.50 이상 (On-Premise S/4HANA 1909+, BTP ABAP Environment)
  • ADT (ABAP Development Tools) 또는 SE80
  • RAP 환경에서도 동일하게 적용 가능 (Behavior Implementation, Determination 등)

릴리스 확인은 SYSTEM->Status 메뉴에서 ABAP 커널/릴리스 번호로 확인할 수 있습니다. 7.40 SP08 미만이라면 COND 자체가 구문 오류이므로 IF 기반 코드를 유지해야 합니다.

COND가 만들어내는 사고의 전환

전통적인 IF 문은 "절차"입니다. "이 조건이면 변수에 값을 대입하고, 아니면 다른 값을 대입한다"는 명령형 흐름이죠. 반면 COND는 "표현"입니다. "이 값은 다음 조건들 중 처음 만족하는 것의 결과"라고 선언합니다. 마치 엑셀의 IFS 함수처럼, 조건들의 리스트를 만들면 그 자체로 하나의 값이 됩니다.

기본 문법은 다음과 같습니다.

DATA(lv_result) = COND string(
  WHEN <condition1> THEN <value1>
  WHEN <condition2> THEN <value2>
  ELSE <default_value>
).

중요한 포인트 세 가지가 있습니다. 첫째, COND 뒤에 결과 타입을 명시합니다 (string, i, abap_bool 등). 둘째, WHEN 절은 위에서 아래로 평가되며 처음 참인 것이 채택됩니다. 셋째, ELSE가 없고 어떤 WHEN도 만족하지 않으면 초기값(initial value)이 반환됩니다. 이 마지막 동작은 NULL과 유사한 함정이므로 가능하면 ELSE를 명시하는 습관이 안전합니다.

SWITCH와의 차이도 명확히 짚어야 합니다. SWITCH는 하나의 변수 값을 여러 상수와 비교하는 패턴(자바의 switch-case)이고, COND는 임의의 부울 표현식을 평가합니다. "주문 상태 코드가 'A' 또는 'B'일 때"는 SWITCH로도 가능하지만, "금액이 10000 초과이고 고객 등급이 GOLD일 때"처럼 복합 조건은 COND만 가능합니다.

가장 단순한 COND 예제부터

먼저 단일 조건 분기를 살펴봅니다. 할인율 계산을 예로 들어 보겠습니다.

DATA(lv_order_amount) = CONV decfloat34( '15000.00' ).

" Before: 명령형 IF
DATA lv_discount_rate TYPE decfloat34.
IF lv_order_amount >= 10000.
  lv_discount_rate = '0.10'.
ELSEIF lv_order_amount >= 5000.
  lv_discount_rate = '0.05'.
ELSE.
  lv_discount_rate = '0.00'.
ENDIF.

" After: COND 표현식
DATA(lv_discount) = COND decfloat34(
  WHEN lv_order_amount >= 10000 THEN '0.10'
  WHEN lv_order_amount >=  5000 THEN '0.05'
  ELSE                              '0.00'
).

코드량은 8줄에서 5줄로 줄었고, 무엇보다 lv_discount가 어떤 규칙으로 결정되는지 한눈에 보입니다. 변수 선언과 값 할당이 분리되지 않은 점도 중요한 개선입니다. 명령형 버전에서는 lv_discount_rate가 잠시 초기값을 가지는 "더러운" 상태가 존재하지만, COND 버전에서는 한 번에 최종값으로 초기화됩니다.

실무 시나리오 — 주문 상태 분류와 로깅

실제 프로젝트에서 자주 나오는 패턴입니다. 영업 주문(SalesOrder)의 상태 코드를 화면 표시용 한글 라벨로 변환하면서, 매핑되지 않은 코드는 로그로 남겨야 합니다.

METHOD map_order_status_label.
  " IMPORTING iv_status_code TYPE c LENGTH 2
  " RETURNING rv_label TYPE string

  rv_label = COND string(
    WHEN iv_status_code = 'OP' THEN |처리중 (Open)|
    WHEN iv_status_code = 'IP' THEN |출고준비 (In Progress)|
    WHEN iv_status_code = 'SH' THEN |출고완료 (Shipped)|
    WHEN iv_status_code = 'IV' THEN |청구완료 (Invoiced)|
    WHEN iv_status_code = 'CL' THEN |종결 (Closed)|
    WHEN iv_status_code = 'CN' THEN |취소 (Cancelled)|
    ELSE THROW cx_so_unknown_status( status_code = iv_status_code )
  ).
ENDMETHOD.

여기서 두 가지 고급 기법이 등장합니다. 첫째, 문자열 템플릿 |...|THEN 절에 바로 사용했습니다. 둘째, ELSE에서 THROW를 사용해 예외를 발생시켰습니다. COND는 표현식이지만 THROW를 통해 예외 흐름까지 포함할 수 있어, 매핑 누락 같은 경계 케이스를 명시적으로 처리할 수 있습니다.

경고: COND 안에 COND를 중첩하는 것은 가능하지만, 2단계를 넘어가면 IF보다 더 읽기 어려워집니다. 이런 경우 별도 메서드로 추출하는 편이 낫습니다.

프로덕션 패턴 — 복합 조건과 단위 테스트

실제 영업 주문 우선순위 산정 로직을 살펴봅니다. VIP 고객, 긴급 배송, 금액 기준이 모두 얽혀 있습니다.

CLASS lcl_order_priority DEFINITION FINAL.
  PUBLIC SECTION.
    TYPES: BEGIN OF ty_order_ctx,
             customer_tier TYPE c LENGTH 1,   " G=Gold S=Silver B=Bronze
             delivery_type TYPE c LENGTH 1,   " E=Express N=Normal
             net_amount    TYPE decfloat34,
             is_first_buy  TYPE abap_bool,
           END OF ty_order_ctx.

    CLASS-METHODS calc_priority
      IMPORTING is_ctx          TYPE ty_order_ctx
      RETURNING VALUE(rv_score) TYPE i.
ENDCLASS.

CLASS lcl_order_priority IMPLEMENTATION.
  METHOD calc_priority.
    rv_score = COND i(
      WHEN is_ctx-customer_tier = 'G' AND is_ctx-delivery_type = 'E'
        THEN 100
      WHEN is_ctx-customer_tier = 'G'
        THEN 80
      WHEN is_ctx-delivery_type = 'E' AND is_ctx-net_amount >= 50000
        THEN 70
      WHEN is_ctx-is_first_buy = abap_true AND is_ctx-net_amount >= 10000
        THEN 60
      WHEN is_ctx-net_amount >= 100000
        THEN 50
      ELSE 10
    ).
  ENDMETHOD.
ENDCLASS.

이 코드의 매력은 비즈니스 규칙이 위에서 아래로 "우선순위 순서"대로 나열된다는 점입니다. 사양서를 그대로 코드로 옮긴 듯한 느낌이죠. 같은 로직을 중첩 IF로 쓰면 5단 중첩이 되어 한 화면에 들어오지도 않습니다.

단위 테스트도 깔끔하게 작성됩니다.

CLASS ltc_priority DEFINITION FOR TESTING
                   RISK LEVEL HARMLESS DURATION SHORT FINAL.
  PRIVATE SECTION.
    METHODS:
      gold_express_returns_100 FOR TESTING,
      first_buy_high_amount    FOR TESTING.
ENDCLASS.

CLASS ltc_priority IMPLEMENTATION.
  METHOD gold_express_returns_100.
    cl_abap_unit_assert=>assert_equals(
      exp = 100
      act = lcl_order_priority=>calc_priority(
              VALUE #( customer_tier = 'G' delivery_type = 'E' ) ) ).
  ENDMETHOD.

  METHOD first_buy_high_amount.
    cl_abap_unit_assert=>assert_equals(
      exp = 60
      act = lcl_order_priority=>calc_priority(
              VALUE #( customer_tier = 'B' delivery_type = 'N'
                       net_amount = 15000 is_first_buy = abap_true ) ) ).
  ENDMETHOD.
ENDCLASS.

COND는 부수효과(side-effect)가 없는 순수 함수형 표현이라 테스트하기 매우 쉽습니다. 입력 컨텍스트 하나에 출력 점수 하나가 보장되니까요.

인라인 선언 결합 — 임시 변수를 아예 없애기

SELECT FROM zsales_order
  FIELDS so_id, status_code, net_amount
  INTO TABLE @DATA(lt_orders).

LOOP AT lt_orders INTO DATA(ls_order).
  DATA(lv_action) = COND string(
    WHEN ls_order-status_code = 'OP' AND ls_order-net_amount > 100000
      THEN 'AUTO_APPROVE'
    WHEN ls_order-status_code = 'OP'
      THEN 'MANUAL_REVIEW'
    WHEN ls_order-status_code = 'CN'
      THEN 'SKIP'
    ELSE 'NO_ACTION' ).
  " ... lv_action에 따라 후처리
ENDLOOP.

흔히 빠지는 함정과 트러블슈팅

FAQ 1. "Type cannot be determined" 오류가 납니다.

COND 뒤의 타입을 생략하거나, 각 THEN의 반환 타입이 일치하지 않을 때 발생합니다. 예를 들어 한 분기는 string, 다른 분기는 i를 반환하면 컴파일러가 결과 타입을 정할 수 없습니다. 해결: COND string( ... )처럼 타입을 명시하고, 숫자 분기에는 CONV string( lv_num )로 변환합니다.

FAQ 2. 초기값이 반환돼서 버그가 났습니다.

ELSE를 생략하면 어떤 WHEN도 매치되지 않을 때 결과 타입의 초기값이 반환됩니다. string은 빈 문자열, i는 0이 되죠. 0과 "정말로 매치된 0"이 구분되지 않아 디버깅이 어려워집니다. 권장: ELSE를 반드시 명시하거나, 매핑이 누락된 경우 예외를 던지도록 ELSE THROW ...를 사용합니다.

FAQ 3. WHEN 절에 메서드 호출을 넣어도 되나요?

가능합니다. 단, 모든 WHEN의 조건식이 평가 순서대로 검사된다는 점을 기억해야 합니다. 만약 첫 번째 WHEN의 메서드 호출이 무겁고 자주 false라면, 두 번째 분기에서 사용할 가벼운 검사를 위로 올리는 것이 좋습니다.

FAQ 4. CONV는 언제 필요한가요?

THEN 절의 리터럴이 결과 타입과 호환되지 않을 때입니다. 예: COND p( WHEN ... THEN '10.50' )처럼 packed 타입에 문자 리터럴을 넣으면 암시적 변환이 일어나지만, 길이/소수점 정밀도가 다른 경우 CONV p( '10.50' )로 명시 변환해야 경고가 사라집니다.

흔한 실수 3가지:

  • WHEN 순서가 잘못되어 더 일반적인 조건이 먼저 매치되는 경우 (좁은 조건을 위로)
  • COND에 10개 이상의 WHEN을 욱여넣어 가독성이 IF보다 나빠지는 경우
  • COND 내부에서 변수에 값을 대입하려고 시도하는 경우 (표현식이지 문장이 아닙니다)

여기서 더 나아가려면

COND를 익혔다면 자연스럽게 다음 표현식들로 시야를 넓힐 수 있습니다. SWITCH는 단일 값 비교에 더 적합하고, REDUCE는 내부 테이블을 하나의 값으로 집계할 때, FILTER는 조건에 맞는 행만 추출할 때, VALUE는 구조체/테이블을 한 줄로 구성할 때 사용합니다. 이들을 조합하면 LOOP 한 줄 없이도 데이터 변환 파이프라인을 선언적으로 구성할 수 있습니다.

특히 RAP의 Behavior Implementation, Determination, Validation 메서드 안에서 COND는 상태 전이 규칙을 깔끔하게 표현하는 도구로 자주 쓰입니다. CDS 뷰의 case ... when ... end와도 사고방식이 유사해, ABAP 코드와 CDS를 오가는 개발에서 일관된 멘탈 모델을 가질 수 있게 도와줍니다.

댓글 0

아직 댓글이 없습니다.