Boş kontrol zinciri ve NullPointerException'ı yakalama


118

Bir web hizmeti çok büyük bir XML döndürüyor ve bunun derinlemesine iç içe geçmiş alanlarına erişmem gerekiyor. Örneğin:

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

Sorun olduğunu getFoo(), getBar(), getBaz()bütün dönüş olabilir null.

Bununla birlikte, nullher durumda kontrol edersem , kod çok ayrıntılı ve okunması zor hale gelir. Dahası, bazı alanların kontrollerini kaçırabilirim.

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();

Yazmak kabul edilebilir mi

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

yoksa bu bir anti-model olarak kabul edilir mi?


29
Zaten bir kod kokusu olduğu için nullçekleri bu kadar önemsemem wsObject.getFoo().getBar().getBaz().getInt(). "Demeter Yasası" nın ne olduğunu okuyun ve kodunuzu buna göre yeniden düzenlemeyi tercih edin. O zaman nullçeklerdeki sorun da ortadan kalkacak. Ve kullanmayı düşünün Optional.
Tom

9
XPath kullanmaya ve değerlendirmelerine bırakmaya ne dersiniz ?
Joop Eggen

15
Bu kod muhtemelen wsdl2javaDemeter Yasasına hiç saygısı olmayan tarafından üretilmiştir .
Adrian Cox

Yanıtlar:


143

Yakalamak NullPointerException, neredeyse her yerde olabileceği için gerçekten sorunlu bir şey . Bir böcekten bir tane almak, onu tesadüfen yakalamak ve her şey normalmiş gibi devam etmek çok kolaydır, böylece gerçek bir sorunu gizler. Başa çıkmak çok zor, bu yüzden tamamen kaçınmak en iyisidir. (Örneğin, bir boşluğun otomatik olarak kutudan çıkarılmasını düşünün Integer.)

Onun Optionalyerine sınıfı kullanmanızı öneririm . Var olan veya olmayan değerlerle çalışmak istediğinizde genellikle en iyi yaklaşım budur.

Bunu kullanarak kodunuzu şu şekilde yazabilirsiniz:

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);
}

Neden isteğe bağlı?

Bulunmayabilecek değerler Optionalyerine s kullanmak, nullbu gerçeği okuyucular için çok görünür ve açık hale getirir ve yazım sistemi, yanlışlıkla unutmamanızı sağlar.

Ayrıca, mapve gibi bu tür değerlerle daha rahat çalışma yöntemlerine erişebilirsiniz orElse.


Devamsızlık geçerli mi yoksa hata mı?

Ancak, ara yöntemlerin null döndürmesinin geçerli bir sonuç olup olmadığını veya bunun bir hata işareti olup olmadığını da düşünün. Her zaman bir hata ise, o zaman özel bir değer döndürmektense bir istisna atmak veya ara yöntemlerin kendilerinin bir istisna atması daha iyidir.


Belki daha fazla seçenek?

Öte yandan, ara yöntemlerde bulunmayan değerler geçerliyse, belki Optionalonlar için de s'ye geçebilirsiniz ?

O zaman onları şu şekilde kullanabilirsin:

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

Neden isteğe bağlı değil?

Kullanmamak için düşünebildiğim tek neden, bunun Optionalkodun gerçekten performans açısından kritik bir parçası olması ve çöp toplama ek yükünün bir sorun haline gelmesi. Birkaç Bunun nedeni Optionalnesnelerin kod çalıştırıldığında her zaman tahsis edilir ve VM olabilir şunları uzak optimize etmek mümkün olmayacaktır. Bu durumda orijinal if-testleriniz daha iyi olabilir.


Çok güzel cevap. Başka olası arıza durumları varsa ve bunları ayırt etmeniz gerekiyorsa, Tryyerine kullanabileceğiniz belirtilmelidir Optional. TryJava API'sinde bulunmamakla birlikte, bir tane sağlayan birçok kitaplık vardır , örneğin javaslang.io , github.com/bradleyscollins/try4j , functionjava.org veya github.com/jasongoodwin/better-java-monads
Landei

8
FClass::getBarvb. daha kısa olacaktır.
Örümcek Boris

1
@BoristheSpider: Belki biraz. Ama genellikle lambdaları yöntem refs'lerine tercih ederim çünkü genellikle sınıf adları çok daha uzundur ve lambdaları okumayı biraz daha kolay buluyorum.
Lii

6
@Lii yeterince adil, ancak bir lambda daha karmaşık derleme zamanı yapıları gerektirebileceğinden, yöntem referansının biraz daha hızlı olabileceğini unutmayın. Lambda, staticçok küçük bir cezaya neden olacak bir yöntemin üretilmesini gerektirecektir .
Örümcek Boris

1
@Lii Aslında biraz daha uzun olsalar bile yöntem referanslarını daha temiz ve daha açıklayıcı buluyorum.
shmosel

14

Düşünmenizi öneririm Objects.requireNonNull(T obj, String message). Her istisna için ayrıntılı bir mesaj içeren zincirler oluşturabilirsiniz.

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

Gibi özel dönüş değerleri kullanmamanızı öneririm -1. Bu bir Java stili değil. Java, C dilinden gelen bu eski moda yoldan kaçınmak için istisnalar mekanizması tasarladı.

Fırlatmak NullPointerExceptionda en iyi seçenek değil. Sen (o yapım kendi istisna sağlayabilir kontrol bir kullanıcı veya tarafından ele olacağını garantiye kontrolsüz daha kolay bir şekilde işlemek için) veya kullandığınız XML çözümleyici belirli bir özel durumu kullanın.


1
Objects.requireNonNullsonunda fırlatır NullPointerException. Yani bu durumu return wsObject.getFoo().getBar().getBaz().getInt()
şundan

1
@ArkaGhosh, ayrıca bir bol önler ifOP gösteriyordu s
Andrew Tobilko

4
Bu tek mantıklı çözüm. Diğerleri, bir kod kokusu olan akış kontrolü için istisnaların kullanılmasını önerir. Bir yan not olarak: OP tarafından yapılan yöntem zincirlemesini de bir koku olarak görüyorum. Üç yerel değişkenle çalışacak olsaydı ve buna karşılık gelen eğer durum çok daha net olurdu. Ayrıca sorunun sadece bir NPE etrafında çalışmaktan daha derin olduğunu düşünüyorum: OP kendine alıcıların neden boş döndüğünü sormalı. Null ne anlama geliyor? Belki bazı boş nesneler daha iyi olur? Veya anlamlı bir istisna dışında bir alıcıda çarpışmak mı? Temelde her şey akış kontrolü istisnalarından daha iyidir.
Marius K.

1
Geçerli bir dönüş değerinin bulunmadığına işaret etmek için istisnaların kullanılmasına yönelik koşulsuz tavsiye çok iyi değildir. İstisnalar, bir yöntem arayanın kurtarması zor olan ve programın başka bir bölümündeki bir dene-yakala ifadesinde daha iyi işlenen bir şekilde başarısız olduğunda yararlıdır. Basitçe bir dönüş değerinin yokluğunu işaret etmek için Optionalsınıfı kullanmak veya boş değer döndürmek daha iyidirInteger
Lii

6

Durumun görüldüğü gibi, sınıf yapısının gerçekten kontrolümüz dışında olduğunu varsayarsak, performans büyük bir endişe olmadıkça, soruda önerildiği gibi NPE'yi yakalamanın gerçekten makul bir çözüm olduğunu düşünüyorum. Küçük bir gelişme, dağınıklığı önlemek için fırlat / yakala mantığını sarmak olabilir:

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

Şimdi şunları yapabilirsiniz:

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

return get(() -> wsObject.getFoo().getBar().getBaz().getInt(), "");derleme zamanında sorunlu olabilecek bir hata vermez.
Philippe Gioseffi

5

Tom'un yorumda işaret ettiği gibi ,

Aşağıdaki açıklama Demeter Kanununa aykırıdır ,

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

İstediğin şey intve onu alabilirsin Foo. Law of Demeter , yabancılarla asla konuşma diyor . Davanızın için, kaputunun altında gerçek uygulama gizleyebilirsiniz Foove Bar.

