자연스럽게 NullPointerException을 발생시키지 않고 명시 적으로 던지는 이유는 무엇입니까?


182

JDK 소스 코드를 읽을 때 작성자가 매개 변수가 null 인 경우 매개 변수를 확인한 다음 새 NullPointerException ()을 수동으로 throw하는 것이 일반적입니다. 왜 그렇게합니까? 메소드를 호출 할 때 새로운 NullPointerException ()이 발생하기 때문에 그렇게 할 필요가 없다고 생각합니다. (예를 들어 HashMap의 소스 코드는 다음과 같습니다.)

public V computeIfPresent(K key,
                          BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
    if (remappingFunction == null)
        throw new NullPointerException();
    Node<K,V> e; V oldValue;
    int hash = hash(key);
    if ((e = getNode(hash, key)) != null &&
        (oldValue = e.value) != null) {
        V v = remappingFunction.apply(key, oldValue);
        if (v != null) {
            e.value = v;
            afterNodeAccess(e);
            return v;
        }
        else
            removeNode(hash, key, null, false, true);
    }
    return null;
}

32
코딩의 핵심은 의도
무서운 웜뱃

19
이것은 첫 번째 질문에 매우 좋은 질문입니다! 나는 약간의 편집을했다; 나는 당신이 상관하지 않기를 바랍니다. 또한 일반적으로 그런 질문은 SO 질문의 일부가 아니기 때문에 감사와 첫 번째 질문에 대한 메모를 제거했습니다.
David Conrad

11
나는 C #입니다. 컨벤션은 ArgumentNullException(이 아닌 NullReferenceException) 이와 같은 경우 에 제기 하는 것입니다. 실제로 NullPointerException(다른 것이 아닌) 명시 적으로 여기를 올리는 이유에 대해 실제로 좋은 질문 입니다.
EJoshuaS-Monica Monica 복원

21
그것은이다 @EJoshuaS 오래된 논쟁 던져할지 여부 IllegalArgumentException또는 NullPointerException널 인수는. JDK 컨벤션은 후자입니다.
shmosel

33
실제 문제는 오류를 발생 시키고이 오류를 일으킨 모든 정보를 버리는 것 입니다. 이 보인다 는 IS 실제 소스 코드. 단순한 피의 끈 메시지조차도 아닙니다. 슬퍼.
Martin Ba

답변:


254

기억해야 할 여러 가지 이유가 있습니다.

빠른 실패 : 실패 할 경우 나중에보다는 빨리 실패하는 것이 가장 좋습니다. 이를 통해 문제를 소스에 더 가깝게 파악하여 쉽게 식별하고 복구 할 수 있습니다. 또한 실패 할 수있는 코드에서 CPU주기 낭비를 방지합니다.

의도 : 예외를 명시 적으로 throw하면 오류가 의도적으로 있으며 작성자가 결과를 알고 있음을 유지 관리자에게 명확하게 알 수 있습니다.

일관성 : 오류가 자연스럽게 발생하도록 허용 된 경우 모든 시나리오에서 발생하지 않을 수 있습니다. 예를 들어 매핑이 없으면 remappingFunction사용되지 않으며 예외가 발생하지 않습니다. 입력을 미리 검증하면보다 결정적인 동작과보다 명확한 문서화가 가능 합니다.

안정성 : 코드는 시간이 지남에 따라 발전합니다. 예외가 발생하는 코드는 자연스럽게 약간의 리팩토링 후에는 그렇게하지 않거나 다른 상황에서 그렇게 할 수 있습니다. 명시 적으로 던지면 행동이 실수로 변경 될 가능성이 줄어 듭니다.


14
또한 :이 방법으로 예외가 발생하는 위치는 확인 된 하나의 변수에 연결됩니다. 그렇지 않으면 여러 변수 중 하나가 null이기 때문에 예외가 발생할 수 있습니다.
Jens Schauder

45
또 다른 하나 : NPE가 자연스럽게 발생하기를 기다리는 경우 중간 코드 중 일부는 이미 부작용을 통해 프로그램 상태를 변경했을 수 있습니다.
토마스

6
이 스 니펫은이를 수행하지 않지만 new NullPointerException(message)생성자를 사용하여 널 (null)을 명확히 할 수 있습니다 . 소스 코드에 액세스 할 수없는 사람들에게 좋습니다. 그들은 Objects.requireNonNull(object, message)유틸리티 방법을 사용하여 JDK 8에서 하나의 라이너로 만들었습니다 .
Robin

