개발관련/오류노트

확인 된 예외에 대한 사례

Rateye 2021. 6. 27. 14:10
728x90
반응형

 

질문 : 확인 된 예외에 대한 사례

몇 년 동안 나는 다음 질문에 대한 적절한 대답을 얻지 못했습니다. 왜 일부 개발자는 체크 된 예외에 반대합니까? 나는 수많은 대화를 나누고, 블로그에서 무언가를 읽고, Bruce Eckel이 말한 것을 읽었습니다.

저는 현재 새로운 코드를 작성하고 있으며 예외를 처리하는 방법에 매우주의를 기울이고 있습니다. 나는 "우리는 확인 된 예외를 좋아하지 않는다"군중의 관점을 보려고 노력하고 있는데 여전히 그것을 볼 수 없다.

내가 가진 모든 대화는 같은 질문에 답이없는 상태로 끝납니다. 설정하겠습니다.

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

  • Error 는 절대 잡히면 안되는 것들에 대한 것입니다 (VM에 땅콩 알레르기가 있고 누군가 땅콩 한 병을 떨어 뜨 렸습니다)
  • RuntimeException 은 프로그래머가 잘못한 일에 대한 것입니다 (프로그래머가 배열의 끝을 벗어남).
  • Exception ( RuntimeException 제외)는 프로그래머가 제어 할 수없는 항목에 대한 것입니다 (파일 시스템에 쓰는 동안 디스크가 가득 차서 프로세스에 대한 파일 핸들 제한에 도달하여 더 이상 파일을 열 수 없음).
  • Throwable 은 단순히 모든 예외 유형의 부모입니다.

내가 듣는 일반적인 주장은 예외가 발생하면 개발자가해야 할 일은 프로그램을 종료하는 것입니다.

내가 듣는 또 다른 일반적인 주장은 예외를 확인하면 코드를 리팩토링하기가 더 어렵다는 것입니다.

"내가하려고하는 것은 나가는 것뿐"이라는 주장에 대해 나는 나가는 경우에도 합리적인 오류 메시지를 표시해야한다고 말합니다. 오류 처리에 대해 펀팅하는 경우 이유를 명확하게 표시하지 않고 프로그램이 종료 될 때 사용자가 지나치게 만족하지 않을 것입니다.

"리팩터링을 어렵게 만드는"군중의 경우 적절한 추상화 수준이 선택되지 않았 음을 나타냅니다. 오히려 방법은 슬로우 선언보다 IOException 상기 IOException 더 진행되는 것에 대해 적합 예외로 변환한다.

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

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

Exception 하위 클래스 대신 RuntimeException 하위 클래스를 던지면 무엇을 잡아야하는지 어떻게 알 수 있습니까?

대답이 catch Exception 이면 시스템 예외와 같은 방식으로 프로그래머 오류를 처리하는 것입니다. 그것은 나에게 잘못된 것 같습니다.

Throwable 을 잡으면 시스템 예외와 VM 오류 (등)를 같은 방식으로 처리하는 것입니다. 그것은 나에게 잘못된 것 같습니다.

만약 당신이 알고있는 예외 만 던져진다는 대답이 있다면, 어떤 예외가 던져 졌는지 어떻게 알 수 있습니까? 프로그래머 X가 새로운 예외를 던지고 그것을 포착하는 것을 잊었을 때 어떻게됩니까? 그것은 나에게 매우 위험한 것 같습니다.

스택 추적을 표시하는 프로그램이 잘못되었다고 말하고 싶습니다. 확인 된 예외를 좋아하지 않는 사람들은 그렇게 생각하지 않습니까?

따라서 확인 된 예외가 마음에 들지 않으면 왜 안되는지 설명하고 답변이없는 질문에 답해 주시겠습니까?

사람들이에서 연장 이유 중 하나 모델을 사용하는 경우에 대한 조언을 찾고 있지 않다, 내가 무엇을 찾고 있어요 것은 RuntimeException 그들이에서 연장처럼되지 않기 때문에 Exception 및 / 또는 왜 그들은 예외를 캐치 한 다음 다시 발생 RuntimeException 추가 던졌습니다보다는 그들의 방법에. 확인 된 예외를 싫어하는 동기를 이해하고 싶습니다.

