2 minute read

이 장에서는 오류처리에 대해 알아본다. 또한 깨끗하고 튼튼한 코드에 한걸음 더 다가가는 단계로 나아간다. 프로그래머에게 오류처리는 매우 중요하지만, 오류 처리코드로 인해 프로그램 논리를 이해하기 어려워지면 안된다.

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

  • 이전에는 예외를 지원하지 않는 프로그래밍 언어가 많았으나 지금은 아니다.
  • 오류를 if문으로 처리하려 하지 말고, throw로 예외를 던지자
public class DeviceController {
    public void sendShutDown() {
        DeviceHandle handle = getHandle(DEV1);
        //디바이스 상태를 점검한다.
        if (handle != DeviceHandle.INVALID) {
            //레코드 필드에 디바이스 상태 저장
            retrieveDeviceRecord(handle);
            //디바이스가 일시정지 상태가 아니라면 종료한다.
            if(record.getStatus() != DEVICE_SUSPENDED) {
                pauseDevice(handle);
                clearDeviceWorkQueue(handle);
                closeDevice(handle);
            } else {
                logger.log("Device suspended. Unalbe to shut Down");
            }
        } else {
            logger.log("Invailed handle for: " + DEV1.toString());
        }
    }
}
  • if문으로 처리하는 예시
public class DeviceController {
    public void sendShutDown() {
        try{
            tryToShutDown();
        } catch (DeviceShotDownError e) {
            logger.log(e);
        }
    }
    
    private void tryToShutDown() throws DeviceShutDownError {
        DeviceHandle handle = getHandle(DEV1);
        DeviceRecord record = retrieveDeviceRecord(handle);
        
        pauseDevice(handle);
        clearDeviceWorkQueue(handle);
        closeDevice(handle);
    }
    
    private DeviceHandle getHandle(DeviceID id) {
        throw new DeviceShutDownError("Invalid handle for: " + id.toString());
    }
}
  • throw로 예외를 던지는 예시
두 가지의 로직을 분리함으로서 코드도 꺠끗해지고 품질도 나아졌다.

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

  • 예외가 발생할 코드를 짤때는 try-catch-finally 문으로 시작하자
  • catch 블록으로 넘어가더라도 프로그램은 일관성 있게 유지되어야 한다
  • 나머지 논리를 추가하기 쉽고 호출자가 기대하는 상태를 정의하기 쉬워진다.

Unchecked 에외를 사용하라

  • C#, C++, Python, Ruby는 Checked예외를 지원하지 않아도 안정적인 소프트웨어를 구현하기에 무리가 없다.
  • Checked 예외는 최상위 함수가 아래 함수를 호출하고 타고 들어간 최하단 함수가 변경해 새로운 오류를 던진다면, 그 중간에 지나친 함수들도 수정해야 하므로 OCP를 위반한다

예외에 의미를 제공하라

  • 에러 오류메시지에 정보를 담아 예외와 함께 던지자
  • 로깅 기능이 가능하다면 catch 블록에서 오류를 기록하도록 충분한 정보를 넘겨라

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

  • 오류의 유형은 다양하나, 가장 중요한건 오류를 잡아내는 방법이다.
  • 외부에서 호출하는 라이브러리를 한번 감싸도록 하자. (의존성 감소)
  • 외부 클래스의 지정된 예외를 감싼 클래스에 넣어서 처리할 수도 있다.

정상 흐름을 정의하라

  • 꼭 예외를 던지고 처리하는 것만 항상 정답은 아니다.
  • 가끔은 특수사례 패턴(SPECIAL CASE PATTERN) 로 특수 사례를 처리하는 방식이 나을수 있다.

null을 반환하지 마라

  • null을 반환시 매 로직마다 null check 를 하게된다
  • 호출자에게 문제를 던져버리는 꼴이다
  • 사용하려는 외부 API가 null을 반환한다면 감싸기식 메서드를 구현해 예외를 던지거나, 특수 사례 객체를 반환하는 방식을 고려한다
  • null 대신 빈배열을 반환하자 Collections.emptyList()
    public void registerItem(Item item) {
      if(item != null) {
          ItemRegistry registry = peristentStore.getItemRegistry();
          if(registry != null) {
              Item existing = registry.getItem(item.getID());
              if(existing.getBillingPeriod().hasRetailOwner()) {
                  existing.register(item);
              }
          }
      }
    }
    
  • null 확인 코드 예시

public List<Employee> getEmployees() {
    ...
    if(employee.isNotExist()) {
        return Collections.emptyList();
    }
}
List<Employee> employees = getEmployees();

  • 빈 배열 반환 예시

null을 전달하지 마라

  • 메서드에서 또는 메서드로 null을 전달 하는 방식은 매우 나쁘다.
  • null이 넘어온다면 위처럼 null 확인코드를 작성하거나 다른 확인 방법이 추가되어야 하므로 지저분 해진다
  • 인수로 null이 넘어오면 코드에 문제가 있을 수 있다

마치며..

가장 크게 와닿았던 것은 null을 전달하지 않는것, 그리고 Unchecked 예외를 사용하라 였다. 예시로 개발 도중에 잘못된 param, 잘못된 데이터가 던져지면 확인한 예외라 생각하고 Checked 예외를 던졌다. 크게 동작에 문제는 안되지만 @Transactional 을 이용해 rollback을 고려시 Unchecked 예외만 rollback 된다 하더라. (설정해서 checked 예외도 rollback 할 수 있긴 하다.) 이런 점에서 Checked 예외를 권하는건지? 는 모르겠지만 이런 내용들이 프레임워크에 녹아들어 있는걸까 싶다.

Categories:

Updated:

Leave a comment