1. 개요 및 이 글에서 얻어갈 것
ABAP 7.40 SP02부터 도입된 DATA(...) 인라인 선언은 단순한 문법 설탕(syntactic sugar)이 아니라, ABAP 개발 스타일 자체를 바꾼 핵심 기능입니다. 변수를 사용하는 위치에서 곧바로 선언함으로써 코드의 가독성이 향상되고, 타입 추론(type inference)으로 인해 LIKE/TYPE 절을 반복적으로 작성하는 수고를 덜어줍니다.
이 글에서 다룰 범위는 다음과 같습니다.
- 전통적인 선행 DATA 선언과 인라인 선언의 차이와 트레이드오프
LOOP AT ... INTO DATA(ls_row),READ TABLE ... INTO DATA(ls_entry)패턴- 메서드 호출 결과를
DATA(lo_result) = obj->method()로 받는 방식 - ABAP Unit 테스트에서 인라인 선언이 가독성에 기여하는 방식
FIELD-SYMBOLS와 인라인 DATA의 선택 기준- 실제 프로젝트에서 자주 마주치는 함정과 회피 방법
대상 릴리스는 ABAP 7.40 SP08 이상이며, 일부 패턴(예: FOR 표현, REDUCE 등과의 조합)은 7.50 이상에서 더 자연스럽게 사용할 수 있습니다. S/4HANA Cloud ABAP Environment(Steampunk) 및 RAP(RESTful ABAP Programming Model)에서도 동일하게 권장되는 스타일입니다.
2. 핵심 개념 — 선행 선언과 인라인 선언의 차이
전통적인 ABAP은 절차적 언어의 관례를 따라 모든 변수는 사용 전에 선언한다는 규칙을 가집니다. 따라서 폼/메서드 상단에 DATA 블록이 모여 있고, 본문에서 그 변수를 사용하는 구조가 일반적이었습니다.
" 전통적인 방식
DATA: lt_sflight TYPE STANDARD TABLE OF sflight,
ls_sflight TYPE sflight,
lv_count TYPE i.
SELECT * FROM sflight INTO TABLE lt_sflight.
LOOP AT lt_sflight INTO ls_sflight.
lv_count = lv_count + 1.
ENDLOOP.
인라인 선언은 이 구조를 다음과 같이 압축합니다.
" 인라인 방식 (7.40+)
SELECT * FROM sflight INTO TABLE @DATA(lt_sflight).
LOOP AT lt_sflight INTO DATA(ls_sflight).
" 사용
ENDLOOP.
차이를 비유하자면, 선행 선언은 회의 시작 전 모든 참석자 명단을 칠판에 적어두는 방식이고, 인라인 선언은 필요한 사람이 등장하는 시점에 호명하는 방식이라 할 수 있습니다. 후자는 한 화면에서 흐름을 따라가기 쉽지만, 변수의 생명주기와 스코프를 정확히 이해해야 의도하지 않은 재사용/충돌을 피할 수 있습니다.
중요한 점은 인라인 선언이 컴파일 시점에 타입을 추론한다는 사실입니다. 우변(또는 LOOP 대상 테이블)의 타입이 명확해야 하며, 모호하면 컴파일 에러가 발생합니다. 또한 인라인으로 선언된 변수의 스코프는 가장 가까운 블록(메서드/폼 등)이며, LOOP 내부에서 선언했다 하더라도 그 변수는 LOOP 종료 후에도 접근 가능합니다.
3. 1단계: 기본 DATA( ) 인라인 선언
가장 단순한 형태부터 살펴봅니다. 할당문의 좌변에 DATA(변수명)을 두면, 우변의 타입에 맞춰 변수가 새로 선언됩니다.
" 기본 인라인 선언
DATA(lv_message) = `Hello, Inline World`. " string 으로 추론
DATA(lv_today) = sy-datum. " d (date) 로 추론
DATA(lv_pi) = CONV f( '3.141592' ). " CONV로 명시적 타입 지정
" 구조체 / 테이블도 가능
DATA(ls_sflight) = VALUE sflight( carrid = 'LH' connid = '0400' ).
DATA(lt_carriers) = VALUE string_table( ( `LH` ) ( `AA` ) ( `KE` ) ).
몇 가지 주의점이 있습니다. 첫째, 우변이 리터럴 숫자일 때는 ABAP이 기본적으로 i(integer)로 추론하므로, 소수점이나 통화금액을 다룰 때는 CONV 또는 VALUE로 타입을 명시해야 합니다. 둘째, 우변이 VALUE #( ... )처럼 #으로 타입을 생략한 경우, 좌변이 인라인 선언이라면 ABAP은 좌변에서 타입을 찾을 수 없어 에러가 납니다. 인라인 선언과 #은 동시에 쓸 수 없다는 점을 기억해야 합니다.
" 에러: 양쪽 모두 타입이 모호함
" DATA(ls_x) = VALUE #( carrid = 'LH' ). " 컴파일 에러
" 올바른 사용: 우변에 명시적 타입
DATA(ls_x) = VALUE sflight( carrid = 'LH' ).
SELECT 문에서는 @DATA(...) 처럼 escape 문자 @를 붙여야 합니다. Open SQL의 호스트 변수 표기와 일관성을 맞추기 위한 규칙입니다.
SELECT carrid, connid, fldate
FROM sflight
INTO TABLE @DATA(lt_flights)
WHERE carrid = 'LH'.
4. 2단계: LOOP/READ TABLE과 결합
실무에서 가장 빈번하게 사용되는 패턴은 LOOP와 READ TABLE입니다. 작업 영역(work area)을 별도로 선언하지 않고 곧바로 인라인으로 받으면 코드가 한결 깔끔해집니다.
" LOOP에서 INTO DATA(...) 패턴
LOOP AT lt_flights INTO DATA(ls_flight).
IF ls_flight-carrid = 'LH'.
cl_demo_output=>write( ls_flight ).
ENDIF.
ENDLOOP.
" READ TABLE에서 INTO DATA(...) 패턴
READ TABLE lt_flights INTO DATA(ls_lh)
WITH KEY carrid = 'LH'
connid = '0400'.
IF sy-subrc = 0.
cl_demo_output=>write( ls_lh-fldate ).
ENDIF.
여기서 매우 중요한 함정이 있습니다. 같은 블록 안에서 같은 이름으로 인라인 선언을 두 번 할 수 없습니다. 즉, LOOP가 끝난 뒤 다시 다른 LOOP를 돌면서 INTO DATA(ls_flight)를 또 쓰면 "이미 선언됨" 에러가 발생합니다.
LOOP AT lt_flights INTO DATA(ls_flight).
" ...
ENDLOOP.
" 에러: ls_flight는 이미 위 LOOP에서 선언됨
" LOOP AT lt_other INTO DATA(ls_flight).
" ...
" ENDLOOP.
" 해결: 다른 이름을 사용하거나, 기존 변수를 재사용
LOOP AT lt_flights INTO ls_flight. " 재사용
" ...
ENDLOOP.
또 한 가지 흔한 패턴은 READ TABLE 실패 케이스 처리입니다. 인라인으로 선언된 구조체는 READ TABLE이 실패해도 초기값으로 존재합니다. 따라서 sy-subrc 체크를 빠뜨리면 빈 구조체를 정상 데이터로 오인할 수 있습니다.
성능과 메모리 관점에서는 인라인 선언이 별도 비용을 만들지 않습니다. 컴파일러가 동일한 작업 영역을 만들어 주는 것과 동등하며, 가독성 측면에서만 차이가 있다고 보면 됩니다.
5. 3단계: 메서드 호출 결과 인라인으로 받기
객체지향 ABAP에서 메서드 반환값을 받을 때 인라인 선언이 가장 빛납니다. 팩토리 메서드, getter, 빌더 패턴 등에서 반환 타입을 일일이 적지 않아도 됩니다.
" 인스턴스 생성과 반환값 수신
DATA(lo_logger) = NEW zcl_app_logger( iv_object = 'ZAPP' ).
DATA(lo_service) = zcl_sflight_service=>get_instance( ).
" 메서드 반환값을 인라인으로 받기
DATA(lt_results) = lo_service->search_flights(
iv_carrid = 'LH'
iv_from_date = sy-datum ).
" 체이닝과 결합
DATA(lv_count) = lines( lo_service->search_flights( iv_carrid = 'LH' ) ).
EXPORTING/IMPORTING이 여러 개인 메서드는 인라인을 각 파라미터별로 적용할 수 있습니다.
lo_service->get_flight_details(
EXPORTING
iv_carrid = 'LH'
iv_connid = '0400'
IMPORTING
es_header = DATA(ls_header)
et_items = DATA(lt_items)
ev_status = DATA(lv_status) ).
예외 처리와 로깅까지 포함한 좀 더 실무적인 예시는 다음과 같습니다.
TRY.
DATA(lo_pricing) = NEW zcl_pricing_engine( ).
DATA(ls_quote) = lo_pricing->calculate(
iv_material = 'M-001'
iv_qty = 10 ).
cl_demo_output=>write( ls_quote-net_amount ).
CATCH zcx_pricing_error INTO DATA(lo_err).
" 인라인으로 받은 예외 객체를 즉시 로깅
DATA(lv_text) = lo_err->get_text( ).
MESSAGE lv_text TYPE 'E'.
ENDTRY.
RAP 모델의 비헤이비어 구현이나 BAdI 구현 내부에서도, IMPORTING 파라미터에서 파생된 데이터를 인라인으로 받아 처리하는 스타일이 일반적으로 권장됩니다. 코드가 위에서 아래로 한 방향으로 흐르게 만들어 리뷰가 쉬워지는 효과가 큽니다.
6. 4단계: ABAP Unit 테스트에서의 활용
ABAP Unit 테스트 메서드는 일반적으로 짧고 의도를 명확히 드러내야 합니다. 인라인 선언은 Arrange-Act-Assert 패턴을 시각적으로 강조하기에 매우 적합합니다.
CLASS ltc_calculator DEFINITION FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.
PRIVATE SECTION.
METHODS:
add_two_positive_numbers FOR TESTING,
add_with_overflow FOR TESTING.
ENDCLASS.
CLASS ltc_calculator IMPLEMENTATION.
METHOD add_two_positive_numbers.
" Arrange
DATA(lo_calc) = NEW zcl_calculator( ).
" Act
DATA(lv_result) = lo_calc->add( iv_a = 2 iv_b = 3 ).
" Assert
cl_abap_unit_assert=>assert_equals(
act = lv_result
exp = 5
msg = |2 + 3 should be 5, but was { lv_result }| ).
ENDMETHOD.
METHOD add_with_overflow.
DATA(lo_calc) = NEW zcl_calculator( ).
TRY.
DATA(lv_x) = lo_calc->add( iv_a = cl_abap_math=>max_int4
iv_b = 1 ).
cl_abap_unit_assert=>fail( msg = 'Overflow expected' ).
CATCH zcx_overflow INTO DATA(lo_ex).
cl_abap_unit_assert=>assert_not_initial( act = lo_ex->get_text( ) ).
ENDTRY.
ENDMETHOD.
ENDCLASS.
테스트 더블(Test Double)을 사용할 때도 동일합니다. cl_abap_testdouble 또는 OSQL/CDS 테스트 더블 프레임워크에서 반환되는 객체를 인라인으로 받아 모킹 설정을 이어가는 것이 일반적인 스타일입니다.
DATA(lo_double) = CAST zif_repository(
cl_abap_testdouble=>create( 'ZIF_REPOSITORY' ) ).
cl_abap_testdouble=>configure_call( lo_double )->returning(
VALUE zif_repository=>ty_t_entity( ( id = '1' name = 'A' ) ) ).
lo_double->find_all( ).
7. 자주 겪는 함정과 FAQ
Q1. LOOP에서 인라인 선언한 변수가 LOOP 밖에서도 보이는데, 의도한 동작인가요?
네, 정상입니다. 인라인 선언의 스코프는 가장 가까운 메서드/폼 블록이며, LOOP/IF/CASE 같은 구조의 내부에서 선언하더라도 해당 블록 전체에서 접근 가능합니다. 다만 LOOP가 한 번도 실행되지 않으면 그 변수는 초기값 상태이므로, 외부에서 참조할 때 주의해야 합니다.
Q2. 왜 같은 변수명으로 두 번 인라인 선언이 안 되나요?
같은 스코프에 동일한 이름의 변수가 둘 있을 수 없기 때문입니다. 보통은 이름을 분리(ls_flight_in, ls_flight_out)하거나, 두 번째 LOOP에서는 첫 번째에서 만든 변수를 재사용하면 됩니다.
Q3. DATA(...) 와 FIELD-SYMBOLS <...> 중 무엇을 써야 하나요?
읽기/디버깅 위주의 짧은 로직은 DATA(...)가 직관적이고 안전합니다. 반면 대량 내부 테이블을 LOOP하면서 요소를 직접 수정하거나, 복사 비용을 피하고 싶을 때는 FIELD-SYMBOLS <fs>가 일반적으로 권장됩니다. 인라인 필드심볼은 ASSIGNING FIELD-SYMBOL(<fs_row>) 문법으로 작성합니다.
" 수정이 필요할 때 - FIELD-SYMBOLS 권장
LOOP AT lt_flights ASSIGNING FIELD-SYMBOL(<fs_flight>).
<fs_flight>-price = <fs_flight>-price * '1.1'.
ENDLOOP.
" 단순 조회 - DATA( ) 면 충분
LOOP AT lt_flights INTO DATA(ls_flight).
WRITE: / ls_flight-carrid, ls_flight-fldate.
ENDLOOP.
Q4. VALUE #( ) 에서 인라인 선언이 안 되는 이유는?
#은 "타입은 좌변에서 추론하라"는 의미인데, 좌변이 DATA(...) 인라인 선언이라면 좌변에도 타입이 없습니다. 양쪽 모두 모호하므로 컴파일 에러가 발생합니다. 우변에 명시적 타입을 적어주거나, 좌변을 미리 선언해 두어야 합니다.
Q5. SELECT ... INTO DATA(...)에서 컬럼명과 다른 필드명을 받고 싶다면?
SELECT 리스트에서 AS로 별칭을 지정하면 인라인으로 만들어지는 구조체의 컴포넌트 이름도 그 별칭을 따릅니다. 다만 추론된 구조체는 익명 타입이므로, 인터페이스 경계를 넘는 곳에 직접 전달하지 말고 명시 타입으로 변환해 사용하는 것이 안전합니다.
8. 응용 패턴 — FIELD-SYMBOLS와의 선택 기준, 인라인 TYPES/CONSTANTS
마지막으로 실무에서 자주 쓰는 응용 패턴을 정리합니다.
(1) 인라인 선언 가능한 키워드: DATA(...) 외에도 FINAL(...) (7.57+, 한 번만 할당되는 변수), FIELD-SYMBOL(<...>), 그리고 메서드 시그니처의 RECEIVING 파라미터 등이 인라인 선언과 함께 사용 가능합니다. FINAL은 사실상 ABAP의 val(불변 변수)로 동작하여, 재할당을 막아 의도를 명확히 합니다.
" FINAL: 한 번만 할당되는 인라인 선언 (NetWeaver 7.57 이상)
FINAL(lv_carrid) = 'LH'.
" lv_carrid = 'AA'. " 에러: FINAL 변수는 재할당 불가
(2) DATA vs FIELD-SYMBOLS 선택 기준:
- 대량 테이블을 순회하며 읽기만 한다면, 가독성이 좋은
INTO DATA(...)가 일반적으로 무난합니다. - 테이블 항목을 수정하거나, 깊은 중첩 구조의 컴포넌트를 짧은 별칭으로 다루고 싶다면
ASSIGNING FIELD-SYMBOL(<fs>)가 권장됩니다. - 심볼이 unassigned 상태일 위험이 있으면 반드시
IS ASSIGNED체크를 함께 사용해야 합니다.
(3) 인라인이 불가능한 경우: TYPES, CONSTANTS, CLASS-DATA, 그리고 메서드의 IMPORTING/EXPORTING 파라미터 정의 등은 인라인 선언이 불가능합니다. 이들은 컴파일 시점의 구조 정의이거나 클래스 레벨 멤버이므로, 본문에서 즉시 선언될 수 없기 때문입니다.
(4) 코드 컨벤션 관점: 인라인 선언을 남용하면 변수가 어디서 처음 등장했는지 찾기 어려워질 수 있습니다. 한 메서드가 50줄을 넘어가면 변수 추적이 어려워지므로, 짧고 응집력 있는 메서드 단위로 리팩토링하면서 인라인 선언을 쓰는 것이 일반적으로 권장되는 스타일입니다.
(5) 다음으로 탐색해볼 주제: FOR 표현을 이용한 테이블 생성, REDUCE를 이용한 집계, COND/SWITCH 표현식, 그리고 RAP에서 EML(Entity Manipulation Language) 호출 결과를 인라인으로 받는 패턴 등을 이어서 학습하면 모던 ABAP 스타일을 완성할 수 있습니다.
참고로 SAP Help Portal의 ABAP Keyword Documentation에서 "Inline Declarations", "DATA, Inline Declaration", "ASSIGNING FIELD-SYMBOL" 항목을 함께 참조하면 릴리스별 차이와 제약 사항을 정확히 확인할 수 있습니다. ABAP Cloud(Steampunk) 환경에서도 동일한 문법이 지원되며, Clean ABAP 가이드에서도 인라인 선언 사용을 일반적으로 권장하고 있습니다.
댓글 0
아직 댓글이 없습니다.