[java] 확인 및 확인되지 않은 예외를 선택하는시기

Java (또는 예외가 확인 된 다른 언어)에서 자체 예외 클래스를 만들 때 어떻게 확인해야하는지 여부를 어떻게 결정합니까?

내 본능은 호출자가 생산적인 방법으로 복구 할 수있는 경우에 검사 된 예외가 호출된다는 것입니다.



답변

점검 된 예외는 언제 사용해야하는지 이해하는 한 훌륭합니다. Java 코어 API가 SQLException (때로는 IOException)에 대한 이러한 규칙을 따르지 않으므로 끔찍합니다.

검사 예외 를 사용해야 예측 하지만, 예방할 수 있는 오류 를 복구하는 것이 합리적 .

다른 모든 항목에는 검사되지 않은 예외를 사용해야합니다.

대부분의 사람들이 이것이 의미하는 바를 오해하기 때문에이 문제를 해결해 드리겠습니다.

  1. 예측 가능하지만 예측 불가능 : 호출자가 입력 매개 변수의 유효성을 검증하기 위해 자신의 권한 내에서 모든 작업을 수행했지만 제어 외부의 일부 조건으로 인해 작업이 실패했습니다. 예를 들어 파일을 읽으려고하지만 파일이 존재하는지 확인한 시간과 읽기 작업이 시작되는 시간 사이에 누군가 파일을 삭제합니다. 확인 된 예외를 선언하면 호출자에게이 실패를 예상하도록 지시합니다.
  2. 복구의 합리적 : 호출자에게 복구 할 수없는 예외를 예상하도록 지시하는 것은 의미가 없습니다. 사용자가 존재하지 않는 파일에서 읽으려고하면 호출자는 새 파일 이름을 입력하라는 메시지를 표시 할 수 있습니다. 반면에, 프로그래밍 버그 (유효하지 않은 메소드 인수 또는 버그가있는 메소드 구현)로 인해 메소드가 실패하는 경우 애플리케이션이 실행 중 문제를 해결하기 위해 수행 할 수있는 작업이 없습니다. 할 수있는 최선의 방법은 문제를 기록하고 개발자가 나중에 문제를 해결하기를 기다리는 것입니다.

던지는 예외 가 위의 모든 조건을 충족하지 않으면 검사되지 않은 예외를 사용해야합니다.

모든 수준에서 재평가 : 때때로 확인 된 예외를 포착하는 방법이 오류를 처리하기에 적합한 곳이 아닙니다. 이 경우 자신의 발신자에게 합리적인 것을 고려하십시오. 예외가 예측 가능하고, 예측 불가능하며, 복구하기에 합리적이라면 확인 된 예외를 직접 처리해야합니다. 그렇지 않은 경우, 점검되지 않은 예외로 예외를 랩해야합니다. 이 규칙을 따르면 사용중인 계층에 따라 확인 된 예외를 확인되지 않은 예외로 또는 그 반대로 변환 할 수 있습니다.

확인 된 예외와 확인되지 않은 예외 모두 올바른 추상화 수준을 사용하십시오 . 예를 들어, 두 개의 서로 다른 구현 (데이터베이스 및 파일 시스템)와 코드 저장소는 던져 구현 고유의 정보를 노출하지 않도록해야 SQLException하거나 IOException. 대신, 모든 구현 (예 :)에 걸친 추상화로 예외를 래핑해야합니다 RepositoryException.


답변

에서 자바 학습자 :

예외가 발생하면 예외를 잡아서 처리하거나, 메소드가 해당 예외를 처리한다고 선언하여 처리 할 수 ​​없다고 컴파일러에 알려야합니다. 그러면 메소드를 사용하는 코드는 해당 예외를 처리해야합니다. 또한 예외를 처리 할 수없는 경우 예외가 발생한다고 선언하도록 선택할 수 있습니다).

