ABAP

아직도 DATA 타입 직접 선언하나요? #shorts #SAP #ABAP

개요 및 학습 포인트

ABAP 7.40 이후 도입된 DATA(...) 인라인 선언은 변수 타입을 컴파일러가 컨텍스트로부터 자동 추론하게 해주는 기능입니다. 기존에는 모든 변수를 DATA 절에서 미리 명시적으로 선언해야 했고, 타입을 별도 TYPES로 정의하거나 데이터 사전(DDIC)을 참조해야 했습니다. 이 글에서는 인라인 선언이 어떻게 동작하고, 어떤 상황에서 코드량을 극적으로 줄이며, 반대로 어떤 상황에서는 가독성과 유지보수를 해치는지 살펴봅니다.

  • 기존 DATA 선언과 DATA(...) 인라인 선언의 차이를 코드로 비교한다
  • SELECT, LOOP AT, SPLIT, 메서드 반환값 컨텍스트에서의 활용을 익힌다
  • 타입 추론의 동작 원리와 컴파일러의 결정 규칙을 이해한다
  • 인라인 선언을 피해야 하는 케이스(가독성, 스코프, 암묵적 타입)에 대해 판단할 수 있다

읽기 전 알아두면 좋은 것

ABAP의 DATA / TYPES 문법, 내부 테이블(internal table)과 구조체(work area) 개념, SELECT INTO 절의 기본 형식, 메서드 호출과 반환값 처리에 대한 기본 이해가 있으면 좋습니다. Open SQL의 SELECT ... INTO TABLE 구문도 알고 있다면 인라인 선언이 가져오는 변화가 더 명확히 느껴집니다.

버전 요건 및 사전 준비

인라인 선언은 ABAP 릴리즈 7.40 SP02부터 도입되었으며, 이후 7.50, 7.54, ABAP for Cloud(스팀펑크 환경)까지 동일하게 사용 가능합니다. 따라서 다음 환경에서 모두 활용할 수 있습니다.

  • SAP NetWeaver AS ABAP 7.40 SP02 이상 (온프레미스)
  • SAP S/4HANA 1610 이상 (Embedded Steampunk 포함)
  • ABAP Cloud / Steampunk (BTP ABAP Environment)
  • 개발 도구: SAP GUI의 SE80, SE38 또는 ADT (Eclipse 기반 ABAP Development Tools) 권장

일반적으로 ADT를 사용하면 인라인 선언된 변수에 마우스 오버 시 추론된 타입을 즉시 확인할 수 있어 학습 효율이 높습니다. 권장 ADT 버전은 3.20 이상입니다.

인라인 선언의 동작 원리

전통적인 ABAP에서 변수는 먼저 선언하고 나중에 사용한다는 원칙을 따릅니다. 즉, DATA lv_amount TYPE p LENGTH 13 DECIMALS 2.처럼 타입을 명시한 후에야 해당 변수를 다른 문장에서 쓸 수 있었습니다. 이 방식의 장점은 모든 타입이 명시적이라는 점이지만, 반복적인 보일러플레이트(boilerplate)가 늘어나고 한 변수의 정의와 첫 사용 사이의 거리가 멀어진다는 단점이 있었습니다.

DATA(...)는 이 흐름을 뒤집습니다. 변수 사용 위치에서 직접 선언하면서, 컴파일러가 대입되는 값 또는 결과 표현식의 타입을 추론해서 변수 타입을 자동으로 결정합니다. 비유하자면, "이 박스의 크기를 미리 정해두고 물건을 담는" 방식에서 "물건을 보고 박스를 그 자리에서 만들어주는" 방식으로 바뀐 것입니다.

컴파일러의 타입 결정은 다음과 같은 규칙으로 이루어집니다.

  • 대입 연산자 왼편(LHS)에 위치한 DATA(var): 오른편 표현식의 정적 타입을 그대로 가져옵니다
  • SELECT ... INTO TABLE @DATA(itab): 필드 리스트에 따라 STANDARD TABLE OF 구조의 익명 타입이 생성됩니다
  • LOOP AT itab INTO DATA(wa): itab의 라인 타입(line type)이 wa의 타입이 됩니다
  • CALL METHOD ... RECEIVING param = DATA(result): 메서드 시그니처에 선언된 반환 파라미터 타입을 따릅니다
  • SPLIT ... INTO TABLE DATA(parts): STANDARD TABLE OF string으로 추론됩니다

중요한 점은 인라인 선언이 런타임이 아니라 컴파일 타임에 이루어진다는 사실입니다. 따라서 동적인 타입 변경이 일어나는 것이 아니라, 정적 타입 시스템 안에서 단지 선언을 짧게 쓸 수 있게 해주는 신택틱 슈가(syntactic sugar)에 가깝습니다. 또한 인라인 선언된 변수의 스코프는 해당 변수가 처음 등장한 처리 블록(processing block) 전체입니다. 즉, IF 블록 안에서 인라인 선언했더라도 그 변수는 메서드 종료까지 살아 있습니다. 이는 다른 언어의 블록 스코프와 다른 부분이라 주의가 필요합니다.

