ABAP

APPEND 금지 — VALUE 초기화 패턴 3개 #shorts #SAP #ABAP

▶ YouTube에서 보기

VALUE 연산자가 등장한 배경

ABAP 개발자라면 누구나 한 번쯤 내부 테이블을 채우기 위해 APPEND 구문을 수십 줄씩 반복해 본 경험이 있을 것입니다. 작업 영역(work area)을 선언하고, 필드별로 값을 할당한 뒤, 테이블에 APPEND하고, CLEAR로 초기화하는 패턴은 ABAP의 오랜 관례였습니다. 그러나 이 방식은 코드가 길어지고, 의도가 흐려지며, 휴먼 에러가 발생하기 쉽다는 단점이 있습니다.

SAP NetWeaver 7.40 SP02부터 도입된 VALUE 연산자(VALUE operator)는 이러한 문제를 해결하기 위해 등장했습니다. 표현식 기반 ABAP(Expression-Based ABAP)의 핵심 구성 요소 중 하나로, 구조체·내부 테이블·심지어 참조 변수까지 한 줄에 선언하고 값을 채울 수 있게 해줍니다. 7.50, 7.54를 거치며 인라인 선언(DATA(...))과 결합해 더욱 강력해졌고, 오늘날 클린 ABAP(Clean ABAP) 가이드라인에서도 권장되는 작성 스타일입니다.

이 글에서는 VALUE 연산자를 활용하여 내부 테이블을 APPEND 없이 선언 시점에 채우는 다양한 패턴을 다룹니다. 다음 항목들을 차근차근 이해하실 수 있습니다.

  • VALUE 연산자의 기본 문법과 타입 추론 메커니즘
  • 플랫 구조 내부 테이블 인라인 초기화 방법
  • 중첩 구조(BAPI 형태) 테이블 초기화 패턴
  • BASE 절을 이용한 기존 테이블 확장
  • FOR 반복문을 결합한 동적 생성 기법
  • APPEND 방식과의 성능·가독성 비교

읽기 전 알아두면 좋은 사항

이 글은 ABAP 초급 개발자를 대상으로 합니다. 다만 다음 개념들에 익숙하다면 훨씬 빠르게 이해할 수 있습니다. 내부 테이블(Standard/Sorted/Hashed) 종류와 차이, 구조체(STRUCTURE)와 작업 영역(work area)의 관계, TYPES로 사용자 정의 타입을 선언하는 방법, 그리고 DATA(...) 인라인 선언 문법입니다. ABAP Objects의 기본 클래스 정의나 메서드 호출에 대한 지식도 후반부 예제 이해에 도움이 됩니다.

실습 환경과 준비물

VALUE 연산자는 ABAP 언어 버전에 따라 지원 범위가 달라지므로 환경 확인이 중요합니다. 일반적으로 다음 환경에서 동작이 검증됩니다.

  • ABAP 언어 버전: 7.40 SP02 이상 (스칼라/구조체), 7.40 SP05 이상 (FOR 반복), 7.50 이상 (모든 패턴 권장)
  • 시스템: SAP NetWeaver AS ABAP 7.50+, S/4HANA 1909 이상, ABAP Cloud(Steampunk)
  • 개발 환경: ABAP Development Tools(ADT) for Eclipse 또는 SE80/SE38
  • 테스트 트랜잭션: SE38(리포트), SE24(클래스), 또는 ADT에서 Class Run via F9

구문 검사 시 "구문이 알려져 있지 않습니다"라는 오류가 나면 시스템의 ABAP Kernel 버전을 먼저 확인하시기 바랍니다. SYSTEM-CALL 또는 시스템 정보 메뉴에서 커널 릴리스를 점검할 수 있습니다.

표현식의 사고방식과 동작 원리

전통적인 ABAP은 명령형(imperative) 스타일입니다. "변수를 만들어라 → 값을 넣어라 → 테이블에 추가해라" 식으로 단계별 명령을 나열합니다. 반면 VALUE 연산자는 선언형(declarative) 스타일에 가깝습니다. "이 테이블은 다음 행들로 구성된다"를 한 번에 표현합니다.

