Exceptions

man soup·2020년 7월 2일
0

자바 문법

목록 보기
9/15

Summary

  • 프로그램은 예외를 에러가 발생함을 알려주는 용도로 사용할 수 있다.
  • 예외를 던지기 위해 throw statement를 발생한 에러의 정보를 알려줄 수 있는 예외 객체(Throwable의 자식)와 함께 사용한다.
  • 잡히지않은 checked 에러를 던지는 메소드는 throws 구를 선언부에 꼭 넣어야 한다.
  • 프로그램은 에외를 try,catch,finally 블록을 통해 잡을 수 있다.
    • try 블록은 어떤 예외가 발생할 수 있는 코드블록이다.
    • catch 블록은 예외 핸들러라고 불리는 코드블록으로 특정 타입의 예외를 처리할 수 있다.
    • finally 블록은 실행됨을 보장받는 코드블록으로, 파일을 닫거나 리소스 회복, try블록의 코드들의 clean up을 하는 곳으로 사용된다.
  • try statement는 최소한 1개 이상의 catch 블록 또는 finally 블록이 존재해야한다. ( 여러개의 catch 블록 가능)
  • 예외객체의 클래스는 어떤 예외 타입이 던져졌는지를 알려준다.
  • 예외 객체는 에러에 대한 정보와 에러 메세지를 포함 할 수 있다.
  • Exception chaining을 통해 예외는 그 예외를 발생시킨 것을 가르킬 수 있다.

What Is an Exception?

  • exception이란 예외적인 이벤트의 줄임말이다
  • 정의 : 예외란 프로그램 실행중 발생하는 프로그램 인스트럭션의 기본 흐름을 방해하는 이벤트이다.
  • 메소드안에서 에러가 발생하면, 메소드는 객체를 생성하고 런타임 시스템에게 준다.
  • 이 객체는 예외 객체라고 불리고 에러의 타입, 에러 발생시의 프로그램 상태의 정보를 포함하고 있다.
  • 예외 객체를 생성하고 런타임 시스템에게 주는 것을 throwing an exception이라고 한다.
  • 메소드가 예외를 throw하면 런타임 시스템은 그것을 처리할 무언가를 찾기를 시도한다.
  • 예외를 처리할 무언가들의 집합은 에러가 발생한 메소드를 호출했던 메소드들의 정렬된 리스트이다.
  • 이 메소드들의 리스트는 call stack이라고 부른다.
  • 런타임 시스템은 콜스택을 검색해 예외를 처리할 수 있는 코드 블록을 가진 메소드를 찾는다.
  • 이 코드블럭을 exception handler라고 한다.
  • 검색은 에러가 발생된 메소드부터 시작해 메소드 호출의 반대순서로 진행된다.
  • 적절한 핸들러가 발견되면 런타임 시스템은 예외를 핸들러에게 전달한다.
  • 예외 핸들러가 처리할 수 있는 타입과 thrown된 예외 객체의 타입과 맞는 경우 적절하다고 여겨진다.
  • 예외 핸들러가 선택된 것을 catch the exception이라고 한다.
  • 만약 런타임 시스템이 콜스택의 모든 메소드를 검색해도 적절한 예외 핸들러를 찾지 못하면 런타임 시스템은 종료된다. -> 프로그램 종료
  • 예외를 통해 에러를 관리하면 전통적인 에러 관리 기술보다 많은 장점이 존재한다.

The Catch or Specify Requirement

  • 유효한 Java 프로그래밍 언어 코드는 Catch or Specify Requirement을 준수해야한다.
  • 코드가 특정 예외를 throw할 경우 다음 중 하나로 감싸야한다.
    • 예외를 잡는 try statement로 try는 항상 예외를 처리할 수 있는 핸들러를 제공해야한다.
    • 예외를 throw할 수 있다고 특정지은 메소드. 메소드는 예외들의 리스트를 throws 구를 통해 제공해야한다.
  • Catch or Specify Requirement를 준수하지 않은 코드는 컴파일되지 않는다.
  • 모든 예외가 Catch or Specify Requirement의 대상이 아니다.
  • 이유를 이해하기위해 3가지 기본 종류의 예외를 알아본다.
  • 이 중 한가지만 Catch or Specify Requirement의 대상

