Neden doğal olarak olmasına izin vermek yerine NullPointerException özel olarak atılmalı?


182

JDK kaynak kodunu okurken, yazarın parametreleri null olup olmadığını kontrol edip yeni NullPointerException () 'ı manuel olarak atacağını ortak buluyorum. Neden yapıyorlar? Herhangi bir yöntem çağırdığında yeni NullPointerException () atacak beri bunu gerek yok düşünüyorum. (Örneğin, HashMap'in bazı kaynak kodu:

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
kodlamanın kilit noktası niyettir
Scary Wombat

19
Bu ilk sorunuz için çok iyi bir soru! Bazı küçük düzenlemeler yaptım; Umarım umursamazsın. Ayrıca teşekkürü ve ilk sorunuz olan notu kaldırdım çünkü normalde bu tür şeyler SO sorularının bir parçası değil.
David Conrad

11
Ben C # kongre böyle ArgumentNullExceptiondurumlarda (yerine NullReferenceException) yükseltmektir - aslında neden NullPointerExceptionburada (farklı bir tane yerine) açıkça ortaya koyacağınız konusunda gerçekten iyi bir soru .
EJoshuaS - Monica

21
@EJoshuaS Bu eski bir tartışma atmak isteyip IllegalArgumentExceptionya NullPointerExceptionboş argüman için. İkincisi JDK sözleşmesidir.
shmosel

33
GERÇEK problem, bir hata atıp bu hataya yol açan tüm bilgileri atmasıdır . Bunun gerçek kaynak kodu olduğu anlaşılıyor . Basit bir kanlı dize mesajı bile değil. Üzgün.
Martin Ba

Yanıtlar:


254

Birçoğu yakından ilişkili olan akla gelen birkaç neden vardır:

Fail-fast: Başarısız olursa, en iyisi daha sonra değil, daha erken başarısız olur. Bu, sorunların kaynaklarına daha yakın yakalanmasına izin vererek tanımlanmasını ve ondan kurtulmasını kolaylaştırır. Ayrıca, başarısız olması gereken koddaki CPU döngülerini boşa harcamayı da önler.

Amaç: İstisnayı açık bir şekilde atmak, koruyucunun hatanın bilerek olduğunu ve yazarın sonuçların farkında olduğunu açıkça belirtir.

Tutarlılık: Hatanın doğal olarak gerçekleşmesine izin verildiyse, her senaryoda ortaya çıkmayabilir. Örneğin hiçbir eşleme bulunmazsa remappingFunctionasla kullanılmaz ve istisna atılmaz. Girişi önceden doğrulamak, daha belirleyici davranışlar ve daha açık belgeler sağlar .

Kararlılık: Kod zamanla gelişir. Doğal olarak bir istisna ile karşılaşan kod, biraz yeniden düzenleme yaptıktan sonra bunu yapmayı durdurabilir veya farklı koşullar altında yapabilir. Açıkça atmak, davranışın istemeden değişme olasılığını azaltır.


14
Ayrıca: bu şekilde kural dışı durumun atıldığı konum, denetlenen tam olarak bir değişkene bağlanır. Bu olmadan, istisna birden çok değişkenten birinin boş olması olabilir.
Jens Schauder

45
Bir diğeri: NPE'nin doğal olarak gerçekleşmesini beklerseniz, aradaki bazı kodlar programınızın durumunu yan etkiler yoluyla değiştirmiş olabilir.
Thomas

6
Bu pasaj bunu yapmasa da, new NullPointerException(message)neyin boş olduğunu açıklığa kavuşturmak için yapıcıyı kullanabilirsiniz . Kaynak kodunuza erişimi olmayan kişiler için iyi. Hatta bunu JDK 8'de Objects.requireNonNull(object, message)yarar yöntemiyle bir astar haline getirdiler .
Robin

3
ARIZA, ARIZA yakınında olmalıdır. "Hızlı Başarısız" bir kuraldan daha fazlasıdır. Bu davranışı ne zaman istemezsiniz? Diğer davranışlar, hataları gizlediğiniz anlamına gelir. "ARIZALAR" ve "ARIZALAR" vardır. FAILURE, bu programların bir NULL işaretçisi sindirip çöktüğü zamandır. Ancak bu kod satırı FAULT'un bulunduğu yerde değildir. NULL bir yerden geldi - bir yöntem argümanı. Bu argümanı kim geçti? Yerel bir değişkeni referans alan bazı kod satırlarından. Nerede ... Gördün mü? Bu berbat. Kötü bir değerin depolandığını görmek kimin sorumluluğunda olmalı? Programınız çöktü.
Noah Spurrier