답변

나는 당신이했던 것과 같은 Bruce Eckel 인터뷰를 읽었다 고 생각합니다. 그리고 그것은 항상 나를 괴롭 혔습니다. 사실,이 주장은 인터뷰 대상자가 .NET과 C #의 MS 천재 인 Anders Hejlsberg에 의해 만들어졌습니다.

http://www.artima.com/intv/handcuffs.html

팬은 비록 내가 Hejlsberg와 그의 작품에 속하지만,이 주장은 항상 나를 가짜라고 생각했습니다. 기본적으로 다음과 같이 요약됩니다.

"확인 된 예외는 프로그래머가 항상이를 포착하고 무시하여 남용하기 때문에 사용자에게 표시되는 문제가 숨겨지고 무시됩니다."

"그렇지 않으면 사용자에게 표시됨"이란 런타임 예외를 사용하는 경우 게으른 프로그래머가이를 무시하고 (빈 catch 블록을 사용하는 경우가 아니라) 사용자가이를 볼 수 있음을 의미합니다.

이 주장의 요약 요약은 "프로그래머들은 그것들을 적절하게 사용하지 않을 것이고 그것들을 적절하게 사용하지 않는 것은 그것들을 가지고 있지 않은 것보다 더 나쁘다"라는 것 입니다.

이 주장에는 약간의 진실이 있으며, 사실 Java에 연산자 오버라이드를 넣지 않은 Goslings의 동기는 비슷한 주장에서 비롯된 것 같습니다. 종종 남용되기 때문에 프로그래머를 혼란스럽게합니다.

그러나 결국 나는 그것이 Hejlsberg의 가짜 주장이며 아마도 잘 생각한 결정보다는 부족을 설명하기 위해 만들어진 포스트-혹의 주장이라고 생각합니다.

확인 된 예외를 과도하게 사용하는 것은 나쁜 일이고 사용자가 부주의하게 처리하는 경향이 있지만이를 적절히 사용하면 API 프로그래머가 API 클라이언트 프로그래머에게 큰 이점을 줄 수 있다고 주장합니다.

이제 API 프로그래머는 확인 된 예외를 사방으로 던지지 않도록주의해야합니다. 그렇지 않으면 단순히 클라이언트 프로그래머를 괴롭힐 것입니다. 매우 게으른 클라이언트 프로그래머는 (Exception) {} 를 잡는 데 의지 할 것이며 모든 혜택이 손실되고 지옥이 뒤따를 것입니다. 그러나 어떤 경우에는 양호한 검사 예외를 대체 할 수 없습니다.

저에게 전형적인 예는 파일 열기 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를 올바르게 사용하도록 권장합니다.

(루비와 같이 동적으로 입력되는 언어에서는 파일 이름으로 float 등 무엇이든 전달할 수 있습니다. 그러면 컴파일됩니다. 메서드 인수를 제어하지 않을 경우 예외를 확인하여 사용자를 번거롭게하는 이유는 무엇입니까? 여기에서 만든 인수는 정적으로 형식화 된 언어에만 적용됩니다.)

그렇다면 확인 된 예외는 어떻습니까?

여기 파일을 여는 데 사용할 수있는 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확인 된 예외입니다.

API 프로그래머는 다음과 같이 말합니다. "이 생성자를 사용하여 새 FileInputStream을 만들 수 있지만

a) 파일 이름을 문자열로 전달 해야합니다.
b) 런타임에 파일을 찾을 수 없을 가능성을 허용 해야합니다. "

그리고 그것이 제가 생각하는 한 요점입니다.

핵심은 기본적으로 질문이 "프로그래머의 통제를 벗어난 것들"이라고 말하는 것입니다. 첫 번째 생각은 그 / 그녀가 API 프로그래머가 제어 할 수없는 것을 의미한다고 생각했습니다. 그러나 사실, 적절하게 사용될 때 체크 된 예외는 실제로 클라이언트 프로그래머와 API 프로그래머가 제어 할 수없는 것들에 대한 것이어야합니다. 이것이 확인 된 예외를 남용하지 않는 열쇠라고 생각합니다.

