Clean Code_7장. 오류 처리
#노마드코더 #북클럽 #노개북
TIL (Today I Learned)
오류를 적절히 처리하는 방법에 대해서 학습했다.
오늘 읽은 범위
7장. 오류 처리
책에서 기억하고 싶은 내용을 써보세요.
뭔가 잘못될 가능성은 늘 존재한다. 뭔가 잘못되면 바로 잡을 책임은 바로 우리 프로그래머에게 있다.
오류 코드보다 예외를 사용하라
오류가 발생하면 예외를 던지는 편이 낫다. 그러면 호출자 코드가 더 깔끔해진다. 논리가 오류 처리 코드와 뒤섞이지 않으니까.
try-catch-finally 문부터 작성하라
예외가 발생할 코드를 짤 때는 try-catch-finally 문으로 시작하는 편이 낫다.
예외가 발생하는 테스트 케이스를 작성한 후 테스트를 통과하도록 코드를 작성하면 자연스럽게 try 블록의 트랜잭션 범위부터 구현하게 되므로 범위 내에서 트랜잭션 본질을 유지하기 쉽다.
Unchecked 예외를 사용하라
Checked Exception은 메서드를 선언할 때 메서드가 반환할 예외를 모두 열거해야 한다.
Checked Exception은 OCP를 위반한다.
예외에 의미를 제공하라
- 오류 메시지에 정보를 담아 예외와 함께 던진다.
- 실패한 연산 이름과 실패 유형도 언급한다.
- 어플리케이션이 로깅 기능을 사용한다면 catch 블록에서 오류를 기록하도록 충분한 정보를 넘겨준다.
호출자를 고려해 예외 클래스를 정의하라
대다수 상황에서 우리가 오류를 처리하는 방식은 원인과 무관하게 비교적 일정하다.
- 오류를 기록한다.
- 프로그램을 계속 수행해도 좋은지 확인한다.
외부 라이브러리를 호출하는 부분에서 오류를 처리해야 할 때 라이브러리 API를 감싸면서 예외 유형 하나를 반환하면 훨씬 간결해진다.
public class LocalPort {
private ACMEPort innerPort;
public LocalPort(int portNumber) {
innerPort = new ACMEPort(portNumber);
}
public void open() {
try {
innerPort.open();
} catch (DeviceResponseException e) {
throw new PortDeviceFailure(e);
} catch (ATM1212UnlockedException e) {
throw new PortDeviceFailure(e);
} catch (GMXError e) {
throw new PortDeviceFailure(e);
}
}
...
}
ACMEPort 라는 API를 LocalPort 클래스로 한 번 감쌌다. 그리고 open 메소드 내부에서 모든 오류를 catch해서 PortDeviceFailure 라는 하나의 오류를 던지도록 했다.
LocalPort port = new LocalPort(12);
try {
port.open();
} catch (PortDeviceFailure e) {
reportError(e);
logger.log(e.getMessage(), e);
} finally {
...
}
그러면 위와 같이 깔끔하게 PortDeviceFailure 예외에 대해서만 처리하면 된다.
외부 API를 감싸면 외부 라이브러리와 프로그램 사이에 의존성이 크게 줄어든다. 다른 라이브러리도 갈아타는 비용도 적고, 테스트도 쉬워진다. 그리고 특정 업체가 API를 설계한 방식에 발목 잡히지 않는다.
정상 흐름을 정의하라
예외를 던지지 않는 방향으로 설계할 수 있다면 그렇게 하는 것이 좋다.
클래스를 만들거나 객체를 조작해 특수 사례를 처리하는 방식이 있는데 이를 특수 사례 패턴(SPECIAL CASE PATTERN) 이라고 부른다.
null을 반환하지 마라

