삽질 주도 개발
article thumbnail

오늘은 자바의 예외에 대해서 다뤄보려고 한다.

 

오라클에서는 예외를 다음과 같이 정의하고 있다.

 

예외는 프로그램 실행 중에 발생하여 프로그램 명령의 정상적인 흐름을 방해하는 이벤트입니다.

 

자바에서 예외는 Throwable을 상속한 모든 하위 객체들로 구성할 수 있다. 그리고 Throwable을 상속받은 Exception 객체와 Error 객체로 나눌 수 있다. 또 Exception 객체를 상속받은 RuntimeException(UncheckedException)과 그 외의 Exception(CheckedException) 객체들로 나눌 수 있다.

 

위의 구성을 각 섹션으로 나누어 알아보자.

 

Error와 Excpetion

https://techdifferences.com/difference-between-error-and-exception.html

Java에서는 예외와 오류는 모두 "Throwable" 클래스의 서브 클래스이며 프로그램 실행 중 문제를 나타내는 데에 사용된다.

 

예외는 프로그램의 흐름에서 발생하는 비정상적인 이벤트이며 try-catch 블록을 사용하여 코드 내에서 잡아서 처리할 수 있다.

Java 예외의 예로는 IndexOutOfBoundsException, IOExceptionSQLException 등이 있다.

 

반면에 오류는 프로그램의 정상 흐름을 방해하는 것 이상으로 JVM 자체의 문제를 나타내는 더 심각한 문제이다. 오류는 예외와 같이 코드 내에서 잡아서 처리할 수 없다. 일반적으로 프로그램을 디버깅하거나 다시 시작하여 처리하도록 설계되어 있다.

Java 오류의 예로는 OutOfMemoryErrorStackOverflowError 등이 있다. 

 

 

RuntimeException과 그 외 Exception들

https://codegym.cc/quests/lectures/questsyntax.level09.lecture04

Exception을 상속받은 여러 예외 중에서 RuntimeException과 그 외의 Exception으로 구분할 수 있다.

그리고 이 구분을 Unchekced Exception과 Checked Exception으로도 구분할 수 있다.

사실 Error 클래스도 Unchecked Exception이긴 하지만, 크게 다루지 않을 내용이기 때문에 Exception에 한해서 설명한다.

 

위의 Unchecked/Checked Exception은 특징을 가지고 있다.

  • Unchecked Exception(RuntimeException 및 이의 하위 Exception): 컴파일러는 해당 예외를 catch 하거나 throws 키워드를 사용하여 메서드에서 선언하도록 강제하지 않는다.
  • Checked Exception(RuntimeException을 상속받지 않은 Exception): catch 혹은 throws 키워드를 선언해야 한다.

 


 

왜 Unchecked Exception은 catch나 throws를 강제하지 않는 것일까? 반대로 Checked Exception은 catch나 throws를 강제하는 것일까?

 

Java Language Specification에서 그 이유를 확인할 수 있는데, 해당 내용의 일부를 발췌하여 번역해보았다.

 

11.2. Compile-Time Checking of Exceptions
Unchecked Exception 중에서 Error 클래스는 컴파일 시간에 확인되는 사항에서 제외됩니다. Error는 프로그램의 여러 지점에서 발생할 수 있고 복구가 어렵거나 불가능하기 때문에 제외됩니다. 그러한 예외를 선언하는 프로그램은 무의미하게 복잡해집니다. 정교한 프로그램은 아직 이러한 조건 중 일부를 포착하고 복구하려고 시도할 수 있습니다.
Unchecked Exception 중에서 Run-Time 예외 클래스들은 제외됩니다. Java 프로그래밍 언어 설계자의 판단에 따라 그러한 예외를 선언해야 하는 것은 프로그램의 정확성을 확립하는 데 크게 도움이 되지 않기 때문입니다. Java 프로그래밍 언어의 많은 작업 및 구성으로 인해 런타임에 예외가 발생할 수 있습니다.
Java 컴파일러가 사용할 수 있는 정보와 컴파일러가 수행하는 분석 수준은 런타임 예외가 프로그래머에게 명백할지라도 일반적으로 이러한 런타임 예외가 발생할 수 없음을 설정하는 데는 충분하지 않습니다.
그러한 예외 클래스를 선언하도록 요구하는 것은 단순히 프로그래머를 짜증나게 할 것입니다.
예를 들어 특정 코드는 구성상 null 참조를 포함할 수 없는 순환 데이터 구조를 구현할 수 있습니다. 그런 다음 프로그래머는 NullPointerException이 발생할 수 없음을 확신할 수 있지만 Java 컴파일러가 이를 증명하기는 어렵습니다. 이러한 데이터 구조의 전역 속성을 설정하는 데 필요한 정리 증명 기술은 이 사양의 범위를 벗어납니다.

 

