java 파라미터 확인 된 예외에 대한 사례




자바 exception 메시지 (24)

몇 년 동안 나는 왜 다음과 같은 질문에 대한 적절한 답변을 얻을 수 없었습니다. 왜 일부 개발자는 예외 검사를 거부합니까? 나는 수많은 대화를 나누었고, 블로그에서 일을 읽고, Bruce Eckel이 말한 것을 읽었습니다.

나는 현재 몇 가지 새로운 코드를 작성 중이며 예외 처리 방법에 대해 신중하게주의를 기울이고있다. 나는 "우리는 체크 된 예외가 싫어"군중의 견해를 보려고 노력하고 있는데, 나는 아직도 그것을 볼 수 없다.

내가 가진 모든 대화는 끝나지 않은 동일한 질문으로 끝납니다.

일반적으로 (Java가 설계된 방식에서) 일반적으로

  • 오류는 결코 잡히지 않아야합니다 (VM에는 땅콩 알레르기가 있고 다른 사람은 땅콩 한 병을 떨어 뜨렸습니다)
  • RuntimeException 프로그래머가 잘못한 것에 대한 것입니다 (프로그래머가 배열의 끝에서 벗어났다)
  • 예외 (RuntimeException 제외)는 프로그래머가 제어 할 수없는 것 (파일 시스템에 쓰는 동안 디스크가 가득 차고 프로세스의 파일 핸들 제한에 도달하여 더 이상 파일을 열 수 없음)
  • Throwable는 모든 예외 유형의 상위 요소입니다.

내가 듣는 공통된 주장은 예외가 발생하면 모든 개발자가 프로그램을 종료한다는 것입니다.

필자가 듣는 또 다른 공통적 인 주장은 체크 된 예외로 인해 코드를 리팩토링하는 것이 더 어렵다는 것입니다.

"나가하기 위하여 가고있는 모두는 출구이다"논쟁 나는 출구가 있더라도 당신은 적당한 오류 메시지를 표시 할 필요가있다. 만약 당신이 오류를 처리하는 것에 단둘 만 있다면, 사용자가 왜 그 이유에 대한 명확한 표시없이 프로그램이 종료 될 때 지나치게 행복하지 않을 것입니다.

"리팩터링하기가 어렵다"는 군중은 적절한 수준의 추상화가 선택되지 않았 음을 나타냅니다. 메소드가 IOException을 throw한다고 선언하는 대신, IOException은 발생중인 상황에 더 적합한 예외로 변환되어야합니다.

나는 프로그램을 정상적으로 종료 할 수 있도록 catch (Exception) (또는 경우에 따라 catch (Throwable))를 사용하여 Main을 래핑하는 데 문제가 없지만 항상 필요한 예외를 catch합니다. 최소한 적절한 오류 메시지를 표시하십시오.

사람들이 회신하지 않는 질문은 다음과 같습니다.

Exception 서브 클래스 대신 RuntimeException 서브 클래스를 던지면, 어떻게 잡아야하는지 어떻게 알 수 있습니까?

그 답이 catch Exception 인 경우 시스템 예외와 같은 방식으로 프로그래머 오류도 처리합니다. 그건 나에게 잘못된 것처럼 보인다.

Throwable을 잡으면 시스템 예외 및 VM 오류 (및 이와 유사한)를 동일한 방식으로 처리합니다. 그건 나에게 잘못된 것처럼 보인다.

그 대답이 던져진 예외를 잡는 것이라면, 던져진 것이 무엇인지 어떻게 알 수 있습니까? 프로그래머 X가 새로운 예외를 던지고 그것을 잡는 것을 잊었을 때 어떻게됩니까? 그것은 나에게 매우 위험한 것처럼 보입니다.

스택 추적을 표시하는 프로그램이 잘못되었다고 말할 수 있습니다. 점검 된 예외가 마음에 들지 않는 사람들은 그렇게 느끼지 않습니까?

따라서 체크 예외가 마음에 들지 않으면 왜 설명 할 수 없습니까? 그리고 대답을 얻지 못하는 질문에 답하십시오.

편집 : 어떤 모델을 사용할 때에 대한 조언을 찾고 있어요, 사람들이 RuntimeException에서 확장하는 이유는 그들이 Exception 및 / 또는 왜 예외를 잡아 내고 RuntimeException 대신에 rethrow하는 것을 좋아하지 않기 때문입니다. 메소드에 throw를 추가합니다. 확인 된 예외를 싫어하는 동기를 이해하고 싶습니다.


좋습니다 ... 확인 된 예외는 이상적이지 않으며주의해야하지만 목적을 수행합니다. API를 만들 때이 API의 계약적인 특정 실패 사례가 있습니다. Java와 같이 강력하게 정적으로 유형화 된 언어의 컨텍스트에서 확인 된 예외를 사용하지 않는 경우 오류 발생 가능성을 알리기 위해 특별 문서 및 규칙을 사용해야합니다. 이렇게하면 컴파일러가 처리 오류를 초래할 수있는 모든 이점이 제거되고 프로그래머의 선의로 완전히 떠나게됩니다.

따라서 C #에서 수행 된 것과 같이 Checked 예외가 제거됩니다. 그러면 어떻게 프로그래밍 방식으로 그리고 구조적으로 오류 가능성을 전달할 수 있습니까? 그러한 오류가 발생할 수 있으며 처리되어야한다는 클라이언트 코드를 알리는 방법은 무엇입니까?

나는 체크 된 예외를 다룰 때 모든 종류의 공포를 듣는다. 틀린 예외를 오용하고있다. 그러나 확실하다. 그러나 체크되지 않은 예외이다. 몇 년 동안 API가 많은 계층으로 쌓여서 실패를 전달하기 위해 구조화 된 수단의 반환을 요청할 때까지 기다려야한다고 말합니다.

예외가 API 레이어의 맨 아래 어딘가에 던져 졌을 때의 사례를 생각해보십시오. 아무도이 오류가 발생할 수 있음을 알지 못했기 때문에이 오류를 발생할 수 있습니다. 이는 호출 코드 예를 들어 VogonsTrashingEarthExcept ...와 반대로 FileNotFoundException을 던졌습니다. 처리 할 항목이 없기 때문에 처리하지 않아도 상관 없습니다.

많은 사람들은 파일을로드 할 수 없다는 것이 거의 항상 프로세스의 세계 끝이었으며 끔찍하고 고통스런 죽음을 맞아야한다고 주장했습니다. 그래서 그래 .. 물론 ... 확인 ... 당신은 API를 빌드하고 어떤 시점에서 파일을로드합니다 ... API의 사용자가 응답 할 수 있기 때문에 ... "도대체 언제 내 결정을 내리는거야? 프로그램이 충돌해야합니다! " 물론 예외가 잡히고 트레이스를 남기지 않는 Marianna 트렌치보다 더 깊은 스택 트레이스를 가진 EletroFlabbingChunkFluxManifoldChuggingException을 선택하면 나는 망설이지 않고 후자를 취할 것이다. 그러나 이것은 예외를 다루는 바람직한 방법이라는 것을 의미한다 ? 중간에 어딘가에있을 수는 없습니까? 예외가 다시 채워지고 새로운 추상화 수준으로 넘어갈 때마다 랩핑되어 실제로 의미가있는 부분이 될 수 있습니까?

마지막으로, 대부분의 인수는 "예외를 처리하고 싶지 않고 많은 사람들이 예외를 처리하기를 원하지 않습니다. 검사 된 예외로 인해 해당 예외를 처리해야하므로 예외가 검사되지 않습니다."이러한 메커니즘을 완전히 제거하고 그것을 지옥의 틈으로 강등 시키십시오. 지옥은 단지 어리 석고 용기와 시력이 부족합니다.

검사 된 예외를 제거하면 함수의 반환 형식을 제거하고 항상 "anytype"변수를 반환 할 수 있습니다. 그러면 삶이 훨씬 더 간단해질 것입니다.


글쎄, 스택 추적을 표시하거나 자동으로 충돌하는 것이 아닙니다. 레이어간에 오류를 전달할 수있는 것입니다.