Şimdi, yöntemi oluşturabilir Foogetirilemedi intgelen Baz. Nihayetinde, sahip Fooolacak Barve doğrudan maruz kalmadan Barerişebiliriz . Dolayısıyla, boş kontroller muhtemelen farklı sınıflara bölünür ve sınıflar arasında yalnızca gerekli öznitelikler paylaşılır.IntBazFoo


4
WsObject muhtemelen sadece bir veri yapısı olduğundan Demeter Yasasına uyup uymadığı tartışmalıdır. Buraya bakın: stackoverflow.com/a/26021695/1528880
DerM

2
@DerM Evet, bu mümkün, ancak OP zaten XML dosyasını ayrıştıran bir şeye sahip olduğundan, gerekli etiketler için uygun model sınıfları oluşturmayı da düşünebilir, böylece ayrıştırma kitaplığı onları eşleyebilir. Daha sonra bu model sınıfları null, kendi alt etiketlerinin kontrolü için mantığı içerir .
Tom

4

Cevabım @janki ile neredeyse aynı satırda, ancak kod pasajını aşağıdaki gibi biraz değiştirmek istiyorum:

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;

Bu wsObjectnesnenin boş olma ihtimali varsa, boş bir kontrol de ekleyebilirsiniz .


4

Bazı yöntemlerin "geri dönebileceğini null" söylüyorsunuz, ancak hangi durumlarda döndüklerini söylemiyorsunuz null. Yakaladığını söylüyorsun NullPointerExceptionama neden yakaladığını söylemiyorsun. Bu bilgi eksikliği, istisnaların ne için olduğu ve alternatiften neden üstün oldukları konusunda net bir anlayışa sahip olmadığınızı gösterir.

