[스터디할래 09] 예외처리
이 글은 ‘백기선’ 개발자님과 함께하는 온라인 자바 스터디에 참여하여 준비/학습한 내용을 정리하는 글입니다.📚
- 9주차 : https://github.com/whiteship/live-study/issues/9
- 목표 : 자바의 예외 처리에 대해 학습
정리는 Java Tutorials - Excepton를 기반으로 하였습니다.
Exception 이란 ?
- 프로그램의 실행 도중에 정상적 플로우를 방해하는 이벤트. Exceptional Event의 준말
- 메소드 내에서 에러가 발생하면, 메소드는 특정 객체를 만들어 런타임 시스템에게 전달한다. 이 객체가
exception
이다. 예외 객체에는 에러 정보, 타입과 프로그램 실행 상태에 대한 정보를 담고 있다. 예외 객체를 생성해서 런타임 시스템에 전달하는 것을 throwing an exception 이라 한다. - 메소드가 exception을 던지면, 시스템은 call stack이라는 메소드 실행 스택을 뒤져서 exception handler를 찾는다. 예외가 발생한 메소드부터 거꾸로 실행된 메소드를 탐색해서 예외를 handle하고 있는 부분을 찾는다.
- exception handler는 예외의 타입이 일치하면 동작한다. exception handler가 동작하는 것을 catch the exception이라고 표현한다.
- 자바에서는 예외의 종류에 따라 반드시 아래 두 가지 중 하나를 구현해야 하는 경우가 있다(=Checked Exception). 이를 Catch or Specify Requirement라 한다.
try
문으로 예외를 catch 해서 예외 핸들러를 추가한다.throws
로 예외를 위임함을 명시한다.
자바가 제공하는 예외 종류
- 자바에서 예외는 예외핸들러 또는 throws를 강제하는 Checked Exception(Runtime Exception + Error)과 Unchecked Exception으로 나뉜다.
- Checked Exception
- 어플리케이션이 에러 핸들러를 구현해서 에러 상황으로 부터 ‘recover’ 하길 기대하는 에외이다.
- Checked Exception 은 Catch or Specify Requirement의 대상이다. 또한,
Error
,RuntimeException
과 그 subclass를 제외한 모든 예외는 checked exception 이다.
- Error
- 애플리케이션 외부 요인으로 인한 에외적 상황에서 사용된다. Catch or Specify Requirement의 대상이 아니다.
Error
및 그 서브클래스 객체이다.- 대표적 Error는
OutOfMemoryError
,StackOverflowError
가 있다. - Exception과 달리 에러 상황에서는 프로그램을 복구하지 않고 종료시키는 것이 관례이다.
- Runtime Exception
- 애플리케이션 내부에서 일어났으나, 핸들을 강제하지 않는 예외이다.
RuntimeException
과 그 하위클래스이다.- Error와 Runtime Exception을 묶어서 unchekced exceptions라 한다.
Unchecked Exception - Controversy
(https://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html)
- 자바에서 Exception은 갑론을박이 많다. Checked Exception으로 예외 핸들링을 강제하기 때문에 코드가 지저분해지고 귀찮고 번거롭다. 그래서 RuntimeException 계층 예외를 던지는 프로그래머도 많다.
- 거두절미하고 documentation의 가이드를 요악하면 : 클라이언트가 예외 상황에서 복구가 가능하면 Checked Exception을, 복구 불가능하면 Unchecked Exception을 써라
자바에서 예외 처리 방법 (try, catch, throw, throws, finally)
- 예외 핸들러에는
try, catch, finally
세 가지 요소가 있다. - 자바7 부터는
Closeable
를 구현한 리소스나 stream 처리에 사용할 수 있는try-with-resources
라는 구문이 추가되었다.
try, catch, finally
try {
// 에외를 throw 하는 코드
} catch (ExceptionType1 | ExceptionType2 ... name) {
// throw된 예외 타입과 핸들러의 타입이 일치하는 첫 번째 핸들러가 실행된다.
// 하나의 핸들러에 여러 타입을 명시할 수도 있다. 이 경우 name은 내부적으로 final 변수이다.
} finally {
// final 문은 선택적으로 선언할 수 있다. try 블럭이 실행된 이후에 반드시 실행되는 코드 블럭이다.
// cleanup code 를 작성하는 데에 사용된다.
// resource leak을 방지하는 주요 방법이다.
}
try-with-resources
try-with-resource
문은 자바 프로그래머들이 과거에 왕왕 사용하던 예외 패턴을 개선한 구문이다.- IO가 있는 리소스를 열어서 하는 작업에서
try/catch/finally
패턴을 많이 작성하게 되는데, 대게finally
문에서 리소스를 정리하는 문을 작성한다. 문제는finally
에서 리소스를 정리하다가 또 예외가 발생할 수 있다는 것이고, 2중 3중 try문이 겹치게 되는 경우도 있다.try-with-resources
는AutoCloseable
이 구현된 리소스를 try문 이후에 알아서 닫아준다. - try/catch만 사용하면, 예외 핸들러에서 예외 처리를 하고 리소스 정리를 빼먹는 경우가 엄청 많다.(실제 우리 팀에서도 이것 때문에 장애가 난 적이 있었다.) try-with-resources는 코드가 깔끔해지는 것 뿐만 아니라, 개발자가 실수로 리소스 정리를 까먹는 경우를 방지해준다.
- 즉, 가능한 경우라면 try/catch/finally 대신 try-with-resources를 무조건 사용하자.
Before
static String readFirstLineFromFileWithFinallyBlock(String path)
throws IOException {
// 리소스 선언
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
// finally에서 리소스를 정리해 주어야 함.
// 예외처리만 하다가 리소스 정리를 까먹는 경우도 왕왕 있다.
// 그런 경우에 제대로 정리되지 않은 리소스사 쌓여서 leak이 발생하는 경우도 엄청 많다.
if (br != null) br.close();
}
}
After
- 앞의 예제는 아래처럼 바꿀 수 있다.
close
문이 없어진 것을 알 수 있다.static String readFirstLineFromFile(String path) throws IOException { // try (AutoCloseable 리소스 할당) try (BufferedReader br = new BufferedReader(new FileReader(path))) { return br.readLine(); } catch (Exception e) { // 예외 핸들러 구현 } finally { // *중요* // try-with-resources 문에서 catch, finally 문은 // try 문에 할당된 리소스가 close된 이후에 실행된다 } }
커스텀한 예외 만드는 경우
- 자바에서 제공하는 기본 예외 클래스를 사용할 수도 있고, 직접 만들 수도 있다.
- 아래의 경우에만 직접 만들자.
- 자바 플랫폼에서 제공하는 예외 타입으로는 설명할 수 없는 타입인가?
- 직접 정의한 클래스를 유저가 보고 이해하기 쉬운가?
- 한 번 이상 그러한 예외 상황이 발생하는가?