Dev/Books

[CleanCode] 7장. 예외 처리

kyeoneee 2020. 5. 16. 13:03
반응형

목차

  1. 오류 코드보다 예외를 사용하라
  2. Try-Catch-Finally 문부터 작성하라
  3. 미확인 예외를 사용하라
  4. 예외에 의미를 제공하라
  5. 호출자를 고려해 예외 클래스를 정의하라
  6. 정상 흐름을 정의하라
  7. null을 반환하지 마라
  8. null을 전달하지 마라

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

오류 코드를 받아 처리 로직을 추가하는 것보다 오류가 발생하면 예외를 던지는게 좋음
Bad : 함수를 호출한 즉시 오류를 확인하지 않으면 문제가 발생할 확률이 높음

public class DeviceController {
  ...
  public void sendShutDown() {
    DeviceHandle handle = getHandle(DEV1);
    if (handle != DeviceHandle.INVALID) {
      retrieveDeviceRecord(handle);
      if (record.getStatus() != DEVICE_SUSPEND) {
        pauseDevice(handle);
        ...
      } else {
        logger.log("Device suspend.");
      }
    } else {
      logger.log("Invalid handle for: ");
    }
  }
}

Good :

public class DeviceController {
  ...
  public void sendShutDown() {
    try {
      tryToShutDown();
    } carch (DeviceShutDownError e) {
      logger.log(e);
    }
  }

  private void tryToShutDown() throws DeviceShutDownError {
    DeviceHandle handle = getHandle(DEV1);
    DeviceHandle record = retrieveDeviceRecord(handle);

    pauseDevice(handle);
    ...
  }

  private DeviceHandle getHandle(DeviceID id) {
    ...
       throw new DeviceShutDownError("Invalid handle for: ");
    ...
  }
}

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

try-catch-finally 문으로 시작하면 try 블록에서 무슨 일이 생기는지 호출자가 기대하는 상태를 정의하기 쉬워진다.


미확인 예외를 사용하라

확인된 예외

OCP(Open Closed Principle)를 위반하고 캡슐화가 꺠짐
예외를 던지는 메서드가 catch 블록이 있는 메서드가 아닌 더 하위에 있다면 그 사이의 메서드에서 모두 해당 예외를 선언부에 추가하거나 catch 블록에서 처리해야 함

public void get() {
  try {

  } catch (InvalidGetException e) {
    logger.log(e);
  }
}

public void getById() throws InvalidGetException {
  call();
}

public void call() throws InvalidGetException {
  throw new InvalidGetException();
}

예외에 의미를 제공하라

호출 스택만으로 사용자가 의도를 파악하기 어려우므로 오류 메세지에 정보를 담아 예외와 함께 던져야 한다.


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

프로그래머는 오류를 정의할 때 오류를 잡아내는 방법을 고려해야 한다.
외부 API의 다양한 예외를 직접 노출하지 않고 감싸기 기법을 통해 새로운 클래스를 만들어 캡슐화
Bad : 다른 종류의 예외를 처리 로직은 모두 같으므로 의미가 없음

ACMEPort port = new ACMEPort(12);

try {
  port.open();
} catch (DeviceResponseException e) {
  reportPortError(e);
  logger.log("Device response exception", e);
} catch (ATM1212UnlockedException e) {
  reportPortError(e);
  logger.log("Unlock exception", e);
} catch (GMXError e) {
  reportPortError(e);
  logger.log("Device response exception", e);
} finally {
  ...
}

Good :

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);
    }
  }
  ...
}

정상 흐름을 정의하라

외부 API를 감싸 독자적인 예외를 던져 중단한 뒤 호출하는 코드에서 처리를 정의해 중단된 계산을 처리하는 것은 대게 적합하지만 중단이 적합하지 않은 경우도 있다. 이러한 경우 특수 사례 패턴(SPECIAL CASE PATTERN)을 적용해 개선한다.
특수 사례 패턴이란?
반환할 값이 없을 때 예외를 던지는 것이 아니라 기본값을 반환

Bad :

try {
  MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
  m_total += expenses.getTotal();
} catch (MealExpensesNotFound e) {
  m_total += getMealPerDien();
}

Good :

public int testMethod() {
  ...
    MealExpenses expenses = expenseReportDAO.getMeals(employee.getId());
    m_total += expenses.getTotal();
  ...
}

public class PerDiemMealExpenses implements MealExpenses {
  private static final int MEAL_EXPENSES_DEFAULT = 3000;
  public int getTotal() {
        ...
    // 반환 값이 없을 경우 기본값으로 일일 기본 식비를 반환한다.
    return MEAL_EXPENSES_DEFAULT;
  }
}

null을 반환하지 마라

메서드가 null을 반환하면 해당 메서드를 사용하는 클라이언트는 null 체크 코드를 추가할 수 밖에 없다. 이 과정에서 null 체크가 누락되면 버그로 이어지고, null 체크 코드는 아름답지 못하다. 특수 사례 객체를 통해 이러한 문제를 해결할 수 있다.
Bad :

List<Employee> employees = getEmployees();
if (eployees != null) {
  for (Employee e : employees) {
    totalPay += e.getPay();
  }
}

Good :

List<Employee> employees = getEmployees();
for (Employee e : employees) {
  totalPay += e.getPay();
}

public List<Employee> getEmployees() {
  if (.. 직원이 없다면 ..) {
    return Collections.emptyList();
  }
}

null을 전환하지 마라

메서드의 argument로 null을 전달하게 되면 메서드는 내부에서 null 체크 또는 assert를 이용하여 처리하는 로직을 추가할 수 밖에 없다. 이러한 로직을 통해 exception을 던지는 메서드가 되면 해당 메서드를 사용하는 쪽에서도 exception 처리를 추가해야 한다.
호출자가 넘기는 null을 처리하는 방법은 없다. 정책적으로 null을 넘기지 못하게 하는것이 가장 합리적이다.

반응형

'Dev > Books' 카테고리의 다른 글

[CleanCode] 9장. 단위 테스트  (0) 2020.05.18
[CleanCode] 8장. 경계  (0) 2020.05.18
[CleanCode] 6장. 객체와 자료 구조  (0) 2020.05.15
[CleanCode] 5장. 형식 맞추기  (0) 2020.05.12
[CleanCode] 4장. 주석  (0) 2020.05.12