Bir eylemi gerçekleştirmesi amaçlanan bir sınıf yöntemini düşünün, ancak yöntem , denetimi dışındaki koşullar nedeniyle eylemi gerçekleştireceğini garanti edemez (aslında Java'daki tüm yöntemler için geçerlidir ). Biz buna metodu diyoruz ve geri dönüyor. Bu yöntemi çağıran kodun başarılı olup olmadığını bilmesi gerekir. Nasıl bilebilir? Başarı veya başarısızlık olmak üzere iki olasılıkla başa çıkacak şekilde nasıl yapılandırılabilir?

İstisnaları kullanarak, post koşulu olarak başarılı olan yöntemler yazabiliriz . Yöntem geri dönerse, başarılı olmuştur. Bir istisna atarsa, başarısız olmuştur. Bu, netlik açısından büyük bir kazanç. Normal, başarılı durumu net bir şekilde işleyen kod yazabilir ve tüm hata işleme kodunu catchcümleciklere taşıyabiliriz . Genellikle, bir yöntemin nasıl veya neden başarısız olduğunun ayrıntılarının arayan için önemli olmadığı ortaya çıkar, bu nedenle aynı catchcümle birkaç tür arızayı ele almak için kullanılabilir. Ve çoğu zaman bir yöntem yakalamak istisnalar gerekmez olur hiç ama sadece onları yayılmasına izin verebilir onun arayan. Program hatalarından kaynaklanan istisnalar bu ikinci sınıftadır; Bir hata olduğunda birkaç yöntem uygun şekilde tepki verebilir.

Yani, geri dönen yöntemler null.

  • nullDeğer, kodunuzdaki bir hatayı mı gösteriyor? Eğer öyleyse, istisnayı hiç yakalamamalısınız. Ve kodunuz kendisini ikinci kez tahmin etmeye çalışmamalıdır. Sadece işe yarayacağı varsayımı üzerine açık ve öz olanı yazın. Yöntem çağrıları zinciri açık ve öz mü? O zaman sadece onları kullanın.
  • Bir mu nulldeğer programınıza geçersiz giriş belirtmek? Varsa, a NullPointerExceptionatmak için uygun bir istisna değildir, çünkü geleneksel olarak hataları belirtmek için ayrılmıştır. Muhtemelen IllegalArgumentException( kontrol edilmemiş bir istisna istiyorsanız ) veya IOException(kontrol edilmiş bir istisna istiyorsanız ) türetilmiş özel bir istisna atmak istersiniz. Geçersiz giriş olduğunda programınızın ayrıntılı sözdizimi hata mesajları sağlaması gerekiyor mu? Öyleyse, her yöntemi bir nulldönüş değeri için kontrol edip, yapabileceğiniz tek şey uygun bir tanılama istisnası atmaktır. Programınızın ayrıntılı tanılama sağlamasına gerek yoksa, yöntem çağrılarını birbirine zincirlemek, herhangi birini yakalamak NullPointerExceptionve ardından özel özel durumunuzu atmak en açık ve özlüdür.

Cevaplardan biri, zincirleme yöntem çağrılarının Demeter Yasasını ihlal ettiğini ve dolayısıyla kötü olduğunu iddia ediyor . Bu iddia yanlıştır.

  • Program tasarımına gelince, neyin iyi neyin kötü olduğuna dair gerçekten mutlak kurallar yoktur. Yalnızca buluşsal yöntemler vardır: çoğu zaman (hatta neredeyse tamamı) doğru olan kurallar. Programlama becerisinin bir kısmı, bu tür kuralları çiğnemenin ne zaman uygun olacağını bilmektir. Dolayısıyla, "bu X kuralına aykırıdır" şeklindeki kısa bir iddia gerçekten bir cevap değildir. Kuralın çiğnenmesi gereken durumlardan biri bu mu?
  • Demeter Hukuku gerçekten API veya sınıf arayüz tasarımı hakkında bir kuraldır. Sınıfları tasarlarken, bir soyutlama hiyerarşisine sahip olmak yararlıdır. İşlemleri doğrudan gerçekleştirmek ve nesneleri dil ilkellerinden daha yüksek bir soyutlamada temsil etmek için dil ilkellerini kullanan düşük düzey sınıflarınız var. Düşük seviyeli sınıflara delege eden ve düşük seviyeli sınıflardan daha yüksek seviyede operasyonlar ve temsiller uygulayan orta seviye sınıflarınız var. Orta seviye sınıflara delege eden ve daha yüksek seviye işlemleri ve soyutlamaları uygulayan yüksek seviye sınıflarınız var. (Burada sadece üç soyutlama seviyesinden bahsetmiştim, ancak daha fazlası mümkündür). Bu, kodunuzun kendini her seviyede uygun soyutlamalarla ifade etmesine ve böylelikle karmaşıklığı gizlemesine izin verir. Demeter Yasasının mantığıbir yöntem çağrıları zinciriniz varsa, bu, doğrudan düşük seviyeli ayrıntılarla ilgilenmek için orta seviye bir sınıfa ulaşan yüksek seviyeli bir sınıfa sahip olduğunuzu ve bu nedenle orta seviye sınıfınızın orta seviye bir soyut işlem sağlamadığını gösterir. üst düzey sınıfın ihtiyaç duyduğu. Ancak , burada sahip olduğunuz durum bu değil gibi görünüyor : sınıfları yöntem çağrıları zincirinde tasarlamadınız, bunlar otomatik olarak oluşturulan bazı XML serileştirme kodunun sonucudur (değil mi?) Ve çağrı zinciri azalan değil bir soyutlama hiyerarşisi aracılığıyla, çünkü serileştirilmemiş XML, soyutlama hiyerarşisinin aynı düzeyinde (değil mi?)?

3

Okunabilirliği iyileştirmek için birden çok değişken kullanmak isteyebilirsiniz, örneğin

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();

Bu benim görüşüme göre çok daha az okunabilir. Yöntemi, yöntemin gerçek mantığından tamamen alakasız olan bir sürü boş kontrol mantığı ile küçültür.
Developer102938

2

Yakalama NullPointerException. Nereden geldiğini bilmiyorsunuz (sizin durumunuzda olası olmadığını biliyorum ama belki başka bir şey onu fırlattı) ve yavaş. Belirtilen alana erişmek istiyorsunuz ve bunun için diğer her alanın boş olmaması gerekir. Bu, her alanı kontrol etmek için mükemmel bir geçerli nedendir. Muhtemelen birinde kontrol eder ve sonra okunabilirlik için bir yöntem oluştururdum. Başkalarının da belirttiği gibi -1'i döndürmek çok eski okul ama bunun için bir nedeniniz olup olmadığını bilmiyorum (örneğin başka bir sistemle konuşmak).

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;
}