컴파일러는 두 가지 중 하나 (캐치 또는 선언)를 수행했는지 확인합니다. 이를 검사 예외라고합니다. 그러나 컴파일러에서 오류 및 런타임 예외를 검사하지 않습니다 (필수 또는 선언하도록 선택할 수는 있지만). 따라서이 두 가지를 검사되지 않은 예외라고합니다.

오류는 시스템 충돌과 같이 응용 프로그램 외부에서 발생하는 조건을 나타내는 데 사용됩니다. 런타임 예외는 일반적으로 응용 프로그램 논리에서 오류로 발생합니다. 이 상황에서는 아무것도 할 수 없습니다. 런타임 예외가 발생하면 프로그램 코드를 다시 작성해야합니다. 따라서 이들은 컴파일러에서 확인하지 않습니다. 이러한 런타임 예외는 개발 및 테스트 기간에 공개됩니다. 그런 다음 이러한 오류를 제거하려면 코드를 리팩터링해야합니다.


답변

내가 사용하는 규칙은 확인되지 않은 예외를 사용하지 마십시오! (또는 당신이 주위에 어떤 방법도 보지 못할 때)

반대의 경우에는 매우 강력한 경우가 있습니다. 확인 된 예외를 사용하지 마십시오. 나는 토론에서 편견을 꺼려하지만 체크 된 예외를 도입하는 것이 뒤늦게 잘못된 결정이라는 광범위한 합의가있는 것 같습니다. 메신저를 쏘지 말고 주장을 참조하십시오 .


답변

여러 계층이있는 충분히 큰 시스템에서 예외를 처리하는 방법을 처리하기위한 아키텍처 수준 전략이 필요하기 때문에 확인 된 예외는 쓸모가 없습니다 (장애 장벽 사용).

예외를 확인하면 오류 처리 상태가 미세 관리되며 모든 대형 시스템에서 견딜 수 없습니다.

API 호출자가 어떤 계층에 있는지 알지 못하기 때문에 대부분의 경우 오류가 “복구 가능한”것인지 알 수 없습니다.

정수의 문자열 표현을 Int로 변환하는 StringToInt API를 작성한다고 가정 해 봅시다. “foo”문자열로 API를 호출하면 확인 된 예외를 처리해야합니까? 복구가 가능합니까? 그의 계층에서 내 StringToInt API의 호출자가 이미 입력의 유효성을 검사했을 수 있으므로이 예외가 발생하면 버그 또는 데이터 손상 이며이 계층에서 복구 할 수 없기 때문에 모르겠습니다.

이 경우 API 호출자는 예외를 포착하지 않습니다. 그는 예외를 “거품”으로 만들고 싶어합니다. 확인 된 예외를 선택하면이 호출자는 예외를 인위적으로 다시 던지기 위해 쓸모없는 캐치 블록을 많이 갖게됩니다.

복구 가능한 것은 대부분 API 작성기에 의존하지 않고 API 호출자에 따라 다릅니다. 확인되지 않은 예외 만 예외를 포착하거나 무시하도록 선택할 수 있으므로 API는 확인 된 예외를 사용해서는 안됩니다.


답변

당신은 맞습니다.

검사되지 않은 예외 는 시스템이 빠르게 실패 하도록하는 데 사용됩니다 . 제대로 작동하려면 방법이 무엇인지 명확하게 기술해야합니다. 이 방법으로 입력을 한 번만 확인할 수 있습니다.

예를 들어 :

/**
 * @params operation - The operation to execute.
 * @throws IllegalArgumentException if the operation is "exit"
 */
 public final void execute( String operation ) {
     if( "exit".equals(operation)){
          throw new IllegalArgumentException("I told you not to...");
     }
     this.operation = operation;
     .....
 }
 private void secretCode(){
      // we perform the operation.
      // at this point the opreation was validated already.
      // so we don't worry that operation is "exit"
      .....
 }

예를 들어 보자. 요점은 시스템이 빨리 실패하면 어디에서 왜 실패했는지 알 수 있습니다. 다음과 같은 스택 추적이 나타납니다.

 IllegalArgumentException: I told you not to use "exit"
 at some.package.AClass.execute(Aclass.java:5)
 at otherPackage.Otherlass.delegateTheWork(OtherClass.java:4569)
 ar ......