위와 같이 null을 확인하는 코드로 가득한 것은 나쁜 코드다.
- null 확인으로 불필요한 작업이 많아진다
- 호출자에게 문제를 떠넘긴다
- 누구 하나 null 확인을 빼 먹으면 어떤 오류가 발생될 지 모른다
null을 반환하고픈 유혹이 든다면 그 대신 예외를 던지거나 특수 사례 객체를 반환한다.
사용하려는 외부 API가 null을 반환한다면 래퍼 메서드를 구현해 예외를 던지거나 특수 사례 객체를 반환하는 방식을 고려한다.
null을 전달하지 마라
대다수 프로그래밍 언어는 호출자가 실수로 넘기는 null을 적절히 처리하는 방법이 없다. 그렇다면 애초에 null을 넘기지 못하도록 금지하는 정책이 합리적이다.
결론
-
깨끗한 코드는 읽기도 좋아야 하지만 안정성도 높아야 한다
-
오류 처리를 프로그램 논리와 분리해 독자적인 사안으로 고려하면 독립적인 추론이 가능해지며 코드 유지보수성도 크게 높아진다
오늘 읽은 소감은? 떠오르는 생각을 가볍게 적어보세요
이 책(클린 코드)이 자바를 기준으로 하다 보니 자바에서만 해당하는 내용도 실리게 된 것 같다. 예제 설명이 아닌 일반적인 내용들은 더 다양한 언어의 경우를 소개해주거나 자바에만 해당하는 내용이라는 점을 명시해주면 더 좋지 않았을까 하는 아쉬움이 생긴다.
추가로, Checked/Unchecked Exception에 대해서 찾아보다가 국내 대다수 블로그에 트랜잭션 롤백에 관한 내용이 있는 것을 보았다. 대부분 자세한 설명 없이 뜬금 없이 등장하였는데 우연히 유튜브 영상을 보고 잘못된 내용이 많은 블로그에 전파되었던 것을 알게 되었다. 트랜잭션에 관한 부분은 순수 Java가 아닌 Spring에 해당하는 내용이라고 한다. 현재 수정된 블로그도 있고, 여전히 잘못된 내용이 기재되어 있는 블로그도 남아 있다. 블로그에 적힌 글은 검증이 제대로 되어 있지 않으니 되도록이면 공식 문서나 StackOverflow를 참고해야겠다는 생각이 들었다.
궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.
Checked Exception vs Unchecked Exception
이 부분은 자바의 Exception에 대한 내용이었다. Linkedin 개발자의 포스트를 참고하였다.
Checked Exception
컴파일 타임에 확인할 수 있는 오류이다. RuntimeException의 하위 클래스가 아니면서 Exception 클래스의 하위 클래스인 예외이다.
- 복구 전략이 필요하다
- 많은 보일러 플레이트를 발생시켜서 클래스나 함수를 읽기 어렵게 만든다
- 컴파일러가 프로그래머에게 예외 처리를 알린다
- 존재하지 않는 파일 이름 입력, 잘못된 클래스 이름 등
Unchecked Exception
RuntimeException의 하위 클래스이다. 말 그대로 실행 중(runtime)에 발생할 수 있는 예외이다.
- null 접근, 배열 인덱스 오류, 잘못된 메서드 인수와 같은 프로그래밍 오류로 인해 발생한다
Checked Exception 핸들링 전략
본 책(클린 코드)에서는 어떻게 Unchecked Exception을 사용하라고 말해주지 않았다. 포스트에 보면 Checked Exception을 핸들링하는 좋은 전략은 UncheckedException으로 감싸라는 것이다.
asset 예약어
assert는 자바에서 애플리케이션을 테스트하는데 사용하는 예약어이다. Boolean 값으로 조건을 판단하고, 만약 false라면 AssertionError를 발생시킨다.
public class MetricsCalculator {
public double xProjection(Point p1, Point p2) {
assert p1 != null : "p1 should not be null";
assert p2 != null : "p2 should not be null";
return (p2.x - p1.x) * 1.5;
}
...
}
위 코드는 null을 전달하지 마라에서 소개된 예제이다.
p1 / p2가 null이라면 AssertionError를 발생시킨다.