IS BOUND vs IS INITIAL — 헷갈리는 두 연산자
ABAP 객체지향 코드를 작성하다 보면 참조 변수(reference variable)가 실제로 객체를 가리키고 있는지, 아니면 그 객체가 특정 클래스의 인스턴스인지를 자주 확인하게 됩니다. 이때 자주 등장하는 두 연산자가 IS BOUND와 IS INSTANCE OF입니다. 두 연산자 모두 참조 변수에 사용되지만, 던지는 질문 자체가 다릅니다.
학습 체크리스트
- IS BOUND와 IS INITIAL의 차이를 설명할 수 있다
- IS INSTANCE OF로 다운캐스팅 전 안전 체크를 수행할 수 있다
- CAST 연산자와 IS INSTANCE OF를 조합하는 패턴을 이해한다
- CX_SY_MOVE_CAST_ERROR를 회피하는 방어 코드를 작성할 수 있다
- 다형성 분기 처리에서 두 연산자를 올바른 순서로 사용할 수 있다
한 문장으로 요약하면, IS BOUND는 "참조가 살아있는가?"를 묻고, IS INSTANCE OF는 "이 참조가 가리키는 객체가 특정 타입이거나 그 서브타입인가?"를 묻습니다. 비슷해 보여도 잘못 쓰면 런타임 단락(short dump)으로 직결됩니다.
참고로 IS INITIAL은 모든 타입에 사용 가능한 일반 연산자입니다. 참조 변수에 대해서는 "초기값(null reference)인가?"를 검사하므로 IS NOT BOUND와 동일한 의미를 갖습니다. 하지만 가독성과 의도 명확성 측면에서 참조 변수에는 IS BOUND를 사용하는 것이 일반적으로 권장됩니다. 예를 들어 IF lo_ref IS INITIAL보다 IF lo_ref IS NOT BOUND 쪽이 "이 참조는 객체를 가리키지 않는다"는 의도를 더 직접적으로 전달합니다.
IS BOUND 동작 원리와 사용법
IS BOUND는 참조 변수가 유효한 객체(또는 데이터 객체)를 가리키고 있는지를 boolean으로 반환합니다. 메모리 관점에서 보면 참조 변수가 내부적으로 들고 있는 메모리 주소(handle)가 null이 아닐 때 참(true)이 됩니다. 객체가 GC(가비지 컬렉션)되어 사라졌거나 CREATE OBJECT가 한 번도 호출되지 않았다면 거짓(false)이 됩니다.
" IS BOUND 기본 사용
DATA lo_order TYPE REF TO zcl_order.
" 이 시점에서 lo_order는 초기값(null reference) 상태
IF lo_order IS BOUND.
lo_order->process( ). " 실행되지 않음
ELSE.
WRITE: / '참조가 아직 비어있습니다'.
ENDIF.
" 객체 생성 후
lo_order = NEW zcl_order( iv_id = '0001' ).
IF lo_order IS BOUND.
lo_order->process( ). " 정상 실행
ENDIF.
가장 흔한 사용 시나리오는 메서드의 IMPORTING 파라미터로 들어온 참조가 caller에서 채워졌는지 검증하는 것입니다. 만약 IS BOUND 검사 없이 곧바로 lo_ref->method( )를 호출하면 참조가 비어 있을 때 CX_SY_REF_IS_INITIAL 예외가 발생하며 단락됩니다.
IS INSTANCE OF 동작 원리와 타입 가드
IS INSTANCE OF는 참조 변수가 가리키는 객체가 특정 클래스(또는 인터페이스)의 인스턴스이거나, 그 서브클래스의 인스턴스인지를 검사합니다. 즉 다운캐스팅이 안전한지 사전에 확인하는 "타입 가드(type guard)" 역할을 합니다.
" 상위 타입 참조에 하위 객체가 담긴 상황
DATA lo_item TYPE REF TO zcl_item.
lo_item = NEW zcl_cold_item( ). " 업캐스팅
" IS INSTANCE OF + CAST 패턴
IF lo_item IS INSTANCE OF zcl_cold_item.
DATA(lo_cold) = CAST zcl_cold_item( lo_item ).
lo_cold->cool_down( ).
ENDIF.
주의할 점은 IS INSTANCE OF가 참조가 비어 있을 때(NOT BOUND) 무조건 false를 반환한다는 사실입니다. 따라서 단독으로 사용해도 단락은 일어나지 않지만, 의도와 다르게 분기가 누락될 수 있으므로 BOUND 체크와 함께 사용하는 것이 안전합니다.
인터페이스에 대해서도 동일하게 동작합니다. 예를 들어 IF lo_obj IS INSTANCE OF zif_printable처럼 작성하면, 해당 객체가 zif_printable을 구현했는지 확인할 수 있습니다.
CAST와 IS INSTANCE OF 조합 패턴
ABAP 7.40 이후 도입된 CAST 연산자는 기존의 ?= (move-cast) 문을 인라인 표현식 형태로 대체합니다. 그러나 캐스팅 대상이 실제로 호환되지 않는 타입이라면 CX_SY_MOVE_CAST_ERROR가 발생합니다. 이를 방지하기 위한 표준 패턴이 다음과 같습니다.
METHOD handle_item.
" io_item: IMPORTING TYPE REF TO zcl_item
" 1단계: 참조 유효성
IF io_item IS NOT BOUND.
RAISE EXCEPTION TYPE cx_parameter_invalid_range.
ENDIF.
" 2단계: 타입 체크 후 안전 캐스팅
IF io_item IS INSTANCE OF zcl_cold_item.
DATA(lo_cold) = CAST zcl_cold_item( io_item ).
lo_cold->cool_down( ).
ELSEIF io_item IS INSTANCE OF zcl_hot_item.
DATA(lo_hot) = CAST zcl_hot_item( io_item ).
lo_hot->heat_up( ).
ELSE.
" 알 수 없는 서브타입은 기본 처리
io_item->process_default( ).
ENDIF.
ENDMETHOD.
일부 코드 스타일 가이드에서는 TRY ... CATCH cx_sy_move_cast_error로 캐스팅 실패를 잡아내는 방식을 보여주기도 합니다. 그러나 예외 처리는 비용이 크고, 정상 흐름의 일부로 예외를 사용하는 것은 안티패턴으로 간주됩니다. 사전 체크 방식이 일반적으로 더 권장됩니다.
조합 사용 시 순서 원칙 (BOUND 먼저)
BOUND와 INSTANCE OF를 함께 쓸 때는 항상 BOUND를 먼저 검사합니다. 비록 IS INSTANCE OF가 null reference에서도 단락 없이 false를 반환하지만, 이후 코드에서 메서드 호출이나 속성 접근이 이어진다면 BOUND 검사가 누락된 경로에서 단락이 발생할 수 있습니다.
" 올바른 조합 순서
CHECK io_item IS BOUND.
IF io_item IS INSTANCE OF zcl_special.
DATA(lo_spec) = CAST zcl_special( io_item ).
lo_spec->do_special( ).
ENDIF.
" CHECK 문은 조건 미충족 시 현재 처리 블록을 빠져나갑니다.
" 메서드 도입부에서 가드 절(guard clause)로 자주 활용됩니다.
패턴을 일반화하면 다음 3단계가 됩니다.
- 존재 확인:
IS BOUND— 참조가 유효한가? - 타입 확인:
IS INSTANCE OF— 기대하는 타입인가? - 안전 캐스팅:
CAST— 하위 타입 인터페이스 사용
이 순서를 어기면 BOUND 체크 누락으로 인한 CX_SY_REF_IS_INITIAL, 또는 타입 체크 누락으로 인한 CX_SY_MOVE_CAST_ERROR가 발생할 수 있습니다.
실무 시나리오: 다형성 처리
주문 처리 시스템에서 zcl_order_item의 여러 하위 클래스(zcl_cold_item, zcl_hot_item, zcl_fragile_item)를 한 리스트에 담아 일괄 처리한다고 가정합니다. 각 타입별로 다른 후처리를 적용해야 한다면 다음과 같이 작성할 수 있습니다.
METHOD process_items.
" it_items: IMPORTING TYPE ztt_item_refs (참조 테이블)
LOOP AT it_items INTO DATA(lo_item).
" 가드: null 엔트리 스킵
IF lo_item IS NOT BOUND.
CONTINUE.
ENDIF.
" 공통 처리
lo_item->validate( ).
" 타입별 분기
CASE TYPE OF lo_item.
WHEN TYPE zcl_cold_item INTO DATA(lo_cold).
lo_cold->cool_down( ).
WHEN TYPE zcl_hot_item INTO DATA(lo_hot).
lo_hot->heat_up( ).
WHEN TYPE zcl_fragile_item INTO DATA(lo_fragile).
lo_fragile->add_padding( ).
WHEN OTHERS.
lo_item->process_default( ).
ENDCASE.
ENDLOOP.
ENDMETHOD.
ABAP 7.50부터 도입된 CASE TYPE OF ... WHEN TYPE ... INTO 구문은 사실상 "여러 IS INSTANCE OF + CAST"를 한 줄로 표현한 syntactic sugar입니다. 동일한 의미를 IF/ELSEIF 사슬로 풀어 쓰면 다음과 같습니다.
IF lo_item IS INSTANCE OF zcl_cold_item.
DATA(lo_cold) = CAST zcl_cold_item( lo_item ).
lo_cold->cool_down( ).
ELSEIF lo_item IS INSTANCE OF zcl_hot_item.
DATA(lo_hot) = CAST zcl_hot_item( lo_item ).
lo_hot->heat_up( ).
ENDIF.
읽기 쉬운 쪽을 선택하면 됩니다. 분기가 3개 이상이라면 CASE TYPE OF가 일반적으로 더 간결합니다.
주의사항과 흔한 실수
실수 1: IS BOUND 없이 메서드 호출
" 위험한 코드
METHOD use_handler.
io_handler->execute( ). " io_handler가 비어있으면 단락
ENDMETHOD.
" 안전한 코드
METHOD use_handler.
CHECK io_handler IS BOUND.
io_handler->execute( ).
ENDMETHOD.
실수 2: IS INSTANCE OF 없이 다운캐스팅
" 위험한 코드 — 타입 불일치 시 CX_SY_MOVE_CAST_ERROR
DATA(lo_cold) = CAST zcl_cold_item( lo_item ).
" 안전한 코드
IF lo_item IS INSTANCE OF zcl_cold_item.
DATA(lo_cold) = CAST zcl_cold_item( lo_item ).
ENDIF.
실수 3: IS INITIAL과 IS NOT BOUND 혼동
참조 변수에 대해서는 두 표현이 같은 결과를 내지만, 일반 데이터 객체에 IS BOUND를 쓰면 문법 에러가 납니다. 반대로 참조 변수에 IS INITIAL을 쓰면 동작은 하지만 의도가 불분명해집니다. 참조 변수에는 IS BOUND 계열을, 일반 변수에는 IS INITIAL을 사용하는 것이 일반적인 컨벤션입니다.
실수 4: 인터페이스 참조에 대한 오해
인터페이스 참조 변수에 대해서도 IS BOUND와 IS INSTANCE OF가 동일하게 동작합니다. 다만 인터페이스가 여러 클래스에 의해 구현될 수 있으므로, 특정 클래스로 캐스팅하기 전에는 반드시 IS INSTANCE OF로 확인해야 합니다.
실수 5: 자기 참조와 부모 타입 체크
IS INSTANCE OF는 정확한 타입이 아니라 "그 타입 또는 서브타입"을 검사합니다. 예를 들어 lo_cold(타입 zcl_cold_item)에 대해 IF lo_cold IS INSTANCE OF zcl_item은 항상 참입니다. 정확한 타입 매칭이 필요하면 RTTI(cl_abap_classdescr)를 사용해야 합니다.
핵심 한 줄
IS BOUND는 "참조가 살아있는가?"를, IS INSTANCE OF는 "그 객체가 기대한 타입인가?"를 묻는다 — 다운캐스팅 전에는 반드시 BOUND → INSTANCE OF → CAST 순서를 지킨다.
두 연산자는 경쟁 관계가 아니라 보완 관계입니다. BOUND는 메모리 차원의 안전망이고, INSTANCE OF는 타입 차원의 안전망입니다. 둘을 함께 적용하면 CX_SY_REF_IS_INITIAL과 CX_SY_MOVE_CAST_ERROR 두 가지 대표적인 런타임 단락을 동시에 방어할 수 있습니다. 코드 리뷰 시 CAST가 보이면 그 직전 줄에 IS INSTANCE OF 가드가 있는지, 그리고 메서드 도입부에 IS BOUND 또는 CHECK ... IS BOUND가 있는지 확인하는 습관을 들이는 것이 좋습니다.
댓글 0
아직 댓글이 없습니다.