ABAP

타입 고정 그만 — ABAP 제네릭 실수 3가지 #shorts #SAP #ABAP

▶ YouTube에서 보기

ABAP 제네릭 타입 심층 다이브 - TYPE ANY와 동적 데이터 처리

ABAP 개발에서 같은 로직을 여러 구조체나 테이블에 반복 적용해야 하는 상황은 매우 흔합니다. 예를 들어, 어떤 내부 테이블이 들어오든 특정 필드 값을 추출해 로그를 남기는 유틸리티 메서드를 작성한다고 가정해 봅시다. 고정 타입(TYPE ty_customer, TYPE ty_order 등)으로 시그니처를 짜면 타입별로 메서드를 N개 만들어야 합니다. 이때 제네릭 타입(Generic Types)이 등장합니다.

이 글에서 다룰 항목을 먼저 정리하면 다음과 같습니다.

  • 제네릭 타입의 본질과 고정 타입 대비 트레이드오프
  • TYPE ANY, TYPE ANY TABLE의 동작 원리
  • ASSIGN COMPONENT ... OF STRUCTURE로 동적 필드 접근
  • 런타임 타입 안정성을 확보하는 RTTI 패턴
  • 퍼포먼스 영향과 흔한 함정 회피법

제네릭 타입의 작동 모델

ABAP의 변수는 일반적으로 컴파일 타임에 타입이 결정됩니다. DATA lv_count TYPE i처럼 선언하면 정수만 담을 수 있고, 컴파일러가 타입 검증까지 수행합니다. 반면 제네릭 타입은 컴파일 시점에는 "어떤 타입이든 받을 수 있다"는 자리만 마련해 두고, 실제 타입은 런타임에 바인딩됩니다.

ABAP의 주요 제네릭 타입 계층은 다음과 같습니다.

  • ANY - 모든 데이터 타입(엘리멘터리, 구조체, 테이블)
  • ANY TABLE - 모든 내부 테이블 종류
  • INDEX TABLE - STANDARD 및 SORTED 테이블(인덱스 접근 가능)
  • DATA - 모든 데이터 객체

중요한 점은 제네릭 타입을 가진 변수에 직접 값을 대입하거나 연산을 수행할 수 없다는 것입니다. 반드시 필드 심볼이나 데이터 참조를 통해 "실체화"된 후에 접근해야 합니다.

1단계 — TYPE ANY 기본 선언

가장 단순한 예시부터 시작합니다. 메서드가 어떤 엘리멘터리 값이든 받아 문자열로 변환해 출력합니다.

CLASS zcl_any_demo DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    CLASS-METHODS print_value
      IMPORTING iv_value TYPE any.
ENDCLASS.

CLASS zcl_any_demo IMPLEMENTATION.
  METHOD print_value.
    DATA(lv_text) = CONV string( iv_value ).
    WRITE: / lv_text.
  ENDMETHOD.
ENDCLASS.

START-OF-SELECTION.
  zcl_any_demo=>print_value( 'Hello ABAP' ).
  zcl_any_demo=>print_value( 42 ).
  zcl_any_demo=>print_value( sy-datum ).

iv_value는 호출 시 전달된 실제 인수의 타입으로 바인딩됩니다. 단, 구조체나 테이블 전체를 넘기면 변환 시 런타임 예외가 발생할 수 있으므로 주의가 필요합니다.

2단계 — TYPE ANY TABLE로 범용 로깅 메서드

실무에서 가장 자주 마주치는 시나리오는 "어떤 내부 테이블이든 특정 컬럼 값을 뽑아 로그에 남기는" 작업입니다. 다음 예제는 테이블 종류(STANDARD/SORTED/HASHED)에 무관하게 동작합니다.

CLASS zcl_table_logger DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    CLASS-METHODS log_column
      IMPORTING it_data       TYPE ANY TABLE
                iv_field_name TYPE fieldname.
ENDCLASS.

CLASS zcl_table_logger IMPLEMENTATION.
  METHOD log_column.
    FIELD-SYMBOLS: <ls_row>   TYPE any,
                   <lv_field> TYPE any.

    LOOP AT it_data ASSIGNING <ls_row>.
      ASSIGN COMPONENT iv_field_name
        OF STRUCTURE <ls_row> TO <lv_field>.

      IF sy-subrc <> 0.
        MESSAGE |Field { iv_field_name } not found| TYPE 'I'.
        RETURN.
      ENDIF.

      WRITE: / sy-tabix, CONV string( <lv_field> ).
    ENDLOOP.
  ENDMETHOD.