간단하게 설명하자면 Unchecked Exception은 아주 많은 상황에서 발생하는데, 발생하는 이유가 너무 다양하기 때문이라는 것이다.

예를 들어, DB에 잘못된 데이터가 들어가서 null string을 받아 null.methodCall()이 될 수 있고, 사용자가 설정 정보를 잘못 입력했을 수 있다. 그리고 이러한 예외는 어떻게 제어할 방법도 없고, 제어하더라도 꽤나 힘든 작업이 될 수 있다.

 

이러한 예외들은 보통 개발자의 실수로부터 나오는 것이다. 그렇기 때문에 개발자는 RuntimeException예외들이 발생할 가능성이 있는 코드들은 보다 섬세하게 작성하여 예외가 발생하지 않도록 해야 하는 것이 올바른 디자인이라고 한다.

 

즉, null을 확인하거나 해당 변수가 처음부터 null이 될 수 없도록 코드를 작성하여 NullPointerException을 잡는 try-catch를 피할 수 있으며, 문자열로 구성된 숫자에 대한 검사 로직을 구현함으로써 NumberFormatException를 잡는 try-catch를 피할 수 있다.

 


 

반대로 Checked Exception은 개발자가 제어할 수 없는 예외들이다. 예를 들어, 파일 시스템에 쓰는 동안 디스크가 가득 차거나 프로세스에 대한 파일 액세스 제한으로 인해 더 이상 파일을 열 수 없는 상황이다. 개발자는 이러한 예외를 확인시켜 주기 위해 catch 혹은 throws를 강제한다고 한다.

 

참고로 Chekced Exception은 수많은 개발자들의 논란거리 중 하나이다. 

 

스택오버플로우에 많은 논의들이 있는데, 대표적으로 해당 글에서 세계의 개발자들로부터 여러 식견을 얻을 수 있었다.

 


 

자바를 6년 넘게 사용했으나 마스터하진 못한(마스터가 불가능할지도...) 나의 생각도 사실 Checked Exception이 좋게 받아들여지지 않는다. 물론, 내가 실무에서 Checked Exception을 제대로 사용한 경우를 본 적이 없어서 일 수 있다.

 

첫 번째로 Checked Exception의 자원 문제나 파일 액세스와 관련해서 catch 블록에서 복구할 수 있는 전략이 무엇이 있는지 모르겠다. 파일명에 철자가 틀렸다면, 유사도 분석을 해서 관련 이름들을 모두 다시 액세스를 해야 하는 걸까?

 

물론, catch 블록에서 유의미한 메시지를 담은 Unchecked Exception으로 친절하게 사용자에게 전달해 줄 수 있다(이러한 방식을 try-catch-rethrow라고 한다). 그렇게 생각하면 애초에 Unchecked Exception으로 처리할 수 있도록 하면 되는 것이 아닌가?

 

두 번째로 게으름 문제이다. Checked Exception에서 catch 블록을 쓰는 경우를 거의 본 적이 없는 것 같다. 대부분 throws로 상위 메서드에게 그 책임을 떠넘긴다. 이는 하위 메서드에 의존을 하게 만들고, 리팩토링을 어렵게 만든다는 생각이 들었다.

 

모든 개발자가 부지런하게 모든 throw에 대해 catch 블록으로 감싸는 것은 힘들지 않을까 싶고, 앞으로도 해당 문제는 계속해서 발생하고 재밌는 논란거리가 될 것 같다.

 

 

틀린 점이나 개선해야 할 점은 댓글 남겨주세요. 저에게 큰 힘이 됩니다:)