오류 처리는 프로그램에 반드시 필요한 요소 중 하나일 뿐이다. 뭔가 잘못될 가능성은 늘 존재한다. 뭔가 잘못되면 바로 잡을 책임은 바로 우리 프로그래머에게 있다.
오류 처리는 중요하다. 하지만 오류 처리 코드로 인해 프로그램 논리를 이해하기 어려워진다면 깨끗한 코드라 부르기 어렵다. 우아하고 고상하게 오류를 처리하는 기법과 고려 사항 몇 가지를 소개한다.
오류 코드보다 예외를 사용하라
예외를 지원하지 않는 언어는 오류를 처리하고 보고하는 방법이 제한적이었다. 오류 플래그를 설정하거나 호출자에게 오류 코드를 반환하는 방법이 전부였다.
이러한 방법은 호출자 코드가 복잡해진다. 함수를 호출한 즉시 오류를 확인해야 하기 때문이다. 잊어버리기 쉽다. 그래서 오류가 발생하면 예외를 던지는 편이 낫다.
개념(로직)과 오류를 처리하는 부분이 분리된다. 각 개념을 독립적으로 살펴보고 이해할 수 있다.
Try-Catch-Finally 문 부터 작성하라
어떤 면에서 try 블록은 트랜잭션과 비슷하다. try 블록에서 무슨 일이 생기든지 catch 블록은 프로그램 상태를 일관성 있게 유지해야 한다. 이 장에서는 단위 테스트에서, try-catch 구조로 범위를 먼저 정의하고, TDD를 사용해 필요한 나머지 논리를 추가한다.
먼저 강제로 예외를 일으키는 테스트 케이스를 작성한 후 테스트를 통과하게 코드를 작성하는 방법을 권장한다. 그러면 자연스럽게 try 블록의 트랜잭션 범위부터 구현하게 되므로 범위 내에서 트랜잭션 본질을 유지하기 쉬워진다.
미확인(Unchecked) 예외를 사용하라
여러 해 동안 자바 프로그래머들은 확인된(checked) 예외의 장단점을 놓고 논쟁을 벌여왔다. 하지만 지금은 안정적인 소프트웨어를 제작하는 요소로 확인된 예외가 반드시 필요하지는 않다는 사실이 분명해졌다. 확인된 오류가 치르는 비용에 상응하는 이익을 제공하는지 따져봐야한다.
확인된 예외는 OCP(Open Closed Principle)를 위반한다. 메서드에서 확인된 예외를 던졌는데 catch 블록이 세 단계 위에 있다면 그 사이 메서드 모두가 선언부에 해당 예외를 정의해야 한다. 모듈을 다시 빌드한 다음 배포해야 한다는 말이다.
오류를 원거리에서 처리하기 위해 예외를 사용한다는 사실을 감안한다면 이처럼 확인된 예외가 캡슐화를 깨버리는 현상은 참으로 유감스럽다.
-> 복구가 가능하거나, transaction 여부에 따라 선택하기도 한다.
-> unchecked는 roll-back , checked는 복구 등 transaction roll-back이 되지 않는다.
예외에 의미를 제공하라
오류 메시지에 정보를 담아 예외와 함께 던진다.
호출자를 고려해 예외 클래스를 정의하라
애플리케이션에서 오류를 정의할 때 프로그래머에게 가장 중요한 관심사는 오류를 잡아내는 방법이 되어야 한다. 호출하는 라이브러리 API를 감싸면서 예외 유형 하나를 반환하도록 한다.
LocalPort port = new LocalPort(12);
try {
port.open();
} catch (PortDeviceFailure e) {
reportError(e);
logger.log(e.getMessage(), e);
} finally {
...
}
이처럼 외부 API를 감싸면 외부 라이브러리와 프로그램 사이에서 의존성이 크게 줄어든다.
정상 흐름을 정의하라
try {
MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
m_total += expenses.getTotal();
} catch (MealExpensesNotFound e) {
m_total += getMealPerDiem();
}
예외가 논리를 따라가기 어렵게 만든다. 특수 상황을 처리할 필요가 없다면 더 좋지 않을까?
ExpenseReportDAO를 고쳐 언제나 MealExpenses 객체를 반환한다. 청구한 식비가 없다면 일일 기본 식비를 반환하는 MealExpenses 객체를 반환한다. 클래스나 객체가 예외적인 상황을 캡슐화해서 처리하므로 예외적인 상황을 처리할 필요가 없어진다.
null을 반환하지 마라
null을 반환하는 습관때문에 오류를 유발하게 된다. null을 반환하는 코드는 일거리를 늘릴뿐만 아니라 호출자에게 문제를 떠넘긴다. 메서드에서 null을 반환하고픈 유혹이 든다면 그 대신 예외를 던지거나 특수 사례 객체를 반환한다.
List<Employee> employees = getEmployees();
if ( employees != null)
// getEmployees() 가 빈 리스트를 반환한다면 깔끔해진다.
null을 전달하지 마라
메서드로 null을 전달하는 방식은 더 나쁘다. 대다수 프로그래밍 언어는 호출자가 실수로 넘기는 null을 적절히 처리하는 방법이 없다. 그렇다면 애초에 null을 넘기지 못하도록 금지하는 정책이 합리적이다.
결론
오류 처리를 프로그램 논리와 분리해 독자적인 사안으로 고려하면 튼튼하고 깨끗한 코드를 작성할 수 있다.
'개발 > 클린 코드' 카테고리의 다른 글
클린코드 9장 - 단위 테스트 (0) | 2021.04.27 |
---|---|
클린 코드 8장 - 경계 (0) | 2021.04.21 |
클린코드 6장 - 객체와 자료 구조 (0) | 2021.04.21 |
클린코드 5장 - 형식 맞추기 (0) | 2021.04.14 |
클린코드 4장 - 주석 (0) | 2021.04.13 |