3
FAILURE는 FAULT 근처에 있어야합니다. "실패"는 단순한 규칙 이상입니다. 언제이 행동을 원하지 않습니까? 다른 동작은 오류를 숨기고 있음을 의미합니다. "FAULTS"와 "FAILURES"가 있습니다. FAILURE는이 프로그램이 NULL 포인터를 요약하고 충돌하는 경우입니다. 그러나 그 코드 라인은 FAULT가있는 곳이 아닙니다. NULL은 어딘가에서 왔으며 메소드 인수입니다. 누가 그 주장을 통과 했습니까? 로컬 변수를 참조하는 코드 줄에서. 어디 갔어 ... 봐? 짜증나 나쁜 가치가 저장되는 것을 보는 것은 누구의 책임 이었습니까? 그러면 프로그램이 충돌했을 것입니다.
Noah Spurrier

4
@ 토마스 좋은 지적. Shmosel : 토마스의 요점은 실패의 요점에 암시되어있을 수 있지만, 그것은 묻혀 있습니다. 자체 이름 인 failure atomicity 라는 충분한 개념이 있습니다 . Bloch, Effective Java , 항목 46을 참조하십시오 . 페일 패스트보다 의미가 더 강합니다. 별도의 지점에서 호출하는 것이 좋습니다. 전반적으로 탁월한 답변, BTW. +1
스튜어트 마크

40

명확성, 일관성 및 불필요한 불필요한 작업이 수행되는 것을 방지하기위한 것입니다.

메소드 맨 위에 가드 절이 없으면 어떻게 될지 고려하십시오. 그것은 NPE가 던져지기 전에 항상 전화를 걸었 hash(key)getNode(hash, key)때조차도 null지나갔습니다 remappingFunction.

더 나쁜 것은 if조건이 조건을 가지면 브랜치를 false취하는데 else전혀 사용 remappingFunction하지 않기 때문에 a null가 전달 될 때 메소드가 항상 NPE를 던지는 것은 아닙니다 . 지도의 상태에 따라 다릅니다.

두 시나리오 모두 나쁘다. 이 메서드에 null유효한 값이 아닌 경우 remappingFunction호출시 객체의 내부 상태에 관계없이 일관되게 예외가 발생해야하며, 그냥 던질 것이라는 점에서 의미없는 불필요한 작업을 수행하지 않고 수행해야합니다. 마지막으로, 소스 코드를 검토하는 사람이라면 누구나 그렇게 할 수 있다는 것을 쉽게 알 수 있도록 가드를 미리 확보하는 것이 깨끗하고 명확한 코드의 원칙입니다.

현재 모든 코드 분기에서 예외가 발생하더라도 향후 코드 개정으로 변경 될 수 있습니다. 처음에 검사를 수행하면 확실히 수행됩니다.


25

@shmosel의 탁월한 답변에 나열된 이유 외에도 ...

성능 : 일부 JVM의 경우 JVM이 아닌 NPE를 명시 적으로 처리하면 성능상의 이점이있을 수 있습니다.

Java 인터프리터 및 JIT 컴파일러가 널 포인터의 역 참조를 감지하는 전략에 따라 다릅니다. 한 가지 전략은 null을 테스트하지 않고 대신 명령이 주소 0에 액세스하려고 할 때 발생하는 SIGSEGV를 트랩하는 것입니다. 이는 참조가 항상 유효한 경우 가장 빠른 방법이지만 NPE의 경우 비용많이 듭니다 .

null코드에서 명시 적으로 테스트 하면 NPE가 자주 발생하는 시나리오에서 SIGSEGV 성능이 저하되는 것을 피할 수 있습니다.

(이것이 현대 JVM에서 가치있는 마이크로 최적화가 될지는 의문이지만 과거에는 가능했을 것입니다.)


호환성 : 예외에 메시지가없는 이유는 JVM 자체에서 발생하는 NPE와의 호환성 때문입니다. 호환되는 Java 구현에서 JVM에 의해 발생 된 NPE에는 null메시지가 있습니다. (Android Java는 다릅니다.)


20

다른 사람들이 지적한 것 외에도 여기에서 컨벤션의 역할에 주목할 가치가 있습니다. 예를 들어 C #에서는 이와 같은 경우 예외를 명시 적으로 발생시키는 것과 동일한 규칙이 있지만 구체적으로 ArgumentNullException는 좀 더 구체적입니다. (는 C # 규칙은 것입니다 NullReferenceException 항상 아주 간단하게, 그것은 안 - 어떤 종류의 버그를 나타냅니다 , 부여, 생산 코드에서 발생 ArgumentNullException일반적으로도 않지만, 그렇게하지 "의 라인을 따라 더 많은 버그가 수 라이브러리를 올바르게 사용하는 방법을 이해하십시오 ( "종류의 버그).

따라서 기본적으로 C # NullReferenceException에서는 프로그램이 실제로 사용하려고 시도했지만 ArgumentNullException값이 잘못되었음을 인식하여 사용하려고 시도하지 않아도됩니다. 실제 상황에 따라 의미가 다를 수 있습니다 (환경에 따라 다름). 문제 ArgumentNullException의 방법에 아직 부작용이 없었기 때문입니다 (방법의 전제 조건이 실패 했으므로).

덧붙여, 넌 모금 무엇인가를하는 경우 ArgumentNullExceptionIllegalArgumentException, 체크를하는 점의 부품이 있음을 : 당신은 당신이 "보통"했던 것보다 다른 예외를 원하는 얻을.

어느 쪽이든, 예외를 명시 적으로 제기하면 메소드의 사전 조건 및 예상 인수에 대해 명시 적으로 작성하는 것이 좋습니다. 따라서 코드를보다 쉽게 ​​읽고, 사용하고, 유지할 수 있습니다. 명시 적으로 확인 null하지 않은 경우 아무도 null인수를 전달하지 않을 것이라고 생각했기 때문에 어쨌든 모르겠습니다. 어쨌든 예외를 throw하기 위해 계산하고 있거나 확인하는 것을 잊어 버렸습니다.


4
중간 단락의 경우 +1 문제의 코드가 '새로운 IllegalArgumentException ( "remappingFunction은 null 일 수 없음"); 그렇게하면 무엇이 잘못되었는지 즉시 알 수 있습니다. 표시된 NPE는 약간 모호합니다.
Chris Parker 17 년

1
@ChrisParker 나는 같은 의견을 가지고 있었지만 NullPointerException은 null 역 참조 시도에 대한 런타임 응답 외에도 null이 아닌 인수를 기대하는 메소드에 전달되는 null 인수를 나타내는 것으로 밝혀졌습니다. javadoc에서 : "응용 프로그램은이 클래스의 인스턴스를 던져서 다른 불법적 인 null객체 사용을 표시해야 합니다." 나는 그것에 대해 미쳤지 않지만 의도 한 디자인 인 것처럼 보입니다.
VGR

1
@ChrisParker에 동의합니다-예외가 더 구체적이라고 생각합니다 (코드가 값으로 아무것도 시도하지 않았기 때문에 즉시 사용해서는 안된다는 것을 인식했습니다). 이 경우 C # 규칙이 마음에 듭니다. C # 규칙은 NullReferenceException(와 동등한 NullPointerException) 코드가 실제로 코드를 사용 하려고 시도했다는 것을 의미 합니다 (항상 버그 임-프로덕션 코드에서는 절대 발생하지 않아야 함). 사용하십시오. " 또한 ArgumentException다른 이유로 논쟁이 잘못되었음을 의미합니다.
EJoshuaS-Monica Monica 복원

2
나는 이것을 많이 말할 것이다. 나는 항상 설명대로 IllegalArgumentException을 던진다. 나는 컨벤션이 바보 같은 느낌이들 때 항상 편안한 플레잉 컨벤션을 느낍니다.
Chris Parker 17 년

1
@PieterGeerkens-네, NullPointerException 35 행이 IllegalArgumentException ( "Function ca n't be null") 35 행보다 훨씬 낫기 때문에.
Chris Parker

12

따라서 나중에지도를 사용할 때 오류가 발생하지 않고 오류가 발생하자마자 예외가 발생하며 그 이유를 이해할 수 없습니다.


9

겉보기에 불규칙한 오류 조건이 명확한 계약 위반으로 바뀝니다.이 기능에는 올바르게 작동하기위한 몇 가지 전제 조건이 있으므로 미리 확인하여 충족시켜야합니다.

그 결과 computeIfPresent()예외가 발생했을 때 디버깅 할 필요가 없습니다 . 전제 조건 검사에서 예외가 발생하면 잘못된 인수로 함수를 호출 한 것입니다. 검사가없는 경우 computeIfPresent()자체에 예외가 발생하는 일부 버그가있을 가능성을 배제해야합니다 .

분명히, 제네릭을 던지는 것은 NullPointerException계약 위반 자체를 나타내지 않기 때문에 정말 나쁜 선택입니다. IllegalArgumentException더 나은 선택이 될 것입니다.


참고 사항 :
Java가 이것을 허용하는지 알지 못하지만 (의심 할 것입니다) C / C ++ 프로그래머 assert()는이 경우 디버깅을 사용하는 것이 훨씬 낫습니다. 조건이 거짓으로 평가됩니다. 그래서, 당신이 달렸다면

void MyClass_foo(MyClass* me, int (*someFunction)(int)) {
    assert(me);
    assert(someFunction);

    ...
}

디버거와 어떤 NULL인수에 전달 된 것이 있으면 프로그램은 인수가 무엇 인지 알려주는 줄에서 바로 멈추고 NULL여가 시간에 전체 호출 스택의 모든 지역 변수를 검사 할 수 있습니다.


1
assert something != null;그러나 -assertions앱을 실행할 때 플래그 가 필요합니다 . 는 IF -assertions플래그가없는, 어설 키워드는 AssertionException 포기하지 않습니다
조이

나는 이것이 C # 규칙을 선호하는 이유입니다-null 참조, 유효하지 않은 인수 및 null 인수는 일반적으로 어떤 종류 의 버그를 의미하지만 다른 종류 의 버그를 의미 합니다. "널 (null) 참조를 사용하려는 경우"는 "라이브러리를 잘못 사용하는 것"과 매우 다릅니다.
EJoshuaS-복원 Monica Monica

7

그것은 가능하기 때문입니다 하지 자연적으로 발생 할 수 있습니다. 다음과 같은 코드를 보자 :

bool isUserAMoron(User user) {
    Connection c = UnstableDatabase.getConnection();
    if (user.name == "Moron") { 
      // In this case we don't need to connect to DB
      return true;
    } else {
      return c.makeMoronishCheck(user.id);
    }
}

(물론이 샘플에는 코드 품질에 대한 수많은 문제가 있습니다. 완벽한 샘플을 상상하기 위해 게으른 죄송합니다)

상황 c실제로 사용되지 않는 은 가능 NullPointerException하더라도 던져지지 않습니다 c == null.

더 복잡한 상황에서는 그러한 경우를 찾기가 쉽지 않습니다. 이것이 일반적인 점검 if (c == null) throw new NullPointerException()이 더 나은 이유 입니다.


아마도 실제로 필요하지 않을 때 데이터베이스 연결없이 작동하는 코드는 좋은 것이며, 데이터베이스에 연결하여 실패 할 수 있는지 확인하는 코드는 대개 성가신 일입니다.
Dmitry Grigoryev

5

추가 손상을 보호하거나 일관성이없는 상태가되는 것이 의도적입니다.


1

여기에있는 다른 모든 훌륭한 답변 외에도 몇 가지 사례를 추가하고 싶습니다.

자신 만의 예외를 만들면 메시지를 추가 할 수 있습니다

당신이 자신을 던지면 당신 NullPointerException은 메시지를 추가 할 수 있습니다 (확실히해야합니다!)

기본 메시지는 nullfrom new NullPointerException()및이를 사용하는 모든 메소드입니다 (예 :) Objects.requireNonNull. 해당 null을 인쇄하면 빈 문자열로 변환 될 수도 있습니다 ...

조금 짧고 유익하지 않은 ...

스택 추적은 많은 정보를 제공하지만 사용자가 null이 무엇인지 알기 위해서는 코드를 파고 정확한 행을 봐야합니다.

이제 NPE가 랩을 통해 인터넷을 통해 전송된다고 상상해보십시오. 예를 들어 다른 부서 나 조직간에 웹 서비스 오류 메시지로 나타납니다. 최악의 시나리오, 아무도 null의미 하는 것을 알아낼 수 없습니다 ...

연결 메소드 호출은 추측을 유지합니다

예외는 예외가 발생한 행에 대해서만 알려줍니다. 다음 행을 고려하십시오.

repository.getService(someObject.someMethod());

당신은 NPE를 얻을 수 있으며,이 행에있는 중 하나를 가리키는 경우 repositorysomeObject이었다 널 (null)을?

대신, 변수를 얻을 때 이러한 변수를 확인하면 적어도 변수가 처리되는 유일한 행을 가리킬 것입니다. 그리고 앞에서 언급했듯이 오류 메시지에 변수 이름 또는 이와 유사한 이름이 포함되어 있으면 더욱 좋습니다.

많은 입력을 처리 할 때 오류가 식별 정보를 제공해야 함

프로그램이 수천 개의 행을 가진 입력 파일을 처리하고 갑자기 NullPointerException이 있다고 상상해보십시오. 당신은 그 장소를보고 어떤 입력이 틀렸다는 것을 깨닫습니다 ... 어떤 입력? 해당 파일의 어떤 행을 수정해야하는지 이해하려면 행 번호, 열 또는 전체 행 텍스트에 대한 자세한 정보가 필요합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.