ABAP

리턴값 무조건 받는 습관 그만 — ABAP Optional #shorts #SAP #ABAP

▶ YouTube에서 보기

왜 ABAP에서 Optional Return이 중요한가

ABAP 메서드를 설계할 때 가장 자주 마주치는 고민 중 하나는 "리턴값을 강제할 것인가, 선택적으로 둘 것인가"입니다. 자바나 C++에서는 리턴값을 무시하면 컴파일러 경고가 나오거나 명시적인 캐스팅이 필요한 경우가 많지만, ABAP의 RETURNING 파라미터는 호출자가 결과를 받지 않아도 문법적으로 아무런 문제가 없습니다. 이 특성은 잘 활용하면 코드를 극적으로 간결하게 만들지만, 잘못 쓰면 "리턴값이 있는데 왜 안 받지?"라는 코드 리뷰 지적을 받게 됩니다.

이번 글에서는 RETURNING VALUE() 선언의 본질, IF 조건에서의 직접 호출, 부수 효과만 필요한 호출 패턴 세 가지를 실무 관점에서 풀어봅니다. 다 읽고 나면 다음 항목이 명확해집니다.

  • RETURNING과 EXPORTING의 사용 시점 차이를 구분할 수 있다
  • 임시 변수 없이 메서드 결과를 조건문에 바로 사용할 수 있다
  • 리턴값을 의도적으로 버리는 패턴이 안전한 경우와 위험한 경우를 판단할 수 있다
  • ABAP Clean Code 원칙에 부합하는 메서드 시그니처를 설계할 수 있다

알고 있어야 할 사전 지식

ABAP OO(클래스/메서드) 문법에 대한 기본 이해, METHODS/CLASS-METHODS 선언 구문, 인라인 선언(DATA(...)) 사용 경험이 있으면 충분합니다. 함수형 메서드(functional method)와 절차형 메서드의 차이를 어렴풋이라도 알고 있다면 더 빠르게 이해할 수 있습니다.

실습 환경 및 권장 버전

이 패턴들은 ABAP 7.40 SP08 이후 모든 NetWeaver 기반 시스템에서 동작하며, S/4HANA(ABAP Platform 1909 이상)와 ABAP Cloud(Steampunk, BTP ABAP Environment)에서도 동일하게 사용할 수 있습니다. 인라인 선언과 메서드 체이닝은 7.40부터 도입되었으므로 ECC 6.0 EHP7 미만 시스템에서는 일부 문법을 일반 변수 선언으로 풀어 써야 합니다.

  • ABAP 언어 버전: 7.40 SP08 이상 권장, 7.55 이상에서 더 풍부한 함수형 표현 가능
  • 개발 도구: ADT(ABAP Development Tools) for Eclipse 또는 SE80
  • 코드 검사: ATC(ABAP Test Cockpit) + Clean ABAP 체크 변형 활성화
  • 유닛 테스트: ABAP Unit (SE80/ADT 내장)

ABAP Cloud 환경에서는 일부 레거시 구문(예: FORM/PERFORM)이 차단되므로 RETURNING 기반 함수형 메서드 패턴은 사실상 표준 권장 스타일이 되었습니다.

RETURNING의 본질 — 함수형 호출이라는 약속

ABAP에서 메서드 파라미터는 IMPORTING, EXPORTING, CHANGING, RETURNING 네 가지로 나뉩니다. 이 중 RETURNING은 특별한 규칙을 가집니다.

  • 한 메서드에 단 하나만 선언할 수 있다
  • 반드시 VALUE(...)로 감싸야 한다(값 전달만 허용)
  • RETURNING이 있으면 EXPORTING/CHANGING과 같이 쓸 수 없다(함수형 메서드 강제)
  • 호출 시 결과를 받아도 되고 무시해도 된다

마지막 특성이 이 글의 핵심입니다. RETURNING은 "리턴값을 줄 수는 있는데 받지 않아도 호출이 성립한다"는 옵셔널 계약입니다. 마치 자판기에서 음료를 뽑고 영수증을 받아도 되고, 그냥 가버려도 거래가 끝나는 것과 같습니다. 영수증(리턴값)은 보조 정보일 뿐 거래(부수 효과)의 본질이 아닙니다.

