Null 체크 체인 대 NullPointerException 잡기


118

웹 서비스는 거대한 XML을 반환하고 그에 대한 깊이 중첩 된 필드에 액세스해야합니다. 예를 들면 :

return wsObject.getFoo().getBar().getBaz().getInt()

문제는 즉 getFoo(), getBar(),getBaz() 가) 모두 반환 될 수null .

그러나 null모든 경우에 확인하면 코드가 매우 장황 해지고 읽기 어려워집니다. 또한 일부 필드의 수표를 놓칠 수 있습니다.

if (wsObject.getFoo() == null) return -1;
if (wsObject.getFoo().getBar() == null) return -1;
// maybe also do something with wsObject.getFoo().getBar()
if (wsObject.getFoo().getBar().getBaz() == null) return -1;
return wsObject.getFoo().getBar().getBaz().getInt();

쓰는 것이 허용됩니까?

try {
    return wsObject.getFoo().getBar().getBaz().getInt();
} catch (NullPointerException ignored) {
    return -1;
}

아니면 반 패턴으로 간주 될까요?


29
나는 이미 코드 냄새 null이기 때문에 수표에 그렇게 신경 쓰지 않을 것 wsObject.getFoo().getBar().getBaz().getInt()입니다. "데메테르의 법칙" 이 무엇인지 읽고 그에 따라 코드를 리팩토링하는 것을 선호합니다. 그러면 null수표 문제 도 사라질 것입니다. 그리고 Optional.

9
XPath 를 사용 하고 평가에 맡기는 것은 어떻습니까?
윱 Eggen

15
이 코드는 아마도 wsdl2javaDemeter의 법칙을 존중하지 않는에 의해 생성되었을 것입니다 .
아드리안 콕스

답변:


143

잡기는 NullPointerExceptionA는 할 정말 문제가있는 것은 그들은 거의 어디서든 일어날 수 있기 때문이다. 버그에서 하나를 가져 와서 실수로 잡아서 모든 것이 정상인 것처럼 계속해서 실제 문제를 숨기는 것은 매우 쉽습니다. 처리하기가 너무 까다롭기 때문에 아예 피하는 것이 가장 좋습니다. (예를 들어 null의 자동 언 박싱에 대해 생각해보십시오 Integer.)

Optional대신 수업 을 사용하는 것이 좋습니다 . 존재하거나없는 값으로 작업 할 때 가장 좋은 방법입니다.

이를 사용하여 다음과 같이 코드를 작성할 수 있습니다.

public Optional<Integer> m(Ws wsObject) {
    return Optional.ofNullable(wsObject.getFoo()) // Here you get Optional.empty() if the Foo is null
        .map(f -> f.getBar()) // Here you transform the optional or get empty if the Bar is null
        .map(b -> b.getBaz())
        .map(b -> b.getInt());
        // Add this if you want to return null instead of an empty optional if any is null
        // .orElse(null);
        // Or this if you want to throw an exception instead
        // .orElseThrow(SomeApplicationException::new);
}

왜 선택 사항입니까?

사용 Optional의 대신을null부재 할 수있는 값 하면 그 사실이 독자에게 매우 눈에 잘 띄고 명확 해지며 유형 시스템은 실수로 잊어 버리지 않도록합니다.

당신은 또한 같은, 더 편리하게 값 작업을위한 방법에 접근 map하고 orElse.


결석이 유효하거나 오류입니까?

그러나 중간 메서드가 null을 반환하는 것이 유효한 결과인지 또는 그것이 오류의 신호인지 생각해보십시오. 항상 오류 인 경우 특수 값을 반환하거나 중간 메서드 자체가 예외를 throw하는 것보다 예외를 throw하는 것이 좋습니다.


더 많은 선택 사항이 있습니까?

반면에 중간 메서드에없는 값이 유효한 경우 다음으로 전환 할 수 있습니다. Optional s로 할 수도 있습니까?

그런 다음 다음과 같이 사용할 수 있습니다.

public Optional<Integer> mo(Ws wsObject) {
    return wsObject.getFoo()
        .flatMap(f -> f.getBar())
        .flatMap(b -> b.getBaz())
        .flatMap(b -> b.getInt());        
}

선택 사항이 아닌 이유는 무엇입니까?

내가 사용하지 않는다고 생각할 수있는 유일한 이유는 이것이 Optional코드의 성능에 매우 중요한 부분이고 가비지 수집 오버 헤드가 문제로 판명되는 경우입니다. 몇 때문이다 Optional오브젝트 코드가 실행될 때마다 할당하고, VM이 있습니다 사람들을 최적화 할 수 없습니다. 이 경우 원래 if-test가 더 좋을 수 있습니다.


아주 좋은 대답입니다. 다른 가능한 실패 조건이 있고 이들을 구별해야하는 경우 Try대신을 사용할 수 있음을 언급해야합니다 Optional. 어떤이 있지만 Try자바 API에, 예를 들어, 하나를 제공하는 많은 libs가 없습니다 javaslang.io , github.com/bradleyscollins/try4j , functionaljava.org 또는 github.com/jasongoodwin/better-java-monads
Landei

