스프링 공부를 하는 중에 예외 3개가 합쳐진 무려 326줄짜리 에러 메시지를 만났습니다.
순간 머리가 새하얘졌고,
하얀 머리 위로 정확하게 에러 메시지 읽는 법을 익혀야겠다는 생각이 들었습니다.
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)
이 두 에러메시지를 정확히 해석할 줄 아시는 분은 넘어가셔도 좋습니다. 😉
에러 메시지는 보통 다음과 같은 2가지 경우에 출력됩니다.
첫번째 경우 에러메시지의 첫 문장은 다음과 같습니다.
Exception in thread "main" service.OrderException: 재고가 없는 상품은 주문할 수 없습니다.
Excepion in thread "main" : main이라는 이름을 가진 쓰레드에서
service : service 패키지에 있는
OrderException : OrderException이라는 이름을 가진 예외가 발생했습니다.
두번째 경우 에러메시지의 첫 문장은 다음과 같습니다.
service.OrderException: 재고가 없는 상품은 주문할 수 없습니다.
쓰레드에 관한 정보가 없다는 것 빼고는 동일합니다.
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 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 }
어떤 예외를 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 '와 함께 출력됩니다.
이것은 나머지 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)
처음 보여드린 예제 에러 메시지의 실제 코드를 첨부해드릴테니 지금까지 공부한 내용을 바탕으로 해석을 해보시면 좋을 것 같습니다.
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 }
예외가 발생했을 때 대부분 가장 처음에 발생한 근본 예외를 포장하고 추상화시킨 후 가장 윗단에는 개발자가 이해하기 쉬운 형태의 예외와 설명이 적혀 있습니다. 하지만 저 같은 경우에는 그냥 예외가 발생하면 제일 아래에 있는 Caused By로 시작되는 예외 즉 첫 원인이 된 예외를 먼저 파악하고 이해가 안된다면 그 위에 Caused By 확인, 이런 식으로 진행하는게 효율이 좋았습니다. 위 예시를 예로 들면 LowLevelException을 제일 먼저 확인하고, 그 다음 MidLevelException, HighLevelException 순으로 확인하는 것이죠. 참고 삼아서 즐거운 개발 생활 하시길 바랍니다 !