확인 된 예외가 좋은가요 나쁜가요?

Java는 확인 된 예외를 지원합니다. 이 논란의 여지가있는 언어 기능은 대부분의 프로그래밍 언어가 확인 된 예외를 피하고 확인되지 않은 해당 언어 만 지원할 때까지 일부 사람들은 사랑하고 다른 사람들은 싫어합니다.

이 게시물에서는 확인 된 예외를 둘러싼 논란을 살펴 봅니다. 먼저 예외 개념을 소개하고 초보자가 논쟁을 더 잘 이해할 수 있도록 예외에 대한 Java의 언어 지원을 간략하게 설명합니다.

예외 란 무엇입니까?

이상적인 세계에서 컴퓨터 프로그램은 어떤 문제도 발생하지 않습니다. 파일이 존재해야 할 때 존재하고 네트워크 연결이 예기치 않게 닫히지 않으며 null 참조 인 integer-division-by를 통해 메서드를 호출하려는 시도가 없습니다. -제로 시도는 발생하지 않습니다. 그러나 우리의 세계는 이상과는 거리가 멀다. 이상적인 프로그램 실행에 대한 이러한 예외 는 널리 퍼져 있습니다.

예외를 인식하려는 초기 시도에는 실패를 나타내는 특수 값 반환이 포함되었습니다. 예를 들어 C 언어의 fopen()함수는 NULL파일을 열 수 없을 때 반환 됩니다. 또한 SQL 오류가 발생하면 PHP의 mysql_query()함수가 반환 FALSE됩니다. 실제 실패 코드를 다른 곳에서 찾아야합니다. 구현하기 쉽지만 예외를 인식하는이 "특수 값 반환"접근 방식에는 두 가지 문제가 있습니다.

  • 특수 값은 예외를 설명하지 않습니다. NULL또는 FALSE실제로 무엇을 의미합니까? 그것은 모두 특수 값을 반환하는 기능의 작성자에 따라 다릅니다. 또한 사용자에게 의미있는 메시지를 표시 할 수 있도록 예외가 발생했을 때 프로그램의 컨텍스트에 특수 값을 어떻게 연관 시키나요?
  • 특별한 값을 무시하는 것은 너무 쉽습니다. 예를 들어 int c; FILE *fp = fopen("data.txt", "r"); c = fgetc(fp);는이 C 코드 조각이 fgetc()fopen()반환 하더라도 파일에서 문자를 읽기 위해 실행되기 때문에 문제가 됩니다 NULL. 이 경우 fgetc()성공하지 못합니다. 찾기 어려울 수있는 버그가 있습니다.

첫 번째 문제는 예외를 설명하는 클래스를 사용하여 해결됩니다. 클래스 이름은 예외의 종류를 식별하고 해당 필드는 (메서드 호출을 통해) 무엇이 잘못되었는지 확인하기 위해 적절한 프로그램 컨텍스트를 집계합니다. 두 번째 문제는 컴파일러가 프로그래머가 예외에 직접 응답하거나 예외가 다른 곳에서 처리 될 것임을 나타내도록 강제함으로써 해결됩니다.

일부 예외는 매우 심각합니다. 예를 들어, 프로그램은 사용 가능한 메모리가 없을 때 일부 메모리 할당을 시도 할 수 있습니다. 스택을 소모하는 무한 재귀는 또 다른 예입니다. 이러한 예외를 오류 라고 합니다 .

예외 및 Java

Java는 클래스를 사용하여 예외 및 오류를 설명합니다. 이러한 클래스는 클래스를 기반으로하는 계층 구조로 구성됩니다 java.lang.Throwable. ( Throwable이 특수 클래스의 이름을 지정하는 이유 는 곧 분명해질 것입니다.) 바로 아래 Throwable에는 예외와 오류를 각각 설명 하는 java.lang.Exceptionjava.lang.Error클래스가 있습니다.

예를 들어, Java 라이브러리에는 문자열이 UID (Uniform Resource Identifier) ​​참조로 구문 분석 될 수 없음 java.net.URISyntaxException을 확장 Exception하고 나타내는이 포함되어 있습니다. URISyntaxException예외 클래스 이름이 단어로 끝나는 명명 규칙 을 따릅니다 Exception. 유사한 규칙이 java.lang.OutOfMemoryError.

Exceptionjava.lang.RuntimeExceptionJVM (Java Virtual Machine)의 정상적인 작동 중에 발생할 수있는 예외의 수퍼 클래스 인에 의해 서브 클래스 화됩니다 . 예를 들어, java.lang.ArithmeticException정수를 정수 0으로 나누려는 시도와 같은 산술 실패를 설명합니다. 또한 java.lang.NullPointerException널 참조를 통해 개체 멤버에 액세스하려는 시도를 설명합니다.

보는 또 다른 방법 RuntimeException

Java 8 언어 사양의 섹션 11.1.1은 다음과 같이 설명합니다. RuntimeException표현식 평가 중에 여러 이유로 인해 발생할 수 있지만 복구가 여전히 가능할 수있는 모든 예외의 수퍼 클래스입니다.

예외 또는 오류가 발생하면 해당 Exception또는 Error하위 클래스 의 개체 가 만들어져 JVM으로 전달됩니다. 객체를 전달하는 행위를 예외 발생이라고 합니다. Java는 throw이러한 목적을위한 명령문을 제공합니다 . 예를 들어 지정된 텍스트로 초기화 throw new IOException("unable to read file");되는 새 java.io.IOException개체를 만듭니다 . 이 개체는 이후에 JVM으로 전달됩니다.

Java는 try예외가 발생할 수있는 코드를 구분 하는 명령문을 제공합니다 . 이 명령문은 try중괄호로 구분 된 블록 뒤에 오는 키워드로 구성됩니다 . 다음 코드는 보여줍니다 trythrow:

try { method(); } // ... void method() { throw new NullPointerException("some text"); }

이 코드 조각에서 실행은 try블록에 들어가 를 호출 method()하여 NullPointerException.

JVM은 throwable을 수신 하고 예외를 처리 할 핸들러 에 대한 메소드 호출 스택을 검색합니다 . 파생되지 않은 예외 RuntimeException는 종종 처리됩니다. 런타임 예외 및 오류는 거의 처리되지 않습니다.

오류가 거의 처리되지 않는 이유

Errors are rarely handled because there is often nothing that a Java program can do to recover from the error. For example, when free memory is exhausted, a program cannot allocate additional memory. However, if the allocation failure is due to holding onto alot of memory that should be freed, a hander could attempt to free the memory with help from the JVM. Although a handler may appear to be useful in this error context, the attempt might not succeed.

A handler is described by a catch block that follows the try block. The catch block provides a header that lists the types of exceptions that it's prepared to handle. If the throwable's type is included in the list, the throwable is passed to the catch block whose code executes. The code responds to the cause of failure in such a way as to cause the program to proceed, or possibly terminate:

try { method(); } catch (NullPointerException npe) { System.out.println("attempt to access object member via null reference"); } // ... void method() { throw new NullPointerException("some text"); }

In this code fragment, I've appended a catch block to the try block. When the NullPointerException object is thrown from method(), the JVM locates and passes execution to the catch block, which outputs a message.

Finally blocks

A try block or its final catch block can be followed by a finally block that's used to perform cleanup tasks, such as releasing acquired resources. I've nothing more to say about finally because it isn't relevant to the discussion.

Exceptions described by Exception and its subclasses except for RuntimeException and its subclasses are known as checked exceptions. For each throw statement, the compiler examines the exception object's type. If the type indicates checked, the compiler checks the source code to ensure that the exception is handled in the method where it's thrown or is declared to be handled further up the method-call stack. All other exceptions are known as unchecked exceptions.

Java lets you declare that a checked exception is handled further up the method-call stack by appending a throws clause (keyword throws followed by a comma-delimited list of checked exception class names) to a method header:

try { method(); } catch (IOException ioe) { System.out.println("I/O failure"); } // ... void method() throws IOException { throw new IOException("some text"); }

Because IOException is a checked exception type, thrown instances of this exception must be handled in the method where they are thrown or be declared to be handled further up the method-call stack by appending a throws clause to each affected method's header. In this case, a throws IOException clause is appended to method()'s header. The thrown IOException object is passed to the JVM, which locates and transfers execution to the catch handler.

Arguing for and against checked exceptions

Checked exceptions have proven to be very controversial. Are they a good language feature or are they bad? In this section, I present the cases for and against checked exceptions.

Checked exceptions are good

James Gosling created the Java language. He included checked exceptions to encourage the creation of more robust software. In a 2003 conversation with Bill Venners, Gosling pointed out how easy it is to generate buggy code in the C language by ignoring the special values that are returned from C's file-oriented functions. For example, a program attempts to read from a file that wasn't successfully opened for reading.

The seriousness of not checking return values

Not checking return values might seem like no big deal, but this sloppiness can have life-or-death consequences. For example, think about such buggy software controlling missile guidance systems and driverless cars.

Gosling also pointed out that college programming courses don't adequately discuss error handling (although that may have changed since 2003). When you go through college and you're doing assignments, they just ask you to code up the one true path [of execution where failure isn't a consideration]. I certainly never experienced a college course where error handling was at all discussed. You come out of college and the only stuff you've had to deal with is the one true path.

Focusing only on the one true path, laziness, or another factor has resulted in a lot of buggy code being written. Checked exceptions require the programmer to consider the source code's design and hopefully achieve more robust software.

Checked exceptions are bad

Many programmers hate checked exceptions because they're forced to deal with APIs that overuse them or incorrectly specify checked exceptions instead of unchecked exceptions as part of their contracts. For example, a method that sets a sensor's value is passed an invalid number and throws a checked exception instead of an instance of the unchecked java.lang.IllegalArgumentException class.

Here are a few other reasons for disliking checked exceptions; I've excerpted them from Slashdot's Interviews: Ask James Gosling About Java and Ocean Exploring Robots discussion:

  • Checked exceptions are easy to ignore by rethrowing them as RuntimeException instances, so what's the point of having them? I've lost count of the number of times I've written this block of code:
    try { // do stuff } catch (AnnoyingcheckedException e) { throw new RuntimeException(e); }

    99% of the time I can't do anything about it. Finally blocks do any necessary cleanup (or at least they should).

  • Checked exceptions can be ignored by swallowing them, so what's the point of having them? I've also lost count of the number of times I've seen this:
    try { // do stuff } catch (AnnoyingCheckedException e) { // do nothing }

    Why? Because someone had to deal with it and was lazy. Was it wrong? Sure. Does it happen? Absolutely. What if this were an unchecked exception instead? The app would've just died (which is preferable to swallowing an exception).

  • Checked exceptions result in multiple throws clause declarations. The problem with checked exceptions is they encourage people to swallow important details (namely, the exception class). If you choose not to swallow that detail, then you have to keep adding throws declarations across your whole app. This means 1) that a new exception type will affect lots of function signatures, and 2) you can miss a specific instance of the exception you actually -want- to catch (say you open a secondary file for a function that writes data to a file. The secondary file is optional, so you can ignore its errors, but because the signature throws IOException, it's easy to overlook this).
  • Checked exceptions are not really exceptions. The thing about checked exceptions is that they are not really exceptions by the usual understanding of the concept. Instead, they are API alternative return values.

    The whole idea of exceptions is that an error thrown somewhere way down the call chain can bubble up and be handled by code somewhere further up, without the intervening code having to worry about it. Checked exceptions, on the other hand, require every level of code between the thrower and the catcher to declare they know about all forms of exception that can go through them. This is really little different in practice to if checked exceptions were simply special return values which the caller had to check for.

또한 응용 프로그램이 액세스하는 여러 라이브러리에서 생성 된 많은 수의 확인 된 예외를 처리해야한다는 주장을 접했습니다. 그러나이 문제는 Java의 체인 예외 기능을 활용하는 영리하게 설계된 파사드를 통해 극복 할 수 있으며 예외를 다시 던져서 처리해야하는 예외의 수를 크게 줄이면서 발생한 원래 예외를 보존 할 수 있습니다.

결론

확인 된 예외가 좋은가요 아니면 나쁜가요? 즉, 프로그래머가 확인 된 예외를 처리해야하거나 무시할 기회를 주어야합니까? 더 강력한 소프트웨어를 적용하려는 아이디어가 마음에 듭니다. 그러나 Java의 예외 처리 메커니즘은 프로그래머 친화적으로 발전해야한다고 생각합니다. 이 메커니즘을 개선하는 몇 가지 방법은 다음과 같습니다.