ABAP

내부테이블 인라인 실수 3가지 #shorts #SAP #ABAP

개요와 이 글에서 다루는 것

ABAP 7.40 이후 도입된 인라인 선언(DATA(...), FINAL(...))과 표현식 기반 연산자(VALUE, CORRESPONDING, FILTER, REDUCE, FOR)는 코드를 극적으로 짧게 만들어 줍니다. 그러나 실제 프로젝트에서는 이 문법이 오히려 런타임 덤프, 잘못된 필드 매핑, 스코프 오류를 유발하는 원흉이 되기도 합니다. 이 글은 SalesOrder, PurchaseOrder, MaterialDocument 같은 실제 비즈니스 오브젝트를 다루면서 개발자가 가장 자주 저지르는 세 가지 인라인 조작 실수를 짚어보고, 원인 분석과 안전한 대안까지 제시합니다.

  • 인라인 VALUE 선언에서 발생하는 타입 불일치 원인 이해
  • CORRESPONDING의 자동 필드 매핑이 놓치는 지점 파악
  • FILTERWHERE의 동작 차이 구별
  • 인라인 변수의 스코프 함정 회피
  • 프로덕션급 방어 패턴 정립

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

이 글은 ABAP 문법 기초와 내부 테이블(STANDARD/SORTED/HASHED)의 개념, READ TABLE·LOOP AT 같은 고전적 명령을 이미 사용해 본 개발자를 대상으로 합니다. ABAP 7.40 SP08 이상의 신문법(DATA(x) = ..., VALUE #( ), FOR ... IN)에 노출된 경험이 있으면 이해가 훨씬 수월합니다. SAP GUI 또는 ABAP Development Tools(ADT, Eclipse) 사용 경험을 전제합니다.

환경, 버전, 사전 준비

다루는 문법과 예제 코드는 다음 환경 기준으로 검증되었습니다.

  • SAP NetWeaver AS ABAP 7.52 이상(온프레미스) 또는 ABAP Platform 2022 이상
  • SAP BTP ABAP Environment(Steampunk)의 최신 릴리스에서도 동일 문법이 유효하며, 일부 표현식은 Cloud 릴리스에서 더 엄격하게 검증됩니다
  • ADT(Eclipse) 최신 버전 권장 — 인라인 변수의 타입을 툴팁으로 확인할 수 있어 디버깅 효율이 크게 오릅니다
  • 테스트 데이터: 실제 시스템에서는 ZTSALESORDER, ZTPURCHASEORDER 같은 사용자 정의 테이블 또는 CDS View를 활용하는 것을 권장합니다
버전 참고: ABAP 7.40 이전 시스템에서는 아래 코드가 문법 오류를 일으킵니다. 레거시 유지보수를 병행 중이라면 시스템별 문법 사용 정책을 팀 내 컨벤션으로 명문화해 두는 것이 안전합니다.

인라인 조작의 동작 원리

인라인 선언은 컴파일러가 우변(RHS)의 타입을 정적으로 추론하여 좌변(LHS)의 변수를 즉석에서 생성하는 문법 설탕입니다. 예를 들어 DATA(lt_orders) = VALUE tt_salesorder( ( orderid = '4500' amount = 100 ) ).는 겉으로는 한 줄이지만, 내부적으로는 (1) tt_salesorder 타입 확인, (2) 임시 구조체 생성, (3) 초기화된 테이블 반환, (4) 좌변 심볼 테이블에 lt_orders 등록이라는 네 단계를 거칩니다.

비유하자면 인라인 문법은 "포장 완료된 택배"입니다. 발송인(우변 표현식)이 완성된 상품을 보내고, 수령인(좌변)은 상자를 열자마자 사용합니다. 문제는 이 택배가 포장을 열기 전까지는 안에 뭐가 들었는지 모른다는 점입니다. 컴파일러는 알지만 사람은 코드만 봐서는 즉시 파악하기 어렵습니다. 그래서 타입 불일치, 필드 누락, 스코프 이슈가 발생해도 원인을 찾기 힘든 것입니다.

또한 각 연산자는 목적이 명확히 다릅니다.

  • VALUE: 구조체·내부 테이블을 생성하고 값을 채운다
  • CORRESPONDING: 서로 다른 타입 간 동일 명칭 필드를 매핑한다(기본 동작)
  • FILTER: 소스 테이블에서 조건에 맞는 행을 추출한다(SORTED/HASHED 전용 제약 존재)
  • FOR: 반복문을 표현식 내부에서 실행한다(컨스트럭터 표현식과 결합)

각 연산자의 성격을 오해하면 세 가지 대표적 실수가 발생합니다. 이제 실전 코드로 하나씩 살펴보겠습니다.

실전 예제 1단계 — 기본 인라인 조작

먼저 SalesOrder를 다루는 기본 예제입니다. 아래 코드는 정상적으로 동작하지만, 이후 실수 사례와 대조하기 위한 기준점입니다.

TYPES: BEGIN OF ty_sales_order,
         order_id   TYPE c LENGTH 10,
         customer   TYPE c LENGTH 10,
         net_amount TYPE p LENGTH 13 DECIMALS 2,
         currency   TYPE c LENGTH 3,
       END OF ty_sales_order,
       tt_sales_order TYPE STANDARD TABLE OF ty_sales_order
                      WITH EMPTY KEY.

" 인라인 VALUE 로 테이블 생성
DATA(lt_orders) = VALUE tt_sales_order(
  ( order_id = '4500000001' customer = 'C001' net_amount = '1200.00' currency = 'EUR' )
  ( order_id = '4500000002' customer = 'C002' net_amount = '350.75'  currency = 'USD' )
  ( order_id = '4500000003' customer = 'C001' net_amount = '980.00'  currency = 'EUR' ) ).

" 인라인 LOOP with FINAL
LOOP AT lt_orders INTO FINAL(ls_row).
  WRITE: / ls_row-order_id, ls_row-customer, ls_row-net_amount, ls_row-currency.
ENDLOOP.

FINAL(ls_row)는 ABAP 7.57부터 지원되는 불변 인라인 선언으로, 루프 내부에서 실수로 값을 수정하는 것을 컴파일 단계에서 차단합니다. 값을 변경할 필요가 없는 읽기 전용 루프에서는 DATA 대신 FINAL을 권장합니다.

실전 예제 2단계 — 실무 시나리오의 세 가지 실수

실수 A: VALUE 인라인의 타입 불일치

" 잘못된 예: 리터럴 타입이 다름
DATA(lt_bad) = VALUE tt_sales_order(
  ( order_id = 4500000001               " 숫자 리터럴 → 자동 변환 문제
    customer = 'C001'
    net_amount = 1200                   " 정수 → 소수점 손실 가능
    currency = 'EURO' ) ).              " LENGTH 3 초과 → 잘림

컴파일러는 order_id가 CHAR(10)임에도 숫자 리터럴 4500000001을 받아들이고 자동 변환합니다. 결과는 '4500000001'이 아니라 ' 4500000001'처럼 정렬이 어긋난 값이 될 수 있습니다. currency = 'EURO'는 LENGTH 3 필드에 4자를 넣어 'EUR'로 잘려 저장됩니다. 오류가 아니라 조용한 데이터 손상이라는 점이 문제입니다.

" 안전한 예: 리터럴을 명시적으로 CHAR 로
DATA(lt_good) = VALUE tt_sales_order(
  ( order_id = '4500000001'
    customer = 'C001'
    net_amount = CONV #( '1200.00' )
    currency = 'EUR' ) ).

실수 B: CORRESPONDING 자동 매핑 오용

TYPES: BEGIN OF ty_order_summary,
         doc_id     TYPE c LENGTH 10,   " 필드명이 order_id와 다름
         customer   TYPE c LENGTH 10,
         net_amount TYPE p LENGTH 13 DECIMALS 2,
       END OF ty_order_summary,
       tt_order_summary TYPE STANDARD TABLE OF ty_order_summary WITH EMPTY KEY.

" 잘못된 기대: order_id 가 doc_id 로 자동 매핑될 것이라 착각
DATA(lt_summary) = CORRESPONDING tt_order_summary( lt_orders ).

CORRESPONDING은 동일한 이름의 필드만 자동 매핑합니다. order_iddoc_id는 이름이 달라 매핑되지 않고, 결과 테이블의 doc_id는 모두 초기값(공백)이 됩니다. 개발자가 오후 내내 SalesOrder 목록이 왜 비어 보이는지 디버깅하는 전형적 시나리오입니다.

" 안전한 예: MAPPING 절 명시
DATA(lt_summary_ok) = CORRESPONDING tt_order_summary(
                        lt_orders MAPPING doc_id = order_id ).

실수 C: FILTER 조건과 WHERE 절 혼동

" 잘못된 예: STANDARD TABLE 에 FILTER 사용 시도
DATA(lt_eur) = FILTER #( lt_orders WHERE currency = 'EUR' ).
" → 컴파일 오류. FILTER 는 SORTED/HASHED 테이블 또는 SECONDARY KEY 필요

FILTER 연산자는 성능을 위해 키 기반 접근을 요구합니다. STANDARD TABLE 그대로에는 사용할 수 없고, SORTED 테이블이거나 세컨더리 키가 정의되어야 합니다.

" 방법 1: 세컨더리 키를 가진 테이블로 재정의
TYPES tt_orders_keyed TYPE STANDARD TABLE OF ty_sales_order
        WITH EMPTY KEY
        WITH NON-UNIQUE SORTED KEY by_currency COMPONENTS currency.

DATA(lt_keyed) = CORRESPONDING tt_orders_keyed( lt_orders ).
DATA(lt_eur_ok) = FILTER #( lt_keyed USING KEY by_currency WHERE currency = 'EUR' ).

" 방법 2: 단순 조건이면 FOR ... IN ... WHERE 로 대체
DATA(lt_eur_alt) = VALUE tt_sales_order(
  FOR ls IN lt_orders WHERE ( currency = 'EUR' ) ( ls ) ).

실전 예제 3단계 — 프로덕션 방어 코드

실무에서는 인라인 표현식이 예외를 던지거나, 대용량 데이터에서 성능 문제를 유발할 수 있습니다. 다음은 방어 로직과 로깅, 성능 고려를 포함한 패턴입니다.

CLASS zcl_order_processor DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    TYPES: BEGIN OF ty_result,
             order_id TYPE c LENGTH 10,
             status   TYPE c LENGTH 1,
             message  TYPE string,
           END OF ty_result,
           tt_result TYPE STANDARD TABLE OF ty_result WITH EMPTY KEY.

    METHODS process
      IMPORTING it_orders        TYPE tt_sales_order
      RETURNING VALUE(rt_result) TYPE tt_result.
ENDCLASS.

CLASS zcl_order_processor IMPLEMENTATION.
  METHOD process.
    " 방어 1: 입력 검증
    IF it_orders IS INITIAL.
      RETURN.
    ENDIF.

    " 방어 2: 세컨더리 키가 있는 로컬 테이블로 복사 → FILTER 안전
    DATA lt_local TYPE STANDARD TABLE OF ty_sales_order
         WITH EMPTY KEY
         WITH NON-UNIQUE SORTED KEY k_currency COMPONENTS currency.
    lt_local = CORRESPONDING #( it_orders ).

    " 방어 3: 예외 처리 - 통화별 집계
    TRY.
        DATA(lt_eur_only) = FILTER #( lt_local
                                       USING KEY k_currency
                                       WHERE currency = 'EUR' ).

        rt_result = VALUE #(
          FOR ls IN lt_eur_only
          ( order_id = ls-order_id
            status   = COND #( WHEN ls-net_amount > 1000
                               THEN 'H' ELSE 'L' )
            message  = |Amount { ls-net_amount } { ls-currency }| ) ).

      CATCH cx_sy_itab_error INTO DATA(lx_err).
        " 로깅: Application Log 또는 BAL 사용 권장
        rt_result = VALUE #( ( order_id = '*'
                               status   = 'E'
                               message  = lx_err->get_text( ) ) ).
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