8
FClass::getBar등은 더 짧을 것입니다.
보리스 스파이더

1
@BoristheSpider : 아마도 조금. 그러나 클래스 이름이 훨씬 더 길고 람다를 읽기가 조금 더 쉬우므로 일반적으로 메서드 참조보다 람다를 선호합니다.
Lii

6
@Lii는 충분히 공정하지만 람다에는 더 복잡한 컴파일 시간 구성이 필요할 수 있으므로 메서드 참조가 조금 더 빠를 수 있습니다. 람다는 static메서드 생성을 필요로하며 , 이는 매우 사소한 불이익을 초래합니다.
보리스 스파이더

1
@Lii 나는 실제로 메서드 참조가 약간 더 길더라도 더 깨끗하고 설명 적이라는 것을 발견했습니다.
shmosel

14

나는 고려하는 것이 좋습니다 Objects.requireNonNull(T obj, String message). 다음과 같이 각 예외에 대한 자세한 메시지가 포함 된 체인을 만들 수 있습니다.

requireNonNull(requireNonNull(requireNonNull(
    wsObject, "wsObject is null")
        .getFoo(), "getFoo() is null")
            .getBar(), "getBar() is null");

과 같은 특별한 반환 값을 사용하지 않는 것이 좋습니다 -1. 그것은 자바 스타일이 아닙니다. Java는 C 언어에서 나온 이러한 구식 방식을 피하기 위해 예외 메커니즘을 설계했습니다.

던지는 NullPointerException것도 최선의 선택이 아닙니다. 고유 한 예외를 제공하거나 ( 사용자가 처리 할 수 있는지 확인 하거나 더 쉬운 방법으로 처리하기 위해 선택 취소 ) 사용중인 XML 파서의 특정 예외를 사용할 수 있습니다.


1
Objects.requireNonNull결국 NullPointerException. 이보다 다양한 상황을하지 않도록return wsObject.getFoo().getBar().getBaz().getInt()
ARKA 고쉬

1
@ArkaGhosh 또한 그것은의 많은 피할 if영업 이익은 가리 켰을들
앤드류 Tobilko에게

4
이것은 유일한 정상적인 해결책입니다. 다른 모든 사람들은 코드 냄새 인 흐름 제어에 예외를 사용하도록 권장합니다. 참고로 OP에 의해 수행되는 메소드 체인도 냄새라고 생각합니다. 그가 3 개의 지역 변수를 가지고 작업한다면 상황은 훨씬 더 명확 할 것입니다. 또한 문제는 NPE 주변에서 작업하는 것보다 더 깊다고 생각합니다. OP는 게터가 왜 null을 반환 할 수 있는지 스스로에게 물어봐야합니다. null은 무엇을 의미합니까? 널 객체가 더 좋을까요? 아니면 하나의 getter에서 의미있는 예외로 충돌합니까? 기본적으로 모든 것이 흐름 제어에 대한 예외보다 낫습니다.
Marius K.

1
유효한 반환 값이 없음을 알리기 위해 예외를 사용하라는 무조건적인 조언은 그다지 좋지 않습니다. 예외는 호출자가 복구하기 어렵고 프로그램의 다른 부분에서 try-catch-statement에서 더 잘 처리되는 방식으로 메서드가 실패 할 때 유용합니다. 단순히 반환 값이 없음을 OptionalInteger
알리

6

클래스 구조가 실제로 우리의 통제를 벗어났다고 가정하면 성능이 주요 관심사가 아니라면 질문에서 제안한 NPE를 잡는 것이 실제로 합리적인 해결책이라고 생각합니다. 한 가지 작은 개선 사항은 혼란을 피하기 위해 throw / catch 논리를 래핑하는 것입니다.

static <T> T get(Supplier<T> supplier, T defaultValue) {
    try {
        return supplier.get();
    } catch (NullPointerException e) {
        return defaultValue;
    }
}

이제 간단하게 다음을 수행 할 수 있습니다.

return get(() -> wsObject.getFoo().getBar().getBaz().getInt(), -1);

return get(() -> wsObject.getFoo().getBar().getBaz().getInt(), "");문제가 될 수있는 컴파일 시간에 오류를주지 않습니다.
Philippe Gioseffi

5

댓글에서 Tom이 이미 지적했듯이

다음 진술 은 데메테르법칙을 위반합니다 .

wsObject.getFoo().getBar().getBaz().getInt()

원하는 것은 int에서 얻을 수 있습니다 Foo. 데메테르의 법칙은 말한다 낯선 사람에게 이야기하지 않았다 . 귀하의 경우 Foo및 의 후드 아래에서 실제 구현을 숨길 수 있습니다 Bar.

지금, 당신은의 방법을 만들 수 있습니다 Foo가져올 int에서를 Baz. 궁극적으로 Foo해야합니다 Bar및에 Bar우리가 액세스 할 수 있습니다 Int노출하지 않고 Baz직접 Foo. 따라서 널 검사는 아마도 다른 클래스로 나뉘며 필요한 속성 만 클래스간에 공유됩니다.


