Skip to main content

김주엽

1. 📌 핵심 개념 정리

✅ 요약하기

  1. 오류 코드보다 예외를 사용하라
    알고리즘과 오류를 처리하는 알고리즘을 분리하기 위해 예외를 사용하라

    • 개선 전

      public void shutDown() {
        DeviceHandle handle - getHandle(DEV1);
      
        if (handle != DeviceHandle.INVALID) {
          ...
        }
      }
      
    • 개선 후

      public void shutDown() {
        try {
          tryToShutDown();
        } catch(DeviceShutDownError e) {
          logger.log(e);
        }
      }
      

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

    1. try-catch-finally 문에서 try 블록에 들어가는 코드를 실행하면 어느 시점에서든 실행이 중단된 후 catch 블록으로 넘어갈 수 있다.
    2. catch 블록은 프로그램의 상태를 일관성 있게 유지해야 한다.
    3. 예외가 발생할 코드를 작성할 때는 try-catch-finally 문을 먼저 작성하는 것이 좋다.
    4. 강제로 예외를 일으키는 테스트 케이스를 작성하고 테스트를 통과하게 하는 코드를 작성하는 방법을 권장한다.
    • 개선 전

      @Test(expected = StorageException.class)
      public void retrieveSectionShouldThrowOnInvalidFileName() {
        sectionStore.retrieveSection("invalid - file");
      }
      
      public List<RecordedGrip> retrieveSection(String sectionName) {
        return new ArrayList<RecordedGrip>();
      }
      

      이 경우 예외를 던지지 않기에 파일이 없으면 항상 테스트를 실패할 가능성이 있다.

    • 개선 후

      public List<RecordedGrip> retrieveSection(String sectionName) {
        try {
          FileInputStream stream = new FileInputStream(sectionName);
          stream.close();
        } catch (FileNotFoundException e) {
          throw new StorageException("retrieval error", e);
        }
        return new ArrayList<RecordedGrip>();
      }
      

      코드 개선 후 파일이 없으면 예외를 항상 던지기에 프로그램의 상태를 유지할 수 있다.


  1. 미확인 예외를 사용하라
    • 확인된 예외는 OCP(Open Closed Principle) 원칙을 위반할 수 있다.
      • 한 메서드에서 확인된 예외를 던졌을 때 catch 블록은 메서드를 호출하는 블록 세 단계 위에 있다면
        그 사이에 있는 모든 메서드의 해당 예외를 다시 정의해야 한다.
    • 미확인 예외 예시
      • NullPointerException, RuntimeException, ArithmeticException, IndexOutOfBoundsException 등

  1. 예외에 의미를 제공하라
    • 자바는 모든 예외에 호출 스택을 제공하지만 오류에 대한 파악을 위해서는 스택만으로 부족하다.
    • 오류 메시지에 정보를 담아 예외와 함께 던져라.
    • 실패한 연산 이름, 유형, 로깅 등을 활용해서 충분한 정보를 제공해라.

  1. 호출자를 고려해 예외 클래스를 정의하라
    오류를 정의할 때 프로그래머에게 가장 중요한 것은 오류를 잡아내는 방법이 되어야 한다.

    • 개선 전

      ACMEPort port = new ACMEPort(12);
      
      try{
          port.open();
      } catch (DeviceResponseException e) {
            reportPortError(e);
      } catch (ATM1212UnlockedException e) {
            reportPortError(e);
      } catch (GMXError e) {
            reportPortError(e);
      } finally {
        ...
      }
      

      위 코드는 대부분의 프로그래머가 작성하는 방식으로 중복이 심하다.

    • 개선 후

      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);
          }
        }
      
        ...
      }
      
      LocalPort port = new LocalPort(12);
      try {
        port.open();
      } catch (PortDeviceFailure e) {
        reportError(e);
        logger.log(e.getMessage(), e);
      } finally {
        ...
      }
      

      예외에 대한 처리를 wrapper 클래스로 감싸고 예외 유형을 하나로 통일시켜 반환했다.
      이는 외부 API를 사용할 때 감싸기 기법을 사용하면 매우 효과적이다.


  1. null을 반환하지 마라

    1. null을 반환하는 코드는 일을 늘릴 뿐만 아니라 호출자에게 문제를 떠넘긴다.
    2. 누구 하나라도 null 체크를 빼먹는다면 어플리케이션이 망가질 위험이 크다.
    3. 메서드에서 null을 반환하고 싶은 생각이 든다면 예외를 던지거나 특수 사례 객체를 반환해라.
    • 개선 전

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

      List<Employee> employees = getEmployees();
      for(Employee e : employees) {
          totalPay += e.getPay();
      }
      
      public List<Employee> getEmployees() {
          if ( employees.isEmpty() ) {
         	 return Collections.emptyList();
          }
      }
      

      코드 개선 후 빈 리스트를 반환하여 NullPointerException이 발생할 가능성을 감소시켰다.


  1. null을 전달하지 마라
    • 인수로 null을 전달하면 당연히 NullPointerException이 발생한다.
    • 예외를 만들어 던지는 방법도 있지만 assert 문을 사용하는 방법도 있다.
      • 예시
        public class MetricsCalculator {
            public double xProjection(Point p1, Point p2) {
                assert p1 != null : "p1 shoud not be null";
                assert p2 != null : "p2 shoud not be null";
                return (p2.x - p1.x) * 1.5;
            }
        }
        
    • 애초에 null을 인수로 넘기지 못하도록 금지하는 방법이 바람직하다.

2. 🤔 이해가 어려운 부분

🔍 질문하기

  1. 미확인 예외를 사용하라
    • 어려웠던 부분
      확인된 예외와 미확인 예외가 정확히 무엇인지 몰라서 이해하는데 어려웠다.
    • 이해한 점
      • 확인된 예외(Checked Exception)
        • RuntimeException을 상속하지 않는 예외
        • 예외를 처리하지 않으면 컴파일 에러가 발생함
        • 반드시 try-catch문으로 처리하거나 throws키워드로 호출한 곳에서 처리하도록 선언해야함.
        • IOException, SQLException, FileNotFoundException, InterruptedException
      • 미확인 예외(Unchecked Exception)
        • RuntimeExecption을 상속받는 예외
        • 예외 처리를 하지 않아도 컴파일 에러가 발생하지 않음
        • 실행 중에 오류가 발생할 수 있음
        • 잘못된 코드 로직으로 인해서 발생하는 경우가 대다수임
          • null값에 접근하는 경우
          • 잘못된 배열 인덱스를 접근하는 경우
          • 메서드에 잘못된 인수를 전달한 경우
        • NullPointerException, ArrayIndexOutOfBoundsException, ArithmeticException, IllegalArgumentException, NumberFormatException

3. 📚 참고 사항

📢 논의하기

  1. 관련 자료 공유

  1. Try-Catch-Finally 문부터 작성하라
    • 주제
      예외 처리를 할 때 try-catch 문을 사용하여 직접 처리하는 방법과 throw 키워드를 사용하여 예외를 상위로 전달하는 방법이 있습니다. 각각 어떤 상황에서 사용하는 것이 적절할까요?