Düzenleme: WsObject muhtemelen sadece bir veri yapısı olduğundan Demeter Yasasına uyup uymadığı tartışmalıdır ( https://stackoverflow.com/a/26021695/1528880 adresini kontrol edin ).


2

Kodu yeniden düzenlemek istemiyorsanız ve Java 8'i kullanabiliyorsanız, Yöntem referanslarını kullanmak mümkündür.

Önce basit bir demo (statik iç sınıfları mazur görün)

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;
            }
}

Çıktı

241
4

boş
2

Arayüz Gettersadece işlevsel bir arayüzdür, herhangi bir eşdeğerini kullanabilirsiniz.
GetterResultsınıf, erişimciler netlik için çıkarılır, varsa alıcı zincirinin sonucunu veya çağrılan son alıcının dizinini tutar.

Yöntem getterChain, otomatik olarak (veya gerektiğinde manuel olarak) oluşturulabilen basit, standart bir kod parçasıdır.
Kodu, tekrar eden bloğun kendini belli etmesi için yapılandırdım.


Bu mükemmel bir çözüm değil, çünkü hala bir aşırı yük tanımlamanız gerekiyor getterChainHer alıcı için .

Bunun yerine kodu yeniden düzenlerdim, ancak yapamazsanız ve kendinizi uzun alıcı zincirleri kullanarak bulursanız, genellikle 2 ila 10 alıcı arasında değişen aşırı yüklere sahip bir sınıf oluşturmayı düşünebilirsiniz.


2

Diğerlerinin dediği gibi, Demeter Yasasına saygı kesinlikle çözümün bir parçasıdır. Diğer bir kısım, mümkün olan her yerde, bu zincirleme yöntemleri geri dönemeyecek şekilde değiştirmektir null. Arayanın yapacağı her şeyi yapan boş , boş veya başka bir kukla nesneyi nullgeri döndürerek geri dönmekten kaçınabilirsiniz .StringCollectionnull


2

Hatanın anlamına odaklanan bir cevap eklemek istiyorum . Boş istisna kendi başına herhangi bir anlam tam hata sağlamaz. Bu yüzden onlarla doğrudan uğraşmaktan kaçınmanızı tavsiye ederim.

Kodunuzun yanlış gidebileceği binlerce durum vardır: veritabanına bağlanamama, GÇ İstisnası, Ağ hatası ... Bunlarla tek tek ilgilenirseniz (buradaki boş kontrol gibi), çok fazla güçlük olur.

Kodda:

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

Hangi alanın boş olduğunu bilseniz bile, neyin yanlış gittiği hakkında hiçbir fikriniz olmaz. Belki Bar boştur, ama bu bekleniyor mu? Yoksa bir veri hatası mı? Kodunuzu okuyan insanları düşünün

Xenteros'un cevabında olduğu gibi, özel kontrol edilmemiş istisna kullanmayı öneriyorum . Örneğin, bu durumda: Foo boş olabilir (geçerli veri), ancak Bar ve Baz hiçbir zaman boş olmamalıdır (geçersiz veri)

Kod yeniden yazılabilir:

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"
    }
}

Kontrol edilmeyen istisnalar, programcının API'yi kötüye kullandığını gösterir. "Veritabanına bağlanılamıyor, GÇ İstisnası, Ağ hatası" gibi dış sorunlar, kontrol edilen istisnalarla belirtilmelidir.
Kevin Krumwiede

Gerçekten arayanın ihtiyacına bağlıdır. Hatayı işlemeye sizi zorladığı için istisnai yardım kontrol edildi. Ancak diğer durumlarda gerekli değildir ve kodu kirletebilir. Örneğin, Veri katmanınızda bir IOException var, bunu Sunum katmanına atacak mısınız? Bu, istisnayı yakalamanız ve her arayan kişiye yeniden başvurmanız gerektiği anlamına gelir. IOException'ı ilgili bir mesajla özel bir BusinessException ile sarmayı ve global bir filtre onu yakalayıp kullanıcıya mesajı görüntüleyene kadar yığın izlemede görünmesine izin vermeyi tercih ederim.
Hoàng Long