파일 열기가 요점을 멋지게 설명한다고 생각합니다. API 프로그래머는 API가 호출 될 때 존재하지 않는 것으로 판명 된 파일 이름을 제공 할 수 있으며 원하는 것을 반환 할 수 없지만 예외를 throw해야한다는 것을 알고 있습니다. 그들은 또한 이것이 매우 정기적으로 발생하고 클라이언트 프로그래머가 호출을 작성할 때 파일 이름이 정확할 것이라고 기대할 수 있다는 것을 알고 있지만 제어 할 수없는 이유로 런타임에 잘못되었을 수도 있습니다.

그래서 API는 그것을 명시합니다. 당신이 저에게 전화를 걸었을 때이 파일이 존재하지 않는 경우가있을 것이고 당신은 그것을 더 잘 다루었을 것입니다.

이것은 카운터 케이스로 더 명확해질 것입니다. 내가 테이블 API를 작성한다고 상상해보십시오. 이 메서드를 포함하는 API가있는 어딘가에 테이블 모델이 있습니다.

public RowData getRowData(int row) 
                           

이제 API 프로그래머로서 일부 클라이언트가 행에 대해 음수 값을 전달하거나 테이블 외부의 행 값을 전달하는 경우가 있음을 알고 있습니다. 따라서 확인 된 예외를 throw하고 클라이언트가 처리하도록 강제하고 싶을 수 있습니다.

public RowData getRowData(int row) throws CheckedInvalidRowNumberException
                           

(물론 실제로 "체크"라고 부르지는 않을 것입니다.)

이것은 확인 된 예외를 잘못 사용하는 것입니다. 클라이언트 코드는 행 데이터를 가져 오기위한 호출로 가득 차있을 것입니다. 각각은 try / catch를 사용해야하며 무엇을 위해? 사용자에게 잘못된 행을 찾았다 고보고할까요? 아마도 그렇지 않을 것입니다. 내 테이블 뷰를 둘러싼 UI가 무엇이든 사용자가 잘못된 행이 요청되는 상태에 들어가도록 허용해서는 안됩니다. 그래서 그것은 클라이언트 프로그래머의 버그입니다.

IllegalArgumentException 과 같은 런타임 예외로 처리해야한다고 예측할 수 있습니다.

getRowData 에서 확인 된 예외를 사용하면 Hejlsberg의 게으른 프로그래머가 단순히 빈 catch를 추가하는 경우가 분명합니다. 이 경우 잘못된 행 값은 테스터 나 클라이언트 개발자 디버깅에게도 분명하지 않고 소스를 정확히 파악하기 어려운 노크 온 오류로 이어집니다. Arianne 로켓은 발사 후 폭발합니다.

자, 여기에 문제가 있습니다. 확인 된 예외 FileNotFoundException 은 단순히 좋은 것이 아니라 클라이언트 프로그래머에게 가장 유용한 방법으로 API를 정의하기위한 API 프로그래머 도구 상자의 필수 도구라고 말하고 있습니다. 그러나 CheckedInvalidRowNumberException 은 큰 불편을 끼쳐서 잘못된 프로그래밍으로 이어 지므로 피해야합니다. 그러나 차이점을 말하는 방법.