4
@Thomas İyi bir nokta. Shmosel: Thomas'ın noktası başarısızlık-hızlı noktada ima edilebilir, ancak bir tür gömülüdür. Kendi ismine sahip olması gereken yeterince önemli bir kavram: başarısızlık atomisitesi . Bkz. Bloch, Etkili Java , Madde 46. Hata hızından daha güçlü bir anlambilime sahiptir. Ayrı bir noktada çağırmanızı öneririm. Mükemmel cevap genel, BTW. +1
Stuart Marks

40

Netlik, tutarlılık ve fazladan, gereksiz işlerin yapılmasını önlemek içindir.

Yöntemin üstünde bir koruma maddesi olmasaydı ne olacağını düşünün. Her zaman arayacaktı hash(key)ve NPE fırlatılmadan önce geçtiği getNode(hash, key)zaman bile .nullremappingFunction

Daha da kötüsü, ifkoşul falseo zaman elsedalı kullanmıyoruz, bu hiç kullanmıyor remappingFunction, yani yöntem her zaman NPE'yinull geçtiğinde ; yapılıp yapılmayacağı haritanın durumuna bağlıdır.

Her iki senaryo da kötü. İçin nullgeçerli bir değer değilseremappingFunction sürekli çağrı sırasında bakılmaksızın nesnenin iç durumunun bir istisna olmalıdır yöntemle ve sadece atmak için gidiyor göz önüne alındığında anlamsız gereksiz işi olmadan yapmalıdırlar. Son olarak, kaynak kodunu inceleyen herkesin bunu yapacağını kolayca görebilmesi için korumanın hemen önünde olması temiz ve net bir kodun iyi bir ilkesidir.

İstisna şu anda her bir kod dalı tarafından atılmış olsa bile, kodun ileride bir revizyonunun bunu değiştirmesi mümkündür. Kontrolün başlangıçta yapılması kesinlikle yapılmasını sağlar.


25

@ Shmosel'in mükemmel cevabında listelenen nedenlere ek olarak ...

