ABAP

값 복사 없이 REF로 성능 90% 올리기 #shorts #SAP #ABAP

▶ YouTube에서 보기

이 글에서 다루는 내용

ABAP에서 REF TO 참조 변수는 데이터 객체나 인스턴스를 직접 가리키는 포인터 역할을 합니다. 값 복사가 아닌 참조 전달을 통해 메모리 효율성과 다형성을 동시에 확보할 수 있어, 대용량 내부 테이블 처리·동적 디스패치·Factory 패턴 등 실무 곳곳에서 핵심적으로 사용됩니다. 이 글은 참조 변수의 동작 원리부터 역참조, 타입 변환, 안전한 해제 방법까지 단계적으로 정리합니다.

  • 참조 변수가 필요한 상황과 메모리 모델 이해
  • REF #, GET REFERENCE OF, NEW 연산자 활용
  • 역참조(->*, ->field)와 CAST 연산자 적용
  • Factory 패턴·런타임 다형성을 통한 구조 설계
  • IS BOUND 체크와 DANGLING 참조 회피 전략

이 글을 읽기 전 알아두면 좋은 것들

ABAP 기본 변수 선언(DATA), 내부 테이블과 워크 에어리어 개념, 클래스·메서드 구조에 대한 기초적인 이해가 필요합니다. ABAP Objects의 인스턴스 생성 문법(CREATE OBJECT)을 한 번이라도 사용해 봤다면 참조 변수의 개념이 훨씬 직관적으로 다가옵니다. 추가로 인터페이스와 상속 관계를 다뤄봤다면 다형성 시나리오 이해가 빠릅니다.

환경 및 버전 정보

이 글의 예제는 ABAP 7.40 SP08 이상에서 동작합니다. NEW, REF #, CAST 같은 인라인 연산자는 7.40부터 도입된 문법이며, 일부 표현식(예: 인라인 선언 DATA(...))은 7.40 SP02 이상에서 사용 가능합니다. SAP NetWeaver 7.50, S/4HANA 1909/2022, BTP ABAP Environment(Steampunk) 모두에서 동일한 의미로 동작합니다.

  • 개발 도구: ABAP Development Tools(ADT) for Eclipse 권장
  • 대안: SAP GUI의 SE80/SE38 (구문 표시는 다소 다를 수 있음)
  • 테스트: 단위 테스트 클래스(CL_AUNIT_ASSERT) 활용
  • 참고 SAP Note: 1760934 (NEW 연산자 동작 관련)

S/4HANA 2022 기준 RAP(RESTful ABAP Programming Model) 환경에서도 비즈니스 객체 처리 시 참조 변수가 광범위하게 사용되므로, 차세대 ABAP을 다룬다면 필수적으로 익혀야 합니다.

참조 변수의 동작 원리

참조 변수는 메모리 주소를 담는 "표지판" 같은 존재입니다. 일반 변수가 값 자체를 저장하는 상자라면, 참조 변수는 "그 상자가 어디에 있는지" 알려주는 화살표입니다. ABAP에서는 크게 두 가지 형태가 있습니다.

  • 데이터 참조(Data Reference): 임의의 데이터 객체(구조체·테이블·기본 타입)를 가리킴 — REF TO data, REF TO ty_sales_order
  • 객체 참조(Object Reference): 클래스 인스턴스나 인터페이스 구현체를 가리킴 — REF TO cl_invoice_handler

참조 변수가 필요한 대표적인 상황은 다음과 같습니다.

  1. 성능: 수십만 건의 영업 주문 라인을 메서드 간 전달할 때 값 복사를 피해야 함. 참조 전달은 포인터 크기만큼만 이동합니다.
  2. 유연성: 컴파일 시점에 타입이 확정되지 않는 동적 SQL 결과 처리
  3. 다형성: 부모 인터페이스 타입의 참조에 자식 구현체를 담아 런타임에 다른 동작을 호출
  4. 선택적 존재: NULL 가능한 값(있을 수도, 없을 수도 있는 객체)을 표현

