당신의 Checked Exception은 필요 없다

이상민·2021년 10월 17일
14
post-thumbnail

백엔드 API를 만들며 사용자의 잘못된 입력을 Checked 예외, Unchecked 예외 중 어떤 예외로 던질지 고민하면서 처음 생각해보게 됐다. 정의들을 찾아보니 회복 가능성 / 발생 기대성 / 코드 내외부의 문제 여부를 보통 언급하던데 명확하게 이해가 돼진 않았다. 보통 Unchecked로 많이 처리하길래 왜 그러냐고 주변에 같이 공부하는 사람들에게 물어봤을 때 대답은 크게 3가지 였다.

  1. 런타임에 발생하니까 Unchecked 아니에요?
    => (외부 잘못 아니냐 반박후) => 듣고보니 Checked가 맞는거 같네요
  2. 개발자 취향의 문제인거 같아요
  3. 크리티컬한 문제가 아니니 Unchecked일거 같아요

정리하고나서 다시보니 2, 3번이 어느정도 맞는 말이다. 하지만 단순히 한줄로는 충분히 설명되지 않는다.


나의 처음 오해

일단 내가 혼란을 겪었던 이유과 과정을 설명하기 위해 내가 했었던 오해들을 먼저 설명하겠다

1. Unchecked Exception은 Checked Exception에서 파생됐다

자바에서 예외의 클래스 구조는 위와 같다. 여기서 RuntimeException과 이를 상속하는 모든 예외는 Unchecked 예외이고, 나머지 Exception을 상속하는 예외는 Checked 예외이다. Exception이 더 상위 추상체이기 때문에 나는 자연스럽게 "Checked 예외가 우선이고, 모든 예외를 시그니쳐에 적을 수는 없으니까 RuntimeException이 생겼다" 라고 반대로 생각했다.

2. 컴파일 시점에 예외 처리 여부를 알려주니 좋은거 아닌가?

"실행도 하기전에 예외처리 여부를 확인해준다니 와 정말 좋다!" 라고 단순하게 생각했다. 하지만 이는 장점이자 단점이고 Checked 예외를 둘러싼 논란있는 근본적인 원인이다.

3. 사용자의 잘못된 입력은 Checked Exception이다

반은 맞고 반은 틀리다. 일단 최소한 HTTP API에서는 틀리다.

If a client can reasonably be expected to recover from an exception, make it a checked exception. If a client cannot do anything to recover from the exception, make it an unchecked exception.

-- Oracle Docs

오라클 문서 중 Checked 예외에 대해 위에처럼 설명하고 있다. 나는 위를 근거로 Http 400 응답을 하면 클라이언트가 다시 올바른 입력을 보낼테니 회복 가능한 예외라고 단순하게 생각했다.


Checked 예외가 유용한 경우

아래에는 Checked Exception이 필요 없는 이유에 대해 설명할 것이기 때문에 유용할 수 있는 경우를 먼저 짚고 넘어가겠다. API 문서를 작성할 수 없거나 접근이 어렵고, 클라이언트가 반드시 특정 예외를 처리하게 하고 싶어서 Checked Exception의 문제를 감수하고도 필요하다고 느낄때 사용할 수 있다. (사실 실제 어떤 상황이 이럴지는 잘 모르겠다)


당신의 Checked Exception이 필요 없는 이유

1. Open/Closed 원칙의 위배

Checked Exception를 처리하지 않고 상위 계층으로 던지려면 반드시 메소드 시그니처의 throws에 명시해야한다. 예외의 처리를 강제하는 장점이기도하지만, 던진 계층부터 처리한 계층까지 거치는 모든 메소드에서 Checked 예외를 사용하는 경우 시그니처를 수정해야하기 때문에 open-closed 원칙에 위배된다

2. 예외가 아니라 또 다른 리턴 타입이다

throws가 강제되며 발생하는 또 다른 문제로, 예외가 아닌 다른 리턴 타입처럼 돼버린다. 예외라는 아이디어는 어딘가 콜 체인 아래에서 발생한 에러가 처리할 수 있는 곳까지 중간 코드가 걱정할 필요없이 전달되는 것이다. Checked 예외는 모든 단계에서 시그니처에 명시해줘야한다. 결과적으로 호출자가 확인해야하는 특별한 리턴 값과 별 다르지 않다.

public [int or IOException] writeToStream(OutputStream stream) {
    [void or IOException] a = stream.write(mybytes);
    if (a instanceof IOException)
        return a;
    return mybytes.length;
}

3. 없이도 견고한 프로그래밍이 가능하다

다른 언어들은 check exception이 없다 자바의 고유 기능이다. 다른말로하면 checked exception 없이도 견고한 프로그래밍을 할 수 있다. 심지어 post-java jvm언어들 (Groovy, Scala, Kotlin 등)은 컴파일 시 checked exception을 무시한다. 없이도 잘되는데 굳이 위 단점들을 감수하면서 사용해야할까? 이에 대해서는 유명한 책인 클린 코드의 일부분을 첨부한다.

The debate is over. For years Java programmers have debated over the benefits and liabilities of checked exceptions. When checked exceptions were introduced in the first version of Java, they seemed like a great idea. The signature of every method would list all of the exceptions that it could pass to its caller. Moreover, these exceptions were part of the type
of the method. Your code literally wouldn’t compile if the signature didn’t match what your code could do.

At the time, we thought that checked exceptions were a great idea; and yes, they can yield some benefit. However, it is clear now that they aren’t necessary for the production of robust software. C# doesn’t have checked exceptions, and despite valiant attempts, C++ doesn’t either. Neither do Python or Ruby. Yet it is possible to write robust software in all of these languages. Because that is the case, we have to decide—really—whether checked exceptions are worth their price.

Checked exceptions can sometimes be useful if you are writing a critical library: You must catch them. But in general application development the dependency costs outweigh the benefits

-- Clean Code

4. HTTP API는 잘못된 요청에서 회복할 수 없다

클라이언트는 백엔드 API의 예외로부터 회복할 수 없다. 예외 발생 시 request의 라이프 사이클은 400, 404, 500 등 응답을 반환하고 끝이 난다. 클라이언트가 이 request를 회복 시킬 수는 없다. 다만 새로운 요청을 보낼 뿐이다. 클라이언트는 아무것도 할 수 없고 백엔드는 회복할 수 없다. 현재 프로그램의 흐름 내에서 회복할 수 있을때만 checked exception을 사용해야한다


내 나름의 결론

지금의 나는 Checked Exception이 필요한 상황을 생각하지 못하겠기 때문에 이것이 자바의 실수이고, 없애야한다고까지 주장하지는 않겠다. 하지만 누군가 나에게 "이 예외를 Checked로 할지 Unchecked로 할지 고민돼요"라고한다면 "고민된다면 Checked로 하면 안돼요"라고 할것이다.


참고자료

https://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html

https://www.javacodegeeks.com/2012/03/why-should-you-use-unchecked-exceptions.html

https://tapestryjava.blogspot.com/2011/05/tragedy-of-checked-exceptions.html

https://stackoverflow.com/questions/613954/the-case-against-checked-exceptions

profile
편하게 읽기 좋은 단위의 포스트를 추구하는 개발자입니다

2개의 댓글

comment-user-thumbnail
2021년 10월 18일

Exception에 대해 명확히 알지 못했는데 상민님의 이어지는 글들을 읽으면서 Exception에 대해 제대로 알게된 것 같습니다. 좋은 글 감사합니다!

답글 달기
comment-user-thumbnail
2021년 10월 23일

잘보고갑니다~

답글 달기