Arayanların kontrol edilen istisnaları yakalaması ve yeniden atması gerekmez, yalnızca bunların atılacağını bildirin.
Kevin Krumwiede

@KevinKrumwiede: Haklısınız, sadece fırlatılacak istisnayı beyan etmemiz gerekiyor. Yine de beyan etmemiz gerekiyor. Düzenleme: Buna ikinci kez bakıldığında, kontrol edilen ve kontrol edilmeyen istisna kullanımları hakkında oldukça fazla tartışma var (örneğin: programmers.stackexchange.com/questions/121328/… ).
Hoàng Long

2

NullPointerException bir çalışma zamanı istisnasıdır, bu nedenle genel olarak konuşmak gerekirse onu yakalamak için değil, ondan kaçınmak önerilir.

Yöntemi çağırmak istediğiniz her yerde istisnayı yakalamanız gerekecek (veya yığını yayacaktır). Yine de, eğer sizin durumunuzda -1 değeriyle bu sonuçla çalışmaya devam ederseniz ve boş olabilecek "parçaları" kullanmadığınız için yayılmayacağından eminseniz, o zaman bana doğru geliyor yakala onu

Düzenle:

@Xenteros'un sonraki cevabına katılıyorum, örneğin -1'i döndürmek yerine kendi istisnanızı başlatmanız daha iyi olacaktır InvalidXMLException.


3
"Yakalarsanız yakalayın, kodun diğer bölümlerine yayılabilir" derken ne demek istiyorsunuz?
Hulk

Null bu cümlenin içindeyse wsObject.getFoo () Ve kodun sonraki bölümlerinde bu sorguyu tekrar çalıştırırsanız veya wsObject.getFoo (). GetBar () (örneğin) kullanırsanız, yeniden bir NullPointerException oluşturur.
SCouto

Bu, "Yöntemi çağırmak istediğiniz her yerde istisnayı yakalamalısınız (veya yığını çoğaltacaktır)" için alışılmadık bir ifadedir. Eğer doğru anlıyorsam. Buna katılıyorum (ve bu bir sorun olabilir), sadece ifadeleri kafa karıştırıcı buluyorum.
Hulk

Bunu düzelteceğim, üzgünüm, İngilizce benim ilk dilim değil, bu yüzden bazen bu olabilir :) Teşekkürler
SCouto

2

Dünden beri bu gönderiyi takip ediyorum.

NPE'nin yakalanmasının kötü olduğunu söyleyen yorumları yorumluyorum / oyluyorum. İşte bu yüzden bunu yapıyorum.

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;
    }
}

Çıktı

3,216

0.002

Burada açık bir kazanan görüyorum. Çekler, bir istisna yakalamaktan çok daha ucuzsa. Java-8'in nasıl yapıldığını gördüm. Mevcut uygulamaların% 70'inin hala Java-7 üzerinde çalıştığını düşünürsek bu cevabı ekliyorum.

Sonuç olarak Kritik görev uygulamaları için, NPE ile çalışmak maliyetlidir.


En kötü durumda bir milyon istekte fazladan üç saniye ölçülebilir, ancak bu, "kritik uygulamalarda" bile nadiren bir anlaşmayı bozar. Bir isteğe 3,2 mikrosaniyenin eklenmesinin önemli olduğu sistemler vardır ve eğer böyle bir sisteminiz varsa, istisnalar hakkında kesinlikle dikkatlice düşünün. Ancak, bir web servisini çağırmak ve orijinal soruya göre çıktısını seri halinden çıkarmak muhtemelen bundan çok daha uzun sürer ve istisna işlemenin performansı hakkında endişelenmek bu konunun yanında değildir.
Jeroen Mostert

@JeroenMostert: Çek başına 3 Saniye / Milyon. Yani, çek sayısı maliyetini artıracak
NewUser