4
WsObject는 아마도 데이터 구조 일 뿐이므로 Demeter의 법칙을 위반하면 논쟁의 여지가 있습니다. 여기를 참조하십시오 : stackoverflow.com/a/26021695/1528880
DerM

2
@DerM 예, 가능합니다. 그러나 OP에는 이미 XML 파일을 구문 분석하는 것이 있으므로 필요한 태그에 적합한 모델 클래스를 만드는 방법도 고려할 수 있으므로 구문 분석 라이브러리가 매핑 할 수 있습니다. 그런 다음 이러한 모델 클래스에는 null자체 하위 태그 확인을 위한 논리가 포함 됩니다.
Tom

4

내 대답은 @janki와 거의 같은 줄에 있지만 다음과 같이 코드 스 니펫을 약간 수정하고 싶습니다.

if (wsObject.getFoo() != null && wsObject.getFoo().getBar() != null && wsObject.getFoo().getBar().getBaz() != null) 
   return wsObject.getFoo().getBar().getBaz().getInt();
else
   return something or throw exception;

wsObject해당 개체가 null 일 가능성이있는 경우 null 검사도 추가 할 수 있습니다 .


4

일부 메서드는 "반환 할 수있다 null"고 말하지만 어떤 상황에서 반환되는지는 말하지 않습니다 null. 당신은 당신이NullPointerException 고 말하지만 왜 잡았는지 말하지 않습니다. 이러한 정보 부족은 예외가 무엇인지, 왜 예외가 대안보다 우월한 지에 대한 명확한 이해가 없음을 나타냅니다.

조치를 수행하기위한 클래스 메소드를 고려하십시오. 그러나 제어 할 수없는 상황 (사실 Java의 모든 메소드 의 경우)으로 인해 해당 메소드가 조치를 수행 할 것이라고 보장 할 수는 없습니다. . 이 메서드를 호출하면 반환됩니다. 해당 메서드를 호출하는 코드는 성공 여부를 알아야합니다. 어떻게 알 수 있습니까? 성공 또는 실패의 두 가지 가능성에 대처하기 위해 어떻게 구성 할 수 있습니까?

예외를 사용하여 성공한 메소드를 사후 조건으로 작성할 수 있습니다 . 메서드가 반환되면 성공한 것입니다. 예외가 발생하면 실패한 것입니다. 이것은 명확성을위한 큰 승리입니다. 정상적인 성공 사례를 명확하게 처리하는 코드를 작성하고 모든 오류 처리 코드를 catch절로 이동할 수 있습니다. 메서드가 어떻게 또는 왜 실패했는지에 대한 세부 정보가 호출자에게 중요하지 않기 때문에 동일한 catch절을 여러 유형의 실패를 처리하는 데 사용할 수 있습니다. 그리고 종종 방법은 캐치 예외를 필요로하지 않는 일이 전혀 하지만, 단지 그들에게 전파하도록 할 수 호출자. 프로그램 버그로 인한 예외는 후자의 클래스에 있습니다. 버그가있을 때 적절하게 반응 할 수있는 방법은 거의 없습니다.

따라서 null.

  • 않는 null값은 코드에서 버그를 나타냅니다? 그렇다면 예외를 전혀 포착하지 않아야합니다. 그리고 당신의 코드는 두 번째 추측을 시도해서는 안됩니다. 그것이 효과가있을 것이라는 가정에 대해 명확하고 간결한 것을 작성하십시오. 일련의 메서드 호출이 명확하고 간결합니까? 그런 다음 사용하십시오.
  • 않는 null값은 프로그램에 잘못된 입력을 표시? 그렇다면 NullPointerException일반적으로 버그를 표시하기 위해 예약되어 있기 때문에 a 는 적절한 예외가 아닙니다. IllegalArgumentException(확인 되지 않은 예외IOException 를 원하는 경우 ) 또는 (확인 된 예외를 원하는 경우 ) 에서 파생 된 사용자 지정 예외를 throw하고 싶을 것입니다 . 잘못된 입력이있을 때 프로그램에서 자세한 구문 오류 메시지를 제공해야합니까? 그렇다면 각 메서드에서 null반환 값을 확인한 다음 적절한 진단 예외를 throw하는 것이 유일한 방법입니다. 프로그램이 자세한 진단을 제공 할 필요가없는 경우 메서드 호출을 함께 연결하고 임의의 항목을 포착 NullPointerException한 다음 사용자 지정 예외를 throw하는 것이 가장 명확하고 간결합니다.

답변 중 하나는 체인 메서드 호출 이 Demeter법칙을 위반 하므로 나쁘다고 주장합니다 . 그 주장은 잘못된 것입니다.

  • 프로그램 디자인과 관련하여 무엇이 좋은지 나쁜지에 대한 절대적인 규칙은 없습니다. 휴리스틱 (heuristic) 만 있습니다. 규칙은 거의 모든 시간에 적용됩니다. 프로그래밍 기술의 일부는 이러한 종류의 규칙을 어기는 것이 좋을 때를 아는 것입니다. 따라서 "이것은 규칙 X 에 위배된다"는 간결한 주장 은 실제로 전혀 대답이 아닙니다. 이것이 규칙이 해야하는 상황 중 하나입니까? 입니까?
  • 데메테르법칙 은 실제로 API 또는 클래스 인터페이스 디자인에 대한 규칙입니다. 클래스를 디자인 할 때 추상화 계층 구조 를 갖는 것이 유용합니다.. 언어 프리미티브를 사용하여 작업을 직접 수행하고 언어 프리미티브보다 높은 수준의 추상화에서 객체를 나타내는 저수준 클래스가 있습니다. 하위 수준 클래스에 위임하고 하위 수준 클래스보다 높은 수준에서 작업 및 표현을 구현하는 중간 수준의 클래스가 있습니다. 중간 수준의 클래스에 위임하고 더 높은 수준의 작업 및 추상화를 구현하는 높은 수준의 클래스가 있습니다. (여기서는 세 가지 수준의 추상화에 대해 이야기했지만 더 많은 것이 가능합니다.) 이를 통해 코드가 각 수준에서 적절한 추상화 측면에서 자신을 표현할 수 있으므로 복잡성을 숨길 수 있습니다. 데메테르 법칙의 근거메서드 호출 체인이있는 경우 중간 수준의 클래스를 통해 낮은 수준의 세부 정보를 직접 처리하는 높은 수준의 클래스가 있으므로 중간 수준의 클래스가 중간 수준의 추상 연산을 제공하지 않았 음을 의미합니다. 높은 수준의 수업이 필요합니다. 하지만 여기에있는 상황 이 아닌 것 같습니다 . 메서드 호출 체인에서 클래스를 디자인하지 않았고, 자동 생성 된 XML 직렬화 코드의 결과이며 (맞습니까?), 호출 체인이 내림차순이 아닙니다. des-serialized XML이 모두 추상화 계층의 동일한 수준에 있기 때문에 추상화 계층을 통해?

3

가독성을 높이기 위해 다음과 같은 여러 변수를 사용할 수 있습니다.

Foo theFoo;
Bar theBar;
Baz theBaz;

theFoo = wsObject.getFoo();

if ( theFoo == null ) {
  // Exit.
}

theBar = theFoo.getBar();

if ( theBar == null ) {
  // Exit.
}

theBaz = theBar.getBaz();

if ( theBaz == null ) {
  // Exit.
}

return theBaz.getInt();

이것은 제 생각에 훨씬 읽기 어렵습니다. 그것은 메서드의 실제 논리와는 완전히 무관 한 널 검사 논리의 전체 무리로 메서드를 흩뿌립니다.
Developer102938

2

캐치하지 마십시오 NullPointerException. 당신은 그것이 어디에서 오는지 알지 못합니다 (당신의 경우에는 가능성이 없지만 다른 것이 그것을 던졌을 수도 있음을 알고 있습니다) 그리고 그것은 느립니다. 지정된 필드에 액세스하려고하며 다른 모든 필드에 대해 null이 아니어야합니다. 이것은 모든 필드를 확인하는 완벽한 타당한 이유입니다. 나는 아마 하나에서 그것을 확인하고 가독성을위한 방법을 만들 것입니다. 다른 사람들이 이미 지적했듯이 -1을 반환하는 것은 매우 구식이지만 이유가 있는지 여부는 모르겠습니다 (예 : 다른 시스템과 대화).

public int callService() {
    ...
    if(isValid(wsObject)){
        return wsObject.getFoo().getBar().getBaz().getInt();
    }
    return -1;
}


public boolean isValid(WsObject wsObject) {
    if(wsObject.getFoo() != null &&
        wsObject.getFoo().getBar() != null &&
        wsObject.getFoo().getBar().getBaz() != null) {
        return true;
    }
    return false;
}

