깨끗한 코드와 오류처리는 확실한 연관성을 가진다.
오류 처리에 대해 알아보자
오류 코드 사용시 호출코드가 복잡해 진다
따라서 오류가 발생하면 예외를 던지는 편이 더 낫다
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 ); // 1)
stream .close ();
} catch (FileNotFoundException e ) { // 2)
throw new StorageException ("retrieval error" , e );
}
return new ArrayList <RecordedGrip >();
}
굉장히 중요한 로직은 checked 예외를 사용하여 상위로 전파하는 것도 하나의 방법이다.
하지만 현재 대세는 Unckecked 예외이며 C++, C#, 파이썬 루비는 checked 가 없다
제일 큰 이유는 OCP(Open Closed Principle) 위반이다.
메서드에서 checked 예외를 던지면 상위 선언부의 모든 예외 부분을 수정해야 한다.
즉 하위단계에서 코드를 변경하면 상위 단계 메서드 선언부를 전부 고쳐야 한다.
경로상의 모든 함수가 하위 함수에서 던지는 예외를 알아야 하므로 캡슐화도 깨진다.
일반 적인 애플리케이션은 의존성이라는 비용이 이익보다 크다
예외를 던질때는 전 후 상황을 충분히 덧붙여라.
오류 메시지에 정보를 담아 예외와 함께 던지자.
실패한 연산이름, 실패 유형도 언급하자
로깅 기능도 활용하자
오류를 잡아내는 분류하는 방법은 굉장히 많다
하지만 프로그래머에게 가장 중요한 것은 오류를 잡아내는 방법 이 되어야 한다.
오류 처리 방식은 보통 다음과 같다
오류를 기록한다
프로그램을 계속 수행해도 좋은지 확인한다
호출 라이브러리 API 를 감싸 예외 유형 하나를 반환할 수 있다.
즉 감싸기 기법을 추천하고 있다.
외부 API 를 감싸면 외부 라이브러리와 프로그램 사이에서 의존성이 줄고 추후 다른 라이브러리로 갈아타도 비용이 적다. 감싸기 클래스에서 테스트 코드를 넣어 테스트 할 수도 있다.
프로그래머스에서 훈님 말로는 checked Exception 를 감싸기 메서드를 활용하려 unchecked Exception 으로 처리하라는 말도 하였다.
비즈니스 논리와 오류 처리 분리는 중요한 부분이다
예외가 논리를 따라가기 어렵게 한다면 특수 사례 패턴(Special Case Pattern) 을 활용하자
클래스를 만들거나 객체를 조작해 특수 사례를 처리하는 방식이다.
그러면 클라이언트 코드가 예외적인 상황을 처리할 필요가 없어진다.
왜냐하면 클래스나 객체가 상황을 캡슐화해서 처리하기 때문이다.
간단한 예시로 get 메서드는 내부 구현을 드러내기 때문에 사용하지 말라고 했다. 대신에 해당 비즈니스 로직을 엔티티 객체 내부에 메서드로 사용하고 해당 메서드의 이름을 메세지로 전달하여 구현을 추상화하는 방법이 있다.
null 은 Java 의 고질적인 문제
Optional 을 활용하자
null 반환을 해야 하면 예외를 던지거나 특수 사례 객체를 반환하자
외부 API 는 감싸기 메서드를 구현하는 것을 고려하자.
Collections.emptyList() 와 같이 빈 자료구조를 반환하는 방식이 코드가 더 깔끔하다
전달하면 해당 null 가 있어 코드가 복잡하다
assert 를 활용하여 문서화도 가능하다
근본적인 문제해결은 하니다
null 을 인자로 전달하는 거 자체를 금지하자