Doğru. Bununla bile, yine de bunu bir "önce profil" vakası olarak kabul ediyorum. İstek tam bir milisaniye fazladan sürmeden önce bir istekte 300'den fazla kontrole ihtiyacınız olacak. Tasarım düşünceleri, bundan çok daha erken ruhumda ağırlaşırdı.
Jeroen Mostert

@JeroenMostert: :) Kabul Edildi! Sonucu programcıya bırakmak ve bir çağrı almalarına izin vermek istiyorum!
NewUser

1

Verimlilik bir sorunsa, 'yakalama' seçeneği düşünülmelidir. Yayılmasının çünkü ( 'SCouto' tarafından belirtildiği gibi) 'yakalama' kullanılamaz sonra yöntemlerle birden fazla çağrı önlemek için yerel değişkenler kullanabilirsiniz getFoo(), getBar()ve getBaz().


1

Kendi İstisnanızı yaratmayı düşünmeye değer. Buna MyOperationFailedException diyelim. Bunun yerine bir değer döndürmek yerine onu atabilirsiniz. Sonuç aynı olacaktır - işlevden çıkacaksınız, ancak Java anti-kalıbı olan sabit kodlanmış -1 değerini döndürmeyeceksiniz. Java'da İstisnalar kullanıyoruz.

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

DÜZENLE:

Yorumlardaki tartışmaya göre, önceki düşüncelerime bir şeyler eklememe izin verin. Bu kodda iki olasılık vardır. Biri boşluğu kabul ediyorsun, diğeri ise bunun bir hata olmasıdır.

Bu bir hataysa ve meydana gelirse, kesme noktaları yeterli olmadığında, hata ayıklama amacıyla diğer yapıları kullanarak kodunuzda hata ayıklayabilirsiniz.

Kabul edilebilirse, bu boşluğun nerede göründüğü umurunuzda değil. Eğer yaparsanız, kesinlikle bu istekleri zincirlememelisiniz.


2
İstisnayı bastırmanın kötü bir fikir olduğunu düşünmüyor musun? Gerçek zamanlı olarak, bir istisnanın izini kaybedersek, onun gerçek acısı, neler olup bittiğini öğrenmek için! Her zaman zincirleme kullanmamayı öneririm. Gördüğüm ikinci sorun şudur: Bu kod, sonuçtan hangisinin boş olduğu zaman noktasında verilemez.
NewUser

Hayır, İstisnanızın atıldığı yeri kesinlikle işaret eden bir mesajı olabilir. Zincirlemenin en iyi çözüm olmadığına katılıyorum :)
xenteros

3
Hayır, sadece satır numarası hakkında söylerdi. Dolayısıyla, zincirdeki herhangi bir çağrı bir istisnaya yol açabilir.
NewUser

"Bir hataysa ve ortaya çıkarsa, kodunuzda hata ayıklayabilirsiniz" - üretimde değil. Sahip olduğum tek şey bir günlük olduğunda Neyin başarısız olduğunu bilmeyi tercih ederim. Bu tavsiyeyle (ve bu kodla), gerçekten bildiğiniz tek şey 4 şeyden birinin boş olduğu, ancak hangisinin ya da neden olmadığı.
VLAZ

1

Sahip olduğunuz yöntem uzun, ancak çok okunabilir. Kod tabanınıza gelen yeni bir geliştirici olsaydım, ne yaptığınızı oldukça hızlı bir şekilde görebilirdim. Diğer cevapların çoğu (istisnayı yakalamak dahil) şeyleri daha okunaklı hale getiriyor gibi görünmüyor ve bence bazıları onu daha az okunabilir yapıyor.

Büyük olasılıkla oluşturulan kaynak üzerinde kontrolünüz olmadığı ve gerçekten sadece birkaç derin iç içe geçmiş alana erişmeniz gerektiğini varsayarsak, derinlemesine iç içe geçmiş her bir erişimi bir yöntemle sarmalamanızı tavsiye ederim.

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();
}

Kendinizi bu yöntemlerin birçoğunu yazarken bulursanız veya kendinizi bu genel statik yöntemleri yapmak için cazip bulursanız, o zaman yalnızca ilgilendiğiniz alanlarla iç içe geçmiş ayrı bir nesne modeli oluşturur ve web'den dönüştürürdüm. hizmetler, nesne modelinize nesne modelini ekler.