The Three Kinds of Exceptions

첫 번째 예외 : checked exception

  • 이것은 잘 작성된 애플리케이션이 예상하고 복구해야하는 예외적인 조건이다.
  • 예를 들어 어떤 어플에서 유저가 입력 파일 이름을 java.io.FileReader의 생성자에게 전달해 파일을 open한다고 하자.
  • 보통 유저는 존재하고 읽을 수 있는 파일이름을 제공해 filereader 객체 생성에 성공 후 어플은 정상적으로 작동할 것이다.
  • 그러나 가끔은 유저가 존재하지않는 파일 이름을 제공해 생성자가java.io.FileNotFoundException를 throw한다.
  • 잘 만든 어플은 이 예외를 잡고 유저의 실수를 알릴 것이고 올바른 파일 이름을 묻는 메세지를 표시할 것이다.
  • Checked exceptions는 CSR의 대상이다.

두번째 예외 : error

  • 응용 프로그램의 외부적인 예외 상황이고 응용은 이 상황을 복구하거나 예상 할 수 없다.
  • 예를 들어 응용이 성공적으로 입력을 위해 파일을 열었지만 하드웨어나 시스템의 오작동으로 파일을 읽을 수 없는 경우를 생각해보자.
  • 읽기 실패는 java.io.IOError를 throw한다.
  • 응용은 유저에게 문제를 알리기 위해 이 예외를 잡을 수도 있지만 stack trace를 출력하고 종료하는 것도 가능하다.
  • 에러는 Catch or Specify Requirement의 대상이 아니다. 에러는 Error 또는 Error의 서브 클래스로 표시되는 예외이다.

세번째 예외 : runtime exception

  • 응용의 내부적인 예외 상황이고 응용은 보통 이것으로부터 복구 및 예상할 수 없다.
  • 이 예외는 보통 로직 에러 또는 API의 부적절한 사용같은 프로그래밍 버그들을 나타낸다.
  • 예를 들어 첫번째 예인 상황에서 로직 에러가 FILEReader 생성자에게 null을 발생시키면 생성자는 NullPointerException를 throw한다.
  • 응용은 이 예외를 잡을 수 있지만 예외를 발생시키는 에러를 제거하는 것이 더 적절하다.
  • 런타임 예외는 CSR의 대상이 아니다.
  • RuntimeException과 RuntimeException의 SUBCLASS로 표시된다.
  • Error 와 runtime exception은 unchecked exception이라고도 불린다.

Bypassing Catch or Specify

  • 몇몇의 프로그래머들은 Catch or Specify Requirement를 예외 메커니즘에서 심각한 흠이라 여기고 checked exceptions를 unchecked exceptions를 사용해 넘어간다.
  • 보통 추천하는 방식 아니다.

Catching and Handling Exceptions

  • 이번 장에서 3가지 예외 핸들러 컴포넌트를 어떻게 사용하는지 설명
    • try, catch, finally 블록
public class ListOfNumbers {

    private List<Integer> list;
    private static final int SIZE = 10;

    public ListOfNumbers () {
        list = new ArrayList<Integer>(SIZE);
        for (int i = 0; i < SIZE; i++) {
            list.add(new Integer(i));
        }
    }

    public void writeList() {
	// The FileWriter constructor throws IOException, which must be caught.
        PrintWriter out = new PrintWriter(new FileWriter("OutFile.txt"));

        for (int i = 0; i < SIZE; i++) {
            // The get(int) method throws IndexOutOfBoundsException, which must be caught.
            out.println("Value at: " + i + " = " + list.get(i));
        }
        out.close();
    }
}
  • new FileWriter("OutFile.txt")가 생성자를 호출하는 부분이다.
  • 생성자는 파일 출력 스트림을 초기화한다.
  • 만약 파일이 열릴 수 없다면 생성자는 IOException을 throw한다.
  • list.get(i)는 아규먼트의 값이 0보다 작거나 size보다 크면 IndexOutOfBoundsException를 throw한다.
  • 이 클래스를 컴파일하려고 시도하면 컴파일러는 FileWriter 생성자에 의해서 throw되는 예외에대한 에러 메세지를 출력할 것이다.
  • 그러나 get에 의한 예외에 대한 에러 메세지는 보여지지 않는다.
  • IOException은 checked exception이고 인덱스~는 unchecked exception이기 때문

