본문 바로가기

General

[클린 코드] 7장 오류 처리

흩어진 오류 처리 코드는 실제 코드가 하는 일을 파악하기 어렵게 한다는 점에서, 오류 처리는 클린 코드와 밀접하게 연관이 있다.

 

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

오류가 발생하면 예외를 던지는 편이 낫다. 함수를 호출한 즉시 오류를 확인하는 방법은 오류 확인 단계를 잊어버릴 확률이 높다. 반면 예외를 던지면 로직과 오류처리 코드를 분리할 수 있어 호출자 코드도 더 깔끔해진다.

// ❌
class DeviceController {
  sendShutDown(): void {
    const handle: DeviceHandle = getHandle(DEV1);
    
    // 디바이스 상태를 점검한다.
    if (handle !== DeviceHandle.INVALID) {
      // 레코드 필드에 디바이스 상태를 저장한다.
      retrieveDeviceRecord(handle);
      // 디바이스가 일시정지 상태가 아니라면 종료한다.
      if (record.getStatus() !== DEVICE_SUSPENDED) {
        pauseDevice(handle);
        clearDeviceWorkQueue(handle);
        closeDevice(handle);
      } else {
        console.log('Deivce suspended. Unable to shut down');
      }
    } else {
      console.log(`Invalid handle for: ${DEV1.toString()}`);
    }
  }
}

// ✅
class DeviceController {
  sendShutDown(): void {
    try {
      tryToShutDown();
    } catch (e: DeviceShutDownError) {
      console.log(e)
    }
  }
  
  private tryToShutDown(): void {
    const handle: DeviceHandle = getHandle(DEV1);
    const record = retrieveDeviceRecord(handle);
     
    pauseDevice(handle);
    clearDeviceWorkQueue(handle);
    closeDevice(handle);   
  }
  
  private getHandle(id: DeviceID): DeviceHandle {
    // ...
    throw new Error(`Invalid handle for: ${DEV1.toString()}`)
  }
  
  // ...
}

 

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

try 블록은 프로그램 안에 범위를 정의할 수 있게 해준다. catch 블록은 try 블록에서 어떤 일이 일어나더라도 일관성있게 유지해야 한다. 

 

예외에 의미를 제공하라

오류가 발생한 원인과 위치를 찾기 쉽도록, 예외에 대한 전후 상황을 충분히 덧붙인다. 호출스택만으로는 실패한 코드의 의도를 파악하기 어렵다. 실패한 연산 이름, 실패 유형을 언급하는 것이 좋다.

 

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

오류가 발생한 위치, 오류가 발생한 컴포넌트, 유형 등으로 분류해서 예외 클래스를 정의하라. 한 예외는 잡아내고 다른 예외는 무시해도 되는 경우라면 여러 예외 클래스를 사용한다.  예외 클래스에 포함된 정보로 오류를 구분해도 괜찮은 경우는 예외 클래스가 하나만 있어도 충분할 수 있다.오류를 정의할 때 프로그래머에게 가장 중요한 관심사는 오류를 잡아내는 방법이라는 점을 생각하자.

 

정상 흐름을 정의하라

클래스가 예외적인 상황을 캡슐화해서 정의하면 클라이언트 코드가 에외적인 상황을 처리할 필요가 없어진다. 이를 Special Case Pattern 이라고 부른다.

// ❌
try {
  const mealExpenses = expenseReportDAO.getMeals(employee.getID());
  total += expenses.getTotal();
} catch(e: MealExpensesNotFound) {
  total += getMealPerDiem();
}

// ✅
class PerDiemMealExpenses implements MealExpenses {
  getTotal() {
  // 기본값으로 per diem(일일 기본 식비)을 반환
  }
}

// expenseReportDAO는, 청구한 식비가 없을 때도 PerDiemMealExpenses를 통해 언제나 expense를 반환
const mealExpenses = expenseReportDAO.getMeals(employee.getID());
total += expenses.getTotal();

 

null을 반환하지 마라

null을 반환하는 코드는 일거리를 늘릴 뿐만 아니라 호출자에게 문제를 떠넘긴다. 누구 하나라도 null 확인을 빼먹는다면 애플리케이션이 통제 불능에 빠질지도 모른다.

메서드에서 null을 반환하고 싶은 유혹이 든다면, 그 대신 예외를 던지거나 Special Case Pattern 객체를 반환하자. 사용하려는 API가 null을 반환한다면, 감싸는 메서드를 구현해서 예외를 던지거나 Special Case Pattern 객체를 반환하도록 처리를 추가해주자.

 

null을 전달하지 마라

정상적인 인수로 null을 기대하는 API가 아니라면 메서드로 null을 전달하는 코드는 최대한 피한다.

대다수 프로그래밍 언어는 호출자가 실수로 넘기는 null을 적절히 처리하는 방법이 없다. 그렇다면 애초에 null을 넘기지 못하도록 금지하는 정책이 합리적이다. 인수로 null이 넘어오면 코드에 문제가 있다는 말이다. 그만큼 부주의한 실수를 저지를 확률도 높아진다.

 

 

출처: 도서 클린코드 애자일 소프트웨어 장인 정신, 로버트 C.마틴 지음 | 박재호, 이해영

 

'General' 카테고리의 다른 글

[클린 코드] 9장 단위 테스트  (0) 2023.05.28
[클린 코드] 8장 경계  (0) 2023.05.28
[클린 코드] 6장 객체 vs 데이터구조  (0) 2023.05.20
[클린 코드] 5장 포맷 맞추기  (1) 2023.05.18
[클린 코드] 4장 주석  (0) 2023.05.06