그리고 당신은 무슨 일이 있었는지 알게 될 것입니다. “delegateTheWork”메소드 (4569 행)의 OtherClass는 클래스가 “exit”값으로 클래스를 호출했습니다.

그렇지 않으면 코드 전체에 유효성 검사를 뿌려야하며 오류가 발생하기 쉽습니다. 또한, 무엇이 잘못되었는지 추적하기가 어려우며 몇 시간 동안 디버깅에 어려움을 겪을 수 있습니다

NullPointerExceptions에서도 마찬가지입니다. 약 15 개의 메소드가있는 700 개의 라인 클래스가 있고 30 개의 속성을 사용하고 널 (null)이 될 수없는 경우, 각 메소드에서 널 입력 가능 여부를 유효성 검증하는 대신 모든 속성을 읽기 전용으로 만들고 생성자에서 유효성을 검증 할 수 있습니다. 공장 방법.

 public static MyClass createInstane( Object data1, Object data2 /* etc */ ){
      if( data1 == null ){ throw NullPointerException( "data1 cannot be null"); }

  }


  // the rest of the methods don't validate data1 anymore.
  public void method1(){ // don't worry, nothing is null 
      ....
  }
  public void method2(){ // don't worry, nothing is null 
      ....
  }
  public void method3(){ // don't worry, nothing is null 
      ....
  }

확인 된 예외 프로그래머 (귀하 또는 동료)가 모든 것을 올바르게 수행하고, 입력을 검증하고, 테스트를 실행했으며, 모든 코드가 완벽하지만 코드가 다운 (또는 파일 일 수있는) 타사 웹 서비스에 연결되어있을 때 유용합니다. 다른 외부 프로세스 등에 의해 삭제되었습니다). 연결을 시도하기 전에 웹 서비스의 유효성을 검사 할 수도 있지만 데이터 전송 중에 문제가 발생했습니다.

이 시나리오에서는 귀하 또는 동료가이를 도울 수있는 방법이 없습니다. 그러나 여전히 당신은 무언가를해야하고 응용 프로그램이 사용자의 눈에 죽고 사라지지 않도록해야합니다. 확인 된 예외를 사용하고 예외를 처리합니다. 대부분의 경우 오류를 기록하고 작업을 저장하고 (앱 작업) 사용자에게 메시지를 표시하려고합니다. . (사이트 blabla가 다운되었으므로 나중에 다시 시도하십시오.)

확인 된 예외가 과도하게 사용되면 (모든 메소드 서명에 “예외 예외”를 추가하여) 모든 사람이 해당 예외를 무시하고 (너무 일반적이기 때문에) 코드 품질이 심각해지기 때문에 코드가 매우 취약 해집니다. 타협.

확인되지 않은 예외를 과도하게 사용하면 비슷한 일이 발생합니다. 해당 코드의 사용자는 무언가 잘못 될 수 있는지 알지 못하고 많은 try {…} catch (Throwable t)가 나타납니다.


답변

여기 내 ‘최종 엄지 손가락 규칙’이 있습니다.
나는 사용한다:

  • 호출자로 인한 실패에 대한 내 메소드 코드 내에서 확인되지 않은 예외 ( 명확하고 완전한 문서가 포함됨 )
  • 내 코드를 사용하려는 사람에게 명시 적으로 명시 해야하는 수신자 로 인한 실패에 대한 예외확인 했습니다.

이전 답변과 비교할 때, 이것은 하나 또는 다른 (또는 두 가지) 종류의 예외 사용에 대한 명확한 이론적 근거입니다 (하나는 동의하거나 동의하지 않을 수 있음).


두 예외 모두에서, NullPointerException과 같은 매우 일반적인 검사되지 않은 예외를 제외하고 내 응용 프로그램에 대해 자체 검사되지 않은 검사 예외를 생성합니다 ( 여기에서 언급 한 모범 사례 ).