비유하자면, APPEND 방식은 종이에 한 줄 한 줄 손으로 쓰는 것과 같고, VALUE 방식은 엑셀 표를 통째로 붙여넣는 것과 같습니다. 컴파일러 입장에서도 의도가 명확하기 때문에 최적화 여지가 더 큽니다.

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

DATA(변수명) = VALUE 타입명( 필드1 = 값1 필드2 = 값2 ).
DATA(테이블변수) = VALUE 테이블타입( ( 필드1 = 값A1 필드2 = 값A2 )
                                    ( 필드1 = 값B1 필드2 = 값B2 ) ).

핵심은 괄호의 의미입니다. 가장 바깥 괄호는 "VALUE 표현식의 범위"이고, 그 안의 추가 괄호 한 쌍은 "내부 테이블의 한 행(row)"을 나타냅니다. 즉 행이 N개라면 괄호 쌍이 N개 들어갑니다.

또한 타입명 자리에 #(샵)을 쓸 수 있는데, 이는 타입 추론(operand type inference)입니다. 대입 대상 변수의 타입이 명확할 때 컴파일러가 자동으로 타입을 결정합니다. DATA(...) 인라인 선언과 함께 쓸 때는 추론할 대상이 없으므로 명시적 타입을 적어야 합니다.

플랫 내부 테이블 인라인 초기화

판매 주문 헤더 정보를 담는 간단한 내부 테이블을 VALUE로 채우는 예제부터 시작합니다.

REPORT zr_value_basic.

TYPES: BEGIN OF ty_sales_header,
         order_id   TYPE c LENGTH 10,
         customer   TYPE c LENGTH 40,
         net_value  TYPE p LENGTH 9 DECIMALS 2,
         currency   TYPE c LENGTH 3,
       END OF ty_sales_header,
       tt_sales_header TYPE STANDARD TABLE OF ty_sales_header
                       WITH EMPTY KEY.

DATA(lt_orders) = VALUE tt_sales_header(
  ( order_id = '0010000001' customer = 'Acme Industries'  net_value = '15000.00' currency = 'USD' )
  ( order_id = '0010000002' customer = 'Bluewave Corp'    net_value = '8200.50'  currency = 'EUR' )
  ( order_id = '0010000003' customer = 'Cresco Logistics' net_value = '23750.75' currency = 'KRW' ) ).

LOOP AT lt_orders ASSIGNING FIELD-SYMBOL(<fs_order>).
  WRITE: / <fs_order>-order_id,
           <fs_order>-customer,
           <fs_order>-net_value CURRENCY <fs_order>-currency,
           <fs_order>-currency.
ENDLOOP.

위 코드는 APPEND TO lt_orders를 단 한 줄도 사용하지 않고도 3행짜리 테이블을 만들어냅니다. 작업 영역 변수도 필요 없고, CLEAR 호출도 사라졌습니다.

기본값 절로 공통 필드 한 번에 지정하기

모든 행에 공통으로 들어갈 값이 있다면, 행 괄호 바깥에 한 번만 쓰면 됩니다. 가독성이 크게 향상됩니다.

DATA(lt_orders_krw) = VALUE tt_sales_header(
  currency = 'KRW'
  ( order_id = '0020000001' customer = 'Daegu Steel'   net_value = '5000000.00' )
  ( order_id = '0020000002' customer = 'Eunyddu Mart'  net_value = '128900.00'  )
  ( order_id = '0020000003' customer = 'Forest Foods'  net_value = '742300.00'  ) ).

여기서 currency는 첫 번째 괄호 밖에 있으므로 세 행 모두에 적용됩니다. 이를 "기본값 절(default clause)"이라고 부릅니다.

중첩 구조 처리와 에러 핸들링

실무에서는 BAPI 호출 시 헤더-아이템 형태의 중첩 구조를 자주 다룹니다. 예를 들어 한 판매 주문 안에 여러 아이템이 들어가는 경우입니다.

REPORT zr_value_nested.

TYPES: BEGIN OF ty_item,
         item_no   TYPE n LENGTH 6,
         material  TYPE c LENGTH 18,
         quantity  TYPE p LENGTH 5 DECIMALS 3,
         uom       TYPE c LENGTH 3,
       END OF ty_item,
       tt_items TYPE STANDARD TABLE OF ty_item WITH EMPTY KEY.

TYPES: BEGIN OF ty_order_with_items,
         order_id  TYPE c LENGTH 10,
         customer  TYPE c LENGTH 40,
         items     TYPE tt_items,
       END OF ty_order_with_items,
       tt_orders TYPE STANDARD TABLE OF ty_order_with_items WITH EMPTY KEY.

TRY.
    DATA(lt_orders) = VALUE tt_orders(
      ( order_id = '0030000001'
        customer = 'Polaris Aerospace'
        items    = VALUE tt_items(
          ( item_no = '000010' material = 'MAT-ALU-001' quantity = '120.000' uom = 'KG' )
          ( item_no = '000020' material = 'MAT-STL-007' quantity = '50.500'  uom = 'KG' ) ) )
      ( order_id = '0030000002'
        customer = 'Helios Energy'
        items    = VALUE tt_items(
          ( item_no = '000010' material = 'MAT-PNL-220' quantity = '300.000' uom = 'EA' ) ) ) ).

    cl_demo_output=>display( lt_orders ).

  CATCH cx_sy_itab_line_not_found INTO DATA(lx_not_found).
    MESSAGE lx_not_found->get_text( ) TYPE 'E'.
  CATCH cx_sy_conversion_error INTO DATA(lx_conv).
    MESSAGE lx_conv->get_text( ) TYPE 'E'.
ENDTRY.

중첩 테이블 필드(items)에 또 다른 VALUE 표현식을 할당했다는 점이 핵심입니다. 내부 VALUE 표현식은 외부 VALUE의 한 행 안에서 평가되어 그대로 끼워 넣어집니다.

BASE와 FOR로 프로덕션 패턴 만들기

실무에서는 기존 테이블에 행을 추가하거나, 다른 데이터 소스로부터 변환된 행들을 한 번에 채우는 경우가 많습니다. BASE 절과 FOR 반복은 이때 빛을 발합니다.

CLASS lcl_order_builder DEFINITION.
  PUBLIC SECTION.
    TYPES: BEGIN OF ty_raw,
             id      TYPE i,
             name    TYPE string,
             amount  TYPE p LENGTH 9 DECIMALS 2,
           END OF ty_raw,
           tt_raw TYPE STANDARD TABLE OF ty_raw WITH EMPTY KEY.

    TYPES: BEGIN OF ty_target,
             order_id  TYPE c LENGTH 10,
             customer  TYPE c LENGTH 40,
             net_value TYPE p LENGTH 9 DECIMALS 2,
             status    TYPE c LENGTH 1,
           END OF ty_target,
           tt_target TYPE STANDARD TABLE OF ty_target WITH EMPTY KEY.

    METHODS build
      IMPORTING it_raw           TYPE tt_raw
                it_existing      TYPE tt_target OPTIONAL
      RETURNING VALUE(rt_result) TYPE tt_target.
ENDCLASS.

CLASS lcl_order_builder IMPLEMENTATION.
  METHOD build.
    rt_result = VALUE tt_target(
      BASE it_existing
      FOR ls_raw IN it_raw
      ( order_id  = |{ ls_raw-id ALPHA = IN WIDTH = 10 }|
        customer  = ls_raw-name
        net_value = ls_raw-amount
        status    = COND #( WHEN ls_raw-amount > 10000 THEN 'A'
                            WHEN ls_raw-amount > 1000  THEN 'B'
                            ELSE 'C' ) ) ).
  ENDMETHOD.
ENDCLASS.

