오봉이와 함께하는 개발 블로그
CleanCode - 오류 처리 본문
오류 처리
이번 내용은 오류 처리에 관한 내용이다. 책 자체에서 분량이 그렇게 많지 않아 글이 길지는 않을 거 같다.
오류 코드보다 예외를 사용하라
오류 코드보다 예외를 사용하라는 말은 예외 상황에 대하여 조건문(if-else 혹은 switch-case)으로 하지말고, try-catch를 통해 예외를 처리하라는 말이다.
일단 나부터 반성한다. 처음 프로그래밍 공부하던 시절 예외 처리를 조건문으로 하던 버릇이 아직도 남아있어 고치지 못하고 있다.
public void sendShutDown() {
...
if (CODE_ERROR) {
logger.log("!!! error !!!");
}
}
public void sendShutDown() {
...
try {
...
} catch(Exception e) {
logger.log("!!! error !!!");
throw new ShotDownError("=== error trace ===");
e.printStackTrace();
}
}
현재는 예외 처리가 하나라서 문제가 없어 보이지만 수 많은 상황에 대해 예외를 처리했을 때 새로운 예외 사항이 추가되거나, 리팩토링 시점이 다가오면 더 큰 비용이 발생한다. 리팩토링 시점에 예외 상황을 판단하는 코드와 예외 상황에 대응하는 코드가 한 곳에 있는 모습을 상상해보자.
아니 애초에 모든걸 고려하지 않더라도 if-else를 통해 예외 처리하는 거 보단 try-catch를 통해 예외 처리하는 것이 비용이 더 낮을 거 같다.
Try-Catch-Finally 문부터 작성하라.
처음에 내용을 보고 try-catch-finally 부터 작성하라 하는데, finally문이 없어서 함의를 생각했는데, finally랑 상관은 없는 거 같다.
그냥 코드 작성할 때 예외 상황이 있는 코드는 미리 상정하고 작성하라는 뜻으로 이해했고, TDD의 한 방법인듯 하다. (TDD를 해본 경험은 없어 자세하게 알지 못해 공감은 가지 않는다)
특정 테스트 코드가 있을 때 해당 테스트 코드는 예외가 발생해야 통과하는 코드이다.@Test(expected = StorageException.class)
public List<Reco rdedG rip> ret rieveSect ion(St ring sectionName) {
try {
FilelnputStream stream = new FilelnputStream(sectionName)
} catch (Exception e) {
throw new StorageException("retrieval error", e);
}
return new ArrayList<RecordedGrip>();
}
위 코드는 이상한 파일을 넣으면 테스트에 통과한다.
그후 예외의 범위를 줄여 파일을 찾지 못 하는 예외에 대해서만 catch 하도록 한다.
public List<Reco rdedG rip> ret rieveSect ion(St ring sectionName) {
try {
FilelnputStream stream = new FilelnputStream(sectionName);
stream.close();
} catch (FileNotFoundException e) {
throw new StorageException("retrieval error", e);
}
return new ArrayList<RecordedGrip>();
}
테스트 코드 먼저 개발하고, try-catch를 통해 구조를 잡았기 때문에 TDD의 방식으로 나머지 코드를 추가한다.new FilelnputStream(sectionName);
이후에 예외가 발생하지 않았다면 stream.close()
까지는 예외 상황이 없다고 가정한다.
때문에 try에서 트랜잭션 범위부터 구현하게 되어 트랜잭션의 본질을 유지하기 쉬워진다 한다.
미확인(unchecked)예외를 사용하라
간단하다. 프로그램은 상위에서 하위 계층으로 갈수록 더 많은 함수를 호출하는데 제일 말단의 함수에서 checked 예외를 사용하면 함수를 호출하는 최상단까지 throw XXXException을 해야한다. 만약 제일 말단 함수에서 checked 예외가 n개 발생하면 n개의 예외 모두 해야한다.
호출자를 고려해 예외 클래스를 정의하라
저자는 말한다 오류를 정의할 때 프로그래머에게 가장 중요한 관심하는 오류를 잡아내는 방법 이라고
이 부분은 코드로 봐야 이해될 거 같다.
ACMEPort port = new ACMEPort(12);
try {
port.open();
} catch(SomeExceptionOne e) {
reportPortError(e);
logger.log("Error", e);
} catch(SomeExceptionTwo e) {
reportPortError(e);
logger.log("Error", e);
} catch(SomeExceptionThree e) {
reportPortError(e);
logger.log("Error", e);
} finally {
....
}
위 코드는 적절하게 예외 처리를 하고 있지만, 해당 기능을 사용할 때는 다른 곳에서 또 저런 식으로 예외를 처리해야 한다.
그럼 어떻게 수정하냐면 저 코드를 감싸면 된다.
public class LocalPort {
private ACMEPort innerPort;
public LocalPort(int portNumber) {
innerPort = new ACMEPort(portNumber);
}
public void open() {
try {
port.open();
} catch(SomeExceptionOne e) {
throw new PortDeviceFailure(e);
} catch(SomeExceptionTwo e) {
throw new PortDeviceFailure(e);
} catch(SomeExceptionThree e) {
throw new PortDeviceFailure(e);
} finally {
....
}
}
}
위에 코드로 클래스를 통해 감싸고 아래처럼 사용한다.
LocalPort port = new LocalPort(12);
try {
port.open();
} catch (PortDeviceFailure e) {
reportError(e);
logger.log(e.getMessage(), e);
} finally {
...
}
테스트 코드 작성에도 용이하고 다른 곳에서 재사용할 때도 용이하다.
null을 반환하지 마라 & null을 전달하지 마라
문제 해결 방법은 간단하다. return 값에 null이 절대 들어오지 않도록 한다. 그럼 null 체크를 하지 않아도 된다.
또, 메서드 args에 null을 넣지 말자. 메서드 내부에서 null 체크를 하지 않아도 되도록.
'이론' 카테고리의 다른 글
CleanCode - 단위 테스트 (1) | 2024.01.04 |
---|---|
CleanCode - 경계 (1) | 2024.01.04 |
내 코드가 그렇게 이상한가요? - 컬렉션 : 중첩을 제거하는 구조화 테크닉 (0) | 2024.01.01 |
CleanCode - 형식 맞추기 (1) | 2024.01.01 |
CleanCode - 주석 (1) | 2024.01.01 |