RETURNING은 "이 메서드는 함수형으로 동작한다"는 의도 표현이자, "받고 싶으면 받고 아니면 말라"는 호출자 친화적 계약이다.

반면 EXPORTING은 다릅니다. EXPORTING 파라미터는 호출 측에서 명시적으로 받지 않으면 컴파일 오류이거나 사용 의도가 모호해집니다. 그래서 같은 "결과 1개를 돌려준다"는 시나리오라도 RETURNING으로 선언하는 편이 클린 ABAP에서 권장됩니다.

비유 — 영수증 받는 사람, 안 받는 사람

편의점에서 결제 후 "영수증 드릴까요?"라고 물어볼 때, 받든 안 받든 결제는 이미 완료된 상태입니다. RETURNING도 마찬가지로 호출 그 자체로 동작은 끝나며, 리턴값은 필요한 사람만 받아 갑니다. 이 비유를 기억하면 세 가지 패턴이 자연스럽게 연결됩니다.

1단계 — RETURNING VALUE() 기본 선언

가장 단순한 형태부터 봅시다. 카운트를 반환하는 게터(getter) 메서드입니다.

CLASS lcl_order DEFINITION.
  PUBLIC SECTION.
    METHODS get_count
      RETURNING
        VALUE(rv_count) TYPE i.
  PRIVATE SECTION.
    DATA mv_count TYPE i.
ENDCLASS.

CLASS lcl_order IMPLEMENTATION.
  METHOD get_count.
    rv_count = mv_count.
  ENDMETHOD.
ENDCLASS.

이 메서드는 두 가지 방식으로 호출할 수 있습니다.

DATA(lo_order) = NEW lcl_order( ).

" (A) 리턴값을 받는 정석 호출
DATA(lv_cnt) = lo_order->get_count( ).

" (B) 리턴값을 무시하는 호출 — 문법적으로 100% 합법
lo_order->get_count( ).

(B)는 게터에서는 의미가 없지만, 문법은 허용합니다. 이 유연성이 이후 패턴의 토대가 됩니다. VALUE()를 붙인 이유는 값 전달을 보장하기 위함입니다. 참조 전달을 허용하면 외부에서 내부 상태를 의도치 않게 변경할 수 있기 때문에, RETURNING은 무조건 VALUE만 허용합니다.

2단계 — IF 조건에 메서드 결과를 직접 사용

가장 자주 쓰이면서도 가독성에 큰 영향을 주는 패턴입니다. ABAP 7.40 이후 함수형 메서드는 표현식이 허용되는 모든 위치에서 사용할 수 있게 되었습니다. 즉, 임시 변수를 거치지 않고 조건문에 바로 넣을 수 있습니다.

" 안티 패턴 — 불필요한 임시 변수
DATA lv_valid TYPE abap_bool.
lv_valid = lo_validator->is_valid( iv_order_id = lv_order ).
IF lv_valid = abap_true.
  lo_processor->process( lv_order ).
ENDIF.

" 권장 패턴 — 표현식 직접 사용
IF lo_validator->is_valid( iv_order_id = lv_order ) = abap_true.
  lo_processor->process( lv_order ).
ENDIF.

임시 변수 lv_valid는 한 번 쓰이고 버려지는 노이즈입니다. 함수형 메서드를 직접 호출하면 의도가 한 줄에 압축되어 드러납니다.

메서드 체이닝과의 결합

리턴값이 객체 참조이면 그 위에 다시 메서드를 호출하는 체이닝이 가능합니다.

" 체이닝 — get_status( ) 결과가 객체이고, 그 객체의 is_active( )를 즉시 호출
IF lo_order->get_status( )->is_active( ) = abap_true.
  lo_order->activate( ).
ENDIF.

" CASE에서도 동일하게 사용 가능
CASE lo_order->get_status( )->get_code( ).
  WHEN 'OPEN'.
    " ...
  WHEN 'CLOSED'.
    " ...
