İstisnaları, hataları erken “yakalamak” için araç olarak kullanmak uygun mudur?


29

Sorunları erken yakalamak için istisnalar kullanırım. Örneğin:

public int getAverageAge(Person p1, Person p2){
    if(p1 == null || p2 == null)
        throw new IllegalArgumentException("One or more of input persons is null").
    return (p1.getAge() + p2.getAge()) / 2;
}

Programım nullbu fonksiyona asla geçmemelidir . Asla niyetinde değilim. Ancak hepimizin bildiği gibi, programlamada istenmeyen şeyler olur.

Bu sorun ortaya çıkarsa bir istisna atmak, programdaki diğer yerlerde daha fazla soruna neden olmadan önce onu tespit etmeme ve düzeltmeme izin verir. İstisna programı durdurur ve bana "burada kötü şeyler oldu, düzelt" diyor. Bunun yerine nullprogramda dolaşmak başka yerde sorunlara neden oluyor.

Şimdi, haklısın, bu durumda bu hemen hemen nullbir neden olur NullPointerException, bu yüzden en iyi örnek olmayabilir.

Ancak bunun gibi bir yöntemi düşünün:

public void registerPerson(Person person){
    persons.add(person);
    notifyRegisterObservers(person); // sends the person object to all kinds of objects.
}

Bu durumda, nullparametre olarak programın etrafından geçirilir ve daha sonra hatalara neden olabilir, bu da başlangıç ​​noktalarına geri dönmek zor olacaktır.

Fonksiyonu şu şekilde değiştirmek:

public void registerPerson(Person person){
    if(person == null) throw new IllegalArgumentException("Input person is null.");
    persons.add(person);
    notifyRegisterObservers(person); // sends the person object to all kinds of objects.
}

Başka yerlerde garip hatalara neden olmadan önce sorunu daha fazla tespit etmeme izin veriyor.

Ayrıca, nullparametre olarak referans sadece bir örnektir. Geçersiz argümanlardan başkasına kadar birçok sorun olabilir. Onları erken tespit etmek her zaman daha iyidir.


Öyleyse sorum basit: bu iyi uygulama mı? İstisnaları sorun önleyici araçlar olarak kullanmam iyi mi? Bu istisnaların meşru bir uygulaması mı yoksa sorunlu mu?


2
Redüktör bu sorunun neyin yanlış olduğunu açıklayabilir mi?
Giorgio

2
"erken kaza, sık sık kaza"
Bryan Chen

16
Kelimenin tam anlamıyla istisnalar bunun için var.
raptortech97

Kodlama sözleşmelerinde bu tür birçok hatayı statik olarak saptamanıza olanak sağladığını unutmayın. Bu genellikle çalışma zamanında bir istisna atmaktan üstündür. Kodlama sözleşmelerinin desteği ve etkinliği dile göre değişir.
Brian

3
IllegalArgumentException'ı attığınızdan, " Girdi kişisi" demek gereksizdir .
kevin cline

Yanıtlar:


29

Evet, "erken başarısız" çok iyi bir ilkedir ve bu onu uygulamanın basit bir yoludur. Ve belirli bir değeri geri vermek zorunda olan yöntemlerde, kasıtlı olarak başarısız olmak için yapabileceğiniz çok fazla bir şey yoktur - ya istisnalar ya da iddiaları tetikler. İstisnaların 'istisnai' koşulları işaret etmesi beklenir ve bir programlama hatasının tespit edilmesi kesinlikle istisnaidir.


Erken başarısızlık, bir hata veya durumdan kurtulmanın bir yolu yoksa, gitme yoludur. Örneğin, bir dosyayı kopyalamanız gerekiyorsa ve kaynak veya hedef yolu boşsa, hemen bir istisna atmanız gerekir.
Michael Shops,

Aynı zamanda "hızlı fail" olarak bilinir
keuleJ

8

Evet, istisnalar atmak iyi bir fikirdir. Onları erken atın, sık sık atın, hevesle atın.

