수색…


소개

몇 가지 Java 프로그래밍 언어 오용은 올바르게 컴파일되었지만 부정확 한 결과를 생성하는 프로그램을 수행 할 수 있습니다. 이 주제의 주요 목적은 예외 처리 와 관련된 일반적인 함정 을 나열하고 이러한 함정을 피할 수있는 올바른 방법을 제안하는 것입니다.

함정 - 예외를 무시하거나 찌그러 뜨리기

이 예제는 의도적으로 예외를 무시하거나 "스쿼시"하는 것에 대한 것입니다. 또는 더 정확하게 말하자면 예외를 무시하고 처리하는 방법에 관한 것입니다. 그러나이를 수행하는 방법을 설명하기 전에 먼저 스쿼시 예외는 일반적으로이를 처리하는 올바른 방법이 아니라는 점을 지적해야합니다.

예외는 일반적으로 프로그램의 다른 부분에 중요한 (즉 "예외적 인") 이벤트가 발생했음을 알리기 위해 (무엇에 의해) 던져집니다. 일반적으로 (항상은 아니지만) 예외는 어떤 것이 잘못되었다는 것을 의미합니다. 예외를 스쿼시하도록 프로그램을 코딩하면 문제가 다른 형태로 다시 나타날 가능성이 있습니다. 상황을 악화 시키려면 예외를 스쿼시 할 때 예외 오브젝트 및 관련 스택 추적에 정보를 버리게됩니다. 문제의 원천이 무엇인지 알아내는 것이 더 어려워 질 수 있습니다.

실제로 예외 스쿼시는 IDE의 자동 수정 기능을 사용하여 처리되지 않은 예외로 인한 컴파일 오류를 "수정"할 때 자주 발생합니다. 예를 들어 다음과 같은 코드가 표시 될 수 있습니다.

try {
    inputStream = new FileInputStream("someFile");
} catch (IOException e) {
    /* add exception handling code here */
}

분명히 프로그래머는 컴파일 오류를 없애기 위해 IDE의 제안을 수락했지만 제안은 부적절했습니다. (만약 파일이 열리지 않았다면, 프로그램은 그것에 대해 뭔가를 할 것입니다. 위의 "수정"을하면, 프로그램이 나중에 실패 할 가능성이 있습니다; 예를 들어 inputStreamnull 이므로 NullPointerException 이 발생합니다.)

그런데, 여기에 의도적으로 예외를 채우는 예가 있습니다. 논증의 목적을 위해 셀카를 보여주는 동안 인터럽트가 무해하다고 가정한다고 가정 해보십시오.이 의견은 독자가 우리가 예외적으로 의도적으로 괴롭혔다는 것과 그 이유를 독자에게 알려줍니다.

try {
    selfie.show();
} catch (InterruptedException e) {
    // It doesn't matter if showing the selfie is interrupted.
}

예외적 인 변수의 이름으로 이것을 표시하는 이유를 말하지 않고 의도적으로 예외를 제거하는 것을 강조하는 또 다른 일반적인 방법은 다음과 같습니다.

try { 
    selfie.show(); 
} catch (InterruptedException ignored) {  }

변수 이름이 ignored 되도록 설정된 경우 IntelliJ IDEA와 같은 일부 IDE는 빈 catch 블록에 대한 경고를 표시하지 않습니다.

핏방울 - Throwable, Exception, Error 또는 RuntimeException 잡기

경험이 자바 프로그래머를위한 일반적인 생각 패턴은 예외 "문제"또는 "부담"이 처리하는 가장 좋은 방법은 가능한 한 빨리 그들에게 모든 일을 잡을되어 있다는 것입니다. 이렇게하면 다음과 같은 코드가 생성됩니다.

....
try {
    InputStream is = new FileInputStream(fileName);
    // process the input
} catch (Exception ex) {
    System.out.println("Could not open file " + fileName);
}

위의 코드에는 중대한 결함이 있습니다. catch 는 실제로 프로그래머가 기대하는 것보다 많은 예외를 잡을 것입니다. 응용 프로그램의 다른 곳의 버그로 인해 fileName 값이 null 이라고 가정합니다. 이것은 FileInputStream 생성자가 NullPointerException 을 던지게합니다. 핸들러가이를 catch하고 사용자에게 다음과 같이보고합니다.

    Could not open file null

도움이되지 않으며 혼란 스럽습니다. 더욱이, 예기치 않은 예외 (체크되거나 체크되지 않은 것)를 던진 것이 "입력 코드 처리"였다고 가정하십시오. 이제 사용자는 파일을 여는 동안 발생하지 않았던 문제에 대해 오도 된 메시지를 받게되고 I / O와 전혀 관련이 없을 수 있습니다.

문제의 근본 원인은 프로그래머가 Exception 처리기를 코딩했기 때문입니다. 이것은 거의 항상 실수입니다.

  • Catching Exception 은 검사 된 모든 예외와 대부분의 검사되지 않은 예외를 catch합니다.
  • RuntimeException 을 잡으면 대부분의 검사되지 않은 예외를 catch합니다.
  • Catching Error 는 JVM 내부 오류를 알리는 검사되지 않은 예외를 잡아냅니다. 이러한 오류는 일반적으로 복구 할 수 없으므로 포착해서는 안됩니다.
  • Catching Throwable 은 모든 가능한 예외를 catch합니다.

너무 광범위한 예외를 잡는 문제는 핸들러가 일반적으로 모든 핸들을 적절히 처리 할 수 ​​없다는 것입니다. Exception 등의 경우에는 프로그래머가 무엇이 잡힐 지 예측할 없습니다. 즉 무엇을 기대해야하는지.

일반적으로, 올바른 해결책은 발생 예외를 처리하는 것입니다. 예를 들어, 그들을 잡아서 현장에서 처리 할 수 ​​있습니다.

try {
    InputStream is = new FileInputStream(fileName);
    // process the input
} catch (FileNotFoundException ex) {
    System.out.println("Could not open file " + fileName);
}

또는 둘러싼 메소드에 의해 thrown 것으로 선언 할 수 있습니다.


Exception 잡는 것이 적절한 경우는 거의 없습니다. 일반적으로 발생하는 유일한 방법은 다음과 같습니다.

public static void main(String[] args) {
    try {
        // do stuff
    } catch (Exception ex) {
        System.err.println("Unfortunately an error has occurred. " +
                           "Please report this to X Y Z");
        // Write stacktrace to a log file.
        System.exit(1);
    }
}

여기서 우리는 진실로 모든 예외를 다루기를 원합니다. 그래서 Exception (또는 Throwable )을 잡는 것은 정확합니다.


1 - 포켓몬 예외 처리 라고도합니다.

핏방울 - Throwable, Exception, Error 또는 RuntimeException를 Throw하는 중

Throwable , Exception , ErrorRuntimeException 예외를 포착하는 것은 좋지 않지만 던지는 것은 더욱 나빠집니다.

기본적인 문제는 응용 프로그램이 예외를 처리해야 할 때 최상위 예외가 존재하면 다른 오류 조건을 구별하기 어렵다는 것입니다. 예를 들어

try {
    InputStream is = new FileInputStream(someFile);  // could throw IOException
    ...
    if (somethingBad) {
        throw new Exception();  // WRONG
    }
} catch (IOException ex) {
    System.err.println("cannot open ...");
} catch (Exception ex) {
    System.err.println("something bad happened");  // WRONG
}

문제는 우리가 Exception 인스턴스를 던 졌기 때문에 그것을 잡아야한다는 것입니다. 그러나 다른 예제에서 설명한 것처럼 Exception 잡는 것은 나쁘다. 이 상황에서 somethingBadtrue 인 경우 throw되는 Exception 의 "예상 된"사례와 NullPointerException 과 같은 검사되지 않은 예외를 실제로 잡는 예기치 않은 경우를 구분하기가 어려워집니다.

최상위 예외가 전파되도록 허용되면 다른 문제가 발생합니다.

  • 우리는 이제 우리가 최상위 레벨을 던져 버리고 다른 것들을 차별 / 다루는 여러 가지 이유를 모두 기억해야합니다.
  • ExceptionThrowable 의 경우 우리는 예외 전파를 원하면 메소드의 throws 절에 이러한 예외를 추가해야합니다. 이는 아래에 설명 된 것처럼 문제가됩니다.

즉, 이러한 예외를 던지지 마십시오. 일어난 "예외적 인 사건"을보다 자세히 묘사하는보다 구체적인 예외를 던집니다. 필요한 경우 사용자 정의 예외 클래스를 정의하고 사용하십시오.

메소드의 "throws"에서 Throwable 또는 Exception 선언은 문제가 있습니다.

메소드의 throws 절에서 던져진 예외의 긴리스트를 Exception 또는 심지어 Throwable로 대체하려는 유혹을 불러 일으킨다. 이것은 나쁜 생각입니다.

  1. 호출자가 Exception 을 처리 (또는 전파)하도록합니다.
  2. 컴파일러를 사용하여 처리해야하는 특정 확인 예외에 대해 더 이상 알려줄 수 없습니다.
  3. Exception 처리는 올바르게 수행하기가 어렵습니다. 실제 예외가 무엇인지 파악하는 것은 어렵습니다. 무엇이 잡힐 지 모르는 경우 복구 전략이 무엇인지 알기가 어렵습니다.
  4. Throwable 처리하는 것이 더 어렵습니다. 이제는 절대로 복구 할 수없는 잠재적 인 실패에 대처해야합니다.

이 충고는 특정 패턴을 피해야한다는 것을 의미합니다. 예 :

try {
    doSomething();
} catch (Exception ex) {
    report(ex);
    throw ex;
}

위의 경우 모든 예외를 확실하게 처리하지 않고 통과 할 때 모든 예외가 기록됩니다. 불행히도, Java 7 이전에는 throw ex; 명령문을 사용하면 컴파일러에서 Exception 발생할 수 있다고 생각할 수 있습니다. 그렇게하면 throws Exceptionthrows Exception 하는 것으로 둘러싼 메서드를 선언하도록 할 수 있습니다. Java 7부터는 컴파일러가 (다시 던져 질 수있는) 예외 집합이 더 작다는 것을 알고 있습니다.

핏방울 - InterruptedException 잡기

이미 다른 함정에서 지적한 것처럼 모든 예외를 잡기 위해

try {
    // Some code
} catch (Exception) {
    // Some error handling
}

다양한 문제가 있습니다. 하지만 perticular 문제 중 하나는 멀티 스레드 응용 프로그램을 작성할 때 인터럽트 시스템을 손상시킬 수 있기 때문에 교착 상태가 발생할 수 있다는 것입니다.

스레드를 시작하면 일반적으로 다양한 이유로 스레드를 갑자기 중지 할 수 있어야합니다.

Thread t = new Thread(new Runnable() {
    public void run() {
         while (true) {
             //Do something indefinetely
         }
    }
}

t.start();

//Do something else

// The thread should be canceld if it is still active. 
// A Better way to solve this is with a shared variable that is tested 
// regularily by the thread for a clean exit, but for this example we try to 
// forcibly interrupt this thread.
if (t.isAlive()) {
   t.interrupt();
   t.join();
}

//Continue with program

t.interrupt() 는 스레드를 종료하기위한 것보다 InterruptedException을 발생시킵니다. 하지만 스레드가 완전히 멈추기 전에 리소스를 정리해야한다면 어떻게 될까요? 이를 위해 InterruptedException을 catch하고 정리를 수행 할 수 있습니다.

 Thread t = new Thread(new Runnable() {
    public void run() {
        try {
            while (true) {
                //Do something indefinetely
            }
        } catch (InterruptedException ex) {
            //Do some quick cleanup

            // In this case a simple return would do. 
            // But if you are not 100% sure that the thread ends after 
            // catching the InterruptedException you will need to raise another 
            // one for the layers surrounding this code.                
            Thread.currentThread().interrupt(); 
        }
    }
}

그러나 코드에 catch-all 표현식이 있으면 InterruptedException도 catch되고 인터럽트는 계속되지 않습니다. 부모 스레드가 t.join() 사용 t.join() 스레드를 무한정 기다릴 때이 경우 교착 상태가 발생할 수 있습니다.

 Thread t = new Thread(new Runnable() {
    public void run() {
        try {
            while (true) {
                try {
                    //Do something indefinetely
                }
                catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        } catch (InterruptedException ex) {
            // Dead code as the interrupt exception was already caught in
            // the inner try-catch           
            Thread.currentThread().interrupt(); 
        }
    }
}

따라서 개별적으로 예외를 잡는 것이 더 좋습니다. 그러나 catch-all을 사용하기를 원한다면 적어도 InterruptedException을 미리 개별적으로 catch하십시오.

Thread t = new Thread(new Runnable() {
    public void run() {
        try {
            while (true) {
                try {
                    //Do something indefinetely
                } catch (InterruptedException ex) {
                    throw ex; //Send it up in the chain
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        } catch (InterruptedException ex) {
            // Some quick cleanup code 
    
            Thread.currentThread().interrupt(); 
        }
    }
}

Pitfall - 일반적인 flowcontrol에 대한 예외 사용

일부 Java 전문가가 암송하지 않을만한 내용이 있습니다.

"예외는 예외적 인 경우에만 사용해야합니다."

(예 : http://programmers.stackexchange.com/questions/184654 )

이것의 핵심은 정상적인 흐름 제어를 구현하기 위해 예외 및 예외 처리를 사용하는 것이 나쁜 생각입니다 (Java에서). 예를 들어 null 일 수있는 매개 변수를 처리하는 두 가지 방법을 비교하십시오.

public String truncateWordOrNull(String word, int maxLength) {
    if (word == null) {
        return "";
    } else {
        return word.substring(0, Math.min(word.length(), maxLength));
    }
}

public String truncateWordOrNull(String word, int maxLength) {
    try {
        return word.substring(0, Math.min(word.length(), maxLength));
    } catch (NullPointerException ex) {
        return "";
    }
}

이 예에서 우리는 경우 치료 (설계)있는 word 입니다 null 이 비어있는 단어 인 것처럼합니다. 두 버전은 기존의 if ... else 를 사용하거나 try ... catch 를 사용하여 null 처리합니다. 더 나은 버전을 어떻게 결정해야합니까?

첫 번째 기준은 가독성입니다. 가독성을 객관적으로 정량화하기는 어렵지만 대부분의 프로그래머는 첫 번째 버전의 핵심적인 의미를 식별하기 쉽다고 동의합니다. 실제로 두 번째 형식을 올바르게 이해하려면 Math.min 또는 String.substring 메서드로 NullPointerException 을 throw 할 수 없음을 이해해야합니다.

두 번째 기준은 효율성입니다. Java 8 이전의 Java 릴리스에서는 두 번째 버전이 첫 번째 버전보다 훨씬 느립니다. 특히, 예외 객체의 생성은 스택 추적이 필요한 경우에 대비하여 스택 프레임을 캡처하고 기록하는 작업을 수반합니다.

반면에 예외를 사용하는 것이 "예외적 인"이벤트를 처리하기 위해 조건 코드를 사용하는 것보다 더 읽기 쉽고 효율적이며 때로는 더 정확한 상황이 많이 있습니다. 실제로, "예외적이지 않은"이벤트에 사용하는 것이 필요한 희소 한 상황이 있습니다. 즉 비교적 자주 발생하는 사건. 후자의 경우 예외 객체 생성의 오버 헤드를 줄이는 방법을 살펴 보는 것이 좋습니다.

핏방울 - 과도하거나 부적절한 쌓기

프로그래머가 할 수있는 더 성가신 일 중 하나는 코드 전체에서 printStackTrace() 에 호출을 분산시키는 것입니다.

문제는 printStackTrace() 가 stacktrace를 표준 출력에 쓰려고한다는 것입니다.

  • Java 프로그래머가 아닌 최종 사용자를 대상으로하는 응용 프로그램의 경우 스택 추적은 최선의 정보가 아니며 최악의 경우에는 놀라운 것입니다.

  • 서버 측 응용 프로그램의 경우 아무도 표준 출력을 보지 않을 가능성이 있습니다.

더 좋은 방법은 printStackTrace 직접 호출하지 않거나 호출하는 경우 스택 추적이 최종 사용자의 콘솔이 아닌 로그 파일이나 오류 파일에 기록되는 방식으로 수행하는 것입니다.

이를 수행하는 한 가지 방법은 로깅 프레임 워크를 사용하고 예외 오브젝트를 로그 이벤트의 매개 변수로 전달하는 것입니다. 그러나, 예외를 기록하는 경우에도 injudiciously 수행하면 유해 할 수 있습니다. 다음을 고려하세요:

public void method1() throws SomeException {
    try {
        method2();
        // Do something
    } catch (SomeException ex) {
        Logger.getLogger().warn("Something bad in method1", ex);
        throw ex;
    }
}

public void method2() throws SomeException {
    try {
        // Do something else
    } catch (SomeException ex) {
        Logger.getLogger().warn("Something bad in method2", ex);
        throw ex;
    }
}

method2 에서 예외가 발생하면 동일한 실패에 해당하는 로그 파일에 동일한 스택 추적의 사본이 두 개 표시됩니다.

즉, 예외를 기록하거나 더 멀리 던지십시오 (다른 예외로 래핑되었을 수도 있음). 둘 다하지 마십시오.

Pitfall -`Throwable`을 직접 서브 클래스 화

Throwable 에는 2 개의 직접적인 서브 클래스 인 ExceptionError 있습니다. Throwable 직접 확장하는 새로운 클래스를 만들 수도 있지만 많은 응용 프로그램에서는 ExceptionError 만 있다고 가정하므로이 방법을 사용하지 않는 것이 좋습니다.

Throwable 을 직접 서브 클래스 화하는 것은 실용적인 이점은 없지만 결과 클래스는 사실상 체크 예외입니다. 대신 Exception 을 서브 클래 Exception 하면 동일한 동작이 발생하지만 더 명확하게 사용자의 의도를 전달합니다.



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow