## 목차 ##



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

  • 예외를 사용하지 않으면 호출자 코드가 복잡해진다.

    • 함수 호출 즉시 오류를 확인해야하기 때문이다.
    • 오류처리 코드와 뒤섞인 논리코드는 이해하기 힘들어진다.
  • 예외를 사용하면 호출자 코드가 깔끔해진다.

    • 논리코드와 오류처리 코드가 분리되기 때문이다.
// 예외처리를 사용하지 않은 코드
public class DeviceController {
    ...
    public void sendShutDown() {
        if (handle != DeviceHandle.INVALID) {
            retrieveDeviceRecord(handle);
            if (record.getStatus() != DEVICE_SUSPENDED) {
                pauseDevice(handle);
                clearDeviceWorkQueue(handle);
                closeDevice(handle);
            } else {
                logger.log("Device suspended. Unable to shut down");
            }
        } else {
            logger.log("Invalid handle for: " + DEV1.toString());
        }
    }
    ...
}
// 예외처리를 사용하는 코드
public class DeviceController {
    ...
    public void sendShutDown() {
        try {
        tryToShutDown();
        } catch (DeviceShutDownError 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());
        ...
    }
    ...
}



## try-catch-finally 문부터 작성하라 ##

  • try-catch-finally 블록은 트랜잭션과 비슷하다.
    • try블록에서 무슨일이 일어나든지 오류시에 catch블록이 프로그램 상태를 일관성있게 유지해야 한다.



## 미확인 예외(unchecked exception)을 사용하라 ##

  • 확인 예외(checked exception)란?

    • 확인 예외는 컴파일 단계에서 확인되며 반드시 처리해야 하는 예외입니다.
    • IOException, SQLException 등
  • 미확인 예외(unchecked exception)란?

    • 실행 단계에서 확인되며 명시적인 처리를 강제하지는 않는 예외
    • RunTimeException및 자식 예외클래스
    • NullPointerException, IllegalArgumentException, IndexOutOfBoundException, SystemException
  • 확인된 예외(checked excepton)는 OCP를 위반한다.

    • 예외를 던지는 메소드가 수정되면 관련된 모든 메소드가 수정되어야한다.
    • 새로운 예외가 추가되면 상위 메소드들이 throws 절을 추가해야하며 최종 try-catch 블록이 있는 메소드는 catch에서 새로운 예외를 처리해야한다.
    • 즉, 모듈 사이의 의존성이 증가된다.
    • 의존성에 의한 비용 > 확인된 예외처리로 인한 이익(손해 > 이익)



## 예외에 의미를 제공하라 ##

  • 예외를 발생시킬때는 전후 상황을 충분히 덧붙인다.
    • 원인, 실패한 연산의 이름 및 유형, 그리고 실패 위치를 찾기위한 단서들
    • 오류 메시지에 정보를 담아서 함께 던진다.



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

//외부 라이브러리를 호출할 때 호출자가 모든 오류를 잡아내는 클래스

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

위 코드에서 오류 유형과 상관없이 오류를 잡아내는 방식이 일정하다. reportPortError()메소드로 예외를 넘겨주고 logger로 기록한다. 따라서 간결하게 고치기 쉽다.

아래 코드처럼 라이브러리 API를 감싸면서 예외유형 하나를 반환하면 된다.

LocalPort port = new LocalPort(12);
try {
    port.open();
} catch (PortDeviceFailure e) {
    reportError(e);
    logger.log(e.getMessage(), e);
} finally {
    ...
}

LocalPort 클래스는 ACMEPort 클래스가 던지는 예외를 잡아서 처리하는 Wrapper 클래스일 뿐이다.

실제로 최부 API를 사용할때는 감싸기 기법이 최선이다.

외부 API를 감싸면 아래와 같은 장점이 있다.

+ 외부 라이브러리와 프로그램 사이의 의존성이 크게 줄어든다.
+ 외부 API 설계 방식에 의존하지 않아도 되고 라이브러리를 바꿀 때의 비용도 줄어든다.



## 정상 흐름을 정의하라 ##

  • 클래스나 객체가 예외적인 상황을 캡슐화해 처리하여 클라이언트 코드가 예외적인 상황을 처리할 필요가 없도록 할 수 있다.
try {
    MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
    m_total += expenses.getTotal();
} catch(MealExpencesNotFound e) {
    m_total += getMealPerDiem();
}

직원이 청구한 식비를 총계에 더하는 코드인데, 비용을 청구한 적이 없으면(MealExpenses 객체가 반환되지 않으면) 예외가 발생하고 기본 식비를 더하게 된다.

만약 ExpenseReportDAO 클래스를 고쳐 언제나 MealExpenses 객체를 반환하고, 청구한 식비가 없다면 일일 기본식비만 반환하도록 하면된다.

public class PerDiemMealExpenses implements MealExpenses {
    public int getTotal() {
        // 기본값으로 일일 기본 식비를 반환한다.
    }
}

---------------------------------------------------------------
//이제 아래와 같은 간결한 코드가 가능해진다.
MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
m_total += expenses.getTotal();



## null을 반환하지 마라 ##

  • 한 줄 건너 하나씩 null을 확인하는 코드로 도배하지말아라.
    • null을 반환하면 호출자에게 예외처리를 떠넘긴다.
    • null 확인을 빼먹으면 애플리케이션이 통제불느으에 빠질지도 모른다.
    • null 대신 특수 케이스 객체 혹은 예외를 던져라
//getEmployees()가 null을 반환할 수 있는 코드
List<Employee> employees = getEmployees();
if (employees != null) {
    for(Employee e : employees) {
        totalPay += e.getPay();
    }
}
// getEmployees() 메소드를 직원이 없는경우 빈 리스트를 반환하도록 고친다.
if(..직원이 없다면..) {
    return Collections.emplyList();
}

--------------------------------------------------------
//이제 null 처리가 필요없어진다.
List<Employee> employees = getEmployees();
    for(Employee e : employees) {
        totalPay += e.getPay();
    }



## null을 전달하지 마라 ##

  • null을 리턴하는 것도 나쁘지만 null을 메서드 인수로 넘기는 것은 더 나쁘다.

    • 일반적으로 대다수의 프로그래밍 언어들은 파라미터로 들어온 null에 대해 적절한 방법을 제공하지 못한다.
  • 애초에 null을 넘기지 못하도록 금지하는 정책이 합리적이다.



## 결론 ##

  • 깨끗한 코드는 가독성 뿐만 아니라 안정성도 가져야한다.

  • 프로그램 논리 코드와 오류 처리 코드를 분리하면 가독성도 올라가고 독립성이 커지고 결합도가 낮아지며 유지보수가 쉬워진다.

+ Recent posts