ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 예외처리
    BackEnd/자바 2021. 1. 16. 12:20

    오류와 예외 

    오류(Error)

    시스템레벨에서 발생하는 에러로,  컴퓨터 하드웨어의 오동작 또는 고장으로 인한 이상이나

    JVM 실행에 문제가 생겼을 경우에 발생한다.

    개발자가 미리 예측할 수 없기 때문에 오류에 대한 처리는 하지 않는다.

     

    예외(Exception)

    사용자의 잘못된 조작이나 개발자의 잘못된 코딩으로 발생하는 프로그램 오류이다.

    예외가 발생하면 프로그램이 종료되는 것을 오류와 동일하지만 예외처리를 통해 프로그램이 정상적으로 작동하게 할 수 있다. 

     

    예외 계층 구조

    Exception 에는 Checked  UnChecked Exception 이 있다.

    모든 클래스는 Exception 클래스를 상속받는데, Exception 클래스 자체는 checked exception 이다.


    Checked Exception 은 예외처리를 반드시 해야하는 에러이다.  컴파일 단계 에서 확인할 수 있다. 

    UnChecked Exception 은 에러처리를 안해도 되는 Exception 이다. 실행단계 에서 확인할 수 있다.

     

    메서드를 호출할 때 그 메서드가 어떤 예외를 발생시킬 수 있는지에 대해 반드시 인지하고 있어야한다.

     

    메서드를 호출한 객체가 해당 에러를 다룰 수 있는가 ? 
    YES  사용 객체의 예외 명시를 강제한다.  Checked Exception (I/O Exception, SQLException, FileNotFoundException ..)
    NO  사용 객체의 예외 명시를 생략할 수 있다.  Unchecked Exception ( RunTimeException, IllegalArgumentException, OutOfMemory ..)


    RunTimeException (NullPointException 등) 은 개발자의 코드에 의해서 발생하는 에러로 메서드를 호출하는 측에서 이를 알고 대처할 것이라고 예상하기 어렵고, 어디서나 발생할 수 있는 에러이기 때문에 모든 메서드에 명시하도록 하는 것은 프로그램의 명확성이 떨어질 수 있기때문에 강제하지 않는다.

     

    https://wisdom-and-record.tistory.com/46


    예외 처리 방법

    예외를 처리할 때 반드시 지켜야 할 핵심 원칙은 한가지다. 모든 예외는 적절하게 복구되든지 
    아니면 작업을 중단시키고 운영자 또는 개발자에게 분명하게 통보돼야한다.

    1. 예외 복구 
    - 예외 상황을 파악하고 문제를 해결해 정상 상태로 돌려 놓는다.
    - 예외를 복구할 가능성이 있는 경우에 사용한다. API 를 사용하는 개발자로 하여금 예외상황이 발생할 수 있음을 인식하도록 한다.

    2. 예외 처리 회피 
    - throws 로 선언하거나 catch 로 로그를 남기고 다시 예외를 던진다.
    - jdbctemplate 이 사용하는 콜백 오브젝트 메서드는 SQLException 에 대한 예외를 회피하고 템플릿 레벨에서 처리하도록 던져준다.
    - 콜백/템플릿처럼 긴밀한 관계에 있는 다른 객체, 혹은 사용자가 예외처리 책임을 지게 하는 것이다.  

    try {
      throw new Exception;   // throw 로 에러 발생시키기
    } catch (Exception1 e1) {
        // Exception1이 발생했을 때, 이를 처리하기 위한 코드
    } 
    void method() throws Exception1, Exception2, ... ExceptionN {
    	// 메서드 내용
    }

    throws  메서드에 예외를 선언해 메서드를 호출하는 사람이 예외를 try-catch 나 throws 로 처리할 수 있다.

    하지만 예외처리를 명확하지 않은 이유로 계속 throws 를 하고 이를 잡아주는 코드가 없다면 

    main 메서드까지 갈 수 있고 이는 결국 JVM 이 처리하게 되며 예외처리를 하지 않은 것과 다름이 없어지므로

    남발하지 않아야한다.

     

    3. 예외 전환
    - 예외 회피와 달리 예외를 그대로 넘기는 것이 아니라 다른 적절한 예외로 전환해서 던진다.
    - 어차피 복수가 불가능한 예외라면 가능한 빨리 런타임 예외로 포장해 다른 계층의 메서드를 작성할 때 불필요한 throws 선언이 들어가지 않게 한다.

    예외 전환의 목적 
    1. 내부에서 발생한 예외를 그대로 던지는 것이 그 예외상황에 대한 적절한 의미를 부여해주지 못하는 경우에 의미를 분명하게 해줄 수 있는 예외로 바꿔주기 위해서다.
    예) 아이디가 같은 사용자가 존재해서 DB Insert에 실패한 경우 SQLException 을 DuplicateUserldException 로 바꿔준다.
    보통 전환하는 예외에 원래 발생한 예외를 담아서 중첩 예외(nested exception)로 만드는 것이 좋다. 중첩 예외는 getCause( ) 메소드를 이용해서 처음 발생한 예외가 무엇인지 확인 할 수 있다.

    try{
    	.. do something
    }
    catch(SQLException e){
    	throw DuplicateUserIdException();
    }

    2. 예외를 처리하기 쉽고 단순하게 만들기 위해 포장하는 것.

    주로 예외처리를 강제하는 Checked Exception 를 Unchecked Exception 으로 바꾸는 경우에 사용한다.

    Unchecked Exception 으로 던지면 해당 메소드를 사용하는 곳에서 일일히 에러처리를 할 필요가 없어진다.

    private void process() {
        try {
            // code ...
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    • try-catch-finally
    try {
        // 예외가 발생할 가능성이 있는 코드
    } catch (Exception1 e1) {
        // Exception1이 발생했을 때, 이를 처리하기 위한 코드
    }finally {
        // 오류 여부와 관계없이 무조건 실행
    } 
    • try - finally 로 자원 닫기 
    public static void main(String args[]) throws IOException {
        FileInputStream is = null;
        BufferedInputStream bis = null;
        try {
            is = new FileInputStream("file.txt");
            bis = new BufferedInputStream(is);
            int data = -1;
            while((data = bis.read()) != -1){
                System.out.print((char) data);
            }
        } finally {
            // close resources
            if (is != null) is.close();
            if (bis != null) bis.close();
        }
    }
    •  try- with-resources 로 자원 닫기 
    public static void main(String args[]) {
        try (
            FileInputStream is = new FileInputStream("file.txt");
            BufferedInputStream bis = new BufferedInputStream(is)
        ) {
            int data = -1;
            while ((data = bis.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    try () 안에 선언된 객체의 close() 메서드를 자동으로 호출한다.

    다만 모든 객체의 close 를 호출하는 것은 아니고 AutoClosable 이라는 인터페이스를 구현한 객체일 때만 해당된다.

    public abstract class InputStream extends Object implements Closeable {
      ....
    }
    
    public interface Closeable extends AutoCloseable {
        void close() throws IOException;
    }

    커스텀예외만들기

    1. 자바 표준 예외에는 이미 다양한 장점을 가지는 기능들이 있는데, 이미 제공되는 많은 예외들과 비교해서 

    커스텀 예외가 제공하는 장점이 적다면 만드는 이유를 다시 생각해야한다.

    2. 자바 예외 클래스들의 이름은 모두 "Exception" 으로 끝난다. 커스텀 예외 클래스도 이런 네이밍 규칙을 따르자.

    3. 다른 개발자들이 이해할 수 있도록 API 를 문서화해라.

    4. 커스텀 예외를 던지기 전 표준 예외를 catch 하는 케이스가 많은 것을 간과하지 말라.

     

    Exception 과 RunTimeException 은 예외원인을 기술하고 있는 Throwable 을 받을 수 있는 생성자 메소드를 제공한다.

    커스텀 메서드로 이렇게 하는 것이 좋다. 발생한 Throwable 를 파라미터를 통해 가져올 수 있는 생성자를 최소한 하나를 구현하고 슈퍼클래스에 Throwable을 전달해줘야 한다. 

    public class MyBusinessException extends Exception {
        public MyBusinessException(String message, Throwable cause, ErrorCode code) {
                super(message, cause);
                this.code = code;
            }
            ...
    }
    public void wrapException(String input) throws MyBusinessException {
        try {
            // do something
        } catch (NumberFormatException e) {
            throw new MyBusinessException("A message that describes the error.", e, ErrorCode.INVALID_PORT_CONFIGURATION);
        }
    }

    Checked 커스텀 예외를 구현하기 위해  Exception 클래스를 반드시 상속받아야한다.
    UnChecked 커스텀 예외구현에는  RunTimeException 클래스를 반드시 상속받아야한다는 점만 다르다.

     

    /**
     * The MyBusinessException wraps all checked standard Java exception and enriches them with a custom error code.
     * You can use this code to retrieve localized error messages and to link to our online documentation.
     * 
     * @author TJanssen
     */
    public class MyBusinessException extends Exception {
        private static final long serialVersionUID = 7718828512143293558 L;
        private final ErrorCode code;
        public MyBusinessException(ErrorCode code) {
            super();
            this.code = code;
        }
        public MyBusinessException(String message, Throwable cause, ErrorCode code) {
            super(message, cause);
            this.code = code;
        }
        public MyBusinessException(String message, ErrorCode code) {
            super(message);
            this.code = code;
        }
        public MyBusinessException(Throwable cause, ErrorCode code) {
            super(cause);
            this.code = code;
        }
        public ErrorCode getCode() {
            return this.code;
        }
    }
    public void handleExceptionInOneBlock() {
        try {
            wrapException(new String("99999999"));
        } catch (MyBusinessException e) {
            // handle exception
            log.error(e);
        }
    }
    private void wrapException(String input) throws MyBusinessException {
        try {
            // do something
        } catch (NumberFormatException e) {
            throw new MyBusinessException("A message that describes the error.", e, ErrorCode.INVALID_PORT_CONFIGURATION);
        }
    }

    예외처리비용

    Throwable 생성자의 fillInStackTrace() 메소드는 예외가 발생했을 때 Stack Trace 를 모두 출력해주는데, 

    커스텀 예외에서 이를 오버라이딩해 스택 트레이스를 최소화 해줄 수도 있다.

    @Override
    public synchronized Throwable fillInStackTrace() {
        return this;
    }

     


    programming.guide/java/list-of-java-exceptions.html
    stackoverflow.com/questions/7561550/list-of-spring-runtime-exceptions

    https://leegicheol.github.io/whiteship-live-study/whiteship-live-study-09-exception-handling/
    https://www.notion.so/3565a9689f714638af34125cbb8abbe8
    wisdom-and-record.tistory.com/46
    java119.tistory.com/44

     

    반응형
Designed by Tistory.