ABAP

&& 연결 계속 쓰면 큰일 — ABAP String Template #shorts #SAP #ABAP

▶ YouTube에서 보기

개요 및 이 글에서 다루는 것

ABAP에서 문자열을 조립할 때 가장 흔히 쓰던 방식은 && 연산자나 CONCATENATE 문이었습니다. 하지만 ABAP 7.02 이후 도입된 String Template (문자열 템플릿, |...|)은 변수와 텍스트를 자연스럽게 섞어 쓸 수 있어 코드 가독성과 유지보수성을 크게 끌어올립니다. 이 글은 입문자가 String Template를 실무에서 자신 있게 활용할 수 있도록 기본 문법부터 포맷 옵션, 실전 시나리오까지 단계적으로 다룹니다.

  • 파이프 템플릿의 기본 문법과 표현식 임베딩 이해
  • && 연결 방식과의 가독성/유지보수 비교
  • ALPHA, WIDTH, CASE, TIMESTAMP 등 포맷 옵션 활용
  • 구매발주(PO) 확인 이메일 본문 생성 실전 예제
  • 루프 내 성능 주의점과 흔한 실수 정리

알아두면 좋은 배경 지식

이 글을 따라가려면 ABAP의 변수 선언(DATA), 기본 데이터 타입(STRING, C, I, P, D, T), 그리고 WRITEcl_demo_output으로 결과를 확인하는 방법 정도면 충분합니다. ABAP 7.40 SP08 이상에서 도입된 인라인 선언(DATA(...)) 문법에도 익숙하면 코드가 더 간결해집니다. CONCATENATE 문을 한 번이라도 작성해 본 경험이 있다면 두 방식의 차이를 체감하기 쉽습니다.

환경 및 버전 준비물

String Template 자체는 ABAP 7.02부터 사용 가능하지만, 이 글의 예제는 일반적으로 다음 환경을 가정합니다.

  • NetWeaver AS ABAP 7.40 SP08 이상 또는 SAP S/4HANA 1909 이후 — 인라인 선언과 표현식 지향 문법(COND, SWITCH) 활용
  • ABAP Cloud / SAP BTP ABAP Environment — 클라우드 환경에서도 동일하게 동작
  • ADT (ABAP Development Tools in Eclipse) 또는 SAP GUI의 SE38/SE80
  • 테스트 패키지 ($TMP 또는 로컬 객체)와 실행 권한

S/4HANA Embedded Steampunk나 RAP(RESTful ABAP Programming) 환경에서도 문법은 동일합니다. 본 글의 모든 코드는 실행 가능한 로컬 클래스 또는 리포트 형태로 옮길 수 있습니다.

핵심 개념: 파이프가 만드는 두 개의 세계

String Template를 한 줄로 설명하면 “두 개의 파이프 문자(|) 사이를 문자열 리터럴 영역으로 선언하고, 그 안의 중괄호 { } 안쪽만 ABAP 표현식으로 평가하는 구조”입니다. 마치 편지 봉투(파이프)에 본문 텍스트를 적어두고, 빈칸(중괄호) 자리에 우표처럼 동적 값을 끼워 넣는 그림을 떠올리면 됩니다.

기존 && 연결은 다음과 같은 한계를 가집니다.

  • 모든 피연산자가 반드시 STRING이어야 하므로, 숫자/날짜는 별도 변환 필요
  • 리터럴과 변수 사이마다 따옴표·연산자가 반복되어 가독성 저하
  • 공백 한 칸을 빼먹는 실수가 자주 발생

반면 String Template는 다음과 같은 특성을 갖습니다.

  • 중괄호 외부의 모든 문자는 리터럴 텍스트로 그대로 보존 (공백 포함)
  • 중괄호 안에서는 변수, 산술식, 함수형 메서드 호출, COND/SWITCH 표현식까지 사용 가능
  • 결과 타입은 항상 string, 다른 타입은 자동으로 문자열 표현으로 변환
  • 포맷 옵션(ALPHA, WIDTH, CASE 등)을 표현식 뒤에 선언적으로 부여

비유하자면 &&는 부품을 하나하나 용접하는 방식이고, String Template는 미리 만들어진 양식지의 빈칸에 값을 채우는 방식입니다. 양식지 방식이 오타나 공백 누락 같은 실수를 줄여줍니다.

파이프 자체를 문자열에 포함해야 한다면 \|로, 중괄호를 그대로 출력하려면 \{, \}로 탈출시킵니다. 줄바꿈은 , 탭은 로 표현합니다.

실전 코드 1단계: 가장 기본적인 임베딩

먼저 가장 단순한 형태입니다. 사용자 이름과 주문 번호를 한 문장으로 출력하는 예제입니다.

REPORT z_demo_stringtmpl_basic.

DATA(lv_customer)  = `Lee Minji`.
DATA(lv_order_id)  = `4500001234`.
DATA(lv_qty)       = 7.

" 1) 기존 && 방식
DATA(lv_msg_old) = `Hello ` && lv_customer &&
                   `, your order ` && lv_order_id &&
                   ` contains ` && |{ lv_qty }| && ` items.`.

" 2) String Template 방식
DATA(lv_msg_new) = |Hello { lv_customer }, your order { lv_order_id } | &&
                   |contains { lv_qty } items.|.

cl_demo_output=>write( lv_msg_old ).
cl_demo_output=>write( lv_msg_new ).
cl_demo_output=>display( ).

두 결과는 동일하지만, && 방식에서 정수 lv_qty는 그대로 연결할 수 없어 결국 |{ lv_qty }|로 한 번 감싸야 합니다. 즉 숫자가 들어가는 순간 &&만으로는 깔끔하게 처리할 수 없다는 의미입니다. String Template는 정수, 날짜, 패킹된 숫자 등을 별도 변환 없이 바로 보간합니다.

중괄호 안에는 단순 변수뿐 아니라 산술 표현식도 가능합니다.

DATA(lv_unit_price) = CONV p( '15000.00' ).
DATA(lv_total) = |Subtotal: { lv_unit_price * lv_qty } KRW|.
cl_demo_output=>display( lv_total ).

실전 코드 2단계: 포맷 옵션과 PO 확인 이메일 본문

실무에서는 단순 결합을 넘어 “자재번호 앞에 0 채우기”, “날짜를 한국어 포맷으로 출력”, “금액에 천 단위 구분자” 같은 가공이 필요합니다. String Template는 각 표현식 뒤에 포맷 옵션을 선언적으로 붙일 수 있습니다.

CLASS lcl_po_mailer DEFINITION.
  PUBLIC SECTION.
    TYPES: BEGIN OF ty_po_item,
             material   TYPE matnr,
             description TYPE string,
             quantity   TYPE i,
             unit_price TYPE p LENGTH 13 DECIMALS 2,
           END OF ty_po_item.
    TYPES tt_po_items TYPE STANDARD TABLE OF ty_po_item WITH EMPTY KEY.

    METHODS build_mail
      IMPORTING iv_po_number  TYPE ebeln
                iv_vendor     TYPE string
                iv_created_at TYPE timestamp
                it_items      TYPE tt_po_items
      RETURNING VALUE(rv_body) TYPE string.
ENDCLASS.

CLASS lcl_po_mailer IMPLEMENTATION.
  METHOD build_mail.
    DATA(lv_header) =
      |Purchase Order Confirmation
| &&
      |==========================
| &&
      |PO Number : { iv_po_number ALPHA = OUT }
| &&
      |Vendor    : { iv_vendor CASE = UPPER WIDTH = 30 PAD = '.' }
| &&
      |Created   : { iv_created_at TIMESTAMP = USER TIMEZONE = 'Asia/Seoul' }