이 메서드는 두 가지 강력한 기능을 결합합니다. BASE it_existing은 "기존 테이블을 출발점으로 삼고, 그 위에 새 행을 덧붙인다"는 의미입니다. FOR ls_raw IN it_raw는 입력 테이블의 각 행에 대해 변환된 결과 행을 생성합니다. COND #(...)은 조건부 표현식으로, 금액에 따라 등급 코드를 부여합니다.

APPEND 대비 어떤 점이 다른가

관점APPEND 방식VALUE 방식
코드 길이행당 3~5줄행당 1줄
가독성절차적, 의도 파악 어려움선언적, 데이터 구조가 한눈에 보임
작업 영역 필요필요불필요
잔존 값 위험있음 (CLEAR 누락 시)없음
성능일반적으로 동등일반적으로 동등
동적 생성자유로움FOR 절로 가능

일반적으로 고정된 마스터 데이터, 테스트 픽스처, 작은 변환에는 VALUE를 권장합니다. 반면 수천 건 이상의 대량 변환에 복잡한 비즈니스 로직이 섞인다면 LOOP+APPEND가 가독성과 디버깅 용이성 면에서 더 나을 수 있습니다.

실수하기 쉬운 지점과 해결 방법

FAQ 1. 괄호 한 쌍이 빠져서 컴파일 오류가 나요

가장 흔한 실수입니다. VALUE tt_orders( order_id = '...' customer = '...' )처럼 행을 감싸는 안쪽 괄호를 빠뜨리면 컴파일러는 "단일 구조체"를 만들려 하다가 타입 불일치 오류를 냅니다. 내부 테이블이라면 반드시 행마다 추가 괄호 한 쌍이 필요합니다.

FAQ 2. 빈 테이블을 만들고 싶은데 가능한가요

DATA(lt_empty) = VALUE tt_orders( )처럼 행 괄호 없이 바깥 괄호만 두면 빈 테이블이 됩니다. 다만 인라인 선언에서는 타입 추론이 안 되므로 VALUE 뒤에 명시적 타입명을 적어야 합니다. VALUE #( )는 대입 대상이 명확할 때만 사용 가능합니다.

FAQ 3. SORTED TABLE에서 키 중복 오류가 발생합니다

SORTED TABLE이나 HASHED TABLE은 키 유일성을 강제합니다. VALUE 표현식 안에서 같은 키 값을 가진 행을 두 번 넣으면 런타임 예외(CX_SY_ITAB_DUPLICATE_KEY)가 발생합니다. STANDARD TABLE로 먼저 만들고 SORT/DELETE ADJACENT DUPLICATES로 정리한 뒤 옮기거나, 데이터 소스에서 미리 중복을 제거해야 합니다.

FAQ 4. 필드 한두 개만 빠뜨려도 되나요

네, 명시하지 않은 필드는 해당 타입의 초기값(initial value)으로 자동 설정됩니다. 문자열은 빈 문자열, 숫자는 0, 날짜는 '00000000' 등으로 채워집니다. 이는 명시적인 의도 표현에 유리하지만, 필수 필드 누락을 컴파일러가 잡아주지 못하므로 코드 리뷰에서 챙겨야 합니다.

더 깊이 파고들 방향

VALUE 연산자를 자연스럽게 쓸 수 있게 되었다면, 다음 단계로 확장해보시기 바랍니다.

  • CORRESPONDING 연산자: 구조가 다른 두 테이블 간 필드 매핑 자동화
  • REDUCE 연산자: 내부 테이블 집계(합계·평균) 표현식
  • FILTER 연산자: 조건에 맞는 행만 추출하는 표현식
  • NEW 연산자: 객체 인스턴스 생성과 VALUE 결합 패턴
  • Clean ABAP 가이드라인: 표현식 기반 작성 규칙과 한계선
  • RAP(RESTful Application Programming) 모델에서 VALUE를 활용한 BEHAVIOR DEFINITION 테스트 더블 작성

댓글 0

아직 댓글이 없습니다.