ENDCASE.

주의할 점은 체이닝이 깊어질수록 디버깅이 까다로워진다는 것입니다. ADT의 변수 검사 기능이 중간 결과를 잡지 못할 수 있으므로, 두 단계까지를 일반적인 권장 한계로 봅니다. 세 단계 이상은 중간 변수로 끊어 주는 편이 디버깅에 유리합니다.

WHILE, COND, SWITCH에서의 활용

" WHILE 조건
WHILE lo_iterator->has_next( ) = abap_true.
  DATA(lo_item) = lo_iterator->next( ).
  " ...
ENDWHILE.

" COND 표현식 — 한 줄로 분기 결과 할당
DATA(lv_label) = COND string(
  WHEN lo_order->is_paid( ) = abap_true THEN |Paid|
  ELSE |Pending| ).

이런 위치에서 함수형 메서드를 자유롭게 쓸 수 있게 되면서 ABAP 코드가 자연어에 가까워졌습니다.

3단계 — 부수 효과 호출에서 리턴값 의도적으로 버리기

로그 기록, 카운터 증가, 캐시 무효화, 이벤트 발행 같이 "결과가 아니라 동작 자체"가 목적인 호출이 있습니다. 이때 RETURNING이 선언되어 있어도 호출자는 결과를 받지 않습니다.

CLASS lcl_logger DEFINITION.
  PUBLIC SECTION.
    METHODS log_event
      IMPORTING
        iv_event_type TYPE string
        iv_user       TYPE syuname
      RETURNING
        VALUE(rv_log_id) TYPE sysuuid_x16.
ENDCLASS.

" 호출 측 — 로그 ID가 필요 없으므로 리턴값 무시
lo_logger->log_event(
  iv_event_type = 'ACCESS'
  iv_user       = sy-uname ).

" 카운터 증가 — 현재 카운트가 궁금하지 않으면 무시
lo_metrics->increment_counter( iv_key = 'VISITS' ).

이 스타일의 장점은 메서드 설계자가 "혹시 ID나 새 카운트를 알고 싶을 호출자도 있을 것"이라고 예측해 RETURNING을 열어두되, 대부분의 호출자는 신경 쓰지 않아도 되도록 한다는 점입니다. EXPORTING으로 했다면 모든 호출이 변수를 선언해야 했을 것입니다.

실무 시나리오 — 트랜잭션 로깅 래퍼

METHOD save_order.
  TRY.
      mo_persistence->insert( is_order ).

      " 감사 로그 — 실패해도 본 흐름은 진행
      TRY.
          mo_logger->log_event(
            iv_event_type = 'ORDER_SAVED'
            iv_user       = sy-uname ).
        CATCH cx_log_failure.
          " 로그 실패는 경고만, 비즈니스 흐름은 보호
      ENDTRY.

      rv_success = abap_true.
    CATCH cx_persistence INTO DATA(lx_err).
      mo_logger->log_event(
        iv_event_type = 'ORDER_FAILED'
        iv_user       = sy-uname ).
      RAISE EXCEPTION TYPE cx_order_save_failed
        EXPORTING previous = lx_err.
  ENDTRY.
ENDMETHOD.

로그 메서드의 리턴값(로그 ID)은 일반적인 호출에서 필요 없으므로 무시합니다. 그러나 만약 컴플라이언스 추적을 위해 로그 ID를 외부 시스템에 함께 전달해야 한다면, 같은 메서드를 그대로 사용하면서 결과만 받으면 됩니다. 메서드 변경 없이 호출 패턴만 바뀌는 유연성, 이것이 RETURNING 옵셔널 계약의 진짜 가치입니다.

운영급 고려 — 성능과 테스트