편집 : WsObject가 아마도 데이터 구조 일뿐이므로 Demeter의 법칙을 위반하면 논쟁의 여지가 있습니다 ( https://stackoverflow.com/a/26021695/1528880 확인 ).


2

코드를 리팩토링하고 싶지 않고 Java 8을 사용할 수있는 경우 메서드 참조를 사용할 수 있습니다.

먼저 간단한 데모 (정적 내부 클래스를 용서하십시오)

public class JavaApplication14 
{
    static class Baz
    {
        private final int _int;
        public Baz(int value){ _int = value; }
        public int getInt(){ return _int; }
    }
    static class Bar
    {
        private final Baz _baz;
        public Bar(Baz baz){ _baz = baz; }
        public Baz getBar(){ return _baz; }   
    }
    static class Foo
    {
        private final Bar _bar;
        public Foo(Bar bar){ _bar = bar; }
        public Bar getBar(){ return _bar; }   
    }
    static class WSObject
    {
        private final Foo _foo;
        public WSObject(Foo foo){ _foo = foo; }
        public Foo getFoo(){ return _foo; }
    }
    interface Getter<T, R>
    {
        R get(T value);
    }

    static class GetterResult<R>
    {
        public R result;
        public int lastIndex;
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) 
    {
        WSObject wsObject = new WSObject(new Foo(new Bar(new Baz(241))));
        WSObject wsObjectNull = new WSObject(new Foo(null));

        GetterResult<Integer> intResult
                = getterChain(wsObject, WSObject::getFoo, Foo::getBar, Bar::getBar, Baz::getInt);

        GetterResult<Integer> intResult2
                = getterChain(wsObjectNull, WSObject::getFoo, Foo::getBar, Bar::getBar, Baz::getInt);


        System.out.println(intResult.result);
        System.out.println(intResult.lastIndex);

        System.out.println();
        System.out.println(intResult2.result);
        System.out.println(intResult2.lastIndex);

        // TODO code application logic here
    }

    public static <R, V1, V2, V3, V4> GetterResult<R>
            getterChain(V1 value, Getter<V1, V2> g1, Getter<V2, V3> g2, Getter<V3, V4> g3, Getter<V4, R> g4)
            {
                GetterResult result = new GetterResult<>();

                Object tmp = value;


                if (tmp == null)
                    return result;
                tmp = g1.get((V1)tmp);
                result.lastIndex++;


                if (tmp == null)
                    return result;
                tmp = g2.get((V2)tmp);
                result.lastIndex++;

                if (tmp == null)
                    return result;
                tmp = g3.get((V3)tmp);
                result.lastIndex++;

                if (tmp == null)
                    return result;
                tmp = g4.get((V4)tmp);
                result.lastIndex++;


                result.result = (R)tmp;

                return result;
            }
}

산출

241
4


2

인터페이스 Getter는 기능적 인터페이스 일 뿐이며 동등한 인터페이스를 사용할 수 있습니다.
GetterResult클래스, 접근자는 명확성을 위해 제거되고, getter 체인의 결과 (있는 경우) 또는 마지막으로 호출 된 getter의 인덱스를 보유합니다.

이 메서드 getterChain는 자동으로 (또는 필요할 때 수동으로) 생성 할 수있는 간단한 표준 코드 조각입니다.
반복되는 블록이 자명하도록 코드를 구성했습니다.


여전히 하나의 과부하를 정의해야하기 때문에 이것은 완벽한 솔루션이 아닙니다. getterChain게터 수당 .

대신 코드를 리팩토링 할 수 있지만, 그렇게 할 수없고 긴 게터 체인을 자주 사용하는 경우 게터가 2 개에서 10 개까지 걸리는 오버로드가있는 클래스를 만드는 것을 고려할 수 있습니다.


2

다른 사람들이 말했듯이 데메테르의 법칙을 존중하는 것은 확실히 해결책의 일부입니다. 가능한 한 또 다른 부분은를 반환 할 수 없도록 연결된 메서드를 변경하는 것 null입니다. null대신 빈 String, 빈 Collection또는 호출자가 무엇을 할 것인지를 의미하거나 수행하는 다른 더미 객체를 반환하여 반환 을 피할 수 있습니다 null.


2

오류의미에 초점을 맞춘 답변을 추가하고 싶습니다 . Null 예외 자체는 전체 오류를 의미하지 않습니다. 따라서 직접 처리하지 않는 것이 좋습니다.

코드가 잘못 될 수있는 수천 가지 경우가 있습니다. 데이터베이스에 연결할 수 없음, IO 예외, 네트워크 오류 ... 하나씩 처리하면 (여기에서 null 확인과 같이) 너무 번거 롭습니다.

코드에서 :

wsObject.getFoo().getBar().getBaz().getInt();

어떤 필드가 null인지 알고 있어도 무엇이 잘못되었는지 알 수 없습니다. Bar가 null 일 수도 있지만 예상되는 것입니까? 아니면 데이터 오류입니까? 당신의 코드를 읽는 사람들을 생각 해보세요

xenteros의 답변과 마찬가지로 사용자 정의 확인되지 않은 예외 사용을 제안 합니다. 예를 들어, 다음 상황에서 Foo는 null (유효한 데이터) 일 수 있지만 Bar 및 Baz는 null (유효하지 않은 데이터)이 아니어야합니다.

코드를 다시 작성할 수 있습니다.

void myFunction()
{
    try 
    {
        if (wsObject.getFoo() == null)
        {
          throw new FooNotExistException();
        }

        return wsObject.getFoo().getBar().getBaz().getInt();
    }
    catch (Exception ex)
    {
        log.error(ex.Message, ex); // Write log to track whatever exception happening
        throw new OperationFailedException("The requested operation failed")
    }
}


void Main()
{
    try
    {
        myFunction();
    }
    catch(FooNotExistException)
    {
        // Show error: "Your foo does not exist, please check"
    }
    catch(OperationFailedException)
    {
        // Show error: "Operation failed, please contact our support"
    }
}

확인되지 않은 예외는 프로그래머가 API를 오용하고 있음을 나타냅니다. "데이터베이스에 연결할 수 없음, IO 예외, 네트워크 오류"와 같은 외부 문제는 확인 된 예외로 표시되어야합니다.
케빈 Krumwiede

발신자의 필요에 따라 다릅니다. 오류를 처리해야하므로 예외 도움말을 확인했습니다. 그러나 다른 경우에는 필요하지 않으며 코드를 오염시킬 수 있습니다. 예를 들어 데이터 계층에 IOException이있는 경우이를 프레젠테이션 계층에 던지시겠습니까? 즉, 예외를 포착하고 모든 호출자에게 다시 던져야합니다. 관련 메시지와 함께 사용자 지정 BusinessException으로 IOException을 래핑하고 전역 필터가이를 포착하여 사용자에게 메시지를 표시 할 때까지 스택 추적을 통해 팝업되도록하는 것을 선호합니다.
Hoàng Long

호출자는 확인 된 예외를 포착하고 다시 throw 할 필요가 없으며 throw되도록 선언하기 만하면됩니다.
Kevin Krumwiede

@KevinKrumwiede : 맞아요. 예외를 던질 때만 선언하면됩니다. 그래도 선언해야합니다. 편집 : 두 번째로 살펴보면 확인 된 예외와 확인되지 않은 예외 사용에 대한 많은 논쟁이 있습니다 (예 : programmers.stackexchange.com/questions/121328/… ).
호앙 롱

2

NullPointerException 런타임 예외이므로 일반적으로 포착하는 것은 좋지 않지만 피하는 것이 좋습니다.

메서드를 호출 할 때마다 예외를 포착해야합니다 (또는 스택 위로 전파됩니다). 그럼에도 불구하고 귀하의 경우 -1 값으로 해당 결과로 계속 작업 할 수 있고 null 일 수있는 "조각"을 사용하지 않기 때문에 전파되지 않을 것이라고 확신하는 경우 다음을 수행하는 것이 옳은 것 같습니다. 잡아

편집하다:

@xenteros 의 나중 답변에 동의 InvalidXMLException합니다. 예를 들어 -1을 반환하는 대신 자체 예외를 시작하는 것이 좋습니다 .


3
"잡아도 코드의 다른 부분으로 전파 될 수있다"는 의미는 무엇입니까?
헐크

이 문장에 null이 있으면 wsObject.getFoo () 코드의 뒷부분에서 해당 쿼리를 다시 실행하거나 wsObject.getFoo (). getBar ()를 사용합니다 (예를 들어). 다시 NullPointerException이 발생합니다.
SCouto

이것은 "메서드를 호출 할 때마다 예외를 포착해야합니다 (또는 스택 위로 전파 될 것입니다)."에 대한 특이한 표현입니다. 내가 올바르게 이해한다면. 동의합니다 (문제가 될 수 있음). 문구가 혼란 스러울뿐입니다.
헐크

나는이 가끔 발생할 수 있도록 :) 감사합니다 죄송합니다, 영어는 내 모국어가 아닌, 문제를 해결할 수 있습니다
SCouto