역참조(dereferencing)는 화살표가 가리키는 실제 값에 접근하는 행위입니다. ABAP에서는 두 가지 표기를 씁니다.

  • lr_order->* — 참조가 가리키는 데이터 객체 전체에 접근
  • lr_handler->process( ) — 객체 참조의 메서드 호출 (자동 역참조)
  • lr_order->order_id — 데이터 참조의 구조체 필드 접근(7.40+ 단축 문법)

여기서 핵심은 "참조 변수 자체가 비어있을 수 있다"는 점입니다. 가리키는 대상이 없는 상태를 initial reference 또는 null reference라 부르며, 이를 무시하고 역참조하면 즉시 CX_SY_REF_IS_INITIAL 예외가 발생합니다. 따라서 IS BOUND 체크가 모든 역참조의 전제 조건이 됩니다.

1단계 — 기본 참조 변수 선언과 역참조

가장 단순한 예제부터 시작합니다. 영업 주문 구조체를 가리키는 데이터 참조 변수를 만들고, 값에 접근해 보겠습니다.

REPORT z_ref_basic.

TYPES: BEGIN OF ty_sales_order,
         order_id   TYPE c LENGTH 10,
         customer   TYPE c LENGTH 40,
         net_amount TYPE p LENGTH 9 DECIMALS 2,
         currency   TYPE c LENGTH 3,
       END OF ty_sales_order.

DATA: ls_order TYPE ty_sales_order,
      lr_order TYPE REF TO ty_sales_order.

START-OF-SELECTION.
  ls_order = VALUE #(
    order_id   = '4500001234'
    customer   = 'Acme Industrial'
    net_amount = '15800.50'
    currency   = 'EUR'
  ).

  GET REFERENCE OF ls_order INTO lr_order.
  lr_order = REF #( ls_order ).

  WRITE: / 'Order:', lr_order->*-order_id.
  WRITE: / 'Customer:', lr_order->customer,
         / 'Amount :', lr_order->net_amount, lr_order->currency.

2단계 — 동적 처리와 안전한 예외 핸들링

실무에서는 참조 변수가 비어있을 가능성, 캐스팅 실패 가능성을 항상 고려해야 합니다. 다음은 영수증 데이터를 동적으로 받아 처리하는 시나리오로, 로깅과 예외 처리가 포함되어 있습니다.

CLASS lcl_invoice_processor DEFINITION.
  PUBLIC SECTION.
    TYPES: BEGIN OF ty_invoice_item,
             invoice_no TYPE c LENGTH 10,
             posting_dt TYPE d,
             gross_amt  TYPE p LENGTH 11 DECIMALS 2,
           END OF ty_invoice_item.

    METHODS:
      process_reference
        IMPORTING ir_data        TYPE REF TO data
        RETURNING VALUE(rv_ok)   TYPE abap_bool
        RAISING   cx_sy_move_cast_error.
ENDCLASS.

CLASS lcl_invoice_processor IMPLEMENTATION.
  METHOD process_reference.
    DATA: lr_invoice TYPE REF TO ty_invoice_item.

    IF ir_data IS NOT BOUND.
      MESSAGE 'Reference is initial' TYPE 'I'.
      rv_ok = abap_false.
      RETURN.
    ENDIF.

    TRY.
        lr_invoice ?= ir_data.
      CATCH cx_sy_move_cast_error INTO DATA(lx_cast).
        cl_demo_output=>display( |Cast failed: { lx_cast->get_text( ) }| ).
        RAISE EXCEPTION lx_cast.
    ENDTRY.

    cl_demo_output=>display(
      |Processing invoice { lr_invoice->invoice_no } |
      && |amount { lr_invoice->gross_amt }| ).

    rv_ok = abap_true.
  ENDMETHOD.
ENDCLASS.

3단계 — Factory 패턴과 다형성 적용

참조 변수의 진가는 다형성에서 드러납니다. 결제 수단(카드·계좌이체·암호화폐)에 따라 다른 검증 로직을 실행해야 하는 구매 주문 시스템을 가정해 보겠습니다.

INTERFACE lif_payment_validator.
  METHODS validate
    IMPORTING iv_amount      TYPE p
    RETURNING VALUE(rv_valid) TYPE abap_bool.
ENDINTERFACE.

CLASS lcl_card_validator DEFINITION.
  PUBLIC SECTION.
    INTERFACES lif_payment_validator.