단계별 실전 예제

1단계 — 기본 비교 예제. 직원 정보를 데이터베이스에서 조회하는 가장 단순한 케이스를 통해 두 방식의 차이를 봅니다. 시나리오는 인사 관리 모듈에서 활성 직원 목록을 조회하는 상황입니다.

" Before: 전통적인 DATA 선언 방식
TYPES: BEGIN OF ty_employee,
         pernr   TYPE persno,
         ename   TYPE emnam,
         deptid  TYPE kostl,
       END OF ty_employee.

DATA: lt_employees TYPE STANDARD TABLE OF ty_employee,
      ls_employee  TYPE ty_employee,
      lv_full_name TYPE string.

SELECT pernr, ename, deptid
  FROM zhr_emp_master
  INTO TABLE @lt_employees
  WHERE active = abap_true.

LOOP AT lt_employees INTO ls_employee.
  lv_full_name = |{ ls_employee-ename } ({ ls_employee-pernr })|.
  WRITE: / lv_full_name.
ENDLOOP.
" After: DATA() 인라인 선언 방식
SELECT pernr, ename, deptid
  FROM zhr_emp_master
  INTO TABLE @DATA(lt_employees)
  WHERE active = abap_true.

LOOP AT lt_employees INTO DATA(ls_employee).
  DATA(lv_full_name) = |{ ls_employee-ename } ({ ls_employee-pernr })|.
  WRITE: / lv_full_name.
ENDLOOP.

위 두 코드를 비교하면 줄 수가 절반 가까이 줄었고, 별도 TYPES 블록도 사라졌습니다. 컴파일러가 SELECT 필드 리스트인 pernr, ename, deptid를 분석해서 익명 구조체 타입과 그것을 라인 타입으로 하는 STANDARD TABLE을 자동으로 만들어주기 때문입니다.

2단계 — 실무 시나리오. 판매 주문(Sales Order) 조회 결과를 처리하면서 메서드 반환값, 문자열 분리, 예외 처리까지 결합한 예제입니다.

CLASS zcl_sales_processor DEFINITION.
  PUBLIC SECTION.
    METHODS get_open_orders
      IMPORTING iv_customer    TYPE kunnr
      RETURNING VALUE(rt_orders) TYPE ztt_sales_order
      RAISING   zcx_sales_error.

    METHODS process_order_line
      IMPORTING iv_line_text TYPE string.
ENDCLASS.

CLASS zcl_sales_processor IMPLEMENTATION.
  METHOD process_order_line.
    " SPLIT 결과의 타입도 자동 추론 (STANDARD TABLE OF string)
    SPLIT iv_line_text AT ';' INTO TABLE DATA(lt_tokens).

    IF lines( lt_tokens ) < 3.
      MESSAGE 'Invalid order line format' TYPE 'E'.
    ENDIF.

    " READ TABLE의 인덱스 결과도 인라인으로
    READ TABLE lt_tokens INDEX 1 INTO DATA(lv_order_id).
    WRITE: / 'Order ID:', lv_order_id.
  ENDMETHOD.

  METHOD get_open_orders.
    " JOIN 결과 - 컴파일러가 두 테이블의 필드를 합쳐 익명 구조를 생성
    SELECT h~vbeln, h~erdat, i~posnr, i~matnr, i~netwr
      FROM vbak AS h
      INNER JOIN vbap AS i ON h~vbeln = i~vbeln
      INTO TABLE @DATA(lt_join_result)
      WHERE h~kunnr = @iv_customer
        AND h~vbtyp = 'C'.

    IF sy-subrc <> 0.
      RAISE EXCEPTION TYPE zcx_sales_error
        EXPORTING textid = zcx_sales_error=>no_orders_found.
    ENDIF.

    " 로깅: 추론된 라인 수
    cl_log=>write( |Found { lines( lt_join_result ) } order items| ).
    " ... rt_orders 매핑 로직
  ENDMETHOD.
ENDCLASS.

이 예제에서 주목할 부분은 SELECTINNER JOIN 결과입니다. 두 테이블의 컬럼이 섞인 익명 구조체가 자동으로 생성되므로 개발자가 별도 TYPES를 선언할 필요가 없습니다. 예전 같으면 조인 결과를 받기 위해 별도 구조체를 만들거나 데이터 사전에 뷰를 정의해야 했던 작업이 한 줄로 끝납니다.

3단계 — 프로덕션 패턴. 실제 운영 코드에서는 단위 테스트, 성능, 가독성을 모두 고려해야 합니다. 인라인 선언을 남용하지 않고 의미가 분명한 곳에만 적용하는 패턴을 봅니다.

