예외 처리(+트랜잭션)

예외 처리(+트랜잭션)

  • 에러와 예외

    • 어떤 원인에 의해 오동작하거나 비정상적으로 종료되는 경우

    • 심각도에 따른 분류

      • Error

        • 메모리 부족, stack overflow와 같이 일단 발생하면 복구할 수 없는 상황

        • 프로그램의 비정상적 종료를 막을 수 없음 -> 디버깅 필요

      • Exception

        • 프로그램 코드에 의해 수습될 수 있는 상황

        • 읽으려는 파일이 없거나 네트워크 연결이 안 되는 등 수습될 수 있는 비교적 상태가 약한 것들

    • Exception Handling

      • 예외 발생 시 프로그램의 비정상 종료를 막고 정상적인 실행 상태를 유지하는 것

      • 예외의 감지 및 예외 발생 시 동작할 코드 작성 필요

    • 예외 클래스의 계층

      • checked exception

        • 예외에 대한 대처 코드가 없으면 컴파일이 진행되지 않음
      • unchecked exception(RuntimeException의 하위 클래스)

        • 예외에 대한 대처 코드가 없더라도 컴파일은 진행 됨

        • 많은 경우 시스템의 RuntimeException은 처리의 대상이기보다는 디버깅의 대상이다

    • try-catch 구문

        public class Sample {
            public void shouldBeRun() {
                System.out.println("ok thanks.");
            }
      
            public static void main(String[] args) {
                Sample sample = new Sample();
                int c;
                try {
                    c = 4 / 0;
                } catch (ArithmeticException e) {
                    c = -1;
                } finally {
                    sample.shouldBeRun();  // 예외에 상관없이 무조건 수행된다.
                }
            }
        }
      
      • try 블록

        • JVM이 해당 Exception 클래스의 객체 생성 후 던짐(throw)

          • throw new Exception()
      • catch 블록

        • 던져진 exception을 처리

        • 여러 개의 catch 블록 추가 가능

          • 순서대로 catch하므로 작은 범위에서 큰 범위순으로 정의
      • finally 블록

        • 예외 발생 여부와 상관 없이 언제나 실행

        • 중간에 return을 만나도 finally 블록을 먼저 수행 후 return 실행

        • try 블록과 catch 블록에 있는 중복 제거 가능

      • try-with-resources

        • try에 자원 객체를 전달하면, try 코드 블록이 끝나면 자동으로 자원을 종료해주는 기능

        • try에 전달할 수 있는 자원은 AutoCloseable 인터페이스의 구현체로 한정된다.

            try (SomeResource resource = getResource()) {
                use(resource);
            } catch(...) {
                ...
            }
          
        • AutoCloseable은 JDK1.7부터 추가된 인터페이스다.

            /**
             * @author Josh Bloch
             * @since 1.7
             */
            public interface AutoCloseable {
                void close() throws Exception;
            }
          
    • Exception 객체의 정보 활용

      • Throwable의 주요 메서드

        • public String getMessage()

          • 발생된 예외에 대한 구체적인 메시지를 반환한다.
        • public Throwable getCause()

          • 예외의 원인이 되는 Throwable 객체 또는 null을 반환한다.
        • public void printStackTrace()

          • 예외가 발생된 메서드가 호출되기까지의 메서드 호출 스택을 출력한다.

          • 디버깅의 수단으로 주로 사용된다.

  • throws 활용

      class FoolException extends Exception {
      }
    
      public class Sample {
          public void sayNick(String nick) {
              try {
                  if("fool".equals(nick)) {
                      throw new FoolException();
                  }
                  System.out.println("당신의 별명은 "+nick+" 입니다.");
              }catch(FoolException e) {
                  System.err.println("FoolException이 발생했습니다.");
              }
          }
    
          public static void main(String[] args) {
              Sample sample = new Sample();
              sample.sayNick("fool");
              sample.sayNick("genious");
          }
      }
    
      class FoolException extends Exception {
      }
    
      public class Sample {
          public void sayNick(String nick) throws FoolException {
              if("fool".equals(nick)) {
                  throw new FoolException();
              }
              System.out.println("당신의 별명은 "+nick+" 입니다.");
          }
    
          public static void main(String[] args) {
              Sample sample = new Sample();
              try {
                  sample.sayNick("fool");
                  sample.sayNick("genious");
              } catch (FoolException e) {
                  System.err.println("FoolException이 발생했습니다.");
              }
          }
      }
    
    • 메서드에서 처리해야 할 하나 이상의 예외를 호출한 곳으로 전달

    • 예외를 전달받은 메서드는 다시 예외 처리의 책임 발생

    • 처리하려는 예외의 조상 타입으로 throws 처리 가능

    • checked exception은 반드시 try-catch 또는 throws 필요

    • runtime exception은 throws 하지 않아도 전달되지만 결국은 try-catch로 처리해야 함

    • API가 제공하는 많은 메서드들은 사전에 예외가 발생할 수 있음을 선언부에 명시하고 프로그래머가 그 예외에 대처하도록 강요함

    • 메서드 재정의 시 조상 클래스 메서드가 던지는 예외보다 부모예외를 던질 수 없다.

  • 사용자 정의 예외

    • 대부분 Exception 또는 RuntimeException 클래스를 상속받아 작성

      • checked exception 활용

        • 명시적 예외 처리 또는 throws 필요

        • 코드는 복잡해지지만 처리 누락 등 오류 발생 가능성은 줄어듦

      • runtime excepton 활용

        • 묵시적 예외 처리 가능

        • 코드가 간결해지지만 예외 처리 누락 가능성 발생

      • 장점

        • 객체의 활용

          • 필요한 추가정보, 기능 활용 가능
        • 코드의 재사용

          • 동일한 상황에서 예외 객체 재사용 가능
        • throws 메커니즘의 이용

          • 중간 호출 단계에서 return 불필요
  • 트랜잭션 (Transaction)

    • 갑자기 "트랜잭션"이라는 용어가 나와서 뜬금 없다고 생각할 수도 있겠지만 트랜잭션과 예외처리는 매우 밀접한 관련이 있다. 트랜잭션과 예외처리가 서로 어떤 관련이 있는지 알아보도록 하자.

      트랜잭션은 하나의 작업 단위를 뜻한다.

      예를들어 쇼핑몰의 "상품발송"이라는 트랜잭션을 가정해 보자. "상품발송" 이라는 트랜잭션에는 다음과 같은 작업들이 있을 수 있다.

      • 포장

      • 영수증 발행

      • 발송

        쇼핑몰의 운영자는 이 3가지 일들 중 하나라도 실패하면 3가지 모두 취소하고 "상품발송" 전의 상태로 되돌리고 싶을 것이다.

        모두 취소하지 않으면 데이터의 정합성이 크게 흔들리게 된다. 이렇게 모두 취소하는 행위를 전문용어로 롤백(Rollback)이라고 말한다.

        상품발송() {
          포장();
          영수증발행();
          발송();
        }
        
        포장() {
         ...
        }
        
        영수증발행() {
         ...
        }
        
        발송() {
         ...
        }
        
    • 쇼핑몰 운영자는 포장, 영수증발행, 발송이라는 세가지 중 1가지라도 실패하면 모두 취소하고 싶어한다. 이런경우 어떻게 예외처리를 하는 것이 좋을까?

    • 다음과 같이 포장, 영수증발행, 발송 메서드에서는 예외를 throw하고 상품발송 메서드에서 throw된 예외를 처리하여 모두 취소하는 것이 완벽한 트랜잭션 처리 방법이다.

        상품발송() {
            try {
                포장();
                영수증발행();
                발송();
            }catch(예외) {
                모두취소();  // 하나라도 실패하면 모두 취소한다.
            }
        }
      
        포장() throws 예외 {
           ...
        }
      
        영수증발행() throws 예외 {
           ...
        }
      
        발송() throws 예외 {
           ...
        }
      
    • 위와 같이 코드를 작성하면 포장, 영수증발행, 발송이라는 세개의 단위작업 중 하나라도 실패할 경우 "예외"가 발생되어 상품발송이 모두 취소 될 것이다.

    • 그런데 다음처럼 "상품발송" 메서드가 아닌 포장, 영수증발행, 발송메서드에 각각 예외처리가 되어 있다고 가정 해 보자.

        상품발송() {
            포장();
            영수증발행();
            발송();
        }
      
        포장(){
            try {
               ...
            }catch(예외) {
               포장취소();
            }
        }
      
        영수증발행() {
            try {
               ...
            }catch(예외) {
               영수증발행취소();
            }
        }
      
        발송() {
            try {
               ...
            }catch(예외) {
               발송취소();
            }
        }
      
    • 이렇게 각각의 메서드에 예외가 처리되어 있다면 포장은 되었는데 발송은 안되고 포장도 안되었는데 발송이 되고 이런 뒤죽 박죽의 상황이 연출될 것이다. 실제 프로젝트에서도 두번째 경우처럼 트랜잭션관리를 잘못하여 고생하는 경우를 많이 보았는데 이것은 일종의 재앙에 가깝다.

  • 참고자료