스택 트레이스(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가지입니다.
- children
- item
- item.getPortal()
- item.getPortal().getId()
- 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가 발생하는 원인이 될 수 없다.
오류가 발생하면 당황하지 않고 스택 트레이스를 분석하는 습관을 가져보는 것은 어떨까요?
예외를 분석하여 초보 개발자를 벗어나는 쉬운 길 중 하나라고 생각합니다.
읽어주셔서 감사합니다.
레퍼런스
최근댓글