METHOD calculate_invoice_summary.
  " 명시적 타입이 시그니처 일부인 경우는 기존 DATA 유지
  DATA lv_total_amount TYPE bf_betrag_30.

  " 쿼리 결과는 인라인 - 컴파일러 추론이 명확함
  SELECT invoice_id, line_no, net_amount, tax_amount, currency
    FROM zinv_line
    INTO TABLE @DATA(lt_lines)
    WHERE invoice_id = @iv_invoice_id
      AND posting_status = @zcl_inv_const=>c_status_posted.

  " 집계 계산 - REDUCE와 인라인 선언 결합
  DATA(lv_subtotal) = REDUCE bf_betrag_30(
    INIT sum = CONV bf_betrag_30( 0 )
    FOR <line> IN lt_lines
    NEXT sum = sum + <line>-net_amount ).

  lv_total_amount = lv_subtotal.

  " 테스트 가능성을 위해 외부 의존(시간/사용자)은 명시적 변수로
  DATA(lv_calc_timestamp) = cl_abap_tstmp=>utclong_current( ).
  cl_application_log=>write(
    iv_object  = 'ZINV'
    iv_subobj  = 'CALC'
    iv_message = |Invoice { iv_invoice_id } total = { lv_total_amount } at { lv_calc_timestamp }| ).

  rv_total = lv_total_amount.
ENDMETHOD.

프로덕션 코드에서 권장되는 가이드라인은 다음과 같습니다. 메서드의 핵심 비즈니스 의미를 가진 변수(여기서는 lv_total_amount)는 명시적 타입으로 선언해 의도를 분명히 합니다. 반면 쿼리 중간 결과나 임시 집계처럼 그 자리에서만 의미를 갖는 변수는 인라인으로 처리하여 가독성을 높입니다. REDUCE 같은 표현식 기반 구문과 결합하면 함수형 스타일에 가까운 간결한 코드를 얻을 수 있습니다.

흔한 실수와 디버깅 가이드

Q1. "왜 인라인 선언 변수의 타입이 예상과 다르게 추론될까요?"

A1. 가장 흔한 케이스는 숫자 리터럴입니다. DATA(lv_n) = 1.i(정수)로 추론되지만, DATA(lv_p) = '1.5'.char3로 추론됩니다. 통화금액 같은 값을 다룰 때 의도와 다른 타입이 잡히면 산술 연산에서 오버플로나 절삭이 발생할 수 있습니다. 이런 경우 CONV로 명시 변환을 해주거나 차라리 명시적 DATA 선언을 사용하는 편이 안전합니다.

Q2. "IF 블록 안에서 인라인 선언했는데 블록 밖에서도 변수가 보입니다. 버그인가요?"

A2. 버그가 아니라 ABAP의 스코프 규칙입니다. 인라인 선언 변수의 스코프는 그것이 속한 처리 블록(메서드, 폼, 모듈) 전체입니다. 따라서 같은 메서드 안에서 동일 이름으로 두 번 인라인 선언하면 컴파일 에러가 납니다. 의도치 않은 변수 재사용을 막으려면 메서드를 잘게 분리하거나 의미 있는 이름을 부여하세요.

Q3. "READ TABLE ... ASSIGNING FIELD-SYMBOL(<fs>)가 실패했을 때 <fs>를 그대로 쓰면 덤프가 납니다."

A3. 인라인 필드 심볼 선언과 동일한 함정이 있습니다. sy-subrc를 반드시 확인한 뒤에 접근해야 하며, IS ASSIGNED로 가드를 두는 것이 안전합니다. 인라인 선언이 검사를 면제해주는 것은 아니라는 점을 기억해야 합니다.

그 외에 자주 보이는 안티패턴으로는 (1) 메서드 시그니처(IMPORTING, RETURNING)에는 인라인 선언을 쓸 수 없음에도 시도하는 것, (2) 동적 SQL이나 RTTI 기반 코드에서 타입을 추론하지 못해 컴파일 에러가 나는 것, (3) 너무 긴 표현식에 인라인 선언을 박아 넣어 코드 리뷰 시 타입을 한눈에 파악하기 어렵게 만드는 경우가 있습니다. 일반적으로 한 화면에서 변수의 타입이 분명하지 않다면 명시적 선언으로 되돌리는 것이 권장됩니다.

이어서 살펴보면 좋은 주제

인라인 선언을 익혔다면 다음 주제로 자연스럽게 확장할 수 있습니다. FIELD-SYMBOL(<fs>) 인라인 선언, NEW #( ... )VALUE #( ... ) 같은 컨스트럭터 오퍼레이터, FOR 반복 표현식과 REDUCE, FILTER, COND / SWITCH를 활용한 함수형 ABAP 스타일이 대표적입니다. 또한 CDS View와 결합해 SELECT의 인라인 선언을 한층 강력하게 사용하는 패턴, ABAP RAP(RESTful Application Programming Model)에서 핸들러 메서드 작성 시 인라인 선언을 적극 활용하는 방법도 다음 단계로 추천합니다.

더 읽어볼 자료

댓글 0

아직 댓글이 없습니다.