본문 바로가기

책/CleanCode

✨ Clean Code 7장: 오류 처리

🗣 서론

오류 처리는 프로그램에 반드시 필요한 요소 중 하나이다. 잘못된 가능성은 늘 존재한다. 우리는 이러한 잘못된 점을 잡을 책임이 있다. 깨끗한 코드와 오류 처리는 확실하게 연관성이 있다. 여기저기 흩어진 오류 처리 코드로 실제 코드가 하는 일을 파악하기 어려울 때가 있기 때문이다. 따라서 우리는 깔끔하게 오류를 처리하는 기법과 고려 사항들을 인지하고 있어야 한다.

📌 오류 코드보다 예외를 사용하라

  • 오류 플래그나 호출자에게 오류 코드를 반환하여 오류를 처리하는 방법은 논리와 오류 처리 코드가 뒤섞여 깔끔하지 못하다.
  • 오류가 발생하면 오류를 던지는 편이 훨씬 좋다. 그러면 호출자 코드가 더 깔끔해진다.

📌 Try-Catch-Finally 문부터 작성하라

try 블록은 트랜잭션과 비슷하다. try에서 문제가 생기면 catch 블록에서 프로그램 상태를 일관성 있게 유지해야 한다. 따라서 예외가 발생할 수 있는 코드는 try-catch-finaly 문으로 시작하는 것이 좋다. 그러면 try 블록에서 무슨 일이 생기든지 호출자가 기대하는 상태를 정의하기가 쉽기 때문이다.

강제로 예외를 일으키는 테스트 케이스를 먼저 작성하고, 테스트를 통과하게 코드를 작성하는 방법을 권장한다. 자연스럽게 try 블록의 트랜잭션 범위부터 구현하게 되어서 트랜잭션 본질을 유지하기 쉬워진다.

📌 미확인 예외를 사용하라

미확인(Unchecked) 예외,  확인된(Checked) 예외
  • 안정적인 SW를 만들기 위해서 확인된 예외가 반드시 필요하지는 않다.
  • 확인된 예외는 OCP를 위반한다. 해당 메서드에서 예외를 던졌는데 catch 블록이 상위에 있다면 그 사이 메서드 모두 해당 예외를 정의해야 한다. 따라서 하위 단계에서 코드를 변경하면 상위 단계 모두 고쳐야 한다. (연쇄적인 수정 필요)
  • 확인된 예외는 캡슐화가 깨진다. 모든 함수가 최하위 함수의 예외를 알아야 하기 때문이다.

📌 예외에 의미를 제공하라

전후 상황을 충분히 덧붙여야 한다. (Java - 모든 예외에 호출 스택 제공)

오류 메시지에 충분한 정보를 담아 예외와 함께 던진다. 즉 실패한 연산 이름과 유형을 언급해야 하며, 로깅을 사용한다면 충분한 정보를 남겨줘야 한다. (로깅에 중요성에 대해 아래와 같이 기록한 적이 있다)

📌 호출자를 고려하여 예외 클래스를 정의하라

외부 라이브러리가 던질 예외를 모두 잡아 각각 처리하는 것은 비효율적이라 생각한다. 외부 라이브러리가 던질 예외를 잡아 하나의 예외 클래스를 반환하는 감싸기(wrapper) 클래스를 사용하는 것을 고려해보는 것이 좋다. 외부 라이브러리와 프로그램 사이 의존성이 크게 줄어들기 때문이다. 그리고 테스트하기도 쉬워질 수 있어 매우 유용하다.

...

public void hello() {
    try {
		...
    } catch (FirstException e) {
    	throw new 새로_정의한_예외_클래스(e);
    } catch (SecondException e) {
    	throw new 새로_정의한_예외_클래스(e);
    } ...
}

📌 null을 반환하거나 전달하지 마라

null을 확인하는 코드는 가독성을 떨어트리고, 누락될 가능성이 높아 NPE(NullPointException)가 발생할 수 있다. 리스트를 반환하는 경우 Collections.emptyList()를 반환하도록 하자.

메서드에 null을 넘기는 행위는 애초에 null을 넘기지 못하도록 금지하는 정책이 합리적이다. 이러면 부주의한 실수를 저지를 확률이 완전히 없어지는 것은 아니지만 확률이 매우 낮아질 수 있다.