|.

    DATA(lv_lines) = ||.
    LOOP AT it_items INTO DATA(ls_item).
      DATA(lv_amount) = ls_item-quantity * ls_item-unit_price.
      lv_lines = lv_lines &&
        |- { ls_item-material ALPHA = OUT WIDTH = 18 } | &&
        |{ ls_item-description WIDTH = 25 PAD = ' ' } | &&
        |x { ls_item-quantity NUMBER = USER WIDTH = 5 ALIGN = RIGHT } | &&
        |= { lv_amount NUMBER = USER } KRW
|.
    ENDLOOP.

    rv_body = lv_header && lv_lines &&
              |
Thank you for your business.|.
  ENDMETHOD.
ENDCLASS.

위 코드에서 주목할 포맷 옵션은 다음과 같습니다.

  • ALPHA = OUT — 자재번호의 선행 0을 제거하여 표시용으로 변환
  • CASE = UPPER — 공급업체 이름을 대문자로
  • WIDTH = 30 PAD = '.' — 30자리 폭으로 채우고 남는 공간은 점으로 패딩
  • TIMESTAMP = USER TIMEZONE = 'Asia/Seoul' — 사용자 로캘과 서울 타임존에 맞춰 출력
  • NUMBER = USER — 사용자 프로필의 천 단위 구분자/소수점 사용
  • ALIGN = RIGHT — 우측 정렬

이 예제는 발주서 확인 이메일 본문을 한 번에 조립합니다. 같은 결과를 CONCATENATEWRITE ... TO 조합으로 만들었다면 코드 길이가 두세 배로 늘어났을 것입니다.

실전 코드 3단계: 조건 표현식, 멀티라인, 에러 로깅

프로덕션 코드에서는 NULL/빈 값 처리, 조건부 문구, 예외 로깅이 필수입니다. String Template는 COND/SWITCH 같은 표현식과 결합할 때 진가를 발휘합니다.

CLASS lcl_invoice_report DEFINITION.
  PUBLIC SECTION.
    METHODS render
      IMPORTING iv_status   TYPE c LENGTH 1
                iv_amount   TYPE p LENGTH 13 DECIMALS 2
                iv_due_date TYPE d
      RETURNING VALUE(rv_text) TYPE string
      RAISING   cx_sy_conversion_error.
ENDCLASS.

CLASS lcl_invoice_report IMPLEMENTATION.
  METHOD render.
    TRY.
        DATA(lv_today)    = cl_abap_context_info=>get_system_date( ).
        DATA(lv_overdue)  = xsdbool( iv_due_date < lv_today ).

        DATA(lv_status_label) = SWITCH string( iv_status
                                  WHEN 'P' THEN `Paid`
                                  WHEN 'O' THEN `Open`
                                  WHEN 'C' THEN `Cancelled`
                                  ELSE         `Unknown` ).

        rv_text =
          |Invoice Summary
| &&
          |---------------
| &&
          |Status : { lv_status_label }
| &&
          |Amount : { iv_amount NUMBER = USER } KRW
| &&
          |Due    : { iv_due_date DATE = USER }| &&
          | { COND string( WHEN lv_overdue = abap_true
                           THEN `(OVERDUE!)`
                           ELSE `(on schedule)` ) }
| &&
          |Note   : Please contact AR if any discrepancy is found.|.

      CATCH cx_sy_conversion_error INTO DATA(lx_err).
        " 로깅: 변환 실패 시 컨텍스트를 그대로 메시지에 담아둠
        DATA(lv_log) = |[{ sy-uname }@{ sy-datum DATE = ISO }T| &&
                       |{ sy-uzeit TIME = ISO }] Conversion failed: | &&
                       |{ lx_err->get_text( ) }|.
        MESSAGE lv_log TYPE 'E'.
        RAISE EXCEPTION lx_err.
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