2

어제부터이 게시물을 팔로우했습니다.

나는 NPE를 잡는 것이 나쁘다는 의견에 대해 댓글을 달거나 투표했습니다. 이것이 내가 그렇게해온 이유입니다.

package com.todelete;

public class Test {
    public static void main(String[] args) {
        Address address = new Address();
        address.setSomeCrap(null);
        Person person = new Person();
        person.setAddress(address);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            try {
                System.out.println(person.getAddress().getSomeCrap().getCrap());
            } catch (NullPointerException npe) {

            }
        }
        long endTime = System.currentTimeMillis();
        System.out.println((endTime - startTime) / 1000F);
        long startTime1 = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            if (person != null) {
                Address address1 = person.getAddress();
                if (address1 != null) {
                    SomeCrap someCrap2 = address1.getSomeCrap();
                    if (someCrap2 != null) {
                        System.out.println(someCrap2.getCrap());
                    }
                }
            }
        }
        long endTime1 = System.currentTimeMillis();
        System.out.println((endTime1 - startTime1) / 1000F);
    }
}

  public class Person {
    private Address address;

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }
}

package com.todelete;

public class Address {
    private SomeCrap someCrap;

    public SomeCrap getSomeCrap() {
        return someCrap;
    }

    public void setSomeCrap(SomeCrap someCrap) {
        this.someCrap = someCrap;
    }
}

package com.todelete;

public class SomeCrap {
    private String crap;

    public String getCrap() {
        return crap;
    }

    public void setCrap(String crap) {
        this.crap = crap;
    }
}

산출

3.216

0.002

여기에 분명한 승자가 있습니다. if 검사를하는 것은 예외를 잡는 것보다 훨씬 저렴합니다. Java-8 방식을 보았습니다. 현재 애플리케이션의 70 %가 여전히 Java-7에서 실행된다는 점을 고려하면이 답변을 추가하고 있습니다.

결론 미션 크리티컬 애플리케이션의 경우 NPE를 처리하는 데 많은 비용이 듭니다.


최악의 경우 백만 개의 요청 에 대해 3 초가 추가 로 측정 될 수 있지만 "미션 크리티컬 애플리케이션"에서도 거래 중단자가되는 경우는 거의 없습니다. 요청에 3.2 마이크로 초를 추가하는 것이 큰 문제인 시스템이 있으며, 그러한 시스템이 있다면 예외에 대해 신중하게 생각하십시오. 그러나 원래 질문에 따라 웹 서비스를 호출하고 출력을 역 직렬화하는 데는 그보다 훨씬 더 오래 걸리며 예외 처리 성능에 대한 걱정은 그다지 중요하지 않습니다.
Jeroen Mostert