예를 들어 아래의이 특정 기능의 목적은 객체를 만드는 것 (또는 이미 존재하는 경우)을
의미하는 것입니다.

  • 만들거나 얻는 객체의 컨테이너가 있어야합니다 (CALLER
    => 검사되지 않은 예외 에 대한 책임 ,이 호출 된 함수에 대한 javadoc 주석 지우기)
  • 다른 매개 변수는 널
    ( null) 일 수 없습니다 (코더가이를 호출자에 놓도록 선택하십시오. 코더는 널 매개 변수를 검사하지 않지만 코더는 IT 문서를 처리 함)
  • 결과는 NULL이 될 수 없습니다 (수취인
    의 코드에 대한 책임과 선택, 호출자에게 큰 관심을 가질
    선택 => 검사 예외는 모든 호출자가 객체를 만들거나 찾을 수없는 경우 결정을 내려야하기 때문에 반드시 결정해야합니다. 결정은 컴파일시에 적용해야합니다 그들은이와 의미,이 가능성을 처리 할 필요없이이 기능을 사용할 수 없습니다 확인 ) 예외입니다.

예:


/**
 * Build a folder. <br />
 * Folder located under a Parent Folder (either RootFolder or an existing Folder)
 * @param aFolderName name of folder
 * @param aPVob project vob containing folder (MUST NOT BE NULL)
 * @param aParent parent folder containing folder
 *        (MUST NOT BE NULL, MUST BE IN THE SAME PVOB than aPvob)
 * @param aComment comment for folder (MUST NOT BE NULL)
 * @return a new folder or an existing one
 * @throws CCException if any problems occurs during folder creation
 * @throws AssertionFailedException if aParent is not in the same PVob
 * @throws NullPointerException if aPVob or aParent or aComment is null
 */
static public Folder makeOrGetFolder(final String aFoldername, final Folder aParent,
    final IPVob aPVob, final Comment aComment) throws CCException {
    Folder aFolderRes = null;
    if (aPVob.equals(aParent.getPVob() == false) {
       // UNCHECKED EXCEPTION because the caller failed to live up
       // to the documented entry criteria for this function
       Assert.isLegal(false, "parent Folder must be in the same PVob than " + aPVob); }

    final String ctcmd = "mkfolder " + aComment.getCommentOption() +
        " -in " + getPNameFromRepoObject(aParent) + " " + aPVob.getFullName(aFolderName);

    final Status st = getCleartool().executeCmd(ctcmd);

    if (st.status || StringUtils.strictContains(st.message,"already exists.")) {
        aFolderRes = Folder.getFolder(aFolderName, aPVob);
    }
    else {
        // CHECKED EXCEPTION because the callee failed to respect his contract
        throw new CCException.Error("Unable to make/get folder '" + aFolderName + "'");
    }
    return aFolderRes;
}


답변

예외로부터 복구하는 능력의 문제가 아닙니다. 제 생각에 가장 중요한 것은 호출자가 예외를 잡는 데 관심이 있는지 여부입니다.

다른 곳에서 사용할 라이브러리 또는 응용 프로그램의 하위 수준 계층을 작성하는 경우 호출자가 예외를 포착 (알고 있음)하는 데 관심이 있는지 자문 해보십시오. 그렇지 않은 경우 검사되지 않은 예외를 사용하므로 불필요하게 부담을주지 않습니다.

이것은 많은 프레임 워크에서 사용되는 철학입니다. 특히 봄과 최대 절전 모드는 확인 된 예외가 Java에서 과도하게 사용되기 때문에 알려진 확인 된 예외를 확인되지 않은 예외로 정확하게 변환합니다. 내가 생각할 수있는 한 가지 예는 json.org의 JSONException입니다.

그건 그렇고, 예외에 대한 호출자의 관심은 대부분 예외에서 회복하는 능력과 직접적으로 관련이 있지만 항상 그런 것은 아닙니다.