[Java] 에러 메시지 해석하기

Shef·2022년 1월 3일
1

Java

목록 보기
2/3
post-thumbnail

1. 배경


스프링 공부를 하는 중에 예외 3개가 합쳐진 무려 326줄짜리 에러 메시지를 만났습니다.

순간 머리가 새하얘졌고,

하얀 머리 위로 정확하게 에러 메시지 읽는 법을 익혀야겠다는 생각이 들었습니다.

2. 예제 에러 메시지


HighLevelException: HighlevelException is about~
	at Junk.a(Junk.java:13)
	at Junk.main(Junk.java:4)
Caused by: MidLevelException: MidlevelException is about~
	at Junk.c(Junk.java:23)
	at Junk.b(Junk.java:17)
	at Junk.a(Junk.java:11)
	... 1 more
Caused by: LowLevelException
	at Junk.e(Junk.java:30)
	at Junk.d(Junk.java:27)
	at Junk.c(Junk.java:21)
	... 3 more
Exception in thread "main" AnyException
	at SuppressedExample.main(SuppressedExample.java:6)
	Suppressed: java.lang.ArithmeticException: / by zero
		at SuppressedExample.main(SuppressedExample.java:4)

이 두 에러메시지를 정확히 해석할 줄 아시는 분은 넘어가셔도 좋습니다. 😉

3. 에러 메시지의 문법


3.1 첫 문장

에러 메시지는 보통 다음과 같은 2가지 경우에 출력됩니다.

  1. 예외 또는 에러에 의해서 프로그램이 종료된 경우
  2. 예외를 try catch로 잡아 예외 객체의 printStackTrace 메소드를 호출한 경우

첫번째 경우 에러메시지의 첫 문장은 다음과 같습니다.

Exception in thread "main" service.OrderException: 재고가 없는 상품은 주문할 수 없습니다.

Excepion in thread "main" : main이라는 이름을 가진 쓰레드에서
service : service 패키지에 있는
OrderException : OrderException이라는 이름을 가진 예외가 발생했습니다.

두번째 경우 에러메시지의 첫 문장은 다음과 같습니다.

service.OrderException: 재고가 없는 상품은 주문할 수 없습니다.

쓰레드에 관한 정보가 없다는 것 빼고는 동일합니다.

3.2 Stack trace

at으로 시작되는 여러 문장들은 stack trace로 불립니다.
예외 또는 에러가 발생한 시점까지 거쳐온 메소드들과 그 위치를 가장 최근부터 오래된 순으로 나열합니다.

OrderException: OrderException is about~
	at Order.a(Order.java:10) // a 메소드 안, Order.java의 10번째 줄에서 에러 발생
	at Order.main(Order.java:4)  // main 메소드 안, Order.java의 4번째 줄에서 에러 발생 

따라서 이 에러 메시지는 다음과 같은 과정을 거쳐 발생하였다는 것을 알 수 있습니다.

  1. main 메소드 호출
  2. a 메소드 호출 - 4번째 줄
  3. OrderException 발생 - 10번째 줄

에러 메시지를 발생시킨 코드는 다음과 같습니다.

 1 public class Order {
 2    public static void main(String args[]) { 
 3        try {
 4            a(); 
 5        } catch(OrderException e) {
 6            e.printStackTrace();
 7        }
 8    }
 9    static void a() throws OrderException {
10        throw new OrderException("OrderException is about~");
11    }
12 }
13
14 class OrderException extends Exception {
15     public OrderException(String message) {
16         super(message);
17     }
18 }

3.3 Caused, Suppressed

어떤 예외를 try catch문을 이용해 잡았는데 catch문 안에서 다시 한 번 예외가 발생하는 경우가 있습니다.

예를 들면 이런 경우입니다.

public class Example {
    public static void main(String[] args) throws Exception{
        try {
            int s = 5 / 0;
        } catch (Exception e) {
            throw new NewException();
        }
    }
}

class NewException extends Exception{}

----------------------------------------------

코드 실행 결과

Exception in thread "main" NewException
	at Example.main(Example.java:6)

이 때 처음 발생한 예외에 관한 정보는 출력되지 않습니다.

만약 처음 발생한 예외에 관한 정보를 같이 출력해주고 싶다면 두번째 발생한 예외 객체에 처음 발생한 예외 객체를 등록시켜주어야 합니다.

이 때 두 예외가 서로 인과관계를 갖고 있다면 원인(Cause) 예외로 등록하고, 서로 독립적인 관계라면 억제된(Suppressed) 예외로 등록 시켜주면 됩니다.

원인 예외로 등록시켜주는 코드는 다음과 같습니다.

public class CauseExample {
    public static void main(String[] args) throws Exception{
        try {
            throw new SQLException();
        } catch (Exception e) {
            Exception dbException = new DBException();
            dbException.initCause(e);
            throw dbException;
        }
    }
}

