7. 오류 처리
- 깨끗한 코드와 오류처리는 확실한 연관성을 가진다.
- 오류 처리에 대해 알아보자
오류 코드보다 예외를 사용하라
- 오류 코드 사용시 호출코드가 복잡해 진다
- 따라서 오류가 발생하면 예외를 던지는 편이 더 낫다
public class DeviceController {
public void sendShutDown() {
DeviceHandle handle = getHandle(DEV1);
if (handle != DeviceHandle.INVALID) {
retrieveDeviceRecord(handle);
if (record.getStatus() != DEVICE_SUSPENDED) {
pauseDevice(handle);
clearDeviceWorkQueue(handle);
closeDevice(handel);
} else {
logger.log("Device suspended. Unable to shut down");
}
} else {
logger.log("Invalid handle for: " + DEV1.toString());
}
}
}
-----------------------------------------------------------
public class DeviceController {
public void sendShutDown() {
try {
tryToShutDown();
} catch (DeviceShutDownError e) {
logger.log(e);
}
}
private void tryToShutDown() throws DeviceShutDownError {
DeviceHandle handle = getHandle(DEV1);
DeviceRecord record = retrieveDeviceRecord(handle);
pauseDevice(handle);
clearDeviceWorkQueue(handle);
closeDevice(handle);
}
private DeviceHandle getHandle(DeviceId id) {
...
throw new DeviceShutDownError("Invalid handle for:" + id.toString());
...
}
}
- 무엇보다 디바이스를 종료하는 알고리즘과 오류를 처리하는 알고리즘이 분리되어
- 개념적인 파악이 훨씬 수월하다
Try-Catch-Finally 문부터 작성하라
- try 문은 범위를 지정한다는 특징과 트랙잭션과 비슷한 느낌을 주기도 한다.
- 저자는 먼저 강제로 예외를 일으키는 테스트 케이스를 작성한 후 테스트를 통과하게 코드를 작성하는 방법을 권장한다.
public List<RecordedGrip> retrieveSection(String sectionName) {
try {
FileInputStream stream = new FileInputStream(sectionName);
stream.close();
} catch (FileNotFoundException e) {
throw new StorageException("retrieval error", e);
}
return new ArrayList<RecordedGrip>();
}
Unchecked 예외를 사용하라
- 굉장히 중요한 로직은 checked 예외를 사용하여 상위로 전파하는 것도 하나의 방법이다.
- 하지만 현재 대세는 Unckecked 예외이며 C++, C#, 파이썬 루비는 checked 가 없다
- 제일 큰 이유는 OCP(Open Closed Principle) 위반이다.
- 메서드에서 checked 예외를 던지면 상위 선언부의 모든 예외 부분을 수정해야 한다.
- 즉 하위단계에서 코드를 변경하면 상위 단계 메서드 선언부를 전부 고쳐야 한다.
- 경로상의 모든 함수가 하위 함수에서 던지는 예외를 알아야 하므로 캡슐화도 깨진다.
- 일반 적인 애플리케이션은 의존성이라는 비용이 이익보다 크다
예외에 의미를 제공하라
- 예외를 던질때는 전 후 상황을 충분히 덧붙여라.
- 오류 메시지에 정보를 담아 예외와 함께 던지자.
- 실패한 연산이름, 실패 유형도 언급하자
- 로깅 기능도 활용하자
호출자를 고려해 예외 클래스를 정의하라
- 오류를 잡아내는 분류하는 방법은 굉장히 많다
- 하지만 프로그래머에게 가장 중요한 것은 오류를 잡아내는 방법이 되어야 한다.
- 오류 처리 방식은 보통 다음과 같다
- 오류를 기록한다
- 프로그램을 계속 수행해도 좋은지 확인한다
- 호출 라이브러리 API 를 감싸 예외 유형 하나를 반환할 수 있다.
- 즉 감싸기 기법을 추천하고 있다.
- 외부 API 를 감싸면 외부 라이브러리와 프로그램 사이에서 의존성이 줄고 추후 다른 라이브러리로 갈아타도 비용이 적다. 감싸기 클래스에서 테스트 코드를 넣어 테스트 할 수도 있다.
- 프로그래머스에서 훈님 말로는 checked Exception 를 감싸기 메서드를 활용하려 unchecked Exception 으로 처리하라는 말도 하였다.
정상 흐름을 정의하라
- 비즈니스 논리와 오류 처리 분리는 중요한 부분이다
- 예외가 논리를 따라가기 어렵게 한다면 특수 사례 패턴(Special Case Pattern) 을 활용하자
Special Case Pattern
- 클래스를 만들거나 객체를 조작해 특수 사례를 처리하는 방식이다.
- 그러면 클라이언트 코드가 예외적인 상황을 처리할 필요가 없어진다.
- 왜냐하면 클래스나 객체가 상황을 캡슐화해서 처리하기 때문이다.
- 간단한 예시로 get 메서드는 내부 구현을 드러내기 때문에 사용하지 말라고 했다. 대신에 해당 비즈니스 로직을 엔티티 객체 내부에 메서드로 사용하고 해당 메서드의 이름을 메세지로 전달하여 구현을 추상화하는 방법이 있다.
null 을 반환하지 마라
- null 은 Java 의 고질적인 문제
- Optional 을 활용하자
- null 반환을 해야 하면 예외를 던지거나 특수 사례 객체를 반환하자
- 외부 API 는 감싸기 메서드를 구현하는 것을 고려하자.
- Collections.emptyList() 와 같이 빈 자료구조를 반환하는 방식이 코드가 더 깔끔하다
null 을 전달하지 마라
- 전달하면 해당 null 가 있어 코드가 복잡하다
- assert 를 활용하여 문서화도 가능하다
- 근본적인 문제해결은 하니다
- null 을 인자로 전달하는 거 자체를 금지하자