대용량(수십만 건 이상) 처리 시 VALUE 표현식은 임시 테이블을 메모리에 만들기 때문에 메모리 사용량이 급증할 수 있습니다. 이 경우 전통적인 LOOP ... APPEND ... ENDLOOP 방식이 더 안전할 때가 많습니다. 프로파일링(SAT, SE30) 결과에 따라 선택하세요.

흔한 실수와 트러블슈팅

FAQ 1. DATA(x) 로 선언한 인라인 변수의 타입을 어떻게 확인하나요?
ADT에서 변수 위에 마우스를 올리면 툴팁으로 추론된 타입이 표시됩니다. SE80만 사용해야 한다면 DESCRIBE FIELD lv_x TYPE DATA(lv_type).로 런타임 확인이 가능합니다. 팀 코드 리뷰에서는 중요한 타입은 인라인으로 감추지 말고 명시적으로 선언하라는 컨벤션을 두는 것이 유지보수에 유리합니다.

FAQ 2. CORRESPONDING 에서 EXCEPT 와 MAPPING 을 함께 쓸 수 있나요?
네, CORRESPONDING #( source MAPPING a = b EXCEPT c d ) 형태로 가능합니다. 다만 MAPPING은 필드명 재지정, EXCEPT는 자동 매핑 제외라는 역할이 다르므로 함께 쓸 때는 순서와 의미를 명확히 주석으로 남기는 것을 권장합니다. DEEP, DISCARDING DUPLICATES 같은 추가 옵션도 존재하니 문서를 확인하세요.

