- 스택 트레이스란?
· 프로그램이 시작된 시점부터 현재 위치까지의 메서드 호출 목록
· 이를 통해 예외가 어디서 발생했는지 알 수 있음
- 왜 스택 트레시를 볼 줄 알아야 하나?
· 오류 발생 시, 문제의 정확한 원인이 무엇인지 알아야 문제상황을 해결할 수 있기 때문
· 정확한 문제 상황을 인지하지 못하면, 애꿎은 코드 및 환경설정만 손보다가 우연찮게 문제를 해결할 순 있어도, 장기적으로 보면 실력이 발전없이 멈추게된다.
- 스택 트레이스 예시
public class StackTraceTest
{
public static void main(String[] args)
{one();}
public static void one()
{two();}
public static void two()
{three();}
public static void three()
{Integer.parseInt("abcde");}
}
위 메소드를 실행 시, 에러가 발생한 시점부터 프로그램이 시작된 시점까지 거슬러 올라가면서 출력됨
-> 먼저 실행된 메서드가 아래쪽에 위치함
- 스택 트레이스 읽는법
아래는 스택 트레이스 예시이다.
이전에는 읽기 전에 지레 겁을 먹고 주춤했었지만, 이제 읽는 법을 정확히 알아볼 예정이다
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:' 쪽의 트레이스를 읽어보자
아래 5줄까지만 우선 가져와보겠다. (사실 아래로 3줄만 가져와도 충분함)
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으로 시작하는 첫 번째 메시지에 집중하면 된다.
왜냐하면 해당 메서드를 호출한 직후에 NullPointerException이 발생했기 때문이다!
해당 메시지는 아래와 같다
'com.mycompany.service.impl.PortalManagerImpl' 클래스의 deleteMenuItem 메서드를 호출할 때
603번째 줄에서 NullPointerException이 발생했습니다
오케이, 그럼 이 오류를 해결하기 위해 위 클래스의 603번째 줄로 이동해보자
if (item == null) {
throw new NullArgumentException("item");
}
//중간 생략
List<PortalMenu> children = getMenuItems(item.getPortal().getId(), item.getId()); // 603번째 줄
for (PortalMenu child : children) {
deleteMenuItem(child);
}
여기서 이제 어디가 문제인지 진단해야 한다.
이곳에서 NULL 값이 들어갈 수 있는 모든 경우의 수는 아래 5가지이다
- children
- item
- item.getPortal()
- item.getPortal().getId()
- item.getId()
참고로 나는 깊은 고심을 한 다음 children으로 인해 문제가 발생했다고 판단했으나,
이는 NullPointerException을 정확히 이해하지 못한 큰 오판이였다.
왜냐하면 NullPointException은 단순히 변수에 NULL값이 들어가서 생기는 오류가 아니라,
NULL 레퍼런스에 대해 메서드 호출이나 필드 참조, 배열 처리, throw, 동기화 등 작업을 했을 때 생기는 문제이기 때문이다.
위 이유를 기반으로 하나씩 근거를 대며 보기를 소거해보겠다
1번 children
> 단순히 변수에 NULL값을 할당하는 것만으론 NullPointerException이 생기지 않는다
4번, 5번 item.getPortal().getId() / item.getId()
> 위 2개가 NULL이라면 이는 NULL 레퍼런스에 대한 호출이 아니라 NULL값을 'getMenuItems' 라는 메소드의 인자로 넘기는 것이기에 NullPointerException가 생기지 않는다
> 혹여나, 'getMenuItem' 메소드 안에서 해당 인자에 대한 NULL 체크없이 값을 사용하다가 예외가 발생할 순 있다! 하지만 그렇게되면 스택 트레이스의 'Caused By:'바래 아래의 at은 603이 아니라 다른 곳을 가리키고 있을 것이다
2번 item
> item은 위에서 별도로 NULL 체크를 하기 때문에 603번째 줄에서 절대로 NULL값을 가질 수 없다
그래서 결국 문제의 원인은
item.getPortal()인 3번이다.
스택 트레이스는 이런 식으로 보아야한다.
스택 트레이스를 자주 봄과 동시에 여러 오류에 대해 명확하게 알고 있어야겠다고 느낀 하루다.
참조링크
https://okky.kr/articles/338405
OKKY - 초보 개발자를 위한 스택트레이스 읽는 법
몇 년 전에 네이버 카페에 썼던 글인데, 답변을 달려다 보니 링크가 안되서 이 곳에 옮겨 적습니다. 초보 개발자분들이 제대로 된 디버그 방법을 배우지 못해 오류가 나면 무턱대고 검색부터 하
okky.kr
'개인공부' 카테고리의 다른 글
2023.03.18 TIL (0) | 2023.03.18 |
---|---|
Sychronization(동기화) / Synchronous(동기) / Asynchronous(비동기) 관련정보 (0) | 2023.03.14 |
Test 시리즈 1 (사용이유, 테스트 종류) (0) | 2023.03.08 |
Http 관련 핵심 요약정보 (0) | 2023.03.07 |
2023.03.04 TIL (위치기반 정보공유 웹 신규 프로젝트 MVP 소개) (0) | 2023.03.04 |
댓글