The try Block

  • 예외 핸들러 생성에 첫번째 단계는 예외를 발생시킬 수 있는 코드부분을 try 블록으로 감싸는 것이다.
try {
    code
}
catch and finally blocks . . .
  • ListOfNumbers 클래스의 writeList메소드를 위한 예외 핸들러를 생성하기 위해
    메소드의 예외 발생 부분을 try block으로 감싼다.
  • 하나의 try 블록으로 예외를 발생시키는 여러 부분을 한번에 감싼 후 여러 핸들러로 처리하던가 각각을 try 블록으로 따로 감싸는 방법이 존재
public void writeList() {
    PrintWriter out = null;
    try {
        System.out.println("Entered try statement");
        out = new PrintWriter(new FileWriter("OutFile.txt"));
        for (int i = 0; i < SIZE; i++) {
            out.println("Value at: " + i + " = " + list.get(i));
        }
    }
    catch and finally blocks  . . .
}
  • 위 예제에선 두개의 예외발생가능 부분을 하나의 try 블록으로 감싼다.
  • 만약 예외가 try 블록에서 발생하면 그 예외와 연결된 예외 핸들러가 처리한다.
  • 예외 핸들러를 try 블록과 연결시키기 위해 꼭 catch 블록을 사용해야한다.

The catch Blocks

try {

} catch (ExceptionType name) {

} catch (ExceptionType name) {

}
  • 하나 이상의 catch블록을 try 블록 바로 뒤에 제공해 예외 핸들러를 try블록과 연결시킨다.
  • 첫 catch 블록과 try 블록 사이에 어떤 코드도 들어갈 수 없다.
  • 각 catch 블록은 예외 핸들러로 처리할 예외의 타입을 아규먼트로 가르킨다.
  • 아규먼트 타입인 ExceptionType는 핸들러가 처리할 수 있는 예외의 타입을 선언한다.
    • 이 타입은 Throwable 클래스를 상속한 클래스인 예외 타입이여야 한다.
  • 핸들러는 name을 통해 예외에 접근할 수 있다.
  • catch 블록은 예외 핸들러가 호출되면 실행될 코드를 포함한다.
  • 런타임 시스템은 발생된 예외와 맞는(match) ExceptionType을 가진 call stack의 첫번째 예외 핸들러를 호출한다.
  • 시스템은 발생된 에러 객체가 예외 핸들러의 아규먼트로 할당할 수 있는 경우 match한다고 여긴다.
try {

} catch (IndexOutOfBoundsException e) {
    System.err.println("IndexOutOfBoundsException: " + e.getMessage());
} catch (IOException e) {
    System.err.println("Caught IOException: " + e.getMessage());
}
  • 예외 핸들러들은 에러 메세지를 출력할 뿐아니라 프로그램을 정지시킬 수 있다.
  • 에러 회복, 유저에게 결정하게 하기, chained exception을 사용해 높은 레벨의 핸들러에게 에러 전달하기를 할 수 있다.

Catching More Than One Type of Exception with One Exception Handler

  • 자바 SE7 이후부터 하나의 catch 블록은 하나 이상의 예외 타입을 처리할 수 있게됐다.
  • 이 특징은 코드 복제를 줄여주고 지나치게 넓은 예외를 잡으려는 유혹을 줄여준다.
  • catch 구에서 블록이 처리할 수 있는 예외의 타입들을 특정짓고 각각의 예외 타입을 | 로 구분한다.
  • 만약 catch 블록이 하나 이상의 예외 타입을 처리하면 catch 파라미터는 암시적으로 final이다.
catch (IOException|SQLException ex) {
    logger.log(ex);
    throw ex;
}
  • 이 예제에서 ex 파라미터는 final이고 그러므로 블록 안에서 어떠한 값도 할당할 수 없다.