RETURNING이 VALUE로 강제된다는 점은 큰 구조체나 내부 테이블을 반환할 때 성능 부담이 될 수 있습니다. ABAP 커널은 가능한 경우 카피온라이트(copy-on-write) 최적화를 수행하지만, 호출 측에서 결과를 즉시 변경하면 실제 복사가 일어납니다. 빈번하게 호출되는 메서드가 큰 테이블을 RETURNING으로 돌려준다면 다음을 검토합니다.

  • 참조 반환이 필요하면 RETURNING VALUE(ro_data) TYPE REF TO ...로 객체 핸들만 돌려주기
  • 읽기 전용 의도를 명확히 하려면 인터페이스로 캡슐화
  • ABAP Unit 테스트에서는 RETURNING 메서드가 테스트 더블(Test Double)로 모킹하기 가장 쉬움 — cl_abap_testdoubleconfigure_callset_parameter로 손쉽게 스텁 가능
" ABAP Unit에서 RETURNING 메서드 스텁
DATA(lo_double) = CAST if_validator(
  cl_abap_testdouble=>create( 'if_validator' ) ).

cl_abap_testdouble=>configure_call( lo_double )->returning( abap_true ).
lo_double->is_valid( iv_order_id = '0001' ).

흔한 실수와 트러블슈팅

Q1. RETURNING 메서드를 호출했는데 "수식이 잘못되었습니다" 컴파일 오류가 납니다.

괄호 안에 파라미터를 전달할 때 키워드 인자 형식을 빠뜨렸을 가능성이 큽니다. ABAP은 IMPORTING 파라미터가 두 개 이상이면 iv_name = lv_value 형태로 모두 명시해야 합니다. 또한 함수형 호출 시에는 빈 괄호 method( )가 필수이며, 공백 한 칸도 빠뜨리면 안 됩니다(method()는 오류).

Q2. RETURNING과 EXPORTING을 함께 쓰고 싶은데 막혀 있습니다.

의도적 제약입니다. 함수형 메서드는 "입력을 받아 결과를 돌려준다"는 단일 책임을 갖도록 설계되었습니다. 결과가 여러 개라면 (1) 구조체로 묶어 RETURNING으로 돌려주거나, (2) EXPORTING 여러 개로 전환해 일반 메서드로 만드는 두 가지 길이 권장됩니다. 일반적으로 구조체 RETURNING이 호출 측 코드를 더 깔끔하게 만듭니다.

Q3. 메서드 체이닝 중간에 NULL 참조 에러가 발생합니다.

ABAP은 자바와 달리 명시적 null 안전 연산자가 없습니다. 체인 중간 메서드가 INITIAL한 객체 참조를 반환하면 다음 호출에서 CX_SY_REF_IS_INITIAL 예외가 발생합니다. 두 단계 이상 체이닝할 때는 (1) 중간 결과를 변수로 받아 IS BOUND 검사를 하거나, (2) Null Object 패턴으로 항상 유효한 객체를 반환하도록 설계하는 편이 안전합니다.

Q4. 리턴값을 무시하는 호출이 ATC에서 경고로 잡힙니다.

일부 사이트 커스텀 ATC 변형은 "RETURNING 결과 무시"를 코드 스멜로 분류하기도 합니다. 부수 효과 호출이 정당한 경우라면 pragma ##NEEDED나 pseudo comment로 예외 처리하거나, 사이트 ATC 룰셋에 예외 패턴을 추가하는 협의가 필요합니다. 다만 게터 메서드의 결과를 무시하는 호출은 대부분 진짜 실수이므로 경고를 존중하는 편이 좋습니다.

다음으로 살펴볼 주제

RETURNING 패턴에 익숙해졌다면 이어서 살펴볼 가치가 있는 주제는 다음과 같습니다.

  • ABAP 함수형 표현식 전반REDUCE, FILTER, FOR 루프 표현식과 RETURNING 메서드의 결합
  • Null Object 패턴 — 메서드 체이닝의 안전성을 높이는 설계 기법
  • ABAP Unit Test Double — RETURNING 메서드를 깔끔하게 모킹하는 방법
  • Clean ABAP 가이드라인 — 함수형 메서드 명명 규칙과 부수 효과 명시 규약
  • RAP(ABAP RESTful Application Programming) — Behavior Implementation에서 RETURNING 패턴 활용

더 읽어볼 자료

댓글 0

아직 댓글이 없습니다.