ENDCLASS.

CLASS lcl_card_validator IMPLEMENTATION.
  METHOD lif_payment_validator~validate.
    rv_valid = COND #( WHEN iv_amount <= 50000 THEN abap_true ELSE abap_false ).
  ENDMETHOD.
ENDCLASS.

CLASS lcl_transfer_validator DEFINITION.
  PUBLIC SECTION.
    INTERFACES lif_payment_validator.
ENDCLASS.

CLASS lcl_transfer_validator IMPLEMENTATION.
  METHOD lif_payment_validator~validate.
    rv_valid = abap_true.
  ENDMETHOD.
ENDCLASS.

CLASS lcl_validator_factory DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS create
      IMPORTING iv_method     TYPE c
      RETURNING VALUE(ro_val) TYPE REF TO lif_payment_validator
      RAISING   cx_sy_create_object_error.
ENDCLASS.

CLASS lcl_validator_factory IMPLEMENTATION.
  METHOD create.
    CASE iv_method.
      WHEN 'CARD'.
        ro_val = NEW lcl_card_validator( ).
      WHEN 'BANK'.
        ro_val = NEW lcl_transfer_validator( ).
      WHEN OTHERS.
        RAISE EXCEPTION TYPE cx_sy_create_object_error.
    ENDCASE.
  ENDMETHOD.
ENDCLASS.

START-OF-SELECTION.
  DATA(lo_validator) = lcl_validator_factory=>create( 'CARD' ).
  IF lo_validator IS BOUND.
    DATA(lv_ok) = lo_validator->validate( 75000 ).
    WRITE: / 'Valid:', lv_ok.
  ENDIF.
  CLEAR lo_validator.

자주 마주치는 함정과 해결

Q1. CX_SY_REF_IS_INITIAL이 발생합니다.

역참조 직전에 IS BOUND 검사가 누락된 경우입니다. IF lr_data IS BOUND. 블록 안에서만 lr_data->*를 사용하세요. 함수 모듈 반환값처럼 외부에서 받은 참조는 항상 검증이 필요합니다.

Q2. DANGLING 참조 — 가리키던 데이터가 사라졌어요.

로컬 변수의 주소를 참조에 담은 뒤 해당 변수의 스코프가 끝나면 위험합니다. 다만 ABAP은 자동 가비지 컬렉션을 수행해 참조가 살아있는 한 데이터 객체도 유지됩니다. 문제가 되는 패턴은 FIELD-SYMBOLS를 거쳐 임시 행에 참조를 만든 뒤 내부 테이블이 재구성(SORT, DELETE)되는 경우입니다. 이때는 참조를 재획득하세요.

Q3. CREATE OBJECT와 NEW 중 무엇을 써야 하나요?

기능적으로 동일하지만, 7.40 이상 환경에서는 NEW가 권장됩니다. 인라인 선언과 함께 사용 가능하고(DATA(lo_x) = NEW lcl_x( )), 표현식 안에서 즉시 사용할 수 있어 코드가 간결해집니다. 동적 클래스명으로 생성해야 하는 특수한 경우에만 CREATE OBJECT ... TYPE (lv_classname) 형태를 유지합니다.

Q4. ?=와 CAST #( )의 차이는?

의미는 같으나 CAST는 표현식으로 사용 가능해 체이닝이 자연스럽습니다. 예: CAST cl_target( io_source )->do_something( ). 둘 다 실패 시 동일한 CX_SY_MOVE_CAST_ERROR를 던집니다.

참조 변수 활용 확장 주제

참조 변수를 익혔다면 다음 주제로 확장해 보세요. RTTI/RTTC(CL_ABAP_TYPEDESCR)를 활용한 런타임 타입 분석은 동적 SQL 결과 처리에 필수입니다. FIELD-SYMBOLS는 참조와 비슷하지만 자동 역참조되는 가벼운 대안으로, 대량 루프 성능 최적화에 유리합니다. RAP(RESTful ABAP Programming)에서는 비즈니스 객체를 다룰 때 참조 기반 인스턴스 핸들링이 일상적이며, 디자인 패턴(Strategy, Observer, Singleton)도 참조 변수 위에 구축됩니다.

댓글 0

아직 댓글이 없습니다.