The finally Block

  • finally 블록은 항상 try 블록이 끝나면 실행된다.
  • 이것은 finally 블록이 예상치 못한 예외가 발생해도 실행될 것을 보증한다.
  • 그러나 finally는 예외 처리에만 유용한 것이 아니다.
    • return, continue, break으로 인한 의도하지 않은 cleanup code bypass를 피하게 해준다.
    • 예외가 없어도 finally 블록안에 cleanup code를 넣는 것은 언제나 좋은 습관이다.
  • 만약 JVM이 try나 catch 코드를 실행중에 종료되면 finally블록이 실행되지 않을 수 있다.
  • 마찬가지로 만약 try 또는 catch 코드를 실행중인 쓰레드가 interrupt 또는 kill 될 경우 finally블록은 실행되지 않을 수 있다.
  • writeList 메소드의 try 블록은 PrintWriter를 연다.
  • 프로그램은 이 메소드를 나가기 전에 스트림을 닫아야한다.
  • 이것은 복잡한 문제를 만드는데 왜냐하면 try 블록은 다음의 3가지 경우에 종료될 수 있기 때문이다.
    • new FileWriter statement가 실패하고 IOException을 발생시킬 경우
    • list.get 문이 실패하고 IndexOutOfBoundsException 발생시키는 경우
    • 모든게 성공하고 try 블록을 정상적으로 나갈 경우
  • 런타임 시스템은 항상 finally 블록을 try 블록에서 발생한 일과 상관없이 실행하므로 cleanup하기 완벽한 장소이다.
finally {
    if (out != null) { 
        System.out.println("Closing PrintWriter");
        out.close(); 
    } else { 
        System.out.println("PrintWriter not open");
    } 
} 
  • finally 블록은 자원 유출의 핵심 도구이다.
  • 파일을 닫거나 자원을 회복할때 finally 블록안에 코드를 위치시키므로서 자원 recover을 보장하자

The try-with-resources Statement

  • The try-with-resources Statement란 하나 이상의 resource를 선언한 try statement이다.
  • resource란 프로그램이 끝난 후 무조건 closed되야하는 객체이다.
  • The try-with-resources Statement는 각각의 리소스가 statement의 마지막 에서 닫아지는 것을 보장한다.
  • java.io.Closeable를 구현한 객체들을 모두 포함하는 java.lang.AutoCloseable을 구현한 객체 중 아무 객체나 리소스로 사용될 수 있다.
static String readFirstLineFromFile(String path) throws IOException {
    try (BufferedReader br =
                   new BufferedReader(new FileReader(path))) {
        return br.readLine();
    }
}
  • try statement안에 선언됐기 때문에 BufferedReader 객체는 try statement가 정상적으로 종료됨과 상관없이 무조건 닫힌다.
    • BufferedReader.readLine이 IOException을 throw한 결과이여도
  • java SE 7 전까진 , finally 블록을 통해 리소스의 닫음을 보장했다.
static String readFirstLineFromFileWithFinallyBlock(String path)
                                                     throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();
    } finally {
        if (br != null) br.close();
    }
}
  • 만약 readLine 과 close 두곳에서 예외가 발생할 경우 readFirstLineFromFileWithFinallyBlock 메소드는 try블록의 예외는 억제하고 finally 블록의 예외를 던진다.
  • readFirstLineFromFile의 예제에서는 try블록과 try-with-resources구문 동시에 발생하면 try블록의 예제가 던저진다.
  • 자바 SE 7 버전 이후부터 억제된 예외를 얻을 수 있다.
  • try-with-resources구문에 여러개의 리소스를 선언할 수 있다.
    • close 순서는 선언의 반대 순서로
  • try-with-resources에서 catch 나 finally 블록은 선언된 리소스가 닫힌 이후 실행된다.

Suppressed Exceptions

  • Throwable.getSuppressed 메소드를 통해 억제된 예외를 얻을 수 있다.

Classes That Implement the AutoCloseable or Closeable Interface

  • Closeable 인터페이스는 AutoCloseable 인터페이스를 extend 한다.
  • Closeable의 close 메소드는 IOException 타입의 예외를 턴지고 AutoCloseable의 close 메소드는 Exception 타입의 예외를 던진다.
  • 결과적으로 AutoCloseable 인터페이스의 subclass들은 close 메소드의 행동을
    override해 IOException과 같은 특정 예외를 던지거나 아예 안 던질 수 있다.