여기서 COND string( ... )은 중괄호 안에서도 그대로 평가됩니다. 즉 “연체 여부에 따라 다른 문구를 끼우기” 같은 분기 로직을 별도 변수 없이 한 줄로 처리할 수 있습니다. 멀티라인 본문은 으로 구분하거나, 여러 |...|&&로 이어 붙입니다. 한 줄이 너무 길어질 경우 후자가 가독성에 유리합니다.

대량 데이터 처리에서는 다음과 같은 패턴이 권장됩니다.

DATA(lt_lines) = VALUE string_table( ).
LOOP AT it_items INTO DATA(ls_item).
  APPEND |{ ls_item-material ALPHA = OUT } - { ls_item-description }|
         TO lt_lines.
ENDLOOP.
DATA(lv_report) = concat_lines_of( table = lt_lines sep = |
| ).

루프 안에서 매번 문자열을 &&로 누적하면 메모리 재할당이 반복되어 비효율적입니다. 위처럼 문자열 테이블에 모아두었다가 concat_lines_of로 한 번에 결합하는 패턴이 일반적으로 더 빠릅니다.

흔한 실수와 트러블슈팅 FAQ

String Template를 처음 쓸 때 부딪히는 대표적인 함정과 해결책을 정리합니다.

  • 공백이 사라졌어요 — 중괄호 바깥의 공백은 그대로 보존되지만, |hello|{ lv_name }|world|처럼 파이프 사이에 공백이 없으면 단어가 붙어버립니다. 의도한 공백은 명시적으로 적어야 합니다.
  • 중괄호나 파이프를 출력하고 싶어요\|, \{, \} 형태로 백슬래시 탈출을 사용합니다. JSON 본문을 만들 때 \{ "key": "{ lv_value }" \}처럼 활용합니다.
  • 날짜가 이상하게 나옵니다 — 기본은 내부 표현(YYYYMMDD)으로 출력됩니다. DATE = USER, DATE = ISO, DATE = ENVIRONMENT 등 명시적 포맷을 지정해야 사용자에게 친숙한 형식이 됩니다.

그 외 자주 묻는 질문 세 가지입니다.

Q1. &&를 완전히 버려도 되나요? — 아닙니다. 두 개 이상의 String Template를 잇거나, 이미 STRING 변수만으로 구성된 단순 결합은 &&가 자연스럽습니다. 두 문법은 보완 관계입니다.

Q2. 성능이 떨어지지는 않나요? — 단발성 호출에서는 차이가 거의 없습니다. 다만 수만 건 루프 안에서 매번 큰 템플릿을 평가하면 비용이 누적되므로, 위에서 본 것처럼 문자열 테이블 + concat_lines_of 또는 REDUCE 패턴을 권장합니다.

Q3. 다국어 메시지(T100)와 같이 쓰려면? — 메시지 클래스의 텍스트를 MESSAGE ... INTO DATA(lv_msg)로 가져온 뒤, String Template로 사용자별 동적 값을 끼워 넣는 방식이 깔끔합니다. 번역 가능한 골격은 메시지 클래스, 가변 부분은 템플릿이 담당하도록 분리하세요.

그다음 살펴볼 주제들

String Template에 익숙해졌다면 다음 영역으로 시야를 넓혀보길 권장합니다.

  • 표현식 지향 ABAPVALUE, NEW, REF, CONV, EXACT 등 인라인 생성자와 결합하면 한층 더 선언적인 코드 작성 가능
  • 문자열 함수substring, find, match, replace, condense 등 7.40 이후 함수형 표현식
  • 정규표현식(PCRE)matches, find_regex와 템플릿을 함께 사용한 데이터 검증 패턴
  • RAP 메시지 처리 — Behavior Implementation에서 사용자 메시지를 동적으로 조립할 때의 모범 사례
  • 로그/모니터링 — Application Log(BAL)에 구조화된 메시지를 적재할 때 템플릿 활용

더 읽어볼 만한 자료

댓글 0

아직 댓글이 없습니다.