확인 된 예외의 문제점은 사람들이 중요한 세부 사항 (예 : 예외 클래스)을 삼키는 것을 권장한다는 것입니다. 그 세부 사항을 삼키지 않기로 결정했다면, 전체 앱에 선언을 계속 추가해야합니다. 이는 1) 새로운 예외 유형이 많은 함수 시그니처에 영향을 미치고 2) 실제로 catch하려는 예외의 특정 인스턴스가 누락 될 수 있음을 의미합니다 (예를 들어 데이터를 보조 파일은 선택 사항이므로 오류를 무시할 수 있지만 서명 때문에 throws IOException쉽게 간과 할 수 있습니다.

저는 실제로 응용 프로그램에서이 상황을 처리하고 있습니다. 우리는 거의 예외를 AppSpecificException으로 리 패키징했습니다. 이것은 서명을 정말 깨끗하게 만들었고 우리는 throws서명에서 폭발하는 것에 대해 걱정할 필요가 없었습니다 .

물론 이제는 재시도 논리를 구현하여 상위 수준에서 오류 처리를 전문화해야합니다. 모든 것은 AppSpecificException입니다. 따라서 "IOException이 발생하면 다시 시도하십시오"또는 "ClassNotFound가 Throw되면 완전히 중단됩니다"라고 말할 수는 없습니다. 우리는 코드와 제 3 자 코드간에 반복되는 일들이 반복해서 재 패키징되기 때문에 진정한 예외가 생길 수있는 확실한 방법은 없습니다.

이것이 내가 파이썬에서 예외 처리에 대한 큰 팬인 이유입니다. 당신은 당신이 원하거나 취급 할 수있는 것들만 잡을 수 있습니다. 그 밖의 모든 것들은 마치 당신이 스스로 그것을 재연 한 것처럼 거품을 일으 킵니다 (어쨌든 그렇게했습니다).

나는 시간과 시간을 다시 찾았고, 내가 언급 한 프로젝트에서 예외 처리는 3 가지 범주로 나뉜다.

  1. 특정 예외를 잡아서 처리하십시오 . 예를 들어, 재시도 논리를 구현하는 것입니다.
  2. 다른 예외를 잡아서 다시 범하십시오 . 여기에서 일어나는 모든 것은 일반적으로 로깅이며, 보통 "$ filename을 열 수 없습니다"와 같은 진부한 메시지입니다. 이것들은 당신이 아무것도 할 수없는 오류입니다; 더 높은 수준에서만 그것을 처리 할 수 ​​있습니다.
  3. 모든 것을 잡아서 오류 메시지를 표시하십시오. 이것은 일반적으로 디스패처의 루트에 있으며, 예외가 발생하지 않는 메커니즘 (팝업 대화 상자, RPC 오류 개체 마샬링 등)을 통해 호출자에게 오류를 전달할 수 있는지 확인합니다.

프로그래머 는 메서드를 올바르게 사용할 수 있도록 메서드가 throw 할 수있는 모든 예외 를 알아야 합니다. 그래서 예외의 일부만으로 그를 두드리는 것은 반드시 부주의 한 프로그래머가 오류를 피하는 데 도움이되지는 않습니다.

부담스러운 비용 (특히 인터페이스 서명을 지속적으로 수정하는 것이 실용적이지 않은 더 크고 유연하지 않은 코드 기반의 경우)이 슬림 한 이점보다 뛰어납니다.

정적 분석은 훌륭하지만 실제로 신뢰할 수있는 정적 분석은 종종 프로그래머의 엄격한 작업을 유연하게 요구합니다. 비용 편익 계산이 있으며, 컴파일 시간 오류를 유발하는 검사를 위해 막대를 높게 설정해야합니다. IDE가 메소드가 던질 수있는 예외 (피할 수없는 것을 포함하여)를 전달하는 역할을 수행하면 더 유용 할 것입니다. 강제 예외 선언이 없으면 신뢰할 수 없을지라도 대부분의 예외는 문서에서 선언되며 IDE 경고의 신뢰성은 그리 중요하지 않습니다.


Artima 는 .NET의 설계자 인 Anders Hejlsberg와 http://www.artima.com/intv/handcuffs.html 했습니다. Anders Hejlsberg는 확인 된 예외에 대한 논쟁에 대해 철저히 다루고 있습니다. 짧은 시음기 :

throws 절은 적어도 Java에서 구현되는 방식과 달리 반드시 예외를 처리하도록 강요하지는 않지만 처리하지 않으면 예외가 통과 할 수 있음을 정확히 인식해야합니다. 선언 된 예외를 catch하거나 자신의 throws 절에 넣어야합니다. 이 요구 사항을 해결하기 위해 사람들은 어리석은 일을합니다. 예를 들어, 모든 메소드를 "throws Exception"으로 데코 레이팅합니다. 그건 그냥 완전히 기능을 패배시키고, 당신은 프로그래머가 더 많은 쓰레기를 쓸 수있게 만들었습니다. 그건 아무도 도움이 안돼.


Effective Java Exceptions 기사에서는 검사를 사용하지 않을 때와 검사 된 예외를 사용할 때를 자세히 설명합니다. 다음은 주요 요점을 강조하기 위해이 기사에서 인용 한 몇 가지 예입니다.

비상 사태 (Contingency) : 방법의 의도 된 목적으로 표현 될 수있는 방법으로부터의 대안적인 반응을 요구하는 예상 조건. 이 메소드의 호출자는 이러한 종류의 조건을 예상하고 해당 조건을 처리하기위한 전략을 가지고 있습니다.

오류 (Fault) : 메서드가 의도 된 목적을 달성하지 못하게하는 계획되지 않은 조건으로, 메서드의 내부 구현을 참조하지 않고는 설명 할 수 없습니다.

(따라서 테이블을 허용하지 않으므로 원본 페이지 에서 다음을 읽는 것이 좋습니다 .)

우연성

  • 다음으로 간주됩니다 : 디자인의 일부
  • 일어날 것으로 예상됩니다 : 정기적이지만 드물게
  • 누가 신경 쓰는지 : 메서드를 호출하는 업스트림 코드
  • 예 : 대체 반환 모드
  • 베스트 매핑 : 확인 된 예외

결점

  • 다음으로 간주됩니다 : 불쾌한 놀람
  • 일어날 것으로 예상된다 : 결코
  • 누가 그것을 염려 하는가 : 문제를 해결해야하는 사람들
  • 예 : 프로그래밍 버그, 하드웨어 오작동, 구성 실수, 누락 된 파일, 사용할 수없는 서버
  • 최상의 매핑 : 검사되지 않은 예외

이것은 검사 된 예외에 대한 순수한 개념에 대한 논쟁은 아니지만 Java에서 사용하는 클래스 계층 구조는 괴물 쇼입니다. 우리는 항상 단순히 예외를 "예외"라고 부릅니다 . 언어 사양 이 그것도 그렇게 부르기 때문에 올바른 것입니다. 그러나 타입 시스템에서 이름이 지정되고 표현되는 예외는 어떻게됩니까?

수업에서 Exception상상할 수 있을까요? 물론 Exceptions는 예외 이므로 다른 예외가 실제로 발생하기 때문에 s Exception아닌 Exception 예외는 예외입니다. 예외는 예외이며 다른 예외는 Error발생하지 않습니다. 그럴 때, 그리고 때로는 제외하고는 절대 잡아서는 안되는 것이 있습니다. 그 모두가 아니다 제외하고 당신은 또한도 다른 예외 정의 할 수 있기 때문에 Exception의도 Error의하지만 단지 Throwable예외.

다음 중 "확인 된"예외는 무엇입니까? Throwable그들이 인 경우도 제외들, 예외를 체크 Error되지 않은 예외가하고있다있는이야 Exception또한 S, Throwable하나 개의 예외도 그에게있을 것을 제외하고, 이는, S 및 확인이 끝난 예외의 주요 유형을하면 그들은 RuntimeException그것이 다른 종류의 확인되지 않은 예외이기 때문에 또한 s입니다.

무엇을 RuntimeException위해서입니까? 이름에서 알 수 있듯이, 그것들은 모두 예외처럼, 예외적 인 Exception것이며, 실행시에 실제로 모든 예외처럼 발생합니다. 예외를 제외하고는 RuntimeException다른 런타임과 비교하여 Exception예외적입니다. 비록 어리석은 실수를 저질렀지 만, RuntimeExceptions는 결코 존재하지 않으므로 Error, 그것들은 예외적으로 오류가 있지만 실제로는 그렇지 않습니다 Error. 를 제외하고 RuntimeErrorException, 그것은 정말로 s를 RuntimeException위한 것 Error입니다. 그러나 모든 예외가 잘못된 상황을 나타내는 것으로 간주되지는 않습니까? 예, 모두. ThreadDeath예외적으로 예외가 아닌 예외를 제외하고 는 문서에서 "정상적인 발생"이라고 설명하고 '왜 그들이 그것을 일종의Error .

어쨌든, 우리는 모든 예외를 중간에 Errors (예외적 인 예외 실행을위한 것임)와 Exceptions (덜 예외적 인 실행 오류를 제외하고는 그렇지 않은 경우를 제외하고 ) 로 나누기 때문에 , 이제는 두 개의 몇 가지 예외의 각각 다른 종류. 그래서 우리는 필요 IllegalAccessError하고 IllegalAccessException, 및 InstantiationErrorInstantiationExceptionNoSuchFieldErrorNoSuchFieldExceptionNoSuchMethodErrorNoSuchMethodExceptionZipErrorZipException.

예외가 검사 될 때조차 컴파일러를 속여서 검사하지 않고 던지는 방법은 항상 (아주 쉽게) 있습니다. 당신이 할 경우, 당신은 얻을 수 UndeclaredThrowableException그것이로 던질 수있는 다른 사례를 제외하고 UnexpectedException, 또는 UnknownException(관련이있는 UnknownError경우에만 "심각한 예외"에 대한 인), 또는 ExecutionException, 또는 InvocationTargetException, 또는 ExceptionInInitializerError.

아, 그리고 우리는 아주 세련 새로운 자바 (8)의 잊어서는 안 UncheckedIOExceptionA는, RuntimeException당신이 선택 포장하여 창 밖으로 개념을 검사 예외 던질 수 있도록 설계 예외 IOException가 발생하지 않는 I / O 오류로 인한 예외 ( IOError예외, 즉 존재하지만, 너무) 다루기가 매우 어렵 기 때문에 검사하지 않아야합니다.

Java에 감사드립니다!


확인 된 예외는 원래 형태로 실패가 아닌 우발적 인 사건을 처리하려는 시도였습니다. 칭찬할만한 목표는 특정 예측 가능한 포인트 (연결할 수 없음, 파일을 찾을 수 없음 등)를 강조하고 개발자가이를 처리하도록 보장하는 것이 었습니다.

원래의 개념에 포함되지 않은 것은 체계적이고 복구 불가능한 실패의 광범위한 선언을 강요하는 것이 었습니다. 이러한 실패는 검사 된 예외로 선언되지 않았습니다.

오류는 일반적으로 코드에서 가능하며, EJB, web 및 Swing / AWT 컨테이너는 가장 외의 "실패 요청"예외 처리기를 제공하여이 문제를 해결합니다. 가장 기본적인 올바른 전략은 트랜잭션을 롤백하고 오류를 반환하는 것입니다.

한 가지 중요한 점은 런타임 및 확인 된 예외가 기능적으로 동일하다는 것입니다. 검사 예외가 수행 할 수있는 예외 처리 나 복구는 수행 할 수 없습니다.

"확인 된"예외에 대한 가장 큰 논쟁은 대부분의 예외를 수정할 수 없다는 것입니다. 간단한 사실은 파손 된 코드 / 하위 시스템을 소유하고 있지 않다는 것입니다. 우리는 구현을 볼 수 없으며 책임을지지 않으며 수정할 수 없습니다.

우리의 응용 프로그램이 DB가 아닌 경우 DB를 수정해서는 안됩니다. 그것은 캡슐화원칙에 위배 됩니다.

특히 JDBC (SQLException) 및 EJB (RemoteException) 용 RMI 영역이 문제가되고 있습니다. 원래의 "확인 된 예외"개념에 따라 고칠 수있는 우발 상황을 식별하기보다는 실제로 고칠 수없는 이러한 광범위하게 퍼져있는 시스템 안정성 문제를 널리 선언해야합니다.

Java 디자인의 또 다른 심각한 결함은 예외 처리가 가능한 가장 높은 "비즈니스"또는 "요청"레벨에 올바르게 배치되어야한다는 것입니다. 여기서의 원칙은 "일찍 던져서 늦게 붙잡아 라"입니다. 확인 된 예외는 거의 발생하지 않지만이를 방해합니다.

우리는 Java에서 명백한 문제가 있는데, 아무 것도 시도하지 않는 블록이 수천 개 필요하며, 상당한 비율 (40 % 이상)이 잘못 처리됩니다. 이들 중 거의 대부분이 진정한 처리 또는 안정성을 구현하지 않지만 주요 코딩 오버 헤드를 부과합니다.

마지막으로 "확인 된 예외"는 FP 함수 프로그래밍과 거의 호환되지 않습니다.

"즉시 처리"에 대한 그들의 주장은 "늦게 잡는"예외 처리 모범 사례와 루프 또는 제어 흐름을 추상화하는 모든 FP 구조와 잘 맞지 않습니다.

많은 사람들이 확인 된 예외를 처리하는 것에 대해 이야기하지만 모자를 통해 이야기하고 있습니다. null로 실패한 후 계속해서 성공 을 가장하는 불완전하거나 잘못된 데이터 는 아무 것도 처리하지 않습니다. 가장 낮은 형태의 엔지니어링 / 신뢰성 과실입니다.

정상적으로 실패하면 예외를 처리하기위한 가장 기본적인 올바른 전략입니다. 트랜잭션 롤백, 오류 로깅 및 "실패"응답을 사용자에게보고하는 것은 올바른 습관입니다. 가장 중요한 것은 잘못된 비즈니스 데이터가 데이터베이스에 커밋되는 것을 방지하는 것입니다.

예외 처리를위한 다른 전략은 비즈니스, 하위 시스템 또는 요청 수준에서 "다시 시도", "다시 연결"또는 "건너 뛰기"입니다. 이 모든 것은 일반적인 안정성 전략이며 런타임 예외를 제외하고는 더 잘 작동합니다.

마지막으로 잘못된 데이터로 실행하는 것보다 실패하는 것이 훨씬 바람직합니다. 계속하면 원래의 원인과 멀리 떨어져 있고 디버그하기가 힘든 보조 오류가 발생합니다. 결국 잘못된 데이터가 커밋되는 결과를 초래합니다. 사람들은 그것을 해고 당합니다.

만나다:
- literatejava.com/exceptions/…


문제

예외 처리 메커니즘에서 볼 수있는 최악의 문제 는 코드 복제를 대규모로 도입 한다는 것입니다 ! 솔직히 말하자면, 대부분의 프로젝트에서 95 %의 시간에 개발자가 실제로 예외적으로해야 할 일은 사용자에게 어떻게 든 전달하는 것입니다 (경우에 따라 개발 팀에도 전달합니다. 스택 추적이있는 -mail). 따라서 예외가 처리되는 모든 위치에서 일반적으로 같은 줄 / 코드 블록이 사용됩니다.

어떤 유형의 검사 된 예외에 대해 각 catch 블록에 간단한 로깅을 수행한다고 가정 해 보겠습니다.

try{
   methodDeclaringCheckedException();
}catch(CheckedException e){
   logger.error(e);
}

일반적인 예외 인 경우 더 큰 코드베이스에 수백 개의 try-catch 블록이있을 수 있습니다. 이제 콘솔 로깅 대신 팝업 대화 상자 기반 예외 처리를 도입하거나 개발 팀에 전자 메일을 추가로 보내야한다고 가정 해 보겠습니다.

잠시만 기다려주세요. 코드에서 수백 가지 위치를 모두 편집 할 예정입니까?! 당신은 나의 요지를 얻는다 :-).

해결책

이 문제를 해결하기 위해 우리가 수행 한 작업은 예외 처리 를 중앙 집중화 하기 위해 예외 처리기 (EH라고 부르기로 합니다) 개념을 도입하는 것이 었습니다 . 예외를 처리해야하는 모든 클래스에는 예외 처리기의 인스턴스가 Dependency Injection 프레임 워크에 의해 주입됩니다 . 그래서 전형적인 예외 처리 패턴은 다음과 같습니다 :

try{
    methodDeclaringCheckedException();
}catch(CheckedException e){
    exceptionHandler.handleError(e);
}

이제 예외 처리를 사용자 정의하기 위해 코드를 단일 장소 (EH 코드)로 변경하면됩니다.

물론보다 복잡한 경우에는 EH의 여러 하위 클래스를 구현하고 DI 프레임 워크에서 제공하는 기능을 활용할 수 있습니다. DI 프레임 워크 구성을 변경함으로써 우리는 EH 구현을 전 세계적으로 쉽게 전환 할 수 있거나 특별한 예외 처리 요구 사항이있는 클래스 (예 : Guice @Named 주석 사용)에 특정 구현의 EH를 제공 할 수 있습니다.

그렇게하면 우리는 예외 처리 동작을 애플리케이션 개발 버전과 차별화 할 수 있습니다 (예 : 개발 로깅 - 오류 로깅 및 애플리케이션 중지, 세부 정보로 오류 로깅, 애플리케이션 실행 계속).

마지막 한 가지

마지막으로 중요한 예외 처리 클래스에 도착할 때까지 예외를 "up"으로 전달하면 동일한 유형의 중앙 집중식을 얻을 수 있습니다. 하지만이 방법은 코드가 복잡해지고 메서드의 서명이 복잡해지며이 스레드에서 다른 사람들이 언급 한 유지 관리 문제가 발생합니다.


이 기사 는 내가 읽은 Java의 예외 처리에 관한 텍스트 중 가장 좋은 부분이다.

확인 된 예외에 대해서는 선택하지 않는 것이 좋지만이 선택은 매우 간략하게 설명되며 강력한 인수를 기반으로합니다.

이 기사의 내용을 너무 많이 인용하고 싶지는 않습니다. 전체적으로 읽는 것이 가장 좋지만,이 스레드의 확인되지 않은 예외 옹호자에 대한 대부분의 주장을 다루고 있습니다. 특히이 논쟁 (꽤 인기가있는 것)은 다음과 같습니다 :

예외가 API 레이어의 맨 아래 어딘가에 던져 졌을 때의 사례를 생각해보십시오. 아무도이 오류가 발생할 수 있음을 알지 못했기 때문에이 오류를 발생할 수 있습니다. 이는 호출 코드 예를 들어 VogonsTrashingEarthExcept ...와 반대로 FileNotFoundException을 던졌습니다. 처리 할 항목이 없기 때문에 처리하지 않아도 상관 없습니다.

저자 "응답":

모든 런타임 예외를 포착해서 응용 프로그램의 맨 위에 전파해서는 안된다고 가정하는 것은 절대적으로 잘못되었습니다. (...) 시스템 / 비즈니스 요구 사항에 따라 명료하게 처리되어야하는 모든 예외적 조건에 대해 프로그래머는 조건을 포착 한 후이를 파악하고 처리 할 위치를 결정해야합니다. 이는 컴파일러 경고가 아닌 응용 프로그램의 실제 요구 사항에 따라 엄격하게 수행되어야합니다. 다른 모든 오류는 로그 된 최상위 핸들러로 자유롭게 전달되어야하며 정상적인 (아마도 종료) 조치가 취해집니다.

그리고 주요 생각이나 기사는 다음과 같습니다.

소프트웨어의 오류 처리에 관해서는 안전하고 올바른 가정은 존재하는 모든 서브 루틴이나 모듈에서 오류가 발생할 수 있다는 것입니다!

그래서 " 아무도이 오류가 발생할 수 있다는 것을 알지 못했다 면" 그 프로젝트에 잘못된 것이 있습니다 . 이러한 예외는 저자가 제안하는 것처럼 적어도 가장 일반적인 예외 처리기 (예 : 특정 처리기에서 처리하지 않는 모든 예외를 처리하는 예외 처리기)에서 처리해야합니다.

그래서 슬픈 많은 poeple이 위대한 기사를 발견하는 것 같습니다 :-( 나는 진심으로 추천하는 모든 사람에게 접근하는 것을 망설이는 것이 좋습니다.


답변이없는 질문 만 처리하려고 시도하십시오.

Exception 서브 클래스 대신 RuntimeException 서브 클래스를 던지면, 어떻게 잡아야하는지 어떻게 알 수 있습니까?

그 질문에는 이성적인 추론이 포함되어 있습니다. API가 당신에게 그것이 무엇을 의미 하는지를 알려주는 것이 모든 경우에서 같은 방식으로 처리한다는 것을 의미하지는 않습니다. 다시 말해, catch해야하는 예외는 예외를 throw하는 구성 요소를 사용하는 컨텍스트에 따라 다릅니다.

예 :

데이터베이스에 대한 연결 테스터를 작성 중이거나 사용자가 입력 한 XPath의 유효성을 검사 할 항목이 있다면 작업에 의해 throw 된 검사되고 검사되지 않은 모든 예외를 catch하고보고하려고합니다.

그러나 처리 엔진을 작성 중이라면 NPE와 같은 방식으로 XPathException (체크)을 처리 할 것입니다 : 작업자 스레드의 최상위로 실행하고 나머지 일괄 처리를 생략하고 (또는 진단을 위해 지원 부서로 보내십시오). 그리고 사용자가 지원 담당자에게 연락 할 수 있도록 피드백을 남기십시오.


저는 이것이 훌륭한 질문이며 전혀 논란의 여지가 없다고 생각합니다. 제 3 자 라이브러리는 (일반적으로) 검사되지 않은 예외를 throw해야한다고 생각합니다 . 이는 라이브러리에 대한 의존성을 격리 할 수 ​​있다는 것을 의미합니다 (즉, 예외를 다시 던지거나 던지거나 Exception, 일반적으로 나쁜 습관을 가질 필요가 없음 ). Spring의 DAO 레이어 는 이것의 훌륭한 예입니다.

그들이 할 수 있다면 다른 한편으로는, 핵심 자바 API에서 예외는 일반적으로 확인해야합니다 으로 처리 될 수있다. Take FileNotFoundException또는 (내가 좋아하는) InterruptedException. 이러한 조건은 거의 항상 구체적으로 다루어 져야합니다 (즉,에 대한 귀하의 반응 InterruptedException은 귀하의 반응과 동일하지 않습니다 IllegalArgumentException). 예외가 확인된다는 사실은 개발자가 조건이 처리 가능한지 여부를 생각하도록합니다. (즉, InterruptedException제대로 처리되는 경우는 드뭅니다 .)

한 가지 더 - RuntimeException항상 개발자가 잘못된 것을 얻은 것은 아닙니다. 불법 인수 예외는 enum사용 하려고 시도하고 만들 때 그 이름 valueOf이 없습니다 enum. 이것은 개발자가 반드시 실수하는 것은 아닙니다!


사실, 검사 예외는 프로그램의 견고성과 정확성을 증가시킵니다 (인터페이스의 올바른 선언을 강요합니다 - 예외는 기본적으로 특별한 반환 유형입니다). 다른 한편으로는 예외가 "거품을 일으키기"때문에 예외 사항을 변경할 때 매우 많은 메소드 (모든 호출자와 호출자의 호출자 등)를 변경해야하는 문제점이 있습니다. 메서드가 throw됩니다.

Java에서 체크 된 예외는 후자의 문제를 해결하지 못합니다. C #과 VB.NET은 목욕탕으로 아기를 버린다.

중간 도로를 이용하는 좋은 접근법은 이 OOPSLA 2005 논문 (또는 관련 기술 보고서 )에 설명되어 있습니다.

간단히 말해서, 다음과 같이 말할 수 있습니다. method g(x) throws like f(x)즉, g는 모든 예외를 던집니다. Voila, 계단식 변경 문제가없는 예외를 확인했습니다.

그것은 학술 논문이긴하지만 확인 된 예외의 장점과 단점이 무엇인지 설명 할 수 있기 때문에 학술 논문을 읽으시기 바랍니다.


Anders는 확인 된 예외의 함정에 대해 말하고 왜 Software Engineering 라디오 97 호 에 C #에서 빠뜨린 것입니까 ?


예외 카테고리

예외에 대해 이야기 할 때 나는 항상 Eric Lippert의 Vexing 예외 블로그 기사를 참조합니다. 그는 다음 범주에 예외를 배치합니다.

  • 치명적 - 이러한 예외는 귀하의 잘못아닙니다 . 귀하는 그로 인해 막을 수 없으며, 귀하는 현명하게 그러한 문제를 처리 할 수 ​​없습니다. 예를 들어, OutOfMemoryError또는 ThreadAbortException.
  • Boneheaded - 이러한 예외 는 사용자의 잘못입니다 . 사용자는이를 방지해야하며 코드에 버그가 있음을 나타냅니다. 예를 들어 ArrayIndexOutOfBoundsException, NullPointerException또는 IllegalArgumentException.
  • Vexing - 이러한 예외는 예외적 인 것이 아니고 잘못이 아니기 때문에 방지 할 수는 없지만 처리해야합니다. 종종 파싱 ​​실패시 부울 false를 반환 하는 메서드를 제공하는 대신 throw NumberFormatException하는 등의 불행한 디자인 결정의 결과입니다 .Integer.parseIntInteger.tryParseInt
  • 외인성 - 예외 는 일반적으로 예외적 인 것이지 잘못이 아니라 (합리적으로) 예방할 수는 없지만 처리해야합니다 . 예를 들어, FileNotFoundException.

API 사용자 :

  • 치명적 이거나 골치 아픈 예외를 처리 해서는 안됩니다 .
  • 짜증나는 예외를 처리 해야 하지만 이상적인 API에서는 발생하지 않아야합니다.
  • 외생 적 예외 처리 해야합니다 .

확인 된 예외

API 사용자 특정 예외를 처리 해야 한다는 사실 은 호출자와 수신자 간의 메소드 계약의 일부입니다. 계약에서는 호출 수신자가 예상하는 인수의 수와 유형, 호출자가 기대할 수있는 반환 값의 유형 및 호출자가 처리해야하는 예외를 지정합니다 .

고질적 인 예외가 API에 존재해서는 안되기 때문에 이러한 외인성 예외 만이 메소드의 계약에 포함되는 예외검사 해야합니다 . 상대적으로 소수의 예외는 외인적이므로 모든 API는 비교적 적은 수의 예외를 제외하고는 안됩니다.

확인 된 예외는 처리해야하는 예외입니다 . 예외 처리는 삼키는 것처럼 간단 할 수 있습니다. 그곳에! 예외가 처리됩니다. 기간.개발자가 그런 식으로 처리하려고한다면 괜찮습니다. 그러나 그는 예외를 무시할 수 없으며 경고를 받았다.

API 문제

그러나 검사 한 모든 API 애 태우게 하고 치명적인 (예 JCL) 예외는 API 사용자에 불필요한 부담을 줄 것입니다. 이러한 예외 처리 해야 하지만 예외가 매우 일반적이어서 처음부터 예외가되어서는 안되며 그렇지 않으면 처리 할 수 ​​없습니다. 그리고 이로 인해 Java 개발자는 확인 된 예외를 싫어하게됩니다.

또한 많은 API에는 적절한 예외 클래스 계층 구조가 없으므로 모든 외부의 예외적 인 원인이 단일 확인 된 예외 클래스 (예 :)로 표시됩니다 IOException. 또한 이로 인해 Java 개발자는 확인 된 예외를 싫어하게됩니다.

결론

외인성 예외는 귀하의 잘못이 아니며 예방할 수 없었고 어떤 것을 처리해야하는지입니다. 이것들은 던져 질 수있는 모든 예외 중 작은 부분 집합을 형성합니다. API는 외인성 예외검사 하고 다른 모든 예외는 검사하지 않아야합니다. 이렇게하면 API가 향상되고 API 사용자에게 부담이 줄어들어 모든 것을 잡을 필요가 줄어들고 확인되지 않은 예외를 삼키거나 다시 던질 수 있습니다.

따라서 Java 및 확인 된 예외는 싫어서는 안됩니다. 대신, 확인 된 예외를 과도하게 사용하는 API는 싫어하십시오.


사람들은 이미 언급했듯이 자바 바이트 코드에는 검사 예외가 존재하지 않습니다. 그들은 단순히 다른 구문 검사와 달리 컴파일러 메커니즘입니다. 나는 조건부 중복에 대해 불평하는 컴파일러를 보듯이 체크 된 예외를 많이 본다.if(true) { a; } b; . 도움이되었지만 의도적으로이 작업을 수행했을 수 있으므로 경고를 무시하겠습니다.

문제의 사실은 체크 예외를 적용하면 모든 프로그래머에게 "올바른 일을하도록"강요 할 수 없으며 다른 모든 사람들은 당신이 만든 규칙에 대해 당신을 단지 싫어하는 부수적 인 손상이된다.

나쁜 프로그램을 거기 밖으로 고치십시오! 언어를 고치려고하지 마십시오! 대부분의 사람들은 "예외에 대해 무언가를하는 것"은 실제로 사용자에게이를 알려주는 것입니다. 검사되지 않은 예외에 대해서도 사용자에게 알릴 수 있으므로 체크 된 예외 클래스는 내 API에서 제외 시키십시오.


확인 된 예외에 대해 모든 (여러 가지) 이유를 재탕하기보다는 하나만 선택합니다. 이 코드 블록을 작성한 횟수를 잃어 버렸습니다.

try {
  // do stuff
} catch (AnnoyingcheckedException e) {
  throw new RuntimeException(e);
}

99 %는 그것에 대해 아무 것도 할 수 없습니다. 마지막으로 블록은 필요한 정리를 수행합니다 (또는 최소한해야합니다).

나는 또한 이것을 본 횟수를 잃어 버렸습니다.

try {
  // do stuff
} catch (AnnoyingCheckedException e) {
  // do nothing
}

왜? 왜냐하면 누군가는 그것을 다루어야하고 게으 르기 때문입니다. 틀렸어? 확실한. 그런 일이 있습니까? 전혀. 이것이 unchecked exception이라면 어떨까요? 앱이 방금 죽었을 것입니다 (예외를 삼키는 것이 낫습니다).

그리고 java.text.Format 처럼 예외를 흐름 제어의 한 형태로 사용하는 코드가 있습니다. Bzzzt. 잘못된. 양식의 숫자 필드에 "abc"를 넣는 사용자는 예외가 아닙니다.

Ok, 나는 그것이 3 가지 이유가 있었다고 생각한다.


Here 체크 아웃 된 예외 (joelonsoftware.com)에 대한 한 가지 논쟁입니다.

그 이유는 1960 년대 이래로 코드의 한 점에서 다른 점으로 급격하게 이동한다는 점에서 예외가 "goto 's"보다 좋지 않다고 생각하기 때문입니다. 사실 그들은 goto보다 훨씬 심각합니다.

  • 그것들은 소스 코드에서 보이지 않습니다. 예외를 throw 할 수도 있고 그렇지 않을 수도있는 함수를 포함하여 코드 블록을 살펴보면 어떤 예외가 발생할 수 있는지 그리고 어디에서 예외가 발생하는지 확인할 방법이 없습니다. 이것은 신중한 코드 검사로도 잠재적 인 버그가 드러나지 않는다는 것을 의미합니다.
  • 함수에 대해 너무 많은 가능한 종료 점을 만듭니다. 올바른 코드를 작성하려면 함수를 통해 가능한 모든 코드 경로를 고려해야합니다. 예외를 발생시키고 그 자리에서 그것을 잡을 수없는 함수를 호출 할 때마다 갑자기 종료 된 함수로 인해 깜짝 버그가 생길 수 있고 데이터가 일관성없는 상태로 남거나 그렇지 않은 다른 코드 경로가 생길 수 있습니다 생각 해봐.

나는 예외 처리에 대해 많이 읽었지만, 실제로 (대부분의 경우) 내가 확인한 예외의 존재에 대해 행복하거나 슬프다고 말할 수는 없다. 이것이 나의 취지이다. 저수준 코드 (IO, 네트워킹 , OS 등) 및 상위 수준 API / 응용 프로그램 수준에서 확인되지 않은 예외가 있습니다.

그것들 사이에 선을 그리는 것이 그렇게 쉽지는 않더라도, 항상 확인 된 예외를 여러 번 포장하지 않고 같은 지붕 아래 여러 API / 라이브러리를 통합하는 것이 성가신 / 어렵다는 것을 알게됩니다. 그러나 다른 한편으로는 언젠가는 어떤 예외를 잡아 내고 현재의 상황에서 더 의미있는 다른 것을 제공하도록 강요받는 것이 더 유용합니다.

내가 작업하고있는 프로젝트는 많은 라이브러리를 필요로하며 동일한 API, 즉 확인되지 않은 예외에 완전히 기반한 API로 통합합니다.이 프레임 워크는 처음에는 확인 된 예외로 가득 차서 몇 가지 검사되지 않은 높은 수준의 API를 제공합니다 예외 (초기화 예외, ConfigurationException 등) 및 나는 아주 친절 하지 않았다는 것을 말해야한다 . 대부분의 경우 처리해야 할 방법을 모르는 예외를 잡거나 다시 던져야하거나 (예외를 무시해야한다는 사실을 혼동하지 않아야합니다.) 특히 클라이언트 측에서 예외를 무시해야합니다. 클릭하면 10 개의 가능한 (확인 된) 예외가 발생할 수 있습니다.

현재 버전 (3 번째)은 체크되지 않은 예외만을 사용하며, 잡히지 않은 것을 처리 할 책임이있는 전역 예외 핸들러를 가지고있다. API는 예외 처리기를 등록하는 방법을 제공합니다.이 처리기는 예외가 오류라고 간주 될지 결정합니다 (대부분의 경우). 이는 누군가에게 로그 및 알림을 보내거나 다른 것을 의미 할 수 있습니다. 예를 들어 AbortException, 이는 현재 실행 쓰레드를 깨뜨리고 에러가 발생하지 않도록하기 때문에 에러를 기록하지 않는다는 뜻입니다. 물론, 모든 사용자 정의 스레드는 try {...} catch (all)로 run () 메소드를 처리해야합니다.

공공 무효 실행 () {

try {
     ... do something ...
} catch (Throwable throwable) {
     ApplicationContext.getExceptionService().handleException("Handle this exception", throwable);
}

}

WorkerService를 사용하여 모든 것을 처리하는 작업 (Runnable, Callable, Worker)을 예약하는 경우에는 필요하지 않습니다.

물론 이것은 단지 제 의견이며 올바른 것이 아니 겠지만 좋은 접근 방식입니다. 나는 그것이 나에게 좋다고 생각한다면 프로젝트를 공개 한 후에 볼 것이다. 그것은 다른 사람들에게도 좋다. :)


SNR

첫째, 확인 된 예외는 코드의 "신호 대 잡음비"를 줄입니다. Anders Hejlsberg도 유사한 개념 인 명령형 프로그래밍과 선언적 프로그래밍에 대해 이야기합니다. 어쨌든 다음 코드 스 니펫을 고려하십시오.

Java UI가 아닌 스레드에서 UI 업데이트 :

try {  
    // Run the update code on the Swing thread  
    SwingUtilities.invokeAndWait(() -> {  
        try {
            // Update UI value from the file system data  
            FileUtility f = new FileUtility();  
            uiComponent.setValue(f.readSomething());
        } catch (IOException e) {  
            throw new UncheckedIOException(e);
        }
    });
} catch (InterruptedException ex) {  
    throw new IllegalStateException("Interrupted updating UI", ex);  
} catch (InvocationTargetException ex) {
    throw new IllegalStateException("Invocation target exception updating UI", ex);
}

C #의 비 UI 스레드에서 UI 업데이트 :

private void UpdateValue()  
{  
   // Ensure the update happens on the UI thread  
   if (InvokeRequired)  
   {  
       Invoke(new MethodInvoker(UpdateValue));  
   }  
   else  
   {  
       // Update UI value from the file system data  
       FileUtility f = new FileUtility();  
       uiComponent.Value = f.ReadSomething();  
   }  
}  

어느 것이 나에게 훨씬 더 분명해 보입니다. Swing에서 UI 작업이 점점 더 많이 시작되면 체크 된 예외가 실제로 짜증나고 쓸모 없게됩니다.

감옥에 가라.

Java의 List 인터페이스와 같은 가장 기본적인 구현을 구현하기 위해 계약에 의한 설계 도구로서 예외 검사가 실패합니다. 확인 된 예외를 throw하는 데이터베이스 나 파일 시스템 또는 다른 구현에 의해 지원되는 목록을 고려하십시오. 가능한 유일한 구현은 검사 된 예외를 잡아서 검사되지 않은 예외로 다시 던지기위한 것입니다.

@Override
public void clear()  
{  
   try  
   {  
       backingImplementation.clear();  
   }  
   catch (CheckedBackingImplException ex)  
   {  
       throw new IllegalStateException("Error clearing underlying list.", ex);  
   }  
}  

이제 코드 전체에 어떤 점이 있는지 질문해야합니다. 확인 된 예외만으로는 잡음이 추가되고 예외는 발견되었지만 처리되지는 않으며 계약에 의한 설계 (점검 된 예외 측면에서)가 고장났습니다.

결론

  • 예외를 잡는 것은 예외를 처리하는 것과 다릅니다.
  • 확인 된 예외는 코드에 잡음을 추가합니다.
  • 예외 처리는 C # 없이는 잘 작동합니다.

나는 previously 에 이것 previously 관해 blogged했다 .


필자는 지난 3 년 동안 비교적 복잡한 응용 프로그램에서 여러 개발자와 함께 작업 해 왔습니다. Checked Exception을 적절한 오류 처리와 함께 사용하는 코드 기반과 그렇지 않은 코드 기반이 있습니다.

지금까지는 Checked Exceptions를 사용하여 코드 기반으로 작업하는 것이 더 쉬웠습니다. 다른 사람의 API를 사용할 때 코드를 호출하고 올바르게 처리 할 때 어떤 종류의 오류 조건이 발생 하는지를 알 수 있습니다. 로그, 표시 또는 무시 (예, 무시할 수있는 유효한 사례가 있습니다. 예외 (ClassLoader 구현 등) 이렇게하면 코드를 작성하고 복구 할 기회를 얻게됩니다. 모든 런타임 예외는 캐시되고 일부 일반 오류 처리 코드로 처리 될 때까지 전파됩니다. 특정 레벨에서 처리하고 싶지 않은 확인 된 예외를 찾거나 프로그래밍 논리 오류를 고려하면 런타임 오류로 감싸서 버블 링합니다. 정당한 이유없이 예외를 삼가해라. (그리고 이것을하는 좋은 이유는 드물다.)

체크 된 예외가없는 코드베이스로 작업 할 때 함수를 호출 할 때 기대할 수있는 것이 무엇인지 알기가 더 어렵습니다.

물론 이것은 선호도와 개발자 기술의 문제입니다. 프로그래밍과 오류 처리의 두 가지 방법 모두 똑같이 효과적 일 수 있습니다 (또는 비 효과적). 그래서 나는 편도가 있다고 말할 수 없습니다.

결론적으로, 체크 된 예외, 특히 많은 개발자가있는 대규모 프로젝트에서 작업하는 것이 더 쉽습니다.


CheckedExceptionsAreIncompatibleWithVisitorPattern 내 글은 여전히 ​​원래 형식과 거의 동일합니다 : CheckedExceptionsAreIncompatibleWithVisitorPattern

요약하자면:

방문자 패턴과 그 친척은 간접 호출자와 인터페이스 구현 모두 예외에 대해 알고 있지만 인터페이스와 직접 호출자는 알 수없는 라이브러리를 형성하는 인터페이스 클래스입니다.

CheckedExceptions의 기본 가정은 선언 된 모든 예외가 해당 선언으로 메소드를 호출하는 모든 지점에서 발생할 수 있다는 것입니다. VisitorPattern은이 가정이 잘못되었음을 나타냅니다.

이와 같은 경우에 확인 된 예외의 최종 결과는 런타임에 컴파일러의 확인 된 예외 제약 조건을 근본적으로 제거하는 많은 쓸데없는 코드입니다.

근본적인 문제에 관해서는 :

내 생각은 최상위 처리기가 예외를 해석하고 적절한 오류 메시지를 표시해야한다는 것입니다. 거의 항상 IO 예외, 통신 예외 (API가 구별 됨) 또는 작업 치명적인 오류 (프로그램 버그 또는 백업 서버의 심각한 문제)가 표시되므로 심각한 오류에 대해 스택 추적을 허용하면 너무 어렵지 않습니다. 서버 문제.


확인 된 예외의 문제점은 해당 인터페이스의 한 구현으로도 인터페이스의 메소드에 예외가 첨부되는 경우가 종종 있습니다.

확인 된 예외의 또 다른 문제점은 오용되는 경향이 있다는 것입니다. 이것의 완벽한 예에 java.sql.Connection의의 close()방법. SQLException이미 Connection에 대한 작업이 완료 되었다고 명시 적으로 명시 했음에도 불구하고 A를 throw 할 수 있습니다 . 당신이 관심을 가질만한 어떤 정보가 전달 될 수 있습니까?

일반적으로 연결을 닫으면 () *다음과 같이 보입니다.

try {
    conn.close();
} catch (SQLException ex) {
    // Do nothing
}

또한, 다양한 구문 분석 메서드 및 NumberFormatException 시작하지 마라. 예외를 throw하지 않는 .NET의 TryParse는 사용하기가 훨씬 쉽다. Java로 돌아 가야한다는 것은 고통 스럽다. (자바와 내가 일하는 C #).

*추가 코멘트로서, PooledConnection의 Connection.close ()는 접속을 닫지 는 않습니다 만, 체크 된 예외이기 (위해) 때문에 SQLException를 캐치 할 필요가 있습니다.


간단히 말해서 :

예외는 API 디자인 질문입니다. -- 그 이상도 이하도 아닌.

확인 된 예외에 대한 인수는 다음과 같습니다.

확인 된 예외가 좋지 않을 수있는 이유를 이해하려면 질문을 돌아서 질문 해 봅니다 : 언제, 또는 왜 예외가 매력적인 지, 즉 컴파일러가 예외 선언을 시행하도록하려는 이유는 무엇입니까?

때로는 : 대답은 분명 필요 예외를 잡으려고하고, 호출되는 코드에 관심이 오류에 대한 고유의 예외 클래스를 제공하는 경우 그에만 가능합니다.

따라서, 인수 에 대한 확인 예외는 컴파일러는 예외가 슬로우되는 선언 프로그래머를 강제하고 있다는 것입니다 희망 프로그래머가 다음 특정 예외 클래스와 그 원인이 오류를 문서화합니다.

그러나 실제로는 패키지 가 특정 하위 클래스 com.acmeAcmeException아닌 너무 자주 throw 됩니다. 호출자는 처리하거나 선언하거나 다시 신호 AcmeExceptions를 보내야하지만 AcmeFileNotFoundError발생 했는지 여부는 여전히 확실하지 않습니다 AcmePermissionDeniedError.

따라서 만약 당신이에 관심이 있다면 AcmeFileNotFoundError솔루션은 ACME 프로그래머에게 기능 요청을 제출하고 그 서브 클래스를 구현, 선언 및 문서화하도록 지시하는 것입니다 AcmeException.

왜 그렇게 괴롭습니까?

따라서 검사 된 예외가 있더라도 컴파일러는 프로그래머가 유용한 예외 를 throw하도록 할 수 없습니다 . API의 품질에 관한 질문 일뿐입니다.

결과적으로 검사 예외가없는 언어는 대개 훨씬 나쁘지 않습니다. 프로그래머는 일반 Error클래스보다 불특정 한 인스턴스를 던지기를 유혹받을 수 AcmeException있지만 API 품질에 대해 신경 쓰면 결국 도입하는 법을 배우게됩니다 AcmeFileNotFoundError.

전반적으로, 예외의 명세 및 문서화는 일반적인 방법의 명세 및 문서화와 크게 다르지 않다. 그것도 API 디자인 질문입니다. 프로그래머가 유용한 기능을 구현하거나 내보내는 것을 잊어 버린 경우 유용하게 사용할 수 있도록 API를 개선해야합니다.

이 추론을 따르는 경우, Java와 같은 언어에서 흔히 발생하는 예외의 선언, 포착, 재발행의 "번거 로움"은 종종 가치가 거의 없다는 것을 분명히해야합니다.

또한 자바 VM은 않음을 주목할 필요가 없습니다 만 자바 컴파일러를 확인하고 변경된 예외 선언과 클래스 파일은 실행시에 호환 - 체크 된 예외가 있습니다. Java VM 보안은 코딩 된 스타일 만 검사 예외로 개선되지 않습니다.


나는 이것이 낡은 질문이라는 것을 알고 있지만, 나는 체크 된 예외와 씨름하는 동안을 보냈다. 그리고 나는 추가 할 것이있다. 그 길이만큼 나를 용서해주십시오!

확인 된 예외가있는 주요 쇠고기는 다형성을 망칠 수 있다는 것입니다. 다형성 인터페이스로 멋지게 플레이하는 것은 불가능합니다.

좋은 '자바 List 인터페이스를 가져 가라. 우리는 ArrayListLinkedList 와 같은 일반적인 인 메모리 구현을 가지고 있습니다. 새로운 유형의 목록을 쉽게 디자인 할 수 있도록하는 골격 클래스 인 AbstractList 도 있습니다. 읽기 전용 목록의 경우 size()get(int index) 두 가지 메소드 만 구현하면됩니다.

이 예제 WidgetList 클래스는 파일에서 Widget 유형의 일부 고정 크기 객체 (표시되지 않음)를 읽습니다.

class WidgetList extends AbstractList<Widget> {
    private static final int SIZE_OF_WIDGET = 100;
    private final RandomAccessFile file;

    public WidgetList(RandomAccessFile file) {
        this.file = file;
    }

    @Override
    public int size() {
        return (int)(file.length() / SIZE_OF_WIDGET);
    }

    @Override
    public Widget get(int index) {
        file.seek((long)index * SIZE_OF_WIDGET);
        byte[] data = new byte[SIZE_OF_WIDGET];
        file.read(data);
        return new Widget(data);
    }
}

익숙한 List 인터페이스를 사용하여 위젯을 노출하면 항목 ( list.get(123) )을 검색하거나 WidgetList 자체에 대해 알 필요없이 for (Widget w : list) ... )을 반복 할 수 있습니다. 이 목록을 일반 목록을 사용하는 모든 표준 메서드에 전달하거나 Collections.synchronizedList 래핑 할 수 있습니다. 이 코드를 사용하는 코드는 "위젯"이 그 자리에서 만들어 졌는지, 배열에서 왔는지, 파일, 데이터베이스에서 읽었는지, 네트워크를 통해 읽었는지, 미래의 부분 공간 릴레이에서 읽는지 여부를 알거나 신경 쓰지 않아도됩니다. List 인터페이스가 올바르게 구현 되었기 때문에 여전히 올바르게 작동합니다.

그것을 제외하고. 파일 액세스 메소드가 "catch 또는 지정"해야하는 확인 된 예외 인 IOException 던질 수 있기 때문에 위의 클래스는 컴파일되지 않습니다. 던져진 것처럼 지정할 수 없습니다 . 컴파일러는 List 인터페이스의 계약에 위배 될 수 있기 때문에이를 허용하지 않습니다. 그리고 WidgetList 자체가 예외를 처리 할 수있는 유용한 방법이 없습니다 (나중에 설명 WidgetList ).

분명히 할 일은 확인되지 않은 예외를 catch하고 다시 throw하는 것입니다.

@Override
public int size() {
    try {
        return (int)(file.length() / SIZE_OF_WIDGET);
    } catch (IOException e) {
        throw new WidgetListException(e);
    }
}

public static class WidgetListException extends RuntimeException {
    public WidgetListException(Throwable cause) {
        super(cause);
    }
}

(편집 : Java 8은이 케이스에 대해 UncheckedIOException 클래스를 추가했다 : 다형 메소드 경계를 넘는 IOException 을 잡아서 다시 던지기위한 것이다.

따라서 체크 된 예외 이와 같은 경우 작동하지 않습니다 . 당신은 그들을 던질 수 없습니다. 데이터베이스가 지원하는 영리한 Map 위한 Ditto 또는 COM 포트를 통해 양자 엔트로피 소스에 연결된 java.util.Random 의 구현. 다형성 인터페이스의 구현으로 새로운 것을 시도하자마자 확인 된 예외 개념은 실패합니다. 그러나 체크 된 예외는 매우 교활하여 여전히 당신을 안심시키지 못합니다. 왜냐하면 하위 레벨 메소드를 포착하고 다시 재사용해야하기 때문에 코드가 복잡해지고 스택 트레이스가 복잡해지기 때문입니다.

나는 유비쿼터스 Runnable 인터페이스가 체크 된 예외를 던지는 것을 호출 할 때이 코너로 종종 후퇴한다는 것을 발견했다. 예외를 그대로 던질 수는 없으므로 RuntimeException 으로 catch하고 다시 throw하여 코드를 복잡하게 만들 수 있습니다.

실제로 해킹에 의지하면 선언되지 않은 예외를 throw 할 수 있습니다 . JVM은 런타임에 체크 예외 규칙을 신경 쓰지 않으므로 컴파일러 만 속일 필요가 있습니다. 가장 쉬운 방법은 제네릭을 남용하는 것입니다. 이것은 (Java 8 이전에 일반적인 메소드의 호출 구문에서 필요하기 때문에) 클래스 이름을 표시하는 나의 메소드입니다.

class Util {
    /**
     * Throws any {@link Throwable} without needing to declare it in the
     * method's {@code throws} clause.
     * 
     * <p>When calling, it is suggested to prepend this method by the
     * {@code throw} keyword. This tells the compiler about the control flow,
     * about reachable and unreachable code. (For example, you don't need to
     * specify a method return value when throwing an exception.) To support
     * this, this method has a return type of {@link RuntimeException},
     * although it never returns anything.
     * 
     * @param t the {@code Throwable} to throw
     * @return nothing; this method never returns normally
     * @throws Throwable that was provided to the method
     * @throws NullPointerException if {@code t} is {@code null}
     */
    public static RuntimeException sneakyThrow(Throwable t) {
        return Util.<RuntimeException>sneakyThrow1(t);
    }

    @SuppressWarnings("unchecked")
    private static <T extends Throwable> RuntimeException sneakyThrow1(
            Throwable t) throws T {
        throw (T)t;
    }
}

만세! 이것을 사용하면, RuntimeException 랩핑하지 않고 스택 추적을 어지럽히 지 않고 선언 된 예외를 체크하지 않고 스택의 모든 깊이를 검사 할 수 있습니다! "WidgetList"예제를 다시 사용 :

@Override
public int size() {
    try {
        return (int)(file.length() / SIZE_OF_WIDGET);
    } catch (IOException e) {
        throw sneakyThrow(e);
    }
}

불행히도 검사 예외의 최종 모욕은 컴파일러가 결함이있는 의견으로 던져 질 수없는 경우 검사 예외를 잡는 것을 거부한다는 것입니다. (확인되지 ​​않은 예외에는이 규칙이 없습니다.) 숨겨진 예외를 잡으려면 다음을 수행해야합니다.

try {
    ...
} catch (Throwable t) { // catch everything
    if (t instanceof IOException) {
        // handle it
        ...
    } else {
        // didn't want to catch this one; let it go
        throw t;
    }
}

약간 어색하지만, 더하기 측면에서, RuntimeException 에 싸여있는 체크 된 예외를 추출하는 코드보다 여전히 약간 더 간단합니다.

행복하게, throw t; Java 7에서 catch 된 예외를 재발행하는 규칙이 추가되어서 t 유형이 점검 되었더라도 여기서는 합법적입니다.

검사 된 예외가 다형성을 만족하면 반대의 경우도 문제가됩니다. 즉, 메서드가 확인 된 예외를 throw 할 가능성이있는 것으로 specified되었지만 재정의 된 구현은 그렇지 않은 경우입니다. 예를 들어, abstract 클래스 OutputStreamwrite 메소드는 모두 throws IOException 지정합니다. ByteArrayOutputStream 는, 실제의 입출력 소스 대신에, 메모리 내부의 배열에 기입하는 서브 클래스입니다. 오버라이드 (override) 된 write 메소드는 IOException 발생시킬 수 없기 때문에 throws 절이 없으므로 catch-or-specify 요구 사항에 대한 걱정없이 호출 할 수 있습니다.

항상 그런 것은 아닙니다. Widget 에 스트림에 저장하는 메소드가 있다고 가정 해 Widget .

public void writeTo(OutputStream out) throws IOException;

보통의 OutputStream 을 받아들이도록이 메서드를 선언하는 것이 옳은 일이므로 모든 종류의 출력 (파일, 데이터베이스, 네트워크 등)과 함께 다형 적으로 사용할 수 있습니다. 그리고 메모리 내 배열. 그러나 메모리 내 배열을 사용하면 실제로 발생할 수없는 예외를 처리하기위한 가짜 요구 사항이 있습니다.

ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
    someWidget.writeTo(out);
} catch (IOException e) {
    // can't happen (although we shouldn't ignore it if it does)
    throw new RuntimeException(e);
}

평소처럼 검사 된 예외가 발생합니다. 변수가 개방형 예외 요구 사항이 많은 기본 유형으로 선언 된 경우 해당 예외가 응용 프로그램에서 발생하지 않는다고 판단되는 경우에도 해당 예외에 대한 핸들러를 추가해야합니다.

그러나 잠깐 만요, 체크 된 예외는 실제로 그렇게 성가 시며, 심지어 당신이 그 반대를하도록하지 않을 것입니다! 현재 OutputStream 에서 write 호출에 의해 던져진 IOException 잡았지만 변수의 선언 된 유형을 ByteArrayOutputStream 으로 변경하려는 경우 컴파일러는 throw 될 수없는 확인 된 예외를 잡으려고 시도한 것에 대해 당신을 경멸합니다.

그 규칙은 어리석은 문제를 일으킨다. 예를 들어, OutputStream 의 3 개의 write 메소드의 1 개는, ByteArrayOutputStream 의해 오버라이드 (override) 되지 않습니다 . 특히, write(byte[] data) 는 offset (0)과 배열의 길이로 write(byte[] data, int offset, int length) 를 호출하여 전체 배열을 쓰는 편리한 메소드입니다. ByteArrayOutputStream 는 3 개의 인수를 가지는 메소드를 오버라이드 (override) 합니다만, 1 개의 인수를 가지는 편리한 메소드를 그대로 상속합니다. 상속 된 메서드는 정확하게 올바른 작업을 수행하지만 원치 않는 throws 절이 포함됩니다. 아마 ByteArrayOutputStream 디자인에서의 감독 이었지만, 예외를 잡아내는 코드와의 소스 호환성을 깨뜨리지 않기 때문에 결코 수정할 수 없습니다. 예외는 결코 없었고 결코 던지지 않을 것입니다!

이 규칙은 편집 및 디버깅 중에도 성가신 일입니다. 예를 들어, 때때로 메소드 호출을 일시적으로 주석 처리하고, 체크 예외를 발생시킬 수 있다면 컴파일러는 이제 로컬 trycatch 블록의 존재에 대해 불평 할 것입니다. 그래서 나는 그것들을 주석 처리해야만합니다. 그리고 지금 코드를 편집 할 때, IDE는 {} 가 주석 처리되었으므로 잘못된 레벨로 들여 쓰기됩니다. 가! 작은 불만이지만 예외를 확인한 유일한 문제는 문제를 일으키는 것 같습니다.

거의 끝났어. 확인 된 예외에 대한 나의 마지막 불만 은 대부분의 호출 사이트 에서 사용자가 할 수있는 유용한 방법이 없다는 것입니다. 문제가 발생하면 사용자에게 문제를 알리고 적절하게 작업을 끝내거나 다시 시도 할 수있는 유능한 응용 프로그램 별 처리기를 사용하는 것이 이상적입니다. 스택의 상위 핸들러 만이 전체 목표를 알고있는 유일한 핸들러이기 때문에이 작업을 수행 할 수 있습니다.

대신 컴파일러를 종료하는 방법으로 만연한 다음 관용구를 얻습니다.

try {
    ...
} catch (SomeStupidExceptionOmgWhoCares e) {
    e.printStackTrace();
}

GUI 또는 자동화 된 프로그램에서는 인쇄 된 메시지가 표시되지 않습니다. 더 나쁜 것은 예외 이후 코드의 나머지 부분을 계속 사용합니다. 예외가 실제로 오류가 아닌가? 그런 다음 인쇄하지 마십시오. 그렇지 않으면, 뭔가 다른 것이 순간에 파열 될 것이고, 원래 예외 객체는 사라질 것입니다. 이 관용구는 BASIC의 On Error Resume Next 나 PHP의 error_reporting(0); .

어떤 종류의 로거 클래스를 호출하는 것이 그리 나은 것은 아닙니다.

try {
    ...
} catch (SomethingWeird e) {
    logger.log(e);
}

e.printStackTrace(); 와 마찬가지로 게으르다 e.printStackTrace(); 여전히 불확정 상태의 코드로 계속 작동합니다. 또한 특정 로깅 시스템이나 다른 처리기의 선택은 응용 프로그램마다 다르므로 코드 재사용이 어려워집니다.

하지만 기다려! 응용 프로그램 별 처리기를 찾는 쉽고 보편적 인 방법이 있습니다. 그것은 호출 스택보다 높거나 스레드의 캐치되지 않는 예외 처리기 로 설정됩니다. 따라서 대부분의 경우 스택에서 예외를 상위에 던지기 만하면 됩니다. 예 : throw e; . 확인 된 예외는 방해가됩니다.

언어가 설계되었을 때 검사 된 예외가 좋은 생각처럼 들리 겠지만 실제로는 그 모든 것이 귀찮고 도움이되지 않는 것으로 나타났습니다.







checked-exceptions