Specifying the Exceptions Thrown by a Method

  • 어떤 경우엔 예외가 발생하는 곳에서 예외를 catch하는 것이 적절한 경우가 있다.
  • 하지만 종종 더 높은 콜스택에 존재하는 메소드에게 예외를 처리하도록 맡기는게 좋은 경우도 있다.
  • 예를들어 당신이 ListOfNumbers 클래스를 클래스들의 패키지 부분으로 제공할 경우, 당신은 당신의 패키지 사용자들의 모든 욕구를 충족 시킬 수 없을 수 있다.
  • 이러한 경우 예외를 catch하지 않고 더 높은 콜스택에 존재하는 메소드에게 예외를 처리하도록 맡기는게 좋다.
  • throws 키워드를 메소드 뒤에 붙여서 사용
  • checked 예외는 catch하지 않으면 필수로 throws 해야하지만 unchecked는 필수 아님
  • 여러개를 throws할 수 있다.

How to Throw Exceptions

  • 어떤 예외가 발생하던지 throw statement를 통해 예외를 발생시킴
  • 자바 플랫폼은 여러개의 예외 클래스들을 제공한다.
    • 모든 클래스들은 Throwable 클래스의 자식들이고, 프로그램은 프로그램이 실행중 발생된 예외의 여러 타입들을 구분할 수 있다.
  • 당신만의 예외 클래스를 만들어 당신이 작성한 클래스에서 발생할 수 있는 문제를 보여줄 수 있다.
  • 사실 당신이 패키지 개발자라면 당신의 패키지에서 발생한 에러와 자바 플랫폼 또는 다른 패키지에서 발생한 에러를 사용자가 구분할 수 있도록 자신만의 예외 클래스를 생성해야할 수도 있다.

The throw Statement

  • 모든 메소드는 throw statement를 사용해 예외를 발생시킴
  • throw statement는 하나의 아규먼트가 필요하다.
    • throwable 객체
  • throwable 객체들은 Throwable 클래스의 어떤 subclass들의 인스턴스이다.

Throwable Class and Its Subclasses

  • Throwable 클래스를 상속한 객체란 직접 자손(부모가 Throwable) 또는 간접 자손(부모가 Throwable의 자손)을 포함한다.
  • 2개의 직접 자손이 존재한다 : Error & Exception

Error Class

  • JVM에서 동적 링킹이 실패하거나 다른 hard failure가 발생하면 가상머신은 Error를 던진다.
  • 프로그램들은 보통 Error들을 catch 하거나 throw하지 않는다.

Exception Class

  • 대부분의 프로그램들은 Exception 클래스로부터 발생된 객체를 throw하거나 catch한다.
  • Exception은 문제가 발생했지만 심각한 시스템 문제는 아니라는 것을 알려주는 것이다.
  • 당신이 작성한 대부분의 프로그램들은 Exception들을 잡거나 던질 것이다. (Error와 다르게)
  • 자바 플랫폼은 Exception 클래스의 많은 자손들을 정의한다.
  • 이 자손들은 여러 종류의 발생할 수 있는 예외 타입를 가르킨다.
  • 예를 들어 IllegalAccessException는 특정 메소드를 찾을 수 없다는 것을 알리는 것이고 NegativeArraySizeException은 프로그램이 음수 크기의 배열을 생성하려 시도한다는 것을 가리킨다.
  • Exception의 subclass중 하나인 RuntimeException은 API의 잘못된 사용을 가리키는 예외들을 위해 준비된다.

Chained Exceptions

  • 응용은 종종 다른 예외를 던지는 것으로 예외에 대응한다.
  • 사실 첫번째 예외가 두번째 예외를 발생시킨 것이다.
  • 어느 경우에 한 예외가 다른 예외를 발생시키는지 아는 것은 매우 유용하다.
  • Chained Exceptions는 프로그래머가 알 수 있게 도와준다.
Throwable getCause()
Throwable initCause(Throwable)
Throwable(String, Throwable)
Throwable(Throwable)
  • Throwable의 chained exception을 지원하는 메소드와 생성자들
  • initCause메소드와 생성자의 Throwable 아규먼트는 현재 예외를 발생시킨 예외이다.
  • getCause는 현재 예외를 발생시킨 예외를 리턴하고 initCause를 현재 예외의 원인인 예외로 set한다.