ENDCLASS.

핵심 포인트는 세 가지입니다. 첫째, LOOP AT ... ASSIGNING에서 필드 심볼은 행 구조를 그대로 가리키므로 복사 비용이 없습니다. 둘째, ASSIGN COMPONENT는 컬럼명을 런타임 문자열로 받아 해당 필드에 심볼을 바인딩합니다. 셋째, sy-subrc를 반드시 확인해야 합니다.

3단계 — ASSIGN COMPONENT 안전하게 쓰기

ASSIGN COMPONENT는 강력하지만 함정이 많습니다. 프로덕션 레벨에서는 RTTI를 결합해 컬럼 타입을 사전 검증하는 패턴을 사용합니다.

METHOD extract_column.
  FIELD-SYMBOLS: <ls_row>   TYPE any,
                 <lv_field> TYPE any.

  " 1) 첫 행으로 구조 메타데이터 확인 (루프 외부 — 1회만)
  READ TABLE it_data ASSIGNING <ls_row> INDEX 1.
  IF sy-subrc <> 0.
    RETURN. " 빈 테이블
  ENDIF.

  DATA(lo_struct) = CAST cl_abap_structdescr(
    cl_abap_typedescr=>describe_by_data( <ls_row> ) ).

  READ TABLE lo_struct->components
    WITH KEY name = to_upper( iv_field_name )
    TRANSPORTING NO FIELDS.
  IF sy-subrc <> 0.
    RAISE EXCEPTION TYPE cx_sy_move_cast_error.
  ENDIF.

  " 2) 컬럼 존재 보장 후 루프
  LOOP AT it_data ASSIGNING <ls_row>.
    ASSIGN COMPONENT iv_field_name
      OF STRUCTURE <ls_row> TO <lv_field>.
    CHECK sy-subrc = 0.
    APPEND CONV string( <lv_field> ) TO rt_result.
  ENDLOOP.
ENDMETHOD.

RTTI 메타데이터는 루프 외부에서 1회만 조회합니다. 대용량 테이블에서 매 행마다 RTTI를 호출하면 성능이 급격히 나빠집니다.

흔한 실수 3가지와 해결법

실수 1 — sy-subrc 미확인

ASSIGN이 실패하면 필드 심볼은 이전 바인딩 상태를 유지합니다. 이전 행의 값이 출력되는 버그가 발생하므로, 모든 ASSIGN 직후 CHECK sy-subrc = 0 처리가 필수입니다.

실수 2 — ANY TABLE에 키 접근 시도

HASHED/SORTED 테이블 전용 키 접근은 ANY TABLE로는 사용할 수 없습니다. 키 접근이 필요하면 더 구체적인 제네릭 타입(HASHED TABLE)으로 파라미터를 좁혀야 합니다.

실수 3 — RTTI를 루프 안에서 반복 호출

컬럼 존재 여부 확인을 위해 cl_abap_typedescr=>describe_by_data()를 루프 안에서 호출하면 수십만 행 처리 시 성능이 수 배 저하됩니다. 반드시 루프 진입 전 1회만 호출하고 결과를 캐시하세요.

제네릭 타입 활용 체크리스트

  • 가능한 가장 좁은 제네릭 타입(STANDARD TABLE 등)을 선택했는가?
  • ASSIGN/READ 결과의 sy-subrc를 모두 확인했는가?
  • RTTI 메타데이터를 루프 외부에서 1회만 조회하는가?
  • 예외 클래스를 정의해 호출 측에서 복구 가능하게 했는가?
  • 빈 테이블 케이스를 명시적으로 처리했는가?

제네릭 타입을 마스터하면 범용 유틸리티 메서드 작성이 훨씬 간결해집니다. 다음으로 학습하면 좋은 주제는 RTTC(Run Time Type Creation)를 활용한 동적 구조체 생성과 Data References로 동적 데이터를 다루는 패턴입니다.

댓글 0

아직 댓글이 없습니다.