나는 그것이 정확한 과학이 아니라고 생각하며 그것은 Hejlsberg의 주장의 기초가되고 아마도 어느 정도 정당화 될 것이라고 생각합니다. 그러나 나는 여기에 목욕물과 함께 아기를 버리는 것이 행복하지 않으므로 여기에서 좋은 검사 예외를 나쁜 것과 구별하기 위해 몇 가지 규칙을 추출 할 수 있습니다.

  1. 클라이언트의 통제 불능 또는 닫힘 vs 열기 : 체크 된 예외는 오류 사례가 API 클라이언트 프로그래머 모두 통제 불능 인 경우에만 사용해야합니다. 이것은 시스템이 얼마나 열리 거나 닫히는 지 와 관련이 있습니다. 클라이언트 프로그래머가 테이블 뷰 (폐쇄 시스템)에서 행을 추가 및 삭제하는 모든 버튼, 키보드 명령 등을 제어 할 수있는 제한된 UI에서 데이터를 가져 오려고하면 클라이언트 프로그래밍 버그입니다. 존재하지 않는 행. 많은 사용자 / 응용 프로그램이 파일을 추가 및 삭제할 수있는 파일 기반 운영 체제 (개방형 시스템)에서 클라이언트가 요청한 파일이 자신도 모르게 삭제되었으므로 처리 할 것으로 예상 할 수 있습니다. .
  2. 유비 쿼티 : 클라이언트가 자주 수행하는 API 호출에는 확인 된 예외를 사용해서는 안됩니다. 자주 나는 클라이언트 코드의 많은 위치에서 의미합니다. 따라서 클라이언트 코드는 동일한 파일을 많이 열려는 경향이 없지만 내 테이블 뷰는 다른 방법에서 RowData 특히 저는 다음과 같은 많은 코드를 작성할 것입니다.
    if (model.getRowData().getCell(0).isEmpty())
    매번 try / catch로 감싸 야하는 것은 고통 스러울 것입니다.
  3. 사용자에게 알림 : 최종 사용자에게 유용한 오류 메시지가 표시되는 것을 상상할 수있는 경우 확인 된 예외를 사용해야합니다. 이것은 "그리고 그것이 발생하면 무엇을 할 것인가?"입니다. 위에서 제기 한 질문. 또한 항목 1 과도 관련이 있습니다. 클라이언트 -API 시스템 외부에 파일이 존재하지 않을 수 있다는 것을 예측할 수 있으므로 사용자에게 이에 대해 합리적으로 알릴 수 있습니다.
    "Error: could not find the file 'goodluckfindingthisfile'"
    잘못된 행 번호는 내부 버그로 인해 사용자의 잘못이 아니기 때문에 실제로 제공 할 수있는 유용한 정보가 없습니다. 앱에서 런타임 예외가 콘솔에 전달되지 않도록하면 다음과 같은 추악한 메시지가 표시 될 것입니다.
    "Internal error occured: IllegalArgumentException in ...."
    요컨대, 클라이언트 프로그래머가 사용자에게 도움이되는 방식으로 예외를 설명 할 수 없다고 생각한다면 아마도 체크 된 예외를 사용하지 않아야합니다.

 

 

 

그래서 그것이 제 규칙입니다. 다소 인위적이며 예외가있을 것입니다 (원하는 경우 수정하도록 도와주세요). 그러나 내 주요 주장은 FileNotFoundException 과 같은 경우에 확인 된 예외가 매개 변수 유형만큼 API 계약의 일부만큼 중요하고 유용한 경우가 있다는 것입니다. 그래서 우리는 그것이 오용되었다고해서 그것을 포기해서는 안됩니다.

죄송합니다. 이렇게 길고 엉망진창을 만들려고 한 건 아닙니다. 두 가지 제안으로 마무리하겠습니다.

A : API 프로그래머 : 유용성을 유지하기 위해 확인 된 예외를 드물게 사용하십시오. 확실하지 않은 경우 확인되지 않은 예외를 사용하십시오.

B : 클라이언트 프로그래머 : 개발 초기에 래핑 된 예외 (google it)를 만드는 습관을 가지십시오. JDK 1.4 이상 RuntimeException 에 생성자를 제공하지만 직접 생성 할 수도 있습니다. 생성자는 다음과 같습니다.

public RuntimeException(Throwable cause)
                           

그런 다음 확인 된 예외를 처리해야하고 게으르다 고 느낄 때마다 (또는 API 프로그래머가 처음에 확인 된 예외를 과도하게 사용했다고 생각할 때) 습관을들이십시오. 예외를 삼키지 말고 래핑하십시오. 다시 던지십시오.

try {
  overzealousAPI(thisArgumentWontWork);
}
catch (OverzealousCheckedException exception) {
  throw new RuntimeException(exception);  
}

이것을 IDE의 작은 코드 템플릿 중 하나에 넣고 게으르다 고 느낄 때 사용하십시오. 이렇게하면 확인 된 예외를 처리해야하는 경우 런타임에 문제를 확인한 후 강제로 돌아와서 처리해야합니다. 나를 믿으십시오 (그리고 Anders Hejlsberg), 당신은 당신의 TODO로 돌아 오지 않을 것입니다.

catch (Exception e) { /* TODO deal with this at some point (yeah right) */}
                               
출처 : https://stackoverflow.com/questions/613954/the-case-against-checked-exceptions
728x90
반응형