try {

} catch (IOException e) {
    throw new SampleException("Other IOException", e);
}
  • IOException이 잡히면 새로운 SampleException 예외가 original cause에 붙은상태로 생성되고 다음 높은 레벨의 예외 핸들러에게 thrown up 된다.

Accessing Stack Trace Information

  • 더 높은 레벨의 예외 핸들러가 자신만의 형식으로 stack trace를 dump하고 싶은 상황 가정
    • stack trace는 현재 스레드의 실행 히스토리 정보와 예외가 발생했을 당시 호출된 클래스와 메소드의 이름들의 정보를 제공한다.
    • stack trace는 유용한 디버깅 도구로 예외가 발생시 사용하기 좋다.
catch (Exception cause) {
    StackTraceElement elements[] = cause.getStackTrace();
    for (int i = 0, n = elements.length; i < n; i++) {       
        System.err.println(elements[i].getFileName()
            + ":" + elements[i].getLineNumber() 
            + ">> "
            + elements[i].getMethodName() + "()");
    }
}

Logging API

try {
    Handler handler = new FileHandler("OutFile.log");
    Logger.getLogger("").addHandler(handler);
    
} catch (IOException e) {
    Logger logger = Logger.getLogger("package.name"); 
    StackTraceElement elements[] = e.getStackTrace();
    for (int i = 0, n = elements.length; i < n; i++) {
        logger.log(Level.WARNING, elements[i].getMethodName());
    }
}
  • catch 블록에서 발생한 예외의 로그를 snippet함
  • 그러나 System.err메소드로 출력한것과 다르게, 출력을 logging facility를 사용해 파일에 보낸다.

Creating Exception Classes

  • throw할 예외의 타입을 골라야하는 상황에 당신이 자신만의 타입을 작성하거나 자바 플랫폼이 제공하는 많은 예외 클래스를 사용할 수 있다.
  • 다음의 경우에 해당하면 자신이 예외 클래스를 작성해 사용해야한다.
    • 자바 플랫폼이 제공하지 않는 예외 타입이 필요할 경우
    • 당신의 예외를 다른 벤더가 작성항 클래스가 던지는 예외와 구분짓는 것이 도움이 되는 경우
    • 당신의 코드가 하나 이상의 관련된 예외를 throw하는 경우
    • 만약 다른사람의 예외를 사용시, 사용자들이 그 예외에 접근할 수 있는 경우

Unchecked Exceptions — The Controversy

  • 자바 프로그래밍 언어는 메소드가 unchecked 예외들을 catch 또는 specify하길 요구하지 않기떄문에, 프로그래머들은 unchecked 예외를 던지는 코드를 쓰거나 모든 예외 subclass들을 RuntimeException으로 부터 상속받게 만드는 경우가 존재한다.
  • 두 방법 모두 프로그래머들이 코드를 작성할때 예외를 잡거나 특정지으라는 컴파일 에러가 귀찮게 하는 것을 제거해준다.
  • 편해보이지만 catch 와 specify 요구사항의 의도를 회피하는 것이고 다른사람들이 당신의 클래스를 사용할 때 문제가 발생할 수 있다.
  • 디자이너들이 왜 메소드가 메소드 scope안에서 던져지는 잡히지 않은 checked 예외를 specify하기를 강제했을까?
  • 메소드에 의해서 던져질 수 있는 모든 예외들은 메소드의 public 프로그래밍 인터페이스이다.
  • 메소드를 호출하는 사람은 그 메소드가 예외를 던지는 것을 알아야 그것을 어떻게할지 정할 수 있다.
  • 이 예외들은 파라미터와 return 값을 가지므로 메소드의 인터페이스의 한 부분이다.
  • 다음으로 생기는 의문은 메소드 API를 작성할 떄 메소드가 던질 수 있는 예외를 포함시키는 것이 좋다면 왜 Runtime 예외는 특정짓지 않을까?
  • 런타임 예외는 프로그래밍 문제의 결과로 API 클라이언트 코드는 그것을 처리 또는 회복하길 예상하지 않는다.
  • 0으로 나누기 같은 arithmetic 예외,null 래퍼런스를 통한 접근 같은 pointer exception, 잘못된 index로 배열에 접근 같은 indexing exception들이 예이다.
  • 런타임 예외는 프로그램의 어느 곳에서나 발생할 수 있으며 일반적인 예외는 매우 많을 수 있어 런타임 예외를 모든 메소드 선언에 추가하는 것은 프로그램의 clarity를 감소시킨다.
  • 그러므로 컴파일러는 런타임에러를 잡길 요구하지 않는다.

Advantages of Exceptions

Advantage 1: Separating Error-Handling Code from "Regular" Code

  • 예외는 평범하지 않은 일이 프로그램의 주요 논리에서 발생할 때 수행 할 작업의 세부 사항을 분리하는 수단을 제공한다.
  • 전통적인 프로그래밍에서 에러 찾기, 리포팅, 핸들링은 혼잡한 스파게티 코드를 만들어 낼 수 있다.
readFile {
    open the file;
    determine its size;
    allocate that much memory;
    read the file into memory;
    close the file;
}
  • 전체 파일을 메모리에 읽는 슈도 메소드 예제
  • 간단해 보이지만 생길 수 있는 에러들을 무시하고 있다.
    • 파일이 열리지 않는 경우
    • 파일의 길이를 결정할 수 없는 경우
    • 충분한 메모리가 할당되지 않은 경우
    • 읽기가 실패한 경우
    • 파일이 닫히지 않은 경우
  • 이러한 경우들을 처리하기 위해 readFile 함수는 에러 찾기,리포팅,핸들링을 하는 코드가 필요하다.
errorCodeType readFile {
    initialize errorCode = 0;
    
    open the file;
    if (theFileIsOpen) {
        determine the length of the file;
        if (gotTheFileLength) {
            allocate that much memory;
            if (gotEnoughMemory) {
                read the file into memory;
                if (readFailed) {
                    errorCode = -1;
                }
            } else {
                errorCode = -2;
            }
        } else {
            errorCode = -3;
        }
        close the file;
        if (theFileDidntClose && errorCode == 0) {
            errorCode = -4;
        } else {
            errorCode = errorCode and -4;
        }
    } else {
        errorCode = -5;
    }
    return errorCode;
}
  • 너무많은 에러 탐색, 리포팅, 반환이 너무 많아 기존의 7줄짜리 코드를 찾아보기 힘들다.
  • 더 심각한 것은 논리 흐름을 잃어 코드가 제대로 동작하는지 알기 힘들어진다.
    • 충분한 메모리를 얻지 못했을 경우 파일이 진짜로 닫히는가?
  • 작성후 3개월뒤 수정할 경우 제대로 동작할지 보장하기 힘들다.
  • 많은 프로그래머들은 이 문제를 그냥 무시함으로 해결해 결국 프로그램이 오작동시 에러가 리포트된다.
  • 예외는 예외적인 상황을 다른 곳에서 처리할 수 있게 하고 코드의 메인 흐름을 작성할 수 있게 한다.
readFile {
    try {
        open the file;
        determine its size;
        allocate that much memory;
        read the file into memory;
        close the file;
    } catch (fileOpenFailed) {
       doSomething;
    } catch (sizeDeterminationFailed) {
        doSomething;
    } catch (memoryAllocationFailed) {
        doSomething;
    } catch (readFailed) {
        doSomething;
    } catch (fileCloseFailed) {
        doSomething;
    }
}
  • 예외들이 오류 감지,보고 및 처리 작업을 수행하는 노력을 줄여주지 않지만 작업을보다 효과적으로 구성하는 데 도움이된다.

Advantage 2: Propagating Errors Up the Call Stack

  • readFile 메소드가 메인 메소드로부터 4번째로 nested됬다고 가정해보자.
    • method1이 2를 호출 2가 3을 3이 readFile을 호출
method1 {
    call method2;
}

method2 {
    call method3;
}