@JeroenMostert : 수표 당 3 초 / 백만. 그래서, 검사의 수는 비용 증가
NEWUSER

진실. 그래도 여전히 "프로필 우선"의 경우라고 생각합니다. 요청에 1 밀리 초의 추가 시간이 소요되기 전에 한 번의 요청에서 300 개 이상의 확인이 필요합니다. 디자인 고려 사항은 그것보다 훨씬 빨리 내 영혼에 부담을 줄 것입니다.
Jeroen Mostert

@JeroenMostert : :) 동의합니다! 그 결과를 프로그래머에게 맡기고 전화를 받도록하고 싶습니다!
NewUser

1

효율성이 문제라면 'catch'옵션을 고려해야합니다. 이 전파 때문에 ( 'SCouto'에서 언급 한 바와 같이) '캐치'를 사용할 수없는 경우 다음 방법을 여러 번 호출을 피하기 위해 지역 변수를 사용 getFoo(), getBar()하고 getBaz().


1

자신의 예외를 만드는 것을 고려할 가치가 있습니다. MyOperationFailedException이라고합시다. 값을 반환하는 대신 던질 수 있습니다. 결과는 동일합니다. 함수를 종료하지만 Java 안티 패턴 인 하드 코딩 된 값 -1을 반환하지 않습니다. Java에서는 예외를 사용합니다.

try {
    return wsObject.getFoo().getBar().getBaz().getInt();
} catch (NullPointerException ignored) {
    throw new MyOperationFailedException();
}

편집하다:

의견에 대한 토론에 따르면 이전 생각에 무언가를 추가하겠습니다. 이 코드에는 두 가지 가능성이 있습니다. 하나는 null을 받아들이고 다른 하나는 오류라는 것입니다.

오류이고 발생하는 경우 중단 점이 충분하지 않을 때 디버깅 목적으로 다른 구조를 사용하여 코드를 디버깅 할 수 있습니다.

허용되는 경우이 null이 어디에 나타나는지 신경 쓰지 않습니다. 그렇게한다면 이러한 요청을 연결해서는 안됩니다.


2
예외를 억제하는 것이 나쁜 생각이라고 생각하지 않습니까? 실시간으로 예외의 흔적을 잃어 버리면 도대체 무슨 일이 벌어지고 있는지 알아 내기 위해 바닥이 정말 고통 스럽습니다! 나는 항상 연결을 사용하지 말 것을 제안합니다. 두 번째 문제는 다음과 같습니다.이 코드는 어떤 결과가 null인지 시점에 피부 여자가 될 수 없습니다.
NewUser 2016-06-22

아니요, 예외는 확실히 던져진 장소를 가리키는 메시지를 가질 수 있습니다. 나는 체인이 최선의 해결책 : 아닙니다 동의
xenteros

3
아니, 그냥 줄 번호에 대해 말할 것입니다. 따라서 체인의 모든 호출이 예외로 이어질 수 있습니다.
NewUser

"오류이고 오류가 발생하면 코드를 디버그 할 수 있습니다."-프로덕션 환경이 아닙니다. 나는 실패를 일으키는 원인이 된 일을 신성화하려고 노력하는 것보다 내가 가진 모든 것이 로그 일 때 실패한 것이 무엇인지 훨씬 알고 싶습니다. 그 조언 (및 해당 코드)을 통해 실제로 아는 것은 4 가지 중 하나가 null이지만 어느 것이나 그 이유가 아니라는 것입니다.
VLAZ

1

당신이 가진 방법은 길지만 매우 읽기 쉽습니다. 내가 당신의 코드베이스에 오는 새로운 개발자라면 당신이 무엇을하고 있는지 꽤 빨리 볼 수있었습니다. 대부분의 다른 답변 (예외 잡기 포함)은 일을 더 읽기 쉽게 만들고 일부는 내 의견으로는 읽기 어렵게 만듭니다.

생성 된 소스를 제어 할 수 없을 가능성이 높고 여기저기서 몇 개의 깊이 중첩 된 필드에 액세스해야한다고 가정하면 깊이 중첩 된 액세스를 메서드로 래핑하는 것이 좋습니다.

private int getFooBarBazInt() {
    if (wsObject.getFoo() == null) return -1;
    if (wsObject.getFoo().getBar() == null) return -1;
    if (wsObject.getFoo().getBar().getBaz() == null) return -1;
    return wsObject.getFoo().getBar().getBaz().getInt();
}

이러한 메서드를 많이 작성하거나 이러한 공용 정적 메서드를 만들고 싶은 경우에는 원하는 필드 만 포함하여 원하는 방식으로 중첩 된 별도의 개체 모델을 만들고 웹에서 변환합니다. 서비스 개체 모델을 개체 모델에 추가합니다.

원격 웹 서비스와 통신 할 때 "원격 도메인"과 "응용 프로그램 도메인"이 있고 둘 사이를 전환하는 것이 매우 일반적입니다. 원격 도메인은 종종 웹 프로토콜에 의해 제한됩니다 (예 : 순수 RESTful 서비스에서 헬퍼 메서드를 앞뒤로 보낼 수 없으며 여러 API 호출을 피하기 위해 깊이 중첩 된 개체 모델이 일반적 임). 당신의 클라이언트.

