자바를 처음 이해할 때 다들 꽤나 어려움을 겪었던 내용이 있습니다. 메서드 시그니처에 선언(declaration)하는 throws
와 try-catch
문(statement)의 차이입니다. 우선 둘의 차이를 비교하기전에 기본적인 내용을 짚고 가겠습니다.
자바에서는 발생할 수 있는 실패를 Error와 Exception으로 정의했습니다. 또 Exception을 Checked Exception과 Unchecked Exception으로 구분했죠. 애플리케이션을 개발하는 경우 Error를 다루는 경우는 거의 없습니다. 시스템적인 에러를 가지고 있기 때문입니다. 다만 IOException, SQLException 등으로 정의되어있는 Checked Exception과 NullPointerException, IllegalArgumentException 등으로 정의되어있는 Runtime Exception(Unchekced Exception)은 자바 개발자에게 익숙합니다.
Checked Exception은 애플리케이션이 예상하고 복구해야하는 예외적인 조건입니다. 가령 존재하지 않는 파일을 FileReader에게 읽으라는 명령을 했을 때 FileNoteFoundException이 발생할 것이고, 파일을 읽는 로직에서 흔하게 발생할 수 있느 예상이 되고 복구가 필요한 예외입니다. 자바는 이러한 예상 가능한 예외의 복구를 강제하기 위해 메서드 시그니처에 throws
키워드를 명시하는 기능을 추가했습니다.
Runtime Exception(Uncheked Exception)은 애플리케이션 내부적으로 예외적인 상황이므로 일반적으로 이 예외를 예상하거나 복구할 수 없습니다. 따라서 Runtime Exception은 언어 수준에서 복구를 강제하지 않습니다. 그림을 보면서 다시 확인하겠습니다.
The Three Kinds of Exceptions - Oracle
자바에서는 메서드 내 발생할 수 있는 Checked Exception을 throws
하면 그 메서드를 호출하는 쪽에서 예외처리를 강제합니다. 처리를 하려면 그 메서드를 호출하는 쪽으로 또 throws를 하거나 try-catch 등을 해줘야 합니다.
하위 메서드에서 throws한 예외는 상위메서드에서 안잡으면 컴파일 에러가 발생한다. | 하위 메서드에서 넘어온 checked 예외는 또 다른 상위 메서드로 넘기거나 |
try-catch를 해줘야 한다. | 물론 하위 메서드에서도 throws나 try-catch를 하지 않으면 에러가 발생한다. |
이런 언어의 기능을 이해하는데 크게 어렵지 않았습니다. 하지만 저희가 새로운 예외를 정의할 때, 언제 CheckedException을 상속받아야할지, 언제 Unchekced Exception을 상속받아야할지 고민을 많이 됐었습니다. 그런데 막상 실무에 들어서니 Checked Exception을 정의하는 일은 없었고 심지어 throws 해주는 경우도 점점 줄어들었습니다. 자연스레 Checked Exception에 대한 관심도 덜해졌죠.
그런데 코틀린을 배우면서 알게된 점은 코틀린은 checked exception 처리를 강제하지 않는다는 점이었습니다.
음? 이게 뭔가하고 ‘코틀린 인 액션’ 책을 찾아봤죠.
코틀린은 exception을 자바에게 상속받았음에도 불구하고 자바와 동작을 달리한다. 다른 최신 언어들처럼 코틀린 개발자도 Checked Exception을 포함하지 않기로 했다. - Kotlin In Action
마음에 걸리던 내용은 ‘다른 최신 언어들처럼’ 이라는 문구였습니다. Java는 Checked Exception을 언어적인 기능으로 넣었지만 그 이후 사회적 합의가 있었고 Kotlin은 그 내용을 반영해서 제외했다는 뜻으로 추론할 수 있었습니다. 다음은 코틀린 공식 문서입니다.
checked exception - kotlinlang.org
작은 프로젝트에서는 Checked Exceiption이 효과적일 수 있지만 큰 규모의 프로젝트에서는 생산성을 하락시키고 코드 퀄리티도 나아지지 않는다는 이야기였습니다. 추가적으로 아래의 두 글을 참조했습니다.
강한 어조로 checked exception을 반대하는 글이었습니다. 하지만 코틀린은 상호운용성을 위해 Checked Exception을 사용할 수 있는 방법(@Throws
)을 제공하고 있습니다. Swift와 Objective-C에서도 상호운용을 할 수 있게 적용한다 하는데 이 언어들도 관련이 있나봅니다.
우선, 과거에 양립했었던 Checked Exception의 필요 유무에 대한 논쟁을 들여다 보겠습니다.
2003년에 James Gosling을 Checked Exception에 대한 주제로 인터뷰한 내용입니다. 이 당시 일부 개발자는 Checked Exception은 강력한 애플리케이션을 구축하는 데 도움이 된다고 생각하고 반대로 생산성을 저해한다는 의견이 분분했던 시절입니다.
Failure and Exceptions - A Conversation with James Gosling
이번에는 2015년에 나온 ‘엘레강트 오브젝트’에 나온 내용입니다. 엘레강트 오브젝트는 다소 개발자들 사이에서 논란이 많은 책이기도 합니다. 하지만 저자가 제시하는 의견이 일리가 있으니 내용도 확인해볼만 합니다.
Checked vs. Unchecked Exceptions: The Debate Is Not Over
다음은 조슈아 블로크의 ‘이펙티브 자바’에 담긴 내용입니다. checked exception의 사용성을 인정하고 런타임 예외와의 사용법을 구분하였습니다.
스프링 Transactional API는 checked exception은 에러로 잡지 않습니다.
In its default configuration, the Spring Framework’s transaction infrastructure code marks a transaction for rollback only in the case of runtime, unchecked exceptions. That is, when the thrown exception is an instance or subclass of
RuntimeException
. (Error
instances also, by default, result in a rollback). Checked exceptions that are thrown from a transactional method do not result in rollback in the default configuration.
This is defined behaviour. From the docs:
Any RuntimeException triggers rollback, and any checked Exception does not.
This is common behaviour across all Spring transaction APIs. By default, if a
RuntimeException
is thrown from within the transactional code, the transaction will be rolled back. If a checked exception (i.e. not aRuntimeException
) is thrown, then the transaction will not be rolled back.The rationale behind this is that
RuntimeException
classes are generally taken by Spring to denote unrecoverable error conditions.
This behaviour can be changed from the default, if you wish to do so, but how to do this depends on how you use the Spring API, and how you set up your transaction manager.
Transaction Management - Spring Framework
코틀린에서 참조했던 2003년도의 Rod Waldhoff의 글입니다.
Java's checked exceptions were a mistake (Rod Waldhoff)
The Trouble with Checked Exceptions (Anders Hejlsberg)
앞서 말했듯이 코틀린에서는 checked exception이 없습니다. 코틀린 공식 문서에서는 자바를 제외한 다른 언어는 checked exception이 없고 코틀린도 이를 따른다며 checked exception이 없는 이유를 서술했습니다.
Examination of small programs leads to the conclusion that requiring exception specifications could both enhance developer productivity and enhance code quality, but experience with large software projects suggests a different result – decreased productivity and little or no increase in code quality.
Bruce Eckel (Thinking in Java의 저자)
try {
log.append(message)
} catch (IOException e) {
// Must be safe
}
Java's checked exceptions were a mistake (and here's what I would like to do about it)
artima - The Trouble with Checked Exceptions
다음은 “checked exception은 실수다”라며 다소 자극적인 제목의 블로그 글의 내용입니다.
why does transaction roll back on RuntimeException but not SQLException
Checked exceptions: Java's biggest mistake
비교적 최근에 이야기를 다룬 ‘클린코드’ 책에서는 비교적 단정적인 어조로 checked exception에 대해 이야기를 하고 있습니다.
로버트 C. 마틴의 클린코드 내용과 같이 이 논쟁은 checked exception을 사용하지 않자는 결론으로 일단락되었습니다.
추가적으로 Josh Bloch(Java Collections 프레임워크), Rod Johnson(Spring Framework), Anders Hejlsberg(C#의 아버지), Gavin King 및 Stephen Coebourn(JodaTime)과 같은 인물은 모두 checked 예외에 반대했습니다. Spring, Hibernate 등의 자바 프레임워크/벤더는 런타임 예외만 사용합니다.
자바의 예외는 이전 언어에 비해 안정성 및 오류 처리면에서 장점이 있었습니다. checked exception은 ‘실패’가 아닌 ‘우발적인 상황’을 처리하려는 시도였습니다. 예측가능한 예외를 강조하고 개발자가 이를 처리하게 하는 것이었습니다.
하지만 광범위한 시스템과 복구 불가능한 실패를 강제로 선언하는 것에 대해서는 생각을 하지 못했습니다. 이러한 실패는 checked exception으로 선언될 수 없었습니다. 실패는 일반적인 코드에서 가능하며 EJB, Swing/AWT 컨테이너는 가장 바깥쪽에서 예외 핸들러를 두어 이를 처리했습니다. 트랜잭션을 롤백하고 오류를 반환하는 것이었습니다.
java 8 이후에서는 람다는 앞으로의 근본적인 단계입니다. 이러한 기능은 내부의 기능적 작업에서 ‘제어 흐름’을 추상화합니다. checked exception과 ‘즉시 선언 또는 처리’에 대한 요구 사항을 무용지물로 만듭니다.
지금까지 checked exception에 대한 논쟁을 알아보았습니다. 이미 지나간 논쟁이기도 해서 개인적인 의견은 최대한 배제한체 논거있는 의견을 모아서 정리해보았습니다. 다만 오역이 있을 내용이 있으니 글을 읽으신분들께서 주시는 피드백은 최대한 반영하겠습니다 :)
좋은 글 잘보고 갑니다.