method3 {
    call readFile;
}
  • method1은 오직 readFile에서 발생할 수 있는 에러에만 관심있다고 가정해보자

  • 전통적인 오류 통지 기술은 method2 및 method3이 readFile에 의해 리턴 된 오류 코드를 오류 코드가 마지막으로 method1에 도달 할 때까지 호출 스택 위로 전파하도록한다.

  • 자바 런타임 환경이 콜스택을 반대방향으로 검색해 특정 예외를 처리할 수 있는 메소드를 찾는것을 기억해보자

  • 예외를 던진 메소드는 그 예외를 회피할 수 있고 그러므로 콜스택의 위에 존재하는 메소드가 그 것을 잡게 할 수 있다.

  • 그러므로 오직 에러에 관심있는 메소드만 에러 찾기를 걱정하면 된다.

method1 {
    try {
        call method2;
    } catch (exception e) {
        doErrorProcessing;
    }
}

method2 throws exception {
    call method3;
}

method3 throws exception {
    call readFile;
}
  • 그러나 슈도코드가 보여주드시 예외 회피는 middleman 메소드의 도움이 필요하다.
  • 메소드에서 던져지는 모든 checked 예외는 항상 throws로 특정지어야한다.

Advantage 3: Grouping and Differentiating Error Types

  • 프로그램에서 던져지는 모든 예외들은 객체이므로 예외들의 묶음과 카테고리화는 클래스 hierarchy의 자연적 결과이다.
  • 자바 플랫폼에서 관련된 예외 클래스 그룹중 하나는 java.io에 정의되있는 IOException과 그 자식들이다.
  • IOException은 I/O를 수행할때 발생할 수 있는 가장 보편적인 예외이다.
  • 자식들은 더욱 세분화된 에러들이다.
  • 예를들어 FileNotFoundException의 의미는 디스크에 파일이 존재하지 않는다는 뜻
  • 메소드는 특정한 예외를 처리할 수 있는 특정한 핸들러를 작성할 수 있다.
  • FileNOtFoundException 클래스는 자식이 없고 그래서 다음의 핸들러는 오직 한가지 예외만 처리할 수 있다.
catch (FileNotFoundException e) {
    ...
}
  • catch statement에서 예외의 수퍼 클래스를 지정하여 메소드가 그룹 또는 일반 유형을 기반으로 예외를 포착 할 수 있다.
  • 예를들어 모든 I/O 예외를 잡기위해 그것들의 특정한 타입에도 불구하고 예외 핸들러는 IOException 아규먼트를 사용한다.
catch (IOException e) {
    ...
}
  • 예외 핸들러로 들어온 아규먼트를 쿼리함으로 무엇이 발생했는지 디테일을 찾을 수 있다.
  • 예를 들어 printStackTrace를 사용하는 방법이 있다.
catch (IOException e) {
    // Output goes to System.err.
    e.printStackTrace();
    // Send trace to stdout.
    e.printStackTrace(System.out);
}
  • 모든 Exception을 핸들러를 사용할 수도 있다.
// A (too) general exception handler
catch (Exception e) {
    ...
}
  • Exception 클래스는 Throwable 클래스 계층의 꼭대기의 가깝게 존재한다.
  • 그러므로 이 핸들러는 잡으려하는 예외보다 훨씬 많은 예외들을 잡는다.
  • 이러한 방식의 핸들러는 프로그램이 하길 원하는 일이 에러 메세지를 출력하고 종료하는 것일 경우 사용한다.
  • 그러나 대부분의 경우 예외 핸들러가 특정하길 원할 것이다.
  • 그 이유는 핸들러의 첫번째 해야할 일은 핸들러가 최적의 회복 전략을 결정하기 전 어떤 예외의 타입이 발생했는지 결정이기 때문이다.
  • 실제로 특정 오류를 포착하지 않으면 핸들러는 모든 가능성을 수용해야한다.
  • 너무 일반적인 예외 핸들러들은 프로그래머가 의도하지 않은 에러를 잡고 처리하며 에러를 발생시키기 쉬운 코드를 생성한다.
  • 말했듯이 예외의 그룹을 생성하고 에러를 일반적인 방식으로 처리할 수도 있고, 특정한 예외 타입으로 예외를 구분하고 정확한 방식으로 예외를 처리할 수 있다.

why

profile
안녕하세요

0개의 댓글