class SQLException extends Exception{}
class DBException extends Exception{}

----------------------------------------------

코드 실행 결과

Exception in thread "main" DBException
	at CauseExample.main(Example.java:6)
Caused by: SQLException
	at CauseExample.main(Example.java:4)

억제된 예외로 등록시켜주는 코드는 다음과 같습니다.
public class SuppressedExample {
    public static void main(String[] args) throws Exception{
        try {
            int s = 5 / 0;
        } catch (Exception e) {
            AnyException anyException = new AnyException();
            anyException.addSuppressed(e);
            throw anyException;
        }
    }
}

class AnyException extends Exception{}

----------------------------------------------

코드 실행 결과

Exception in thread "main" AnyException
	at SuppressedExample.main(SuppressedExample.java:6)
	Suppressed: java.lang.ArithmeticException: / by zero
		at SuppressedExample.main(SuppressedExample.java:4)

이 때 원인 예외로 등록된 예외는 관계된 예외와 같은 들여쓰기에 선상에서 ' Caused by '와 함께 출력되고, 억제된 예외로 등록된 예외는 관계된 예외 기준으로 한 칸 들여써진 채로 ' Suppressed '와 함께 출력됩니다.

3.3.1 ... N more

이것은 나머지 N만큼의 stack trace가 앞서 나온 stack trace 가장 아래에서 N만큼의 stack trace와 동일하다는 의미입니다.

SomeException: SomeException is about~
	at Something.a(Something.java:15)
	at Something.main(Something.java:4)
Caused by: java.lang.ArithmeticException: / by zero
	at Something.a(Something.java:13)
	... 1 more

따라서 위 예제 에러메시지의 full stack trace는 다음과 같습니다.

SomeException: SomeException is about~
	at Something.a(Something.java:15)
	at Something.main(Something.java:4)
Caused by: java.lang.ArithmeticException: / by zero
	at Something.a(Something.java:13)
	at Something.main(Something.java:4)

4. 예제 에러 메시지 해석


처음 보여드린 예제 에러 메시지의 실제 코드를 첨부해드릴테니 지금까지 공부한 내용을 바탕으로 해석을 해보시면 좋을 것 같습니다.

HighLevelException: HighlevelException is about~
	at Junk.a(Junk.java:13)
	at Junk.main(Junk.java:4)
Caused by: MidLevelException: MidlevelException is about~
	at Junk.c(Junk.java:23)
	at Junk.b(Junk.java:17)
	at Junk.a(Junk.java:11)
	... 1 more
Caused by: LowLevelException
	at Junk.e(Junk.java:30)
	at Junk.d(Junk.java:27)
	at Junk.c(Junk.java:21)
	... 3 more

실제 코드

 1 public class Junk {
 2     public static void main(String args[]) {
 3         try {
 4             a();
 5         } catch(HighLevelException e) {
 6             e.printStackTrace();
 7         }
 8     }
 9     static void a() throws HighLevelException {
10         try {
11             b();
12         } catch(MidLevelException e) {
13             throw new HighLevelException("HighlevelException is about~",e);
14         }
15     }
16     static void b() throws MidLevelException {
17         c();
18     }
19     static void c() throws MidLevelException {
20         try {
21             d();
22         } catch(LowLevelException e) {
23             throw new MidLevelException("MidlevelException is about~",e);
24         }
25     }
26     static void d() throws LowLevelException {
27         e();
28     }
29     static void e() throws LowLevelException {
30         throw new LowLevelException();
31     }
32 }
33 
34 class HighLevelException extends Exception {
35     public HighLevelException(String message, Throwable cause) {
36         super(message, cause);
37     }
38 }
39
40 class MidLevelException extends Exception {
41     public MidLevelException(String message, Throwable cause) {
42         super(message, cause);
43     }
44 }
45 
46 class LowLevelException extends Exception {
47     public LowLevelException() {
48         super();
49     }
50 }

5. 마무리 및 꿀팁


예외가 발생했을 때 대부분 가장 처음에 발생한 근본 예외를 포장하고 추상화시킨 후 가장 윗단에는 개발자가 이해하기 쉬운 형태의 예외와 설명이 적혀 있습니다. 하지만 저 같은 경우에는 그냥 예외가 발생하면 제일 아래에 있는 Caused By로 시작되는 예외 즉 첫 원인이 된 예외를 먼저 파악하고 이해가 안된다면 그 위에 Caused By 확인, 이런 식으로 진행하는게 효율이 좋았습니다. 위 예시를 예로 들면 LowLevelException을 제일 먼저 확인하고, 그 다음 MidLevelException, HighLevelException 순으로 확인하는 것이죠. 참고 삼아서 즐거운 개발 생활 하시길 바랍니다 !

참고 자료

Oracle, "Class Throwable", Java 17 API Docs

0개의 댓글