Bazı istisnai davranışlar (özellikle de programlama hatalarını yansıtması düşünülenler) ile birlikte, hata ayıklama / test yapmaktan ziyade çalışma zamanı için "derlenebilir" iddiaları ile ele alınan bir "istisnalar ve iddialar" tartışması olduğunu biliyorum. Ancak, birkaç ekstra doğruluk kontrolünde tüketilen performansın miktarı modern donanım için çok düşüktür ve fazladan herhangi bir maliyet, doğru, bozulmamış sonuçlara sahip olmanın değeri ile ağır basar. Aslında, çalışma zamanında kontrol edilmesini istediğim (çoğu) kontrollerin uygulandığı bir uygulama kod tabanı ile karşılaşmadım.

Sayısal olarak yoğun kodun sıkı döngülerinde fazladan fazladan kontrol ve koşullama yapmak istemediğimi söylemeye cazip oldum ... ama aslında çok sayıda sayısal hata üretildi ve orada yakalanmazsa, dışa doğru yayılacak. tüm sonuçları etkiler. Yani orada bile, çekler yapmaya değer. Aslında, en iyi, en verimli sayısal algoritmaların bazıları hata değerlendirmesine dayanır.

Ekstra kod konusunda çok bilinçli olmak için son bir yer, ekstra koşulluların boru hattı duraklarına neden olabileceği gecikmeye duyarlı koddur. Böylece, işletim sisteminin ortasında, DBMS ve diğer ara katman çekirdekleri ve düşük seviye iletişim / protokol yönetimi. Fakat yine de, bunlar hataların en fazla gözlenmesi muhtemel yerlerden bazıları ve (güvenlik, doğruluk ve veri bütünlüğü) etkilerinin en fazla zararlı olması.

Bulduğum bir gelişme, sadece temel seviyedeki istisnaları atmamak. IllegalArgumentExceptioniyidir, ama esasen her yerden gelebilir. Özel istisnalar eklemek çoğu dilde fazla bir şey gerektirmez. Kişisel kullanım modülünüz için şunları söyleyin:

public class PersonArgumentException extends IllegalArgumentException {
    public MyException(String message) {
        super(message);
    }
}

