본문 바로가기

책읽기/클린코드

오류 처리

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

지금은 try / catch 구문을 이용하여 예외를 처리하는 방법을 많이 사용하지만

이전에는 그냥 if 문을 통해 오류코드를 반환하고 처리를 했나보다.

 

try / catch를 이용하면 try에서 호출자 코드를 작성하고 catch에서 오류 코드가 분리되기 때문에 깔끔하다.

 

 

 

 

2. try / catch 구문을 먼저 작성하라.

try 안에 있는 구문은 어느 시점에서든 실행이 중단 된 후 catch 로 넘어갈 수 있다.

catch 블록은 try 블록에서 어떤일이 생기든 프로그램의 일관성을 유지해야한다.

 

따라서 예외가 발생할만한 코드는 try / catch 구문을 써놓고 안을 작성하는 것이 좋다.

 

자연스럽게 try / catch 문 부터 작성하는 방법은

  • 먼저 강제로 예외가 발생하는 코드를 작성한다.
  • 예외 테스트를 작성하고 테스트를 실패한다.
  • 테스트를 통과시키는 코드를 try 문에서 차례로 작성한다.

 

 

 

3. 미확인 예외를 사용하라.

확인된 예외(checked exception) 

  • 컴파일 시 확인하는 예외
  • 예외를 처리하지 않으면 컴파일이 되지 않는다.

미확인 예외(unchecked exception)

  • RuntimeException의 예외들
  • 컴파일시 확인하지 않는다.
  • 런타임 중에 코드구현이 잘못 되었으면 프로그램이 강제 종료

확인된 예외는 OCP를 위배한다.

컴파일시 확인하는 예외이다. 예외처리가 되어 있지 않으면 컴파일 조차 안된다.

 

어떤 메서드에서 확인된 메서드를 던졌다고 생각해보자.

그리고 그 예외를 처리하는 catch문은 그 메서드로부터 세 단계 위에 있다.

그럼 그 사이의 모든 단계에서 선언부에 그 예외를 정의해야 한다.

그 사이의 단계들은 코드가 바뀐게 없다. 하지만 바꿔야 한다. 컴파일이 안되니까.

모두 다시 바꾸고 컴파일을 다시하고 다시 배포해야한다. OCP를 위배하게 된다.

또한 중간의 모든 메서드가 최하단의 예외를 알아야 하므로 캡슐화도 깨진다.

 

따라서 미확인 예외를 쓰도록 한다. 

 

 

 

4. 예외에 의미를 제공하라.

자바의 모든 예외는 호출 스텍을 사용하지만, 이것만으론 정보를 얻기 부족하다.

오류에 메세지를 함께 담아 던진다. 또한 메세지에는 전후 상황과 이유를 충분히 덧붙인다.

 

 

 

 

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

오류는 잡아내는 '방법' 이 중요하다.

 

책을 보면 ACMEPort 라는 외부 API를 사용한다.

이 외부 API는 open() 이라는 메소드를 가지고 있으며, 잡아야 할 예외가 아주 많다.

 

우리가 처리하는 예외는 처리방법이 대부분 일정하다. (오류를 기록하고, 프로그램을 계속 수행하는지 결정한다.)

open() 을 처리하면서 일일이 예외를 잡으면 중복이 발생한다.

 

따라서 외부API를 Wrapper 클래스로 감싸고,

그 안에서 open()을 수행하는 메소드를 만든 후,

거기서 예외를 모두 잡는다.

그리고 사용자 정의 예외 클래스를 만들어 하나의 예외만 반환한다.

 

외부 API 예외 같이 호출자를 고려하여 예외가 처리되는 곳을 wrapper 클래스로 만들고

사용자 정의 예외 클래스로 만들어 주고 반환하면, 다른 라이브러리나 모듈로 갈아탈때도 비용이 줄고 의존성이 적어진다.

또한 코드도 깔끔해지게 된다.

 

 

 

 

6. 정상 흐름을 정의하라.

 try {
     MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
     m_total += expenses.getTotal();
 } catch(MealExpencesNotFound e) {
     m_total += getMealPerDiem();
 }
 
 // PerDiem 은 일당 이라는 뜻이다. 여기서는 그날 그사람의 식비 뭐 그정도 되겠다.

이 코드는 식비를 청구하면 식비를 더하고,

청구하지 않으면 기본 식비를 더하는 코드다. (식비를 청구하지 않으면 getMeals() 는 NotFound되는 상태를 반환한다.)

 

이는 특수 사례 패턴을 이용하면 줄일 수 있다. 

클래스를 만들거나 객체를 조작하여 특수한 사례를 처리한다.

ExpenseReportDAO를 고쳐 언제나 MealExpense 객체를 반환하고, 식비가 없다면 기본식비를 가진 MealExpense 를 반환하면 된다.

 

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

그러면 이렇게 쓸 수 있다.

 MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
 m_total += expenses.getTotal();

 

 

 

7. null을 반환하지 마라.

NullPointerException은 우리가 흔히 보는 예외다.

이 예외는 잡지 않으면 프로그램이 통제불능에 빠질 수 있다.

 

null값의 반환이 있다면,

대신 예외를 던지거나, 특수 사례 객체를 던지도록 한다.

 

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

getEmployees() 는 null 을 반환할 수 있다.

하지만 특수 사례 패턴, 객체를 적절히 조작하면 null을 반환하지 않게 할 수 있다.

 

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

getEmployees() 에서 분기를 통해 null 이 아닌 빈 리스트 객체 (Collections 에서 제공하는 메서드)를 반환한다.

이를 특수 사례 객체라 한다.

 

그러면 아래와 같이 간단히 고칠 수 있다.

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

 

 

 

 

8. null을 전달하지 마라.

인수로 Null 을 전달받아 처리하게 되면 역시 NullPointerException이 발생할 것이다.

 

인수를 사용하는 메서드에서 Null을 검사하고 예외를 던지거나,

Assert 문을 이용하여 처리할 수 있다.

 

하지만 제일 좋은 방법은 Null 을 인수로 전달하지 않게 막는 것이다.

'책읽기 > 클린코드' 카테고리의 다른 글

단위 테스트  (0) 2021.01.14
경계  (0) 2021.01.14
객체와 자료 구조  (0) 2021.01.04
형식 맞추기  (0) 2021.01.03
주석  (0) 2020.12.31