Uzak bir web hizmeti ile iletişim kurduğunuzda, bir "uzak etki alanına" ve bir "uygulama etki alanına" sahip olmak ve ikisi arasında geçiş yapmak çok normaldir. Uzak etki alanı genellikle web protokolüyle sınırlıdır (örneğin, saf bir RESTful hizmetinde yardımcı yöntemleri ileri geri gönderemezsiniz ve derinlemesine iç içe geçmiş nesne modelleri, birden çok API çağrısından kaçınmak için yaygındır) ve bu nedenle doğrudan kullanım için ideal değildir müşterin.

Örneğin:

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();

Demeter Yasasını uygulayarak,

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

Diğerlerinden farklı görünen cevap vermek.

Sana kontrol etmek tavsiye NULLiçinde ifs.

Nedeni:

Programımızın çökmesi için tek bir şans bile bırakmamalıyız. NullPointer sistem tarafından oluşturulur. Sistem tarafından oluşturulan istisnaların davranışı tahmin edilemez . Halihazırda kendi başınıza idare etme yolunuz varken programınızı Sistemin ellerine bırakmamalısınız. Ve ekstra güvenlik için İstisna işleme mekanizmasını koyun. !!

Kodunuzun okunmasını kolaylaştırmak için, koşulları kontrol etmek için şunu deneyin:

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

DÜZENLE :

İşte bu değerleri saklamak gerekir wsObject.getFoo(), wsObject.getFoo().getBar(), wsObject.getFoo().getBar().getBaz()bazı değişkenlerde. Bunu yapmıyorum çünkü bu fonksiyonların dönüş türlerini bilmiyorum.

Herhangi bir öneri takdir edilecektir .. !!


GetFoo () 'nun çok zaman alan bir işlem olduğunu mu düşündünüz? Döndürülen değerleri değişkenlerde saklamalısınız, ancak bu bir hafıza kaybıdır. Metodunuz C programlaması için mükemmel.
xenteros

ama bazen 1 milisaniye gecikmeli program @xenteros çöküyor .. !!
Janki Gadhiya

getFoo () başka bir kıtada bulunan bir sunucudan bir değer alabilir. Her an dayanabilir: dakika / saat ...
xenteros

wsObjectWebservice'den döndürülen değeri içerecektir .. !! Hizmet zaten aranacak ve bir web hizmeti yanıtı olarak wsObjectuzun bir XMLveri alacak .. !! Yani başka bir kıtada bulunan sunucu gibisi yoktur, çünkü getFoo()sadece alıcı bir yöntemdir , bir Webservice çağrısı değildir .. !! @xenteros
Janki Gadhiya

1
Alıcıların isimlerinden Foo, Bar ve Baz nesnelerini döndürdüklerini varsayıyorum: P Ayrıca, cevabınızdan bahsedilen çifte güvenliği kaldırmayı düşünün. Kod kirliliği dışında gerçek bir değer sağladığını düşünmüyorum. Mantıklı yerel değişkenler ve boş kontrol ile, kodun doğruluğunu sağlamak için fazlasıyla yeterli yaptık. Bir istisna meydana gelebilirse, bir istisna olarak ele alınmalıdır.
Marius K.

0

SnagNesneler ağacında gezinmek için bir yol tanımlamanıza izin veren adlı bir sınıf yazdım . İşte kullanımına bir örnek:

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

Bu, örneğin ENGINE_NAMEkendisine Car?.getEngine()?.getName()iletilen örneği etkin bir şekilde çağıracağı ve nullherhangi bir referans döndürülürse geri döneceği anlamına gelir null:

final String name =  ENGINE_NAME.get(firstCar);

Maven'de yayınlanmadı, ancak herhangi biri bunu yararlı bulursa, buradadır (elbette garanti olmadan!)

Biraz basit ama işi yapıyor gibi görünüyor. Açıkçası, Java'nın daha yeni sürümleri ve güvenli gezinmeyi destekleyen diğer JVM dilleri ile daha eski Optional.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.