Performans: JVM'nin bunu yapmasına izin vermek yerine NPE'yi açıkça atmanın performans faydaları olabilir (bazı JVM'lerde).

Java yorumlayıcısı ve JIT derleyicisinin boş göstericilerin kayıttan çıkarılmasını algılama stratejisine bağlıdır. Bir strateji null değerini sınamak değil, bunun yerine bir yönerge 0 adresine erişmeye çalıştığında gerçekleşen SIGSEGV'i tuzağa düşürmektir. Bu, başvurunun her zaman geçerli olduğu durumda en hızlı yaklaşımdır, ancak pahalıdır NPE durumunda .

nullKoddaki açık bir test, NPE'lerin sık olduğu bir senaryoda SIGSEGV performans isabetini önleyecektir.

(Bunun modern bir JVM'de değerli bir mikro optimizasyon olacağından şüpheliyim, ancak geçmişte olabilirdi.)


Uyumluluk: İstisnada mesaj olmamasının muhtemel nedeni, JVM'nin kendisi tarafından atılan NPE'lerle uyumluluk içindir. Uyumlu bir Java uygulamasında, JVM tarafından atılan bir NPE'nin bir nullmesajı vardır. (Android Java farklıdır.)


20

Diğer insanların işaret ettiklerinin yanı sıra, burada sözleşmenin rolüne dikkat etmek önemlidir. Örneğin, C # 'da, bu gibi durumlarda açıkça bir istisna oluşturma konusunda aynı kuralınız vardır, ancak bu özellikle bir ArgumentNullException, biraz daha spesifiktir. (C # kuralı, NullReferenceException her zaman bir tür hatayı temsil eder - oldukça basit bir şekilde, üretim kodunda hiç olmamalıdır; verilen, ArgumentNullExceptiongenellikle de öyle, ancak " kütüphanenin doğru bir şekilde nasıl kullanılacağını anlayabilecekler.

Yani, temelde, C # NullReferenceExceptionprogramınızın aslında onu kullanmaya çalıştığı ArgumentNullExceptionanlamına gelir , oysa değerin yanlış olduğunu fark ettiği ve kullanmaya çalışmak bile zahmete girmediği anlamına gelir. Çıkarımlar aslında farklı olabilir (koşullara bağlı olarak), çünkü ArgumentNullExceptionsöz konusu yöntemin henüz yan etkileri olmadığı anlamına gelir (yöntem ön koşullarında başarısız olduğu için).

Bu arada, ArgumentNullExceptionya da gibi bir şey yükseltiyorsanız IllegalArgumentException, bu kontrolü yapmanın bir parçasıdır: "normalde" alacağınızdan farklı bir istisna istiyorsunuz.

Her iki durumda da, istisnanın açıkça yükseltilmesi, yöntemin ön koşulları ve beklenen argümanları hakkında açık olmanın iyi uygulamasını güçlendirir, bu da kodun okunmasını, kullanılmasını ve bakımını kolaylaştırır. Açık bir şekilde kontrol etmediyseniz null, bunun hiç kimsenin bir nulltartışmayı geçmeyeceğini düşündüğünüz için bilmiyorum, yine de istisnayı atmak için sayıyorsunuz ya da sadece bunu kontrol etmeyi unuttunuz.


4
Orta paragraf için +1. Söz konusu kod 'yeni IllegalArgumentException ("remappingFunction boş olamaz") atmak gerektiğini iddia ediyorum; Böylece neyin yanlış olduğu hemen belli oluyor. Gösterilen NPE biraz belirsiz.
Chris Parker

1
@ChrisParker Eskiden aynı görüşteydim, ancak NullPointerException'ın null olmayan bir girişime çalışma zamanı yanıtı olmasının yanı sıra null olmayan bir argümanı bekleyen bir yönteme geçirilen bir null argümanını belirtmesi amaçlanıyor. Javadoc'tan: “Uygulamalar, nullnesnenin diğer yasadışı kullanımlarını belirtmek için bu sınıfın örneklerini atmalıdır .” Bu konuda çılgın değilim, ama amaçlanan tasarım gibi görünüyor.
VGR,

1
Kabul ediyorum, @ChrisParker - Bu istisnanın daha spesifik olduğunu düşünüyorum (kod değeri ile hiçbir şey yapmayı denemediği için hemen kullanmaması gerektiğini fark etti). Bu durumda C # kuralını seviyorum. C # kuralı, NullReferenceException(buna eşdeğer NullPointerException) kodunuzun gerçekten kullanmaya çalıştığı anlamına gelir (her zaman bir hatadır - üretim kodunda asla olmamalıdır), vs. "Argümanın yanlış olduğunu biliyorum, bu yüzden bile kullanmaya çalışın. " Ayrıca var ArgumentException(bu, başka bir nedenden dolayı argümanın yanlış olduğu anlamına gelir).
EJoshuaS - Monica'yı

2
Ben bu kadar söyleyeceğim, DAİMA açıklandığı gibi bir IllegalArgumentException atın. Sözleşmenin aptal olduğunu hissettiğimde, her zaman rahat flouting konvansiyonu hissediyorum.
Chris Parker

1
@PieterGeerkens - evet, çünkü NullPointerException satırı 35, IllegalArgumentException ("İşlev boş olamaz") satır 35'ten ÇOK daha iyidir. Cidden mi?
Chris Parker

12

Bu nedenle, haritayı kullandığınızda değil, hatayı alır almaz istisnayı alacaksınız ve neden olduğunu anlamayacaksınız.


9

Görünüşte düzensiz bir hata koşulunu açık bir sözleşme ihlaline dönüştürür: Fonksiyonun düzgün çalışması için bazı ön koşulları vardır, bu yüzden bunları önceden kontrol eder ve karşılanmalarını zorlar.

Etkisi, computeIfPresent()istisna dışında aldığınızda hata ayıklamak zorunda kalmayacaksınız . Kural dışı durumun önkoşul denetiminden geldiğini gördüğünüzde, işlevi geçersiz bir argümanla çağırdığınızı bilirsiniz. Kontrol orada olmasaydı, computeIfPresent()kendi içinde , istisnanın atılmasına yol açan bazı hatalar olma olasılığını dışlamanız gerekir .

Açıktır ki, jenerik atmak, NullPointerExceptionkendi başına bir sözleşme ihlaline işaret etmediği için gerçekten kötü bir seçimdir. IllegalArgumentExceptiondaha iyi bir seçim olurdu.


Sidenote:
Java'nın buna izin verip vermediğini bilmiyorum (şüpheliyim), ancak C / C ++ programcıları assert()bu durumda hata ayıklama için önemli ölçüde daha iyi bir yöntem kullanır: Sağlanan programın derhal ve olabildiğince sert çökmesini söyler durum yanlış olarak değerlendirilir. Eğer koştuysan

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

    ...
}

bir hata ayıklayıcı altında ve NULLher iki argümana da aktarılan bir şey , program, hangi argümanın olduğunu söyleyen satırda durur NULLve boşta tüm çağrı yığınının tüm yerel değişkenlerini inceleyebilirsiniz.


1
assert something != null;ancak -assertionsuygulamayı çalıştırırken bayrak gerekir . Eğer -assertionsbayrak yoktur, assert anahtar bir AssertionException atmaz
Zoe

Boş Başvuru geçersiz argüman ve boş argüman tüm genel bir tür böcek ima, ancak farklı ima - Burada C # kuralını tercih yüzden en katılıyorum türlü böcek. "Bir boş başvuru kullanmaya çalışıyorsunuz" genellikle "kütüphaneyi yanlış kullanıyorsunuz" dan çok farklı.
EJoshuaS - Monica'yı

7

O mümkün çünkü var olmayan doğal olarak gerçekleşmesi. Bunun gibi bir kod parçasına bakalım:

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

(elbette bu örnekte kod kalitesi ile ilgili çok sayıda sorun var. Mükemmel örneği hayal etmek için tembelim.)

Durum cgerçekten kullanılmayacak ve mümkünse NullPointerExceptionbile atılamayacak c == null.

Daha karmaşık durumlarda, bu tür vakaları avlamak çok kolay olmaz. Bu yüzden genel kontrol if (c == null) throw new NullPointerException()daha iyidir.


Muhtemelen, gerçekten ihtiyaç duyulmadığında bir veritabanı bağlantısı olmadan çalışan bir kod parçası iyi bir şeydir ve sadece başarısız olup olmadığını görmek için bir veritabanına bağlanan kod genellikle oldukça sinir bozucudur.
Dmitry Grigoryev

5

Daha fazla hasarı korumak veya tutarsız duruma geçmek kasıtlıdır.


1

Buradaki diğer mükemmel cevapların yanı sıra, birkaç durum eklemek istiyorum.

Kendi istisnanızı oluşturursanız bir mesaj ekleyebilirsiniz

Kendinizinkini atarsanız NullPointerExceptionbir mesaj ekleyebilirsiniz (ki mutlaka yapmalısınız!)

Varsayılan ileti, bir nullfrom new NullPointerException()ve bunu kullanan tüm yöntemlerdir Objects.requireNonNull. Bu boş değeri yazdırırsanız, boş bir dizeye bile dönüşebilir ...

Biraz kısa ve bilgisiz ...

Yığın izleme çok fazla bilgi verecektir, ancak kullanıcının null olup olmadığını bilmesi için kodu kazması ve tam satıra bakması gerekir.

Şimdi NPE'nin, örneğin farklı departmanlar ve hatta kuruluşlar arasında bir web hizmeti hatasındaki bir mesaj olarak, ağ üzerinden sarıldığını ve gönderildiğini düşünün. En kötü senaryo, kimse ne anlama nullgeldiğini anlayamaz ...

Zincirleme yöntem çağrıları tahmin etmenizi sağlayacaktır

Bir istisna yalnızca istisnanın hangi satırda gerçekleştiğini size bildirir. Aşağıdaki satırı göz önünde bulundurun:

repository.getService(someObject.someMethod());

Bir NPE almak ve onu, bu satırdaki hangi birini işaret ediyorsa repositoryve someObjectoldu null adlı?

Bunun yerine, bu değişkenleri elde ettiğinizde kontrol etmek, en azından ele alınacak tek değişken olan bir satıra işaret edecektir. Daha önce de belirtildiği gibi, hata mesajınız değişkenin veya benzerinin adını içeriyorsa daha da iyidir.

Çok sayıda girdi işlenirken oluşan hatalar tanımlayıcı bilgiler vermelidir

Programınızın binlerce satırdan oluşan bir giriş dosyasını işlediğini ve aniden bir NullPointerException olduğunu düşünün. Bir yere bakıyorsunuz ve bazı girdilerin yanlış olduğunu fark ediyorsunuz ... hangi girdi? Bu dosyadaki hangi satırın düzeltilmesi gerektiğini anlamak için satır numarası, belki sütun veya hatta tüm satır metni hakkında daha fazla bilgiye ihtiyacınız olacaktır.

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.