스택 트레이스(Stack Trace) 란?

Exception이 발생하였을 때 프로그램이 시작된 시점부터 현재 위치까지의 메서드 리스트입니다.

메모리의 스택 영역에 기록된 프로그램의 실행 이력을 역 추적하기 때문에 스택 트레이스로 불리게 되었습니다.

(위키피디아)

 

개발하면서 오류가 발생하면 구글에 검색부터 해보는 경우가 많습니다.

하지만 오류 메시지를 이해하지 못하면 오류 메시지 검색에 시간을 허비할 수 있습니다.

아래 fender님의 NPE(NullPoniterException) 예시로 스택 트레이스를 이해해 보도록 하죠!


첫 번째로 스택 트레이스를 읽을 때 중요한 것은 오류가 발생된 코드를 찾는 것입니다.

트레이스가 상당히 길더라도 'Caused By:'로 표시되는 항목만 찾으면 오류가 발생된 위치를 파악할 수 있습니다.

java.lang.reflect.InvocationTargetException
     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
     at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
     at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
     at java.lang.reflect.Method.invoke(Unknown Source)
     at com.mylibrary.ap.xul.builder.handler.MethodCallback.invokeCallback(MethodCallback.java:32)
     at com.mylibrary.ap.xul.builder.handler.ViewHandler.invokeActionResult(ViewHandler.java:581)
     at com.mylibrary.ap.xul.context.impl.ViewContextImpl$2.onActionResult(ViewContextImpl.java:283)
     at com.mylibrary.ap.xul.context.impl.ActionContextImpl.setResult(ActionContextImpl.java:90)
     at com.mylibrary.ap.xul.action.stock.DialogAction.handleDialogClose(DialogAction.java:156)
     at com.mylibrary.ap.xul.action.stock.DialogAction$1.windowClosed(DialogAction.java:142)
     at com.mylibrary.api.Window.fireWindowClosedEvent(Unknown Source)
     at com.mylibrary.api.Window.onClose(Unknown Source)
     at com.mylibrary.api.platform.WindowBase.BaseClose(Native Method)
     at com.mylibrary.api.platform.WindowBase.BaseClose(Unknown Source)
     at com.mylibrary.api.Window.close(Unknown Source)
     at com.mylibrary.ap.xul.ui.MessageDialog.handleButtonClick(MessageDialog.java:145)
     at com.mylibrary.ap.xul.ui.MessageDialog$1.handleClick(MessageDialog.java:134)
     at com.mylibrary.api.ClickableSupport.fireClickEvent(Unknown Source)
     at com.mylibrary.api.ClickableSupport.handleAction(Unknown Source)
     at com.mylibrary.api.Button.actionHook(Unknown Source)
     at com.mylibrary.api.Component.action(Unknown Source)
Caused by: java.lang.NullPointerException
     at com.mycompany.service.impl.PortalManagerImpl.deleteMenuItem(PortalManagerImpl.java:603)
     at com.mycompany.service.impl.PortalManagerImpl.deletePortal(PortalManagerImpl.java:358)
     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
     at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
     at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
     at java.lang.reflect.Method.invoke(Unknown Source)
     at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:307)
     at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:182)
     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:149)
     at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106)
     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
     at org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:66)
     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
     at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
     at $Proxy54.deletePortal(Unknown Source)
     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
     at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
     at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
     at java.lang.reflect.Method.invoke(Unknown Source)
     at com.mycompany.util.SpringSecurityContextInvocationHandler.invoke(SpringSecurityContextInvocationHandler.java:62)
     at $Proxy84.deletePortal(Unknown Source)
     at com.mycompany.ui.binding.PortalDataProvider.doDelete(PortalDataProvider.java:81)
     at com.mycompany.ui.binding.PortalDataProvider.doDelete(PortalDataProvider.java:12)
     at com.mycompany.ui.binding.AbstractEISDataProvider.delete(AbstractEISDataProvider.java:105)
     at com.mylibrary.ap.xul.binding.dataset.impl.DatasetImpl.doCommit(DatasetImpl.java:90)
     at com.mylibrary.ap.xul.binding.dataset.impl.AbstractDataset.commit(AbstractDataset.java:251)
     at com.mylibrary.ap.xul.binding.dataset.impl.AbstractDataset.deleteRow(AbstractDataset.java:201)
     at com.mylibrary.ap.xul.action.dataset.DeleteDataRowAction.execute(DeleteDataRowAction.java:22)
     at com.mylibrary.ap.xul.context.impl.ViewContextImpl.execute(ViewContextImpl.java:294)
     at com.mycompany.ui.portal.PortalInfoHandler.onPostConfirmDeleteAction(PortalInfoHandler.java:192)
... 21 more

 

가독성을 높이기 위해 Caused by: 부분만 추출해 보았습니다.

Caused by: java.lang.NullPointerException
     at com.mycompany.service.impl.PortalManagerImpl.deleteMenuItem(PortalManagerImpl.java:603)
     at com.mycompany.service.impl.PortalManagerImpl.deletePortal(PortalManagerImpl.java:358)

해석해 보면
com.mycompany.service.impl.PortalManagerImpl' 클래스의 'deletePortal' 메서드에서
'deleteMenuItem' 메서드 호출 시 NullPoniterException 이 발생했다는 것을 알 수 있습니다.


'PortalManagerImpl' 코드를 보고 원인을 파악해 보도록 하겠습니다.

이론적으로 아래의 코드에서 Null 값이 들어갈 수 있는 모든 경우의 수는 5가지입니다.

  1. children
  2. item
  3. item.getPortal()
  4. item.getPortal().getId()
  5. item.getId()
if (item == null) {
    throw new NullArgumentException("item");
}

//중간 생략
List<PortalMenu> children = getMenuItems(item.getPortal().getId(), item.getId()); // 603번째 줄

for (PortalMenu child : children) {
    deleteMenuItem(child);
 }

NullPoniterException은 단순하게 변수에 Null 값이 들어가서 생기는 오류가 아니며

객체의 널 레퍼런스에 대한 메서드 호출이나 필드 참조 등의 작업을 했을 때 발생하는 문제라는 것을 이해하셔야 합니다.

 

 

1. children

children은 변수이므로 null값이 할당되었다고 해서 NPE가 발생할 수 없습니다.

 

2. item

NullArgumentException 체크로직이 존재하여 603번째 줄에서 오류가 발생할 수 없다.

 

3 : item.getPortal()

item 클래스에 Portal 클래스가 연관관계 Mapping 이 적용된 것으로 생각할 수 있으며 

Portal 클래스가 Null 인 경우 NullPoniterException이 발생합니다.

 

4, 5 : item.getPortal().getId(), item.getId()

Null 값을 getMenuItems 메서드의 인자로 넘기는 것뿐이기 때문에 NPE가 발생하는 원인이 될 수 없다.

 

오류가 발생하면 당황하지 않고 스택 트레이스를 분석하는 습관을 가져보는 것은 어떨까요?

예외를 분석하여 초보 개발자를 벗어나는 쉬운 길 중 하나라고 생각합니다.

 

읽어주셔서 감사합니다.

 

레퍼런스

https://okky.kr/articles/338405

반응형
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기