O zaman biri bir tane gördüğünde PersonArgumentException, nereden geldiği açık. Kaç tane özel durum istisnası eklemek istediğinize dair bir dengeleyici hareket var çünkü gereksiz yere eşyaları çarpmak istemiyorsunuz (Occam's Razor). Genellikle sadece birkaç özel istisna, "bu modül doğru verileri alamıyor!" Sinyalini vermek için yeterlidir. veya "bu modül yapması gerekeni yapamaz!" özel ve uyarlanmış bir şekilde, ancak istisna hiyerarşisinin tamamını yeniden uygulamanız gerekecek kadar kesin değil. Sık sık, bu küçük özel istisnalar kümesine, stok istisnaları ile başlayıp ardından kodu tarayarak ve "bu N yerlerin stok istisnaları artırdığını, ancak veriyi almadıkları fikrinin altını çizdiğini" fark ederek anlıyorum. ihtiyaçları var, bırak


I've never actually met an application codebase for which I'd want (most) checks removed at runtime. O zaman performans açısından kritik olan bir kod yapmadınız. Ben şu anda saniyede 37M ops yapan ve onlarda 42M olmayan iddialarla derlenen bir şey üzerinde çalışıyorum. İddialar dışarıdaki girişi doğrulamıyor, kodun doğru olduğundan emin olmak için oradalar. Müşterilerim, ürünlerimin kırılmadığından emin olduğumda% 13 artış elde etmekten çok mutlular.
Blrfl

Özel istisnalar dışında bir kod temeli hazırlamaktan kaçınılmalıdır, çünkü size yardım edebilmesine rağmen, onlarla tanışması gereken başkalarına yardımcı olmaz. Genellikle, kendinizi ortak bir istisna genişletiyorsanız ve herhangi bir üye veya işlevsellik eklemiyorsanız, buna gerçekten ihtiyacınız yoktur. Örneğinizde bile, PersonArgumentExceptionkadar net değil IllegalArgumentException. İkincisi, yasadışı bir argüman iletildiğinde evrensel olarak atıldığı bilinmektedir. Aslında Personbir çağrı için geçersiz bir durumdaysa , eskiden atılmasını bekliyorum ( InvalidOperationExceptionC # 'ya benzer şekilde ).
Selali Adobor 23:14

Dikkatli olmamanız durumunda, aynı istisnayı fonksiyonun başka bir yerinden tetikleyebildiğiniz doğrudur, ancak bu genel istisnaların niteliğinden değil, kodun bir hatasıdır. Ve hata ayıklama ve yığın izlerinin girdiği yer burasıdır. İstisnalarınızı "etiketlemeye" çalışıyorsanız, mesaj oluşturucuyu kullanmak gibi en yaygın istisnaların sağladığı mevcut olanakları kullanın (tam olarak bunun için var). Bazı istisnalar bile, kuruculara sorunlu argümanın adını elde etmek için ek parametreler sağlar, ancak aynı tesisi iyi oluşturulmuş bir mesajla sağlayabilirsiniz.
Selali Adobor 23:14

Sadece ortak bir istisnayı temelde aynı istisna olan özel bir etikete yeniden markalamanın zayıf bir örnek olduğunu ve nadiren çabaya değer olduğunu kabul ediyorum. Uygulamada, modülün amacına daha yakından bağlı, daha yüksek anlamsal düzeyde özel istisnalar yayınlamaya çalışıyorum.
Jonathan Eunice

Sayısal / HPC ve İşletim Sistemi / katman yazılımı alemlerinde performansa duyarlı çalışmalar yaptım. % 13'lük bir performans artışı küçük bir şey değildir. Bunu almak için bazı kontrolleri kapatabilirim, aynı şekilde bir askeri komutan nominal reaktör çıkışının% 105'ini isteyebilir. Ancak kontrolleri, yedekleri ve diğer korumaları kapatmak için bir sebep olarak verilen "bu şekilde daha hızlı çalışacak" olduğunu gördüm. Temel olarak, ekstra performans için esneklik ve güvenlikten faydalanır (çoğu durumda veri bütünlüğü dahil). Buna değip değmeyeceği bir yargılama çağrısıdır.
Jonathan Eunice

3

Bir uygulamada hata ayıklama yaparken en kısa sürede başarısız olmak harika. Eski bir C ++ programında belirli bir segmentasyon hatasını hatırlıyorum: hatanın tespit edildiği yerin tanıtıldığı yerle hiçbir ilgisi yoktu (boş gösterici bellekte bir noktadan diğerine, nihayet bir soruna neden olmadan önce taşındı) ). Yığın izleri bu durumlarda size yardımcı olamaz.

Bu yüzden evet, savunma programlaması böcekleri hızlı bir şekilde tespit etmek ve çözmek için gerçekten etkili bir yaklaşımdır. Öte yandan, özellikle boş referanslarla, fazla abartılabilir.

Özel durumunuzda, örneğin: referanslardan biri boşsa NullReferenceException, bir kişinin yaşını almaya çalışırken bir sonraki ifadeye atılacaktır. Burada bir şeyleri gerçekten kontrol etmenize gerek yok: temel sistemin bu hataları yakalamasına ve istisnalar atmasına izin verin, bu yüzden varlar .

Daha gerçekçi bir örnek için, assertifadeleri kullanabilirsiniz :

  1. Yazmak ve okumak için daha kısa:

        assert p1 : "p1 is null";
        assert p2 : "p2 is null";
    
  2. Yaklaşımınız için özel olarak tasarlanmıştır. Hem iddiaların hem de istisnaların olduğu bir dünyada, onları şu şekilde ayırt edebilirsiniz:

    • İddialar içindir programlama hataları , ( "/ * Bu * / hiçbir zaman olmamalıdır")
    • İstisnalar içindir köşe durumlarda (istisnai ama muhtemel durumlar)

Bu nedenle, başvurunuzun girdileri ve / veya durumu hakkındaki varsayımlarınızı iddialarla ortaya koymak, bir sonraki geliştiricinin kodunuzun amacını biraz daha fazla anlamasını sağlar.

Statik bir analizör (örneğin derleyici) de daha mutlu olabilir.

Son olarak, tek bir anahtar kullanarak konuşlandırılan uygulamadan iddialar kaldırılabilir. Ancak, genel olarak konuşursak, bununla verimliliği arttırmayı beklemeyin: Çalışma zamanında yapılan onaylar önemsizdir.


1

Bildiğim kadarıyla farklı programcılar bir çözümü ya da diğerini tercih ediyor.

İlk çözüm genellikle tercih edilir, çünkü daha özlüdür, özellikle, aynı durumu farklı fonksiyonlarda tekrar tekrar kontrol etmeniz gerekmez.

İkinci çözümü buluyorum, örneğin

public void registerPerson(Person person){
    if(person == null) throw new IllegalArgumentException("Input person is null.");
    persons.add(person);
    notifyRegisterObservers(person); // sends the person object to all kinds of objects.
}

daha sağlam, çünkü

  1. Hatayı en kısa sürede, yani registerPerson()çağrıldığında, boş bir işaretçi istisnası çağrı yığınının bir yerine atıldığında değil , yakalar . Hata ayıklama çok daha kolay hale gelir: geçersiz bir değerin kendisini bir hata olarak göstermeden önce kod boyunca ne kadar ileri gidebileceğini hepimiz biliriz.
  2. Fonksiyonlar arasındaki eşleşmeyi azaltır: argümanı registerPerson()kullanarak diğer fonksiyonların hangi sonuca ulaşacağına personve onu nasıl kullanacağına dair herhangi bir varsayımda bulunmaz : nullbir hata kararı yerel olarak alınır ve uygulanır.

Bu nedenle, özellikle kod oldukça karmaşıksa, bu ikinci yaklaşımı tercih etme eğilimindeyim.


1
Sebebini vermek ("Girdi kişisi boş.") Aynı zamanda kütüphanedeki bazı belirsiz yöntemlerin başarısız olduğu durumlardan farklı olarak sorunu anlamaya yardımcı olur.
Florian F

1

Genel olarak, evet, "erken başarısız" iyi bir fikirdir. Bununla birlikte, sizin özel örneğinizde, açık IllegalArgumentException, a üzerinde kayda değer bir gelişme sağlamaz NullReferenceException- çünkü üzerinde çalışan her iki nesne de zaten fonksiyona argüman olarak sunulur.

Ancak biraz farklı bir örneğe bakalım.

class PersonCalculator {
    PersonCalculator(Person p) {
        if (p == null) throw new ArgumentNullException("p");
        _p = p;
    }

    void Calculate() {
        // Do something to p
    }
}

Yapıcıda denetleme argümanı olmasaydı, bir NullReferenceExceptionarama yaparken bir alırsınız Calculate.

Ancak, parçalanmış kod parçası ne Calculateişlev ne de işlevin tüketicisidir Calculate. Bozuk kod parçası, PersonCalculatornull ile oluşturmaya çalışan koddu Person- bu yüzden istisnanın ortaya çıkmasını istediğimiz yer burasıydı.

Bu açık argüman kontrolünü kaldırırsak , çağrıldığında neden NullReferenceExceptionmeydana geldiğini bulmanız gerekir Calculate. Nesnenin bir nullkişiyle neden oluşturulduğunu izlemek , özellikle hesap makinesini oluşturan kod aslında Calculateişlevi çağıran koda yakın değilse, zorlaşabilir.


0

Verdiğiniz örneklerde değil.

Dediğiniz gibi, açıkça atmak, bundan kısa bir süre sonra bir istisna elde edeceğiniz zaman size çok fazla kazandırmamaktadır. Birçoğu, açık bir istisnaya iyi bir mesajla sahip olmanın daha iyi olduğunu iddia etse de aynı fikirde değilim. Önceden yayımlanmış senaryolarda, yığın izlemesi yeterince iyidir. Sürüm sonrası senaryolarda, çağrı sitesi genellikle işlevin içinden daha iyi mesajlar sağlayabilir.

İkinci form, işleve çok fazla bilgi veriyor. Bu fonksiyonun, diğer fonksiyonların boş girdi atacağını bilmemesi gerekir. Şimdi null girişi atmış olsalar bile , yeniden boşaltma işlemi kod boyunca boş olduğundan yayıldığından bu durumun durması gerektiğinde refaktör için çok zahmetli hale gelir.

Ancak genel olarak, bir şeyin yanlış gittiğini belirledikten sonra bir kez atmalısınız (DRY'ye saygı gösterirken). Bunlar yine de bunun güzel örnekleri değil.


-3

Örnek fonksiyonunuzda kontrol yapmamayı tercih etmenizi ve NullReferenceExceptionbunun olmasına izin vermenizi tercih ederim .

Birincisi, orada bir null boşuna geçmek hiç de mantıklı değil, bu yüzden a'nın atılmasına dayanarak derhal sorunu çözeceğim NullReferenceException.

İkincisi, eğer her fonksiyon ne tür açıkça yanlış girdilerin sağlandığına bağlı olarak biraz farklı istisnalar atıyorsa, kısa zaman sonra, 18 farklı istisna türü atabilecek fonksiyonlara sahip olacaksınız ve kısa bir süre sonra kendinizin de öyle olduğunu söyleyerek bulacaksınız. başa çıkmak için çok iş ve yine de sadece tüm istisnaları bastırmak.

İşlevinizdeki tasarım zamanı hatasının durumuna yardımcı olmak için gerçekten yapabileceğiniz hiçbir şey yok, bu yüzden arızayı değiştirmeden olmasına izin verin.


Birkaç yıldan beri bu prensibe dayanan bir kod temelinde çalışıyorum: işaretçiler ihtiyaç duyulduğunda kolayca çıkarıldı ve NULL'ye karşı hiçbir zaman kontrol edilemedi ve açıkça ele alınmadı. Bu kodun hata ayıklaması her zaman çok zaman alıcı ve zahmetli olmuştur, çünkü istisnalar (veya çekirdek dökümleri), mantıksal hatanın olduğu noktadan çok daha sonra ortaya çıkar. Kelimenin tam anlamıyla belirli hataları aramak için haftalarca harcadım. Bu sebeple, en kısa zamanda başarısız olan stili tercih ediyorum, en azından bir boş gösterici istisnasının nereden geldiği hemen belli olmayan karmaşık kod için.
Giorgio

“Birincisi, orada bir null değerinin boşa gitmesinin bir anlamı yok, bu yüzden NullReferenceException'ın atılmasına dayanarak sorunu hemen çözeceğim.”: Prog'un ikinci örneğinin gösterdiği gibi, mutlaka değil.
Giorgio

“İkincisi, eğer her bir fonksiyon ne tür açıkça yanlış girdilerin sağlandığına bağlı olarak biraz farklı istisnalar atıyorsa, kısa süre sonra 18 farklı istisna türünü atabilecek fonksiyonlara sahip olacaksınız…”: Bu durumda siz Tüm fonksiyonlarda aynı istisnayı (NullPointerException bile) kullanabilir: Önemli olan, erken atmak, her fonksiyondan farklı bir istisna atmamaktır.
Giorgio

RuntimeExceptions bunun içindir. “Olmaması gereken” ancak kodunuzun çalışmasını engelleyen herhangi bir koşul bir tane atmalıdır. Bunları yöntem imzasında beyan etmeniz gerekmez. Ancak bir noktada onları yakalamanız ve talep edilen işlemin başarısız olduğunu bildirmeniz gerekir.
Florian F

1
Soru bir dil belirtmiyor, bu nedenle örneğin güvenerek RuntimeExceptiongeçerli bir varsayım olması gerekmiyor.
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.