FAQ 3. 인라인 변수를 IF 블록 안에서 선언하면 밖에서 못 쓰나요?
ABAP은 C/C++와 달리 메서드/폼 스코프가 기본입니다. IF, LOOP 블록 안에서 DATA(x)로 선언해도 해당 메서드 종료 시까지 유효합니다. 그러나 조건에 따라 초기화되지 않은 채 참조될 수 있으므로 블록 밖에서 명시적으로 선언하고 안에서 대입하는 편이 안전합니다. 이는 문법 오류가 아니라 논리 오류를 유발하는 대표적 함정입니다.

FAQ 4. FILTER 에서 SORTED KEY 지정을 잊으면 어떻게 되나요?
대부분 컴파일 타임에 잡히지만, WHERE 절 필드가 여러 개일 때 컴파일러가 잘못된 키를 선택할 수도 있습니다. USING KEY를 항상 명시하는 습관을 들이면 성능·정확성 문제 모두를 예방할 수 있습니다.

관련 주제와 심화 학습 방향

이 글에서 다룬 세 가지 실수를 극복했다면 다음 주제로 확장해 보시길 권합니다. REDUCE 연산자를 활용한 집계 표현식, SWITCH/COND를 결합한 조건부 인라인 조작, 그리고 CDS View + ABAP RAP(Restful ABAP Programming Model)에서의 표현식 활용은 다음 단계 학습에 유용합니다. 또한 ABAP for Cloud Development(Steampunk)에서는 일부 문법이 더 엄격해지므로, 온프레미스 코드를 클라우드 이행할 때 인라인 표현식의 호환성 검증을 먼저 수행하는 것이 좋습니다.

참고할 만한 자료

핵심 체크리스트

  • 인라인 VALUE에서 리터럴 타입을 필드 타입과 일치시켰는가
  • CORRESPONDING에서 이름이 다른 필드는 MAPPING으로 명시했는가
  • FILTER 대상 테이블에 SORTED KEY 또는 세컨더리 키가 있고 USING KEY를 명시했는가
  • 수정할 필요 없는 루프 변수는 FINAL로 선언했는가
  • 대용량 처리 시 표현식이 만드는 임시 테이블의 메모리 영향을 확인했는가

댓글 0

아직 댓글이 없습니다.