Exception 에 대해 다루게 된 계기
어떤 회사의 면접 시험에서 RuntimeException
, IOException
를 던지는 메소드를
각각 상속받는 클래스에 대한 코드를 보여주고 어떤 결과를 예상하는 지 묻는 문제가 나왔었다.
부끄럽지만, 당시 RuntimeException, IOException 에 대해
‘아! Java가 강제로 제어하는 에러와 개발자가 제어 가능한 RuntimeException 에 대해 묻는 문제구나’
라는 생각으로 안일하게 접근했다가 낭패를 본 경험에서 부터 출발한다.
Exception 이 뭔데?
어떤 프로그램이라도 구동시 어떤 오류(예외)가 발생할 수도 발생 안할 수도 있다.
이 때 발생하는 오류(예외)를 Java에서는 계층구조로 정의하였다.
그리고 오류(예외)가 발생 했을 때
- 적절히 제어해서 회생가능? –> 예외
- 발생하면 더 이상 회생 불가능? –> 오류
로 구분지을 수 있다.
Java Exception 계층구조를 살펴 보자.
구조를 보면 크게 Error, Exceptions 로 나눌 수 있고,
Exception 은 각각 Checked Exceptions 와 Unchecked Exceptions 로 나뉠 수 있다.
Error vs Exception
그렇다면 오류가 발생 했을 때, 이 오류를 해결하고 계속 구동이 불가능한가? 적절한 처리 이후에 가능한가?
이 질문의 답으로 Error 와 Exception 이 나뉠 수 있다.
- Error(오류) : 발생하면 회생 불가
- ThreadDeath
- VirtualMachineError
- AssertionError
- Exception(예외) : 적절한 처리 후에 계속 구동 가능
- 예외는 또 하위로 Checked, Unchecked 예외로 나눌 수 있다.
- IOException
- RuntimeException
Checked vs Unchecked Exception
Unchecked Exception
Java 명세서에서 정의하는 Unchecked Exception 은 다음과 같다.
Runtime Exception 클래스와 Error 클래스를 포함한다.
그 외 모든 Exception 은 Checked Exception 이다.
Unchecked Exception 의 대표적인 종류
- NullPointerException
- ClassCastException
Unchecked Exception 을 한 문장으로 정의해보면
‘Java Program : 에이 설마 이걸 이렇게 쓸 리가 있나?’ 이다.
효용가치가 있는 코드를 작성하는 사람이라면, 아래와 같은 코드를 작성하진 않을 것이다.
1 | Object nullObj = null; |
거진 대부분의 Unchecked Exception 은 ‘실수’로 발생할 수 있는 상황에 대해
예외로 정의해 놓은 것이라고 이해할 수 있다. 따라서 개발자는 Unchecked Exception 이 발생할 수도 있는 부분에는 적절한 조치를 취할 수도, 취하지 않을 수도 있다.
단어에서부터 Unchecked(확인되지 않은 = 확인 할 수 없는) 예외이다.
프로그램 구동 시 배열 index 에 -1 이 들어올지, 나누려는 수가 0 인지 아닌 지는 실제 값이 들어오는 순간에야 알 수 있는 것들이다.
하지만, Java 에서는 이런 상황에서도 예외를 발생 시켜서 오류 상황에 대해 개발자가 알아챌 수 있도록 알려주고 있다.
Checked Exception
반면, 컴파일 이전에 발생할 수도 있는 오류(예외) 케이스에 대해 정의해놓은 예외가 바로 Checked Exception 이다.
Checked Exception 의 대표적인 종류
- IOException
- ClassCastException
아래는 FileInputStream 생성자 내부 코드이다.
file 이 유효하지 않은 경우에 대비하여 Checked Exception 인 FileNotFoundException
을 발생시켜,
개발자에게 적절한 예외 처리를 요구하고 있다.
1 | public FileInputStream(File file) throws FileNotFoundException { |
Checked Exception 을 발생시킬 수 있는 클래스 / 메소드를 다루는 개발자는 반드시 적절한 대처를 해주어야 한다
Exception 왜 씀?
Java 의 기본적인 철학은 ‘잘못 구성된 코드는 실행되지 않는다’ 이다.
특히, Java 에서는 오류와 예외로 구분지어 놓고, 그 하위를 계층 구조로 촘촘하게 나누어 놨다는 것은
위 기본 철학에서 반영하고 있는 ‘잘못 구성된 코드는 실행되지 않는다’를 매우 적절하게 반영하고 있다.
Exception 이 존재하는 이유에 대해서는 ‘만약 Exception 이란 클래스가 정의되어 있지 않다면?’ 으로 반문하면서 찾을 수 있다.
프로그램은 구동시에 반드시 오류(예외) 상황이 발생하게 된다. Exception 이 정의되어 있지 않다면,
프로그램 운영을 담당하는 개발자는 24시간 5분 대기조로 언제 발생할지 모르는 오류를 항상 대비하고 있어야만 할 것 이다.
Exception 를 어떻게 제어하나요?
try - catch 로 제어 (예외 처리, 복구, 전환)
1 | try { |
throws 로 제어권을 메소드 호출자에게 이관(예외 처리 회피)
개발자가 작성중인 코드에서 예외가 발생할 수도 있는 경우,
이에 대한 처리를 메소드 호출자에게 책임을 전가 시킬 수 있다.
1 | void fileRead() throws FileNotFoundException { |
이렇게 되면 이 메소드를 사용하는 입장에서는
Checked Exception 의 경우
- 다시 책임을 전가(throws)
- 적절한 처리(try - catch)
로 처리할 수 있다. 그리고, 이는 강제사항이다.
반면, Unchecked Exception 의 경우, throws로 명시 해줘도
이를 호출한 메소드에서 처리를 해도 되고 안해도 된다.
생각해보자
Exception 에 대해 다시 정리해보면서, ‘이제껏 나는 적절한 Exception Handling 을 하고 있었나?’ 다시 반문하게 되었다.
생각해보니 ‘IDE 에서 제안하는 방법으로 처리’하거나, ‘빌드가 될 수준까지만 예외 처리’를 하고 있었다.
생각없이 코드를 짜고 있었다는 것을 반성 하게 되며 여러가지 생각을 갖게 되었다.
Exception 의 계층구조에 대하여 드는 생각
Exception 에 대한 Java 의 철학(잘못 구성된 코드는 실행되지 않는다)을 다시 곱씹어 보면서,
Exception 이 가진 계층구조에 대해 생각해보는 계기가 되었다.
Java의 특징 중 하나인 상속을 통해 계층구조로 Exception 을 설계했다.
계층구조로 인해, 계층구조의 상위 Exception 으로 갈수록 추상화 정도가 높아지고,
반면 하위 계층으로 갈 수록 구체화 정도가 높아지게 된다.
catch 상위 exception 에 대해 범용적으로 처리하던지
vs
특정 exception 과 그 하위에 대해서만 처리하던지
범위를 개발자가 직접 지정할 수 있게 된다.
따라서, ‘잘못 구성된 코드는 실행되지 않는다.’ 라는 철학의 전반엔 계층구조를 통해,
개발자에게 최소한의 예외 상황에서의 자유도를 보장해주는 것이다.
RuntimeException 을 처리 하지 않아도 될까?
RuntimeException 은 위에서 정의한 것 처럼 ‘실수’로 인해 발생할 수 있는 예외이다.
이 대목에서 숙련된 자바 개발자와 초보 개발자의 차이를 발견할 수 있는 부분인 것 같다.
RuntimeException 에 대해 어떻게 처리할 수 있는가? 더 나아가서 RuntimeException 이 발생하지 않도록 어디까지 해봤니?
라는 질문을 던졌을 때, 대답하는 것에 따라 숙련된 개발자인지 아닌지 구별이 가능하다는 것이다.
면접에서도 이런 저의로 RuntimeException vs IOException 에 대한 질문이 나왔던 것 같다.
예를 들면, NullPointerException 을 발생시키지 않는 간단한 방법 중에서
1 | public boolean isHighLevel(String developer) { |
아주 간단한 코드지만, developer 라는 변수에 만약 null 값이 들어온다면?
바로 NullPointerException 이 발생하게 된다.
아래의 코드로 이런 상황 자체를 발생시키지 않게 할 수 있다.
1 | public boolean isHighLevel(String developer) { |
동일한 로직을 갖고있는 코드지만, 이렇게 RuntimeException 에 대해
- 그 종류를 얼마나 알고 있는 지
- 어떻게 대비할 수 있는 지
숙련도를 어렴풋이 알 수 있을 것 같다.