자바에서 예외는 크게 Checked Exception과 Unchecked Exception으로 나뉩니다.
그 중 Checked Exception은 컴파일 시점에 예외를 처리하는 방식인데요, 클라이언트가 예외를 발생시키면 프로그래머가 안정적으로 문제를 해결할 수 있기에 과거에는 많이 선호하는 예외 처리 방식이었습니다.
하지만 catch
블럭의 사용과 스트림 안에서 직접 예외 처리를 해줄 수 없다는 부분에서 Checked Exception의 사용에 대한 부담감이 높아졌다고 합니다.
책에서 아래와 같은 예시가 나옵니다.
} catch(CheckedException e) {
throw new AssertionError();
}
} catch(CheckedException e) {
e.printStackTrace();
System.exit();
}
위 두 Checked Exception 처리 방식이 올바를까요? 저는 아니라고 생각합니다.
Checked Exception은 예외가 발생하더라도 프로그래머가 의미있게 프로그램을 해결할 수 있도록 만드는 것이 중요하다고 생각합니다. 파일명이 존재하지 않는다는 Checked Exception이 정상적인 default 파일명을 입력할 수 있게 만드는 것처럼 말이죠.
이건 제 생각이었고 이제부턴 이펙티브 자바의 이야기를 집중해보겠습니다.
Checked Exception 하나를 위해 예외처리를 위한 API를 만드는 것은 사용자 친화적이지 못합니다. 사용하는 시점에 try블럭을 추가해야 할 뿐더러 Stream의 사용까지 제한되기 때문입니다. 그래서 이런 상황에는 다른 방법을 고민해봐야 합니다.
직접 Custom Exception을 만든다고 가정해보겠습니다.
public class MemoryRepository {
private static final Map<Long, String> repo = new HashMap<>();
private static Long id = 0L;
public void save(Long id, String name) {
repo.put(++id, name);
}
public String findById(Long id) throws CustomCheckedException {
return repo.get(id);
}
}
위 코드를 실행하는 부분에서는 꼭 try - catch
블럭을 사용하여 예외처리를 해줘야합니다.
try {
memoryRepository.findById(1L);
} catch (CustomCheckedException e) {
e.printStackTrace();
}
매우 귀찮게 되는 것이죠. 이 예외처리의 핵심은 null값이 넘어오는 이슈가 발생하는 것을 막는 것이라고 가정하겠습니다.
그러면 아래처럼 쉽게 문제를 해결할 수 있죠.
public Optional<String> findById(Long id) {
return Optional.of(repo.get(id));
}
혹은 기본 반환값, 비검사 예외처리 등을 해줄 수 있을 것입니다. (충분한 정보를 제공할 수 없기 때문에 지양하기도 함)
또 다른 방법으로는 Checked Exception을 던지는 메서드를 두개로 쪼개 UnChecked Exception으로 바꾸는 방법입니다.
문제가 발생하는 지점을 boolean으로 받아 true일 경우에만 로직을 실행시키는 것이죠.
if (obj.isPermitted(args)) {
obj.action(args);
} ...
만약 멀티 스레드 상황에서 동기화 없이 위 방법대로 리팩터링이 된다면, if문의 시점과 action의 시점 사이에 상태가 변경될 수도 있고, 중복 수행이 발생할 수도 있어 이런 문제는 잘 해결해야합니다. (이렇게 리팩토링 하지 않으면 될듯?)
간단하게 정리하자면 안정성을 높이기 위해서는 Checked Exception 사용이 좋지만 최대한 UnChecked Exception 을 사용하려 해봅시다 예외상황에서 catch 블럭에서 유의미하게 복구할 방법이 없다면 UnChecked Exception을 사용합시다!