예를 들면 :

public static class MyFoo {

    private int barBazInt;

    public MyFoo(Foo foo) {
        this.barBazInt = parseBarBazInt();
    }

    public int getBarBazInt() {
        return barBazInt;
    }

    private int parseFooBarBazInt(Foo foo) {
        if (foo() == null) return -1;
        if (foo().getBar() == null) return -1;
        if (foo().getBar().getBaz() == null) return -1;
        return foo().getBar().getBaz().getInt();
    }

}

1
return wsObject.getFooBarBazInt();

데메테르의 법칙을 적용하여

class WsObject
{
    FooObject foo;
    ..
    Integer getFooBarBazInt()
    {
        if(foo != null) return foo.getBarBazInt();
        else return null;
    }
}

class FooObject
{
    BarObject bar;
    ..
    Integer getBarBazInt()
    {
        if(bar != null) return bar.getBazInt();
        else return null;
    }
}

class BarObject
{
    BazObject baz;
    ..
    Integer getBazInt()
    {
        if(baz != null) return baz.getInt();
        else return null;
    }
}

class BazObject
{
    Integer myInt;
    ..
    Integer getInt()
    {
        return myInt;
    }
}

0

다른 사람과는 다른 대답을 해준다.

s NULL에서 확인하는 것이 좋습니다 if.

이유 :

프로그램이 중단 될 수있는 기회를 한 번도 남겨 두어서는 안됩니다. NullPointer는 시스템에서 생성됩니다. 시스템 생성 예외의 동작을 예측할 수 없습니다 . 이미 직접 처리 할 수있는 방법이있는 경우 시스템에 프로그램을 맡겨서는 안됩니다. 그리고 여분의 안전을 위해 예외 처리 메커니즘을 넣습니다. !!

코드를 읽기 쉽게 만들려면 다음을 시도하여 조건을 확인하십시오.

if (wsObject.getFoo() == null || wsObject.getFoo().getBar() == null || wsObject.getFoo().getBar().getBaz() == null) 
   return -1;
else 
   return wsObject.getFoo().getBar().getBaz().getInt();

편집하다 :

여기에서이 값을 저장할 필요가 wsObject.getFoo(), wsObject.getFoo().getBar(), wsObject.getFoo().getBar().getBaz()어떤 변수. 나는 그 함수의 반환 유형을 모르기 때문에 그것을하지 않고 있습니다.

어떤 제안이라도 감사하겠습니다 .. !!


getFoo ()가 시간이 많이 걸리는 작업이라고 생각 했습니까? 반환 된 값을 변수에 저장해야하지만 메모리 낭비입니다. 귀하의 방법은 C 프로그래밍에 적합합니다.
xenteros

그러나 언젠가는 프로그램이 @xenteros에 충돌하는 것보다 1 밀리 초 늦는 것이 낫습니다 .. !!
장키 Gadhiya

getFoo ()는 다른 대륙에있는 서버에서 값을 가져올 수 있습니다. 그것은 어떤 시간이 지난 수 분 / 시간 ...
xenteros

wsObjectWebservice에서 반환 된 값이 포함됩니다 .. !! 서비스는 이미 호출되고 웹 서비스 응답으로 wsObjectXML데이터를 받습니다 .. !! 그래서 다른 대륙에 위치한 서버 와 같은 것은 없습니다. getFoo()단지 웹 서비스 호출이 아닌 getter 메소드를 얻는 요소 이기 때문 입니다 .. !! @xenteros
장키 Gadhiya

1
게터의 이름에서 Foo, Bar 및 Baz 개체를 반환한다고 가정합니다 .P 또한 답변에서 언급 된 이중 안전을 제거하는 것을 고려하십시오. 나는 그것이 코드의 오염을 제외하고 실제 가치를 제공한다고 생각하지 않습니다. 정상적인 지역 변수와 null 검사를 통해 코드의 정확성을 보장하기에 충분합니다. 예외가 발생하면 예외로 처리해야합니다.
마리우스 K.

0

Snag객체 트리를 탐색 할 경로를 정의 할 수 있는 클래스를 작성했습니다 . 다음은 사용 예입니다.

Snag<Car, String> ENGINE_NAME = Snag.createForAndReturn(Car.class, String.class).toGet("engine.name").andReturnNullIfMissing();

즉, 인스턴스 ENGINE_NAMECar?.getEngine()?.getName()전달 된 인스턴스 를 효과적으로 호출 하고 null참조가 반환되면 반환합니다 null.

final String name =  ENGINE_NAME.get(firstCar);

Maven에 게시되지 않았지만 누군가 유용하다고 생각되면 여기에 있습니다 (물론 보증 없음!)

약간 기본적이지만 제대로 작동하는 것 같습니다. 분명히 안전한 탐색 또는 .NET을 지원하는 최신 버전의 Java 및 기타 JVM 언어에서는 더 이상 사용되지 않습니다 Optional.

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