몇 년 동안 나는 다음 질문에 대한 적절한 대답을 얻을 수 없었습니다. 왜 일부 개발자는 확인 된 예외에 대해 그렇게합니까? 나는 수많은 대화를 나누고 블로그에서 글을 읽고 Bruce Eckel이 말한 것을 읽었습니다.
나는 현재 새로운 코드를 작성하고 있으며 예외를 처리하는 방법에 매우주의를 기울이고 있습니다. “체크 된 예외를 좋아하지 않습니다”라는 군중의 관점을 보려고하는데 여전히 볼 수 없습니다.
내가 가진 모든 대화는 같은 질문으로 대답하지 않고 끝납니다 … 설정하겠습니다.
일반적으로 (Java가 설계된 방식에서)
Error
잡히지 말아야 할 것들입니다 (VM에는 땅콩 알레르기가 있고 누군가 땅콩 항아리를 떨어 뜨 렸습니다)RuntimeException
프로그래머가 잘못한 일을위한 것입니다 (프로그래머가 배열의 끝에서 나갔습니다)Exception
(제외RuntimeException
)는 프로그래머가 제어 할 수없는 것들을위한 것입니다 (파일 시스템에 쓰는 동안 디스크가 가득 차고 프로세스의 파일 핸들 한계에 도달했으며 더 이상 파일을 열 수 없습니다)Throwable
단순히 모든 예외 유형의 부모입니다.
내가 듣는 일반적인 주장은 예외가 발생하면 모든 개발자가 프로그램을 종료한다는 것입니다.
내가 듣는 또 다른 일반적인 주장은 확인 된 예외로 인해 코드를 리팩터링하기가 더 어렵다는 것입니다.
“내가하려고하는 것은 모두 종료이다”라는 주장에 대해, 나는 당신이 나가더라도 합리적인 오류 메시지를 표시해야한다고 말합니다. 오류를 처리하는 데 어려움을 겪고 있다면 이유를 명확하게 표시하지 않고 프로그램이 종료 될 때 사용자는 지나치게 만족하지 않을 것입니다.
“리팩토링하기가 어렵습니다”군중의 경우 적절한 추상화 수준이 선택되지 않았 음을 나타냅니다. 오히려 방법은 발생을 선언보다 IOException
상기가 IOException
더 진행되는 것에 대해 적합 예외로 변환한다.
Main으로 줄 바꿈하는 데 문제가 없으며 catch(Exception)
(또는 경우 catch(Throwable)
에 따라 프로그램을 정상적으로 종료 할 수 있도록 보장하지만 항상 필요한 특정 예외를 잡습니다. 에러 메시지.
사람들이 대답하지 않는 질문은 다음과 같습니다.
만약 당신이
RuntimeException
서브 클래스 대신 서브 클래스 를 던진다면Exception
무엇을 잡아야하는지 어떻게 알 수 있습니까?
대답이 잡히면 Exception
시스템 예외와 같은 방식으로 프로그래머 오류도 처리합니다. 그것은 나에게 잘못 보인다.
잡으면 Throwable
시스템 예외와 VM 오류 등을 같은 방식으로 처리합니다. 그것은 나에게 잘못 보인다.
대답은 당신이 알고있는 예외 만 잡는다는 것입니다. 그렇다면 어떤 예외가 발생했는지 어떻게 알 수 있습니까? 프로그래머 X가 새로운 예외를 던지고 그것을 포착하지 못한 경우 어떻게됩니까? 그것은 나에게 매우 위험한 것 같습니다.
스택 추적을 표시하는 프로그램이 잘못되었다고 말할 수 있습니다. 확인 된 예외를 좋아하지 않는 사람들은 그렇게 느끼지 않습니까?
따라서 확인 된 예외가 마음에 들지 않으면 왜 응답하지 않는 질문에 대답하지 않습니까?
편집 : 나도 모델을 사용하는 경우에 대한 조언을 찾고 있지 않다, 내가 무엇을 찾고있는 것은 왜 사람들이에서 연장 RuntimeException
되지에서 연장처럼 그들이 때문에 Exception
및 / 또는 왜 그들은 예외를 캐치 한 다음 다시 발생 RuntimeException
하지 않고 추가가 발생보다 그들의 방법. 확인 된 예외를 싫어하는 동기를 이해하고 싶습니다.
답변
나는 당신이했던 것과 같은 Bruce Eckel 인터뷰를 읽은 것 같아요. 항상 나를 괴롭 혔습니다. 실제로, 인터뷰 대상자는 .NET과 C #의 MS 천재 인 Anders Hejlsberg가 인터뷰 대상자에 의해 주장되었습니다.
내가 Hejlsberg와 그의 작품을 좋아하는 팬이지만,이 주장은 항상 나를 가짜로 부딪쳤다. 기본적으로 다음과 같이 요약됩니다.
“확인 된 예외는 프로그래머가 예외를 잡아서 무시함으로써 남용하기 때문에 문제가 숨겨 지거나 무시되어 사용자에게 표시 될 수 있기 때문에 예외입니다.”
에 의해 “그렇지 않으면 사용자에게 제공” 당신이 런타임 예외를 사용하는 경우 내 말은 게으른 프로그래머는 그냥 (빈 catch 블록으로 잡는 대) 무시하고 사용자가 표시됩니다.
논쟁의 요약은 “프로그래머는 그것들을 제대로 사용하지 못하고 제대로 사용하지 않는 것이 그들을 갖지 않는 것보다 나쁘다”는 것이다.
이 주장에는 약간의 진실이 있으며 실제로 Java에서 운영자 재정의를하지 않는 Goslings 동기가 비슷한 주장에서 비롯된 것 같습니다. 프로그래머가 종종 학대를 당하기 때문에 프로그래머를 혼란스럽게합니다.
그러나 결국, 나는 그것이 Hejlsberg ‘s와 아마도 사소한 결정이 아니라 부족을 설명하기 위해 만들어진 사후 주장에 대한 가짜 주장을 발견합니다.
확인 된 예외를 과도하게 사용하는 것은 나쁜 일이며 사용자가 부주의하게 처리하는 경향이 있지만 API를 올바르게 사용하면 API 프로그래머가 API 클라이언트 프로그래머에게 큰 이점을 줄 수 있습니다.
이제 API 프로그래머는 모든 곳에서 확인 된 예외를 발생시키지 않도록주의해야합니다. 그렇지 않으면 클라이언트 프로그래머를 귀찮게합니다. 매우 게으른 클라이언트 프로그래머는 (Exception) {}
Hejlsberg가 경고하고 모든 혜택을 잃어 버리고 지옥이 뒤 따르는 것에 따라 잡을 것입니다. 그러나 어떤 상황에서는 잘 확인 된 예외를 대신 할 수있는 방법이 없습니다.
나에게 고전적인 예는 파일 열기 API입니다. 언어 역사 (파일 시스템 이상)의 모든 프로그래밍 언어에는 파일을 열 수있는 API가 있습니다. 이 API를 사용하는 모든 클라이언트 프로그래머는 열려고하는 파일이 존재하지 않는 경우를 처리해야한다는 것을 알고 있습니다. 이 API를 사용하는 모든 클라이언트 프로그래머 는이 사례를 처리 해야한다는 것을 알아야 합니다. API 프로그래머가 주석만으로 처리해야 함을 알릴 수 있거나 실제로 클라이언트가 처리하도록 요구할 수 있습니다 .
C에서 관용구는 다음과 같습니다.
if (f = fopen("goodluckfindingthisfile")) { ... }
else { // file not found ...
여기서 fopen
0과 C를 반환하여 실패를 나타냅니다 (어리석게도) 0을 부울로 취급하고 기본적 으로이 관용구를 배우면 괜찮습니다. 그러나 당신이 멍청하고 관용구를 배우지 않으면 어떻게 될까요? 물론, 당신은 시작합니다
f = fopen("goodluckfindingthisfile");
f.read(); // BANG!
어려운 방법을 배우십시오.
여기서는 강력한 형식의 언어에 대해서만 이야기하고 있습니다. 강력한 형식의 언어로 API가 무엇인지에 대한 명확한 아이디어가 있습니다. 각 언어에 대해 명확하게 정의 된 프로토콜과 함께 사용할 수있는 기능 (메소드)입니다.
명확하게 정의 된 프로토콜은 일반적으로 메소드 서명으로 정의됩니다. 여기서 fopen은 문자열 (또는 C의 경우 char *)을 전달해야합니다. 다른 것을 주면 컴파일 타임 오류가 발생합니다. 프로토콜을 따르지 않았습니다. API를 올바르게 사용하고 있지 않습니다.
일부 (불분명 한) 언어에서는 반환 유형도 프로토콜의 일부입니다. fopen()
변수에 할당하지 않고 일부 언어에서 동등한 언어 를 호출하려고 하면 컴파일 타임 오류가 발생합니다 (void 함수에서만 가능합니다).
: 내가 만들려고 점이다 정적 타입 언어에서 API 프로그래머는 명백한 실수를하는 경우 컴파일에서 자신의 클라이언트 코드를 방지하여 제대로 API를 사용하도록 클라이언트를 권장합니다.
(루비와 같이 동적으로 유형이 지정된 언어에서는 파일 이름으로 플로트와 같은 것을 전달할 수 있으며 컴파일됩니다. 메소드 인수를 제어하지 않더라도 사용자에게 확인 된 예외가 발생하는 이유는 무엇입니까? 여기에 작성된 인수는 정적으로 유형이 지정된 언어에만 적용됩니다.)
그렇다면 확인 된 예외는 어떻습니까?
다음은 파일을 여는 데 사용할 수있는 Java API 중 하나입니다.
try {
f = new FileInputStream("goodluckfindingthisfile");
}
catch (FileNotFoundException e) {
// deal with it. No really, deal with it!
... // this is me dealing with it
}
캐치 보이죠? 해당 API 메소드의 서명은 다음과 같습니다.
public FileInputStream(String name)
throws FileNotFoundException
참고 FileNotFoundException
A는 확인 예외입니다.
API 프로그래머는 다음과 같이 말합니다. “이 생성자를 사용하여 새 FileInputStream을 만들 수 있지만
a) 파일 이름을 문자열로 전달 해야합니다.
b) 파일을 런타임에 찾지 못할 가능성을 수용 해야 합니다. “
그리고 그것이 내가 생각하는 한 요점입니다.
핵심은 기본적으로 질문이 “프로그래머의 통제를 벗어난 것들”이라고 말하는 것입니다. 저의 첫 생각은 API 프로그래머가 통제 할 수없는 것을 의미한다는 것입니다 . 그러나 실제로 올바르게 사용될 때 확인 된 예외는 실제로 클라이언트 프로그래머와 API 프로그래머가 제어하지 않는 것들에 대한 것이어야합니다. 이것이 이것이 확인 된 예외를 남용하지 않는 열쇠라고 생각합니다.
파일 열기가 그 요점을 잘 보여줍니다. API 프로그래머는 API를 호출 할 때 존재하지 않는 것으로 밝혀진 파일 이름을 제공 할 수 있으며 원하는 것을 반환 할 수는 없지만 예외를 throw해야한다는 것을 알고 있습니다. 또한이 문제는 정기적으로 발생하며 클라이언트 프로그래머는 호출 할 때 파일 이름이 정확할 것으로 예상 할 수 있지만 제어 할 수없는 이유로 인해 런타임에 잘못 될 수 있음을 알고 있습니다.
그래서 API는 그것을 명시 적으로 만듭니다 : 당신이 전화 할 때이 파일이 존재하지 않고 당신이 그것을 잘 다루는 경우가 있습니다.
이것은 카운터 케이스와 함께 더 명확합니다. 테이블 API를 작성한다고 상상해보십시오. 이 방법을 포함하여 API가있는 테이블 모델이 있습니다.
public RowData getRowData(int row)
API 프로그래머로서 일부 클라이언트가 테이블 외부의 행 또는 행 값에 대해 음수 값을 전달하는 경우가 있음을 알고 있습니다. 따라서 확인 된 예외를 던져서 클라이언트가 처리하도록 유혹 할 수 있습니다.
public RowData getRowData(int row) throws CheckedInvalidRowNumberException
(물론 “체크 됨”이라고 부르지는 않습니다.)
이것은 확인 된 예외를 잘못 사용하는 것입니다. 클라이언트 코드는 행 데이터를 가져 오기위한 호출로 가득 차 있습니다. 모두 시도 / 캐치를 사용해야하며 무엇을해야합니까? 잘못된 행을 찾았다 고 사용자에게보고합니까? 아마도 아닙니다-테이블 뷰를 둘러싼 UI가 무엇이든, 사용자가 불법 행이 요청되는 상태가되도록해서는 안됩니다. 그래서 그것은 클라이언트 프로그래머의 버그입니다.
API 프로그래머는 여전히 클라이언트가 이러한 버그를 코딩 할 것으로 예상하고와 같은 런타임 예외로 처리해야합니다 IllegalArgumentException
.
에서 예외를 확인하면 getRowData
Hejlsberg의 게으른 프로그래머가 단순히 빈 캐치를 추가하게되는 경우입니다. 이 경우 잘못된 행 값은 테스터 나 클라이언트 개발자에게조차 명확하지 않으며 대신 소스를 찾기 어려운 노크 오류가 발생합니다. 출시 후 Arianne 로켓이 폭발합니다.
문제가 있습니다. 확인 된 예외 FileNotFoundException
는 클라이언트 프로그래머에게 가장 유용한 방법으로 API를 정의하는 API 프로그래머 도구 상자의 좋은 도구 일뿐 만 아니라 필수 도구 라고 말하고 있습니다. 그러나 CheckedInvalidRowNumberException
큰 불편은 프로그래밍에 나쁜 영향을 미치므로 피해야합니다. 그러나 차이점을 말하는 방법.
나는 그것이 정확한 과학이 아니라고 생각하며 Hejlsberg의 주장에 어느 정도 근거하고 아마도 정당화 될 것으로 생각합니다. 그러나 나는 여기에 목욕물을 가지고 아기를 버리는 것이 기쁘지 않으므로 여기에서 규칙을 추출하여 좋은 예외를 나쁜 것과 구별 할 수있게하십시오.
-
고객의 통제 불능 또는 폐쇄 대 개방 :
확인 된 예외는 오류 사례가 API 와 클라이언트 프로그래머 가 제어 할 수없는 경우에만 사용해야합니다 . 이것은 시스템의 개방 또는 폐쇄 와 관련이 있습니다. 클라이언트 프로그래머가 테이블 뷰 (폐쇄 된 시스템)에서 행을 추가하고 삭제하는 모든 버튼, 키보드 명령 등을 제어 할 수 있는 제한된 UI에서 데이터를 가져 오려고 시도하면 클라이언트 프로그래밍 버그입니다. 존재하지 않는 행. 임의의 수의 사용자 / 응용 프로그램이 파일을 추가 및 삭제할 수있는 파일 기반 운영 체제 (개방형 시스템)에서 클라이언트가 요청한 파일이 지식없이 삭제되었으므로 처리해야합니다. .
-
편재:
클라이언트가 자주 수행하는 API 호출에는 확인 된 예외를 사용하지 않아야합니다. 나는 종종 클라이언트 코드의 많은 장소를 의미합니다. 따라서 클라이언트 코드는 동일한 파일을 많이 열려고하지 않지만 내 테이블보기는
RowData
다른 방법에서 온통 얻을 수 있습니다. 특히, 나는 다음과 같은 많은 코드를 작성할 것입니다if (model.getRowData().getCell(0).isEmpty())
그리고 매번 시도 / 잡기를 감싸는 것이 고통 스러울 것입니다.
-
사용자에게 알리기 :
최종 사용자에게 유용한 오류 메시지가 표시 될 수있는 경우 확인 된 예외를 사용해야합니다. 이것이 “어떻게됩니까?” 위의 질문. 또한 항목 1과 관련이 있습니다. 클라이언트 API 시스템 외부에있는 파일로 인해 파일이 없을 수 있다고 예상 할 수 있으므로 사용자에게 다음과 같이 합리적으로 말할 수 있습니다.
"Error: could not find the file 'goodluckfindingthisfile'"
불법 행 번호는 내부 버그와 사용자의 결함으로 인해 발생했기 때문에 실제로 제공 할 수있는 유용한 정보는 없습니다. 앱에서 런타임 예외가 콘솔로 넘어 가지 않도록하면 아마도 다음과 같은 추악한 메시지가 나타납니다.
"Internal error occured: IllegalArgumentException in ...."
간단히 말해, 클라이언트 프로그래머가 사용자에게 도움이되는 방식으로 예외를 설명 할 수 없다고 생각되면 체크 된 예외를 사용하지 않아야합니다.
이것이 저의 규칙입니다. 다소 의욕이 있었고 예외는 의심의 여지가 없습니다 (원한다면 수정하십시오). 그러나 내 주요 주장은 FileNotFoundException
확인 된 예외가 매개 변수 유형만큼 API 계약의 중요하고 유용한 경우와 같은 경우가 있다는 것 입니다. 따라서 오용 된 것만으로 그 사실을 배제해서는 안됩니다.
미안하지만, 이것을 너무 길고 와플하게 만들려는 것은 아닙니다. 두 가지 제안으로 마무리하겠습니다.
A : API 프로그래머 : 유용성을 유지하기 위해 점검 된 예외를 거의 사용하지 마십시오. 의심스러운 경우 검사되지 않은 예외를 사용하십시오.
B : 클라이언트 프로그래머 : 개발 초기에 랩핑 된 예외 (Google 예외)를 만드는 습관을들이십시오. JDK 1.4 이상에서는이를 RuntimeException
위한 생성자를 제공 하지만 쉽게 직접 생성 할 수도 있습니다. 생성자는 다음과 같습니다.
public RuntimeException(Throwable cause)
그런 다음 체크 된 예외를 처리해야하고 게으른 느낌이 들거나 (예 : API 프로그래머가 체크 된 예외를 처음 사용하는 데 지나치게 열중했다고 생각하는 경우) 습관을 들이고 예외를 삼키지 말고 감싸십시오. 다시 던져.
try {
overzealousAPI(thisArgumentWontWork);
}
catch (OverzealousCheckedException exception) {
throw new RuntimeException(exception);
}
이것을 IDE의 작은 코드 템플릿 중 하나에 넣고 게으른 느낌이들 때 사용하십시오. 이렇게하면 실제로 확인 된 예외를 처리 해야하는 경우 런타임에 문제를 본 후 다시 돌아와서 처리해야합니다. 저를 믿으십시오 (Anders Hejlsberg).
catch (Exception e) { /* TODO deal with this at some point (yeah right) */}
답변
확인 된 예외에 대한 것은 개념을 일반적으로 이해함으로써 실제로 예외가 아니라는 것입니다. 대신 API 대체 반환 값입니다.
예외에 대한 전체 아이디어는 콜 체인 아래로 발생하는 오류가 개입하여 코드에 대해 걱정할 필요없이 코드 어딘가에서 거품을 일으켜 처리 할 수 있다는 것입니다. 반면, 확인 된 예외는 던지기와 포수 간의 모든 수준의 코드가 예외를 통과 할 수있는 모든 형태의 예외에 대해 알고 있음을 선언해야합니다. 확인 된 예외가 단순히 호출자가 확인 해야하는 특별한 반환 값 인 경우 실제로는 거의 다릅니다. 예 : [의사 코드] :
public [int or IOException] writeToStream(OutputStream stream) {
[void or IOException] a= stream.write(mybytes);
if (a instanceof IOException)
return a;
return mybytes.length;
}
Java는 대체 반환 값 또는 간단한 인라인 튜플을 반환 값으로 수행 할 수 없으므로 확인 된 예외는 합리적인 응답입니다.
문제는 표준 라이브러리의 큰 스 와이드를 포함하여 많은 코드가 여러 수준을 잘 잡을 수있는 예외적 인 상황에서 예외를 잘못 사용한다는 것입니다. IOException이 RuntimeException이 아닌 이유는 무엇입니까? 다른 모든 언어에서는 IO 예외가 발생하도록 할 수 있으며 처리 할 작업이 없으면 응용 프로그램이 중지되고 유용한 스택 추적을 볼 수 있습니다. 이것이 일어날 수있는 가장 좋은 일입니다.
예제에서 두 개의 메소드를 사용하여 전체 스트림 작성 프로세스에서 모든 IOException을 포착하고 프로세스를 중단하고 오류보고 코드로 이동하십시오. Java에서는 모든 호출 수준, 심지어 IO가없는 수준까지 ‘throws IOException’을 추가하지 않으면 그렇게 할 수 없습니다. 이러한 메소드는 예외 처리에 대해 알 필요가 없습니다. 서명에 예외를 추가 해야하는 경우 :
- 불필요하게 커플 링을 증가시킨다;
- 인터페이스 서명을 변경하기 매우 취약하게 만듭니다.
- 코드를 읽기 어렵게 만듭니다.
- 일반적인 프로그래머의 반응은 ‘throws Exception’, ‘catch (Exception e) {}’와 같은 끔찍한 일을하거나 RuntimeException (디버깅을 더 어렵게 함)에 모든 것을 감싸서 시스템을 물리 치는 것입니다.
그리고 다음과 같은 어리석은 라이브러리 예외가 많이 있습니다.
try {
httpconn.setRequestMethod("POST");
} catch (ProtocolException e) {
throw new CanNeverHappenException("oh dear!");
}
이렇게 우스꽝스러운 코드로 코드를 복잡하게 만들어야 할 때 실제로 이것이 단순한 API 디자인이 아니더라도 확인 된 예외가 많은 증오를받는 것은 놀라운 일이 아닙니다.
또 다른 특별한 나쁜 영향은 구성 요소 A가 일반 구성 요소 B에 콜백을 제공하는 제어의 반전에 있습니다. B에 의해 수정 된 콜백 인터페이스가 변경되기 때문입니다. A는 예외를 더 많이 처리하는 상용구 인 RuntimeException에 실제 예외를 랩핑하여 처리 할 수 있습니다.
Java 및 표준 라이브러리에서 구현 된 확인 된 예외는 상용구, 상용구, 상용구를 의미합니다. 이미 장황한 언어에서는 이것이 승리가 아닙니다.
답변
확인 된 예외에 대해 모든 (다수) 이유를 다시 해치지 않고 하나만 선택하겠습니다. 이 코드 블록을 작성한 횟수를 잃었습니다.
try {
// do stuff
} catch (AnnoyingcheckedException e) {
throw new RuntimeException(e);
}
99 %의 시간 동안 나는 그것에 대해 아무것도 할 수 없습니다. 마지막으로 블록은 필요한 정리 작업을 수행합니다.
나는 또한 이것을 본 횟수를 잃어 버렸다.
try {
// do stuff
} catch (AnnoyingCheckedException e) {
// do nothing
}
왜? 누군가가 그것을 다루어야하고 게으 르기 때문에. 틀렸어? 확실한. 그런가요? 물론. 이것이 검사되지 않은 예외 인 경우 어떻게됩니까? 앱이 방금 사망했을 것입니다 (예외를 삼키는 것이 좋습니다).
그리고 java.text.Format 처럼 예외를 흐름 제어의 형태로 사용하는 화려한 코드 가 있습니다. Bzzzt. 잘못된. “abc”를 양식의 숫자 필드에 넣는 사용자는 예외가 아닙니다.
좋아, 그게 세 가지 이유 인 것 같아.
답변
나는 이것이 오래된 질문이라는 것을 알고 있지만 확인 된 예외로 레슬링을 보내고 있으며 추가 할 것이 있습니다. 그것의 길이 나를 용서 해주세요!
확인 된 예외를 가진 나의 주요 소고기는 다형성을 망치는 것입니다. 다형성 인터페이스로 멋지게 연주하는 것은 불가능합니다.
좋은 자바 List
인터페이스를 가져 가라 . 우리는 ArrayList
and 같은 일반적인 인 메모리 구현을 가지고 있습니다 LinkedList
. 또한 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)
하거나 ( for (Widget w : list) ...
) 목록을 반복 할 수 있습니다 ( ) WidgetList
. 이 목록을 일반 목록을 사용하는 모든 표준 메소드에 전달하거나로 묶을 수 Collections.synchronizedList
있습니다. 이 코드를 사용하는 코드는 “위젯”이 그 자리에서 만들어 졌는지, 어레이에서 가져 왔는지, 또는 파일이나 데이터베이스에서 읽거나 네트워크를 통해 읽거나 미래의 서브 스페이스 릴레이에서 읽거나 알 필요가 없습니다. List
인터페이스가 올바르게 구현 되었으므로 여전히 올바르게 작동합니다 .
그렇지 않다는 것을 제외하고. 위의 클래스는 파일 액세스 메소드가 IOException
“캐치 또는 지정”해야하는 확인 된 예외를 발생 시킬 수 있으므로 컴파일되지 않습니다 . 당신은 그것으로 던져 지정할 수 없습니다 – 즉 계약 위반 때문에 컴파일러가 당신을 못하게 List
인터페이스를. 그리고 WidgetList
나중에 설명 할 것처럼 자체적으로 예외를 처리 할 수있는 유용한 방법은 없습니다 .
분명히 할 일은 확인되지 않은 예외로 catch 된 예외를 catch하고 다시 던지는 것입니다.
@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 또는 java.util.Random
COM 포트를 통해 양자 엔트로피 소스 에 연결된 구현 . 다형성 인터페이스의 구현으로 새로운 것을 시도하자마자 확인 된 예외 개념이 실패합니다. 그러나 확인 된 예외는 너무 교활하여 여전히 안전하지 않습니다. 낮은 수준의 메소드를 잡아서 다시 던져서 코드를 어수선하게하고 스택 추적을 어수선하게해야하기 때문입니다.
유비쿼터스 Runnable
인터페이스가 확인 된 예외를 던지는 무언가를 호출하면 종종이 코너로 돌아갑니다. 그대로 예외를 던질 수 없으므로 할 수있는 모든 것은 코드를 잡아서 다시 던져서 코드를 어지럽히는 것 RuntimeException
입니다.
실제로 해킹에 의존하면 선언되지 않은 확인 된 예외를 던질 수 있습니다 . 런타임시 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
.
유감스럽게도, 예외를 다시 던지는 것에 대해 Java 7에 추가 된 규칙 덕분에 throw t;
유형 t
이 확인 되었지만 여기에서 합법적 입니다.
확인 된 예외가 다형성을 충족하면 반대의 경우도 문제가됩니다. 메소드가 잠재적으로 확인 된 예외를 throw하는 것으로 지정되었지만 재정의 된 구현은 그렇지 않습니다. 예를 들어, 추상 클래스 OutputStream
의 write
메소드는 모두 지정 throws IOException
합니다. ByteArrayOutputStream
실제 I / O 소스 대신 메모리 내 배열에 쓰는 서브 클래스입니다. 재정의 된 write
메소드는 IOException
s를 유발할 수 없으므로 throws
절이 없으므로 catch-or-specify 요구 사항에 대한 걱정없이 호출 할 수 있습니다.
항상 그렇지는 않습니다. 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);
}
평소와 같이 확인 된 예외가 발생합니다. 변수가 개방형 예외 요구 사항이 더 많은 기본 유형으로 선언 된 경우 응용 프로그램에서 해당 예외 가 발생하지 않는다는 것을 알고 있더라도 해당 예외에 대한 처리기를 추가 해야합니다.
그러나 확인 된 예외는 실제로 너무 성가 시므로 반대의 경우도 마찬가지입니다! 현재 에 대한 호출에서 IOException
발생하는 모든 것을 포착한다고 가정 하지만 변수의 선언 유형을 a로 변경 하려고하면 컴파일러는 throw 할 수 없다고 확인 된 예외를 잡으려고 시도하면 요금을 청구합니다.write
OutputStream
ByteArrayOutputStream
이 규칙은 일부 터무니없는 문제를 일으 킵니다. 예를 들어, 세 가지 중 하나 write
의 방법 OutputStream
입니다 하지 에 의해 오버라이드 (override) ByteArrayOutputStream
. 특히 오프셋 0과 배열 길이 write(byte[] data)
로 호출하여 전체 배열을 작성하는 편리한 방법입니다 write(byte[] data, int offset, int length)
. ByteArrayOutputStream
3 인수 메서드를 재정의하지만 1 인수 편의 메서드를 그대로 상속합니다. 상속 된 메소드는 옳은 일을하지만 원치 않는 throws
절을 포함합니다 . 그것은 아마도의 디자인에 대한 감독일지도 ByteArrayOutputStream
모르지만 예외를 잡는 코드와의 소스 호환성을 깨뜨릴 수 있기 때문에 그것을 고칠 수는 없습니다. 예외는 결코, 결코, 결코 던져지지 않을 것입니다!
그 규칙은 편집과 디버깅 중에도 성가시다. 예를 들어, 때로는 메소드 호출을 일시적으로 주석 처리하고 확인 된 예외가 발생했을 경우 컴파일러는 이제 로컬 try
및 catch
블록 의 존재에 대해 불평 합니다. 그래서 나도 그 주석해야하고, 내 코드를 편집 할 때 때문에 이제 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();
불확실한 상태의 코드와 마찬가지로 게으 르며 여전히 쟁기질입니다. 또한 특정 로깅 시스템 또는 다른 처리기의 선택은 응용 프로그램에 따라 다르므로 코드 재사용이 손상됩니다.
하지만 기다려! 응용 프로그램 별 처리기를 찾는 가장 쉽고 보편적 인 방법이 있습니다. 호출 스택보다 높거나 Thread의 catch되지 않은 예외 처리기 로 설정되어 있습니다. 따라서 대부분의 장소 에서 예외를 스택 위로 던지기 만하면 됩니다. 예, throw e;
. 확인 된 예외는 방해가됩니다.
나는 언어가 디자인되었을 때 확인 된 예외가 좋은 생각처럼 들릴 것이라고 확신하지만 실제로는 그것들이 모두 귀찮고 아무런 이점이 없다는 것을 알았습니다.
답변
글쎄, 그것은 스택 트레이스를 표시하거나 자동으로 충돌하는 것이 아닙니다. 계층 간 오류를 전달할 수 있다는 것입니다.
확인 된 예외의 문제는 사람들이 중요한 세부 사항 (즉, 예외 클래스)을 삼키도록 권장한다는 것입니다. 그 세부 사항을 삼키지 않기로 선택한 경우 전체 앱에서 던지기 선언을 계속 추가해야합니다. 이는 1) 새로운 예외 유형이 많은 함수 시그니처에 영향을 미치며, 2) 실제로 포착하려는 예외의 특정 인스턴스를 놓칠 수 있음을 의미합니다 (예 : 데이터를 보조 파일은 선택 사항이므로 오류를 무시할 수 있지만 서명 throws IOException
이므로이를 간과하기 쉽습니다).
실제로 응용 프로그램 에서이 상황을 처리하고 있습니다. 거의 예외를 AppSpecificException으로 다시 패키지했습니다. 이로 인해 서명이 정말 깨끗해졌으며 서명 폭발 throws
에 대해 걱정할 필요가 없었습니다 .
물론, 이제 재시도 로직 등을 구현하여 더 높은 수준의 오류 처리를 전문화해야합니다. 그러나 모든 것은 AppSpecificException이므로 “IOException이 발생하면 다시 시도하십시오”또는 “ClassNotFound가 발생하면 완전히 중단하십시오”라고 말할 수 없습니다. 우리는 코드와 타사 코드 사이를 통과하면서 물건이 계속해서 다시 포장되기 때문에 실제 예외에 도달 하는 신뢰할 수있는 방법이 없습니다 .
이것이 내가 파이썬에서 예외 처리를 좋아하는 이유입니다. 당신이 원하는 것 그리고 / 또는 처리 할 수있는 것만 잡을 수 있습니다. 마치 자신을 다시 던지는 것처럼 다른 모든 것이 버블 링됩니다 (어쨌든 수행 한 것).
필자는 몇 번이고 여러 번 발견했으며 언급 한 프로젝트 전체에서 예외 처리는 3 가지 범주로 나뉩니다.
- 특정 예외를 포착하고 처리하십시오 . 예를 들어, 이것은 재시도 로직을 구현하기위한 것입니다.
- 다른 예외를 잡아서 다시 던지십시오 . 여기서 일어나는 모든 일은 보통 로깅이며, “$ filename을 열 수 없습니다”와 같은 간단한 메시지입니다. 이것들은 당신이 할 수없는 오류입니다. 더 높은 레벨 만 처리 할 수 있습니다.
- 모든 것을 잡아서 오류 메시지를 표시합니다. 이것은 일반적으로 디스패처의 루트에 있으며 예외가 아닌 메커니즘 (팝업 대화 상자, RPC 오류 객체 마샬링 등)을 통해 호출자에게 오류를 전달할 수 있도록합니다.
답변
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 인터페이스와 같은 가장 기본적인 구현을 구현하기 위해 계약에 의한 설계 도구로 예외를 확인했습니다. 데이터베이스 나 파일 시스템 또는 점검 된 예외를 발생시키는 다른 구현으로 지원되는 목록을 고려하십시오. 유일하게 가능한 구현은 확인 된 예외를 잡아서 확인되지 않은 예외로 다시 던지는 것입니다.
@Override
public void clear()
{
try
{
backingImplementation.clear();
}
catch (CheckedBackingImplException ex)
{
throw new IllegalStateException("Error clearing underlying list.", ex);
}
}
이제 모든 코드의 요점이 무엇인지 물어봐야합니다. 확인 된 예외는 소음을 추가하고 예외는 포착되었지만 처리되지 않았으며 계약에 의한 설계 (확인 된 예외 측면에서)가 고장났습니다.
결론
- 예외 포착은 처리와 다릅니다.
- 확인 된 예외는 코드에 노이즈를 추가합니다.
- 예외 처리는 C #에서 잘 처리됩니다.
답변
Artima 는 .NET의 건축가 중 한 명인 Anders Hejlsberg 와의 인터뷰를 통해 확인 된 예외에 대한 주장을 심각하게 다루고 있습니다. 짧은 맛보는 사람 :
throws 절은 적어도 Java로 구현되는 방식으로 예외를 처리하도록 강제하지는 않지만 예외를 처리하지 않으면 예외를 통과 할 수있는 정확한 예외를 인정해야합니다. 선언 된 예외를 잡아 내거나 자신의 throws 절에 넣어야합니다. 이 요구 사항을 해결하기 위해 사람들은 우스운 일을합니다. 예를 들어, 그들은 “예외를 던지다”라고 모든 방법을 장식합니다. 그것은 그 기능을 완전히 무너 뜨리고, 프로그래머가 좀 더 거친 글을 쓰도록 만들었습니다. 그것은 아무도 도움이되지 않습니다.