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

MEUN·2022년 3월 1일
0

< CLEAN-CODE />

목록 보기
10/17
post-thumbnail
post-custom-banner

2 주차

목, 금 | Assignment #11

  • 📚 7장. 오류처리
  • ✔️ TIL

7장. 오류 처리


📘 책에서 기억하고 싶은 내용

  • 뭔가 잘못될 가능성은 늘 존재하고, 뭔가 잘못되면 바로 잡을 책임은 바로 우리 프로그래머에게 있다. (p.130)
  • 상당수의 코드는 흩어진 오류 처리 코드에 의해 좌우되어 실제로 코드가 하는 일을 파악하기 힘들어질 수도 있다.
    오류 처리는 중요하지만, 이로 인해 프로그램의 논리를 이해하기 어려워진다면 깨끗한 코드라 부르기 어렵다. (p.130)
  • 오류 코드보다 예외를 사용하라 (p.130)
    • 오류 코드 사용 시 호출자 코드가 복잡해져 예외를 사용하는 것이 좋다. (논리와 오류 처리 코드의 혼재 방지)
  • Try-Catch-Finally 문부터 작성하라 (p.132)
    • try 블록에서 무슨 일이 생기든지 catch 블록은 프로그램 상태를 일관성 있게 유지해야 하기 때문에,
      예외가 발생할 코드를 짤 때는 try-catch-finally 문으로 시작하는 편이 낫다.
      그렇게 하면 try 블록에서 무슨 일이 생기든지 호출자가 기대하는 상태를 정의하기 쉬워진다.
    • 먼저 강제로 예외를 일으키는 테스트 케이스를 작성한 후 테스트를 통과하게 코드를 작성하는 방법을 권장한다.
      그러면 자연스럽게 try 블록의 트랜잭션 범위부터 구현하게 되므로 범위 내 트랜잭션 본질을 유지하기 쉬워진다.
  • 미확인 예외를 사용하라 (p.134)
    • 현재는 안정적인 소프트웨어를 제작하는 요소로 확인된 예외가 반드시 필요하지 않아졌기 때문에 사용할 경우 의존성이라는 비용보다 이익이 더 크지 않다. (아주 중요한 라이브러리를 작성한다면 모든 예외를 잡아야 함)
    • 확인된 예외는 OCP(Open Closed Principle)를 위반한다.
      • 하위 단계의 코드를 변경하면 상위 단계 메서드 선언부를 모두 고치게 된다.
      • 변경한 함수를 호출하는 모든 함수가 catch 블록에서 새로운 예외를 처리하거나, 선언부에 throws 절을 추가해야 한다.
      • throws 경로에 위치하는 모든 함수가 최하위 함수에서 던지는 예외를 알아야 하므로 캡슐화가 깨진다.
  • 예외에 의미를 제공하라 (p.135)
    • 오류 메시지에 정보를 담아 예외와 함께 던진다. (실패한 연산 이름과 실패 유형도 언급)
    • 애플리케이션이 로깅 기능을 사용할 경우, catch 블록에서 오류를 기록하도록 충분한 정보를 넘겨줘야 한다.
  • 호출자를 고려해 예외 클래스를 정의하라 (p.135)
    • 애플리케이션에서 오류를 정의할 때 프로그래머에게 가장 중요한 관심사는 오류를 잡아내는 방법이 되어야 한다.
    • 대다수 오류 처리 방식은 오류를 일으킨 원인과 무관하게 비교적 일정하다.
      • 오류를 기록한다.
      • 프로그램을 계속 수행해도 좋은지 확인한다.
    • 외부 API 사용 시 해당 API의 모든 예외를 호출자에서 처리하지 말고 Wrapper 클래스 를 정의하여 오류 처리 코드까지 함수에 포함시키고, 해당 클래스를 통해 API를 호출하는 것이 좋다.
      public class LocalPort {
      	private ACMEPort innerPort;
          
          public LocalPort(int portNumber) {
          	innerPort = new ACMEPort(portNumber);
          }
          
          public void open() {
          	try {
              	innerPort.open();
              } catch (DeviceResponesException e) {
              	throw new PortDeviceFailure(e);
              } catch (ATM1212UnlockedException e) {
              	throw new PortDeviceFailure(e);
              } catch (GMXError e) {
              	throw new PortDeviceFailure(e);
              }
          }
      }
    • Wrapper 클래스 를 정의하면 특정 업체가 API를 설계한 방식에 발목 잡히지 않는다.
  • 정상 흐름을 정의하라 (p.137)
    • 지침을 충실히 따른다면 비즈니스 논리와 오류 처리가 잘 분리된 코드가 나온다.
    • 하지만, 외부 API를 감싸 독자적인 예외를 던지고 코드 위에 처리기를 정의해 중단된 계산을 처리하는 것이 적합하지 않을 때가 있다.
    • 특수 상황을 처리할 필요가 없을 때는 특수 사례 패턴을 활용한다.
      // 1) 문제가 되는 코드
      try {
        MealExpences expenses = expenseReportDAO.getMeals(employee.getID());
        m_total += expenses.getTotal(); // 식비를 비용으로 청구했다면 직원이 청구한 식비를 총계에 더한다.
      } catch(MealExpensesNotFound e) {
        m_total += getMealPerDiem(); // 식비를 비용으로 청구하지 않았다면 일일 기본 식비를 총계에 더한다.
      }  

      // 2) 해결 (특수 사례 코드 적용)
      MealExpences expenses = expenseReportDAO.getMeals(employee.getID());
      m_total += expenses.getTotal();
      
      // ExpenseReportDAO를 고쳐 언제나 MealExpense 객체를 반환하도록 수정하면 됨.
      // public class PerDiemMealExpenses implements MealExpenses 
    • 특수 사례 패턴(Special Case Pattern) : 클래스를 만들거나 객체를 조작해 특수 사례를 처리하는 방식으로, 클래스나 객체가 예외적인 상황을 캡슐화해서 처리하므로 클라이언트 코드가 예외적인 상황을 처리할 필요가 없어진다.
  • NULL 을 반환하지 마라 (p.138)
    • null 을 반환하게 되면 호출자 코드에 null 을 체크하는 로직이 추가되고, 누락할 경우 오류가 발생하게 된다.
    • null 을 반환하고자 한다면 예외를 던지거나 특수 사례 객체를 반환하는 방안으로 대체한다.
      (예시 - List 가 비었다면 null 대신 Collections.emptyList(); 리턴)
  • NULL 을 전달하지 마라 (p.140)
    • 메소드로 null을 전달하는 것은 메소드에서 null을 반환하는 것보다 더 나쁘다.
    • null 을 매개변수로 받을 경우 InvalidArgumentException 예외를 처리하거나 assert 문을 사용하여 null이 아님을 검증해야 하는데 이는 이전 코드보다 낫지 못하다.
    • 대다수 프로그래밍 언어는 호출자가 실수로 넘기는 null 을 적절히 처리하는 방법이 없으므로 애초에 넘기지 못하도록 금지하는 정책이 합리적이며, 이를 준수하면 부주의한 실수를 저지를 확률도 적어진다.
  • 결론 (p.142)
    • 깨끗한 코드는 가독성도 좋아야 하지만 안정성도 당연히 높아야 한다.
    • 오류 처리를 프로그램 논리와 분리하면 독립적인 추론이 가능해지고, 코드 유지보수성도 크게 높아질 수 있다.

🤔 소감 및 생각

  • 이전에는 null 을 리턴하는 것이 오히려 빈 값을 리턴하는 것보다 명확하다고 느껴졌었는데 이번 챕터를 읽고 난 후 이러한 방식이 얼마나 많은 불필요한 null 체크 로직을 양산시키는지 깨닫게 되었다.

🔍 새롭게 또는 다시 알게 된 내용

  • 특수 사례 패턴 : 클래스를 만들거나 객체를 조작해 특수 사례를 처리하는 방식

post-custom-banner

0개의 댓글