İstisnalar: Neden erken atıyorsun? Neden geç satarsın?


156

Yalıtımda istisnaların ele alınmasıyla ilgili en iyi bilinen birçok uygulama vardır. "Yap ve Yapılmayacaklar" ı yeterince iyi biliyorum, ancak daha büyük ortamlarda en iyi uygulamalar veya kalıplara gelince işler karmaşıklaşıyor. "Erken at, geç yakala" - defalarca duydum ve hala kafam karıştı.

Düşük seviyeli bir katmanda boş bir işaretçi istisnası atılırsa neden erken atmalı ve geç kalmalıyım? Neden daha yüksek bir katmanda yakalayayım? İş katmanı gibi daha düşük bir seviyede istisna yakalamak benim için mantıklı değil. Her katmanın kaygılarını ihlal ettiği görülüyor.

Aşağıdaki durumu hayal edin:

Rakam hesaplayan bir hizmetim var. Rakamı hesaplamak için hizmet, ham verileri almak için bir havuza ve hesaplama hazırlamak için diğer bazı servislere erişir. Veri alma katmanında bir sorun varsa, neden DataRetrievalException'ı daha yüksek seviyeye atmalıyım? Buna karşılık istisnayı, örneğin bir CalculationServiceException gibi anlamlı bir istisnaya sarmayı tercih ederim.

Neden erken atıyorsun, neden geç kaldın?


104
"Geç avlanmanın" arkasındaki fikir, olabildiğince geç avlanmak yerine, mümkün olduğu kadar erken avlanmaktır. Örneğin, bir dosya çözümleyiciniz varsa, bulunmayan bir dosyayı işlemenin bir anlamı yoktur. Bununla ne yapmayı kastediyorsunuz, kurtarma yolunuz nedir? Bir tane yok, o yüzden yakalama. Lexin'e git, orada ne yapıyorsun, programın devam etmesi için bundan nasıl kurtulursun? İstisna geçmesine izin veremez. Tarayıcınız bunu nasıl idare edebilir? Geçemez, bırakamaz. Arayan kod bunu nasıl halledebilir? Başka bir dosya yolunu deneyebilir veya kullanıcıyı uyarabilir;
Phoshi

16
NullPointerException'ın (NPE'in ne anlama geldiğini kabul ediyorum) yakalanması gereken çok az sayıda durum vardır; Mümkünse ilk etapta kaçınılmalıdır. NullPointerExceptions kullanıyorsanız, düzeltilmesi gereken bazı bozuk kodlarınız vardır. Büyük olasılıkla da kolay bir düzeltme var.
Phil

6
Lütfen beyler, bu soruyu bir kopya olarak kapatmayı önermeden önce, diğer soruya verilen cevabın bu soruyu çok iyi yanıtlamadığını kontrol edin.
Doktor Brown

1
(Citebot) today.java.net/article/2003/11/20/… Bu, teklifin kaynağı değilse, lütfen en muhtemel teklif olduğunu düşündüğünüz kaynağa bir referans verin.
rwong

1
Bu soruya ulaşan ve Android geliştirme yapan herkes için sadece bir hatırlatma. Android'de istisnalar yerel olarak yakalanmalı ve kullanılmalıdır - ilk yakalandığı aynı fonksiyonda. Bunun nedeni, istisnaların ileti işleyicileri arasında yayılmamasıdır - başvurunuz gerçekleşirse öldürülecektir. Yani, Android geliştirme yaparken bu tavsiyeden alıntı yapmamalısınız.
rwong

Yanıtlar:


118

Tecrübelerime göre, hataların meydana geldiği noktaya istisnalar atmak en iyisidir. Bunu yapın, çünkü istisnanın neden tetiklendiğiyle ilgili en çok şey bildiğiniz nokta budur.

İstisna, katmanları geri gevşettiğinden, yakalamak ve yeniden tarama, istisnaya ek bağlam eklemenin iyi bir yoludur. Bu, farklı bir istisna türü atmak anlamına gelebilir, ancak bunu yaptığınızda orijinal istisnayı içerir.

Sonunda istisna, kod akışı hakkında karar verebileceğiniz bir katmana ulaşacaktır (örneğin, kullanıcının işlem yapması istenir). Sonunda istisnayı ele almanız ve normal yürütmeye devam etmeniz gereken nokta budur.

Kod tabanınızla ilgili pratik ve deneyim sayesinde, hatalara ne zaman ek içerik ekleyeceğinizi ve gerçekte en mantıklı olanı, en sonunda hataları ele almanın ne kadar kolay olduğunu değerlendirmek oldukça kolaydır.

Yakala → Yeniden Başlat

Sorunu anlamak için geliştiricinin tüm katmanlar üzerinde çalışmak zorunda kalmasına neden olacak daha fazla bilgi ekleyebileceğiniz bir yerde bunu yapın.

Yakala → Kulp

Bunu, uygun olanın ne olduğuna dair nihai kararlar alabileceğiniz bir yerde yapın, ancak yazılımın içinde farklı yürütme akışı var.

Yakala → Hata İadesi

Bunun uygun olduğu durumlar olmasına rağmen, istisnaları yakalamak ve arayana bir hata değeri döndürmek, Catch → Rethrow uygulamasına yeniden düzenleme yapmak için düşünülmelidir.


Evet, zaten biliyor ve sorunun dışında, hatanın ortaya çıktığı noktaya istisnalar atmam gerektiğini biliyorum. Fakat neden NPE'yi yakalamamalıyım ve bunun yerine Stacktrace'i tırmanmasına izin vermemeliyim? Her zaman NPE'yi yakalar ve anlamlı bir istisnaya sarılırdım. Ayrıca servis veya kullanıcı arayüzüne bir DAO İstisna atmamın neden bir avantajı da göremiyorum. Her zaman servis katmanında yakalar ve ek ayrıntılı bilgilerle bir servisi istisnaya dahil eder, neden servisi aramayı başarısız olur.
shylynx

8
@shylynx Bir istisna yakalamak ve sonra daha anlamlı bir istisna tekrar başlatmak yapmak iyi bir şeydir. Yapmamanız gereken, çok erken bir istisna yakalamak ve sonra onu yeniden yorumlamamak. Söylemenin uyardığı hata, istisnayı çok erken yakalamak ve ardından kodun yanlış katmanında işlemeye çalışmaktır.
Simon B,

İçeriği istisna alma noktasında açık bir hale getirmek, ekibinizdeki geliştiriciler için hayatı kolaylaştırır. Bir NPE, sorunu anlamak için daha fazla araştırma yapılmasını gerektirir
Michael Shaw

4
"kodunuzda bir noktası var Neden @shylynx Bir soru sorabilir edebilir bir atmak NullPointerException? denetlemez Neden null(belki bir istisna ve atmak IllegalArgumentExceptionArayan kötü tam olarak nerede bilmesi er) nullgeçirilen var?" Bunun, "erken atma" kısmının söyleyeceği şeyin olacağına inanıyorum.
jpmc26

2
@jpmc NPE'yi sadece katmanlar ve istisnalar hakkındaki endişeleri vurgulamak için bir örnek olarak aldım. Ben de bir IllegalArgumentException ile değiştirebilirim.
shylynx

56

En kısa zamanda bir istisna atmak istiyorsunuz, çünkü sebebi bulmayı kolaylaştırıyor. Örneğin, belirli argümanlarla başarısız olabilecek bir yöntem düşünün. Argümanları doğrularsanız ve yöntemin en başında başarısız olursanız, hatanın arama kodunda olduğunu hemen anlarsınız. Başarısızlıktan önce argümanlar gerekli olana kadar beklerseniz, yürütmeyi izlemeniz ve hatanın çağıran kodda mı (hatalı argüman) veya yöntemin bir hatası olup olmadığını anlamak zorundasınız. İstisnayı ne kadar erken atarsanız, temel sebebine o kadar yaklaşır ve işlerin nerede yanlış gittiğini bulmak o kadar kolay olur.

İstisnaların daha yüksek seviyelerde ele alınmasının nedeni, düşük seviyelerin hatayı gidermek için uygun hareket tarzının ne olduğunu bilmemesidir. Aslında, çağıran kodun ne olduğuna bağlı olarak aynı hatayı idare etmek için birden fazla uygun yol olabilir. Örneğin bir dosyayı açarak atın. Bir yapılandırma dosyası açmaya çalışıyorsanız ve orada değilse, istisnayı dikkate almamak ve varsayılan yapılandırmaya devam etmek uygun bir yanıt olabilir. Programın yürütülmesi için hayati önem taşıyan özel bir dosyayı açıyorsanız ve bir şekilde eksikse, tek seçeneğiniz muhtemelen programı kapatmaktır.

İstisnaları doğru tiplere sarmak tamamen ortogonal bir konudur.


1
Farklı seviyelerin neden önemli olduğunu net bir şekilde açıklamak için + 1. Dosya sistemi hatası için mükemmel bir örnek.
Juan Carlos Coto

24

Diğerleri neden erken atıldığını oldukça iyi özetledi . Bunun yerine neden geç kalan kısmı yakalamak istediğime odaklanmama izin verin , ki bu da zevkime uygun bir açıklama görmedim.

Peki neden istisnalar?

İlk etapta istisnaların neden olduğu konusunda bir karışıklık var gibi görünüyor. Burada büyük sırrı paylaşmama izin verin: istisnaların ve istisnaların ele alınmasının nedeni ... ÖZET .

Böyle bir kod gördün mü:

static int divide(int dividend, int divisor) throws DivideByZeroException {
    if (divisor == 0)
        throw new DivideByZeroException(); // that's a checked exception indeed

    return dividend / divisor;
}

static void doDivide() {
    int a = readInt();
    int b = readInt(); 
    try {
        int res = divide(a, b);
        System.out.println(res);
    } catch (DivideByZeroException e) {
        // checked exception... I'm forced to handle it!
        System.out.println("Nah, can't divide by zero. Try again.");
    }
}

İstisnalar böyle kullanılmamalıdır. Yukarıdaki gibi kodlar gerçek hayatta var, ama bunlar daha çok sapma ve gerçekten istisnadır (pun). Bölünmenin tanımı, örneğin, saf matematikte bile, koşulludur: giriş alanını sınırlamak için her zaman istisnai sıfır durumuyla başa çıkmak zorunda olan "arayan kod" dur. Çirkin Arayan için her zaman acıdır. Yine de, bu gibi durumlar için yapılacak iş şekli doğal yoldur:

static int divide(int dividend, int divisor) {
    // throws unchecked ArithmeticException for 0 divisor
    return dividend / divisor;
}

static void doDivide() {
    int a = readInt();
    int b = readInt();
    if (b != 0) {
        int res = divide(a, b);
        System.out.println(res);
    } else {
        System.out.println("Nah, can't divide by zero. Try again.");
    }
}

Alternatif olarak, aşağıdaki gibi OOP tarzında tam bir komando gidebilirsiniz:

static class Division {
    final int dividend;
    final int divisor;

    private Division(int dividend, int divisor) {
        this.dividend = dividend;
        this.divisor = divisor;
    }

    public boolean check() {
        return divisor != 0;
    }

    public int eval() {
        return dividend / divisor;
    }

    public static Division with(int dividend, int divisor) {
        return new Division(dividend, divisor);
    }
}

static void doDivide() {
    int a = readInt();
    int b = readInt(); 
    Division d = Division.with(a, b);
    if (d.check()) {
        int res = d.eval();
        System.out.println(res);
    } else {
        System.out.println("Nah, can't divide by zero. Try again.");
    }
}

Gördüğünüz gibi, arayan kodu ön kontrolün yükünü taşıyor, ancak sonrasında istisna işlemesi yapmıyor. Bir ArithmeticExceptionçağrı yapmaktan herhangi bir şey geliyorsa divideya da eval, o zaman SİZİ unuttum, çünkü istisna yapmak zorundasınız ve kodunuzu düzeltmek zorundasınız check(). Benzer nedenlerden dolayı, bir yakalamak NullPointerExceptionhemen hemen her zaman yapılacak yanlış şeydir.

Şimdi yöntem / işlev imzasında istisnai durumları görmek istediklerini, yani çıktı alanını açıkça genişletmek istediklerini söyleyenler var . Bunlar, kontrol edilen istisnaları destekleyenler . Tabii ki, çıktı alanını değiştirmek herhangi bir doğrudan arayan kodunu uyarlamaya zorlamalıdır ve bu gerçekten de kontrol edilen istisnalar ile başarılabilir. Ancak bunun için istisnalara ihtiyacınız yok! Bu yüzden Nullable<T> genel sınıflara , vaka sınıflarına , cebirsel veri türlerine ve birleşim türlerine sahipsiniz . Bazı OO çalışanları bu gibi basit hata durumları için geri dönmeyi bile tercih edebilir null:

static Integer divide(int dividend, int divisor) {
    if (divisor == 0) return null;
    return dividend / divisor;
}

static void doDivide() {
    int a = readInt();
    int b = readInt(); 
    Integer res = divide(a, b);
    if (res != null) {
        System.out.println(res);
    } else {
        System.out.println("Nah, can't divide by zero. Try again.");
    }
}

Teknik olarak istisnalar olabilir yukarıdaki gibi bir amaç için kullanılabilir, ancak burada noktasıdır edilmesi: istisnalar bu tür bir kullanım için mevcut değildir . İstisnalar pro soyutlamadır. İstisna dolaylı aktarma ile ilgilidir. İstisnalar, doğrudan müşteri sözleşmelerini bozmadan "sonuç" alanını genişletmeye ve hata işlemeyi "başka bir yere" ertelemeye izin verir . Kodunuz, aynı kodun doğrudan arayanlarda işlenen istisnalar atarsa, aralarında bir soyutlama katmanı yoksa, o zaman YANLIŞ yaparsınız.

NASIL GEÇERLİ OLACAK?

İşte buradayız. Yukarıdaki senaryolarda istisnaları kullanmanın istisnaların nasıl kullanılmadığını gösterme yolunda bulunduğumu tartışmıştım. Ancak, istisnaların ele alınması ile sunulan soyutlama ve dolaysızlığın vazgeçilmez olduğu özgün bir kullanım durumu vardır. Bu tür bir kullanımın anlaşılması, gecikme önerisinin de anlaşılmasına yardımcı olacaktır .

Bu kullanım durumu şudur: Kaynak Soyutlamalarına Karşı Programlama ...

Evet, iş mantığı , somut uygulamalara değil, soyutlamalara karşı programlanmalıdır . Üst düzey IOC "kablolama" kodu, kaynak soyutlamalarının somut uygulamalarını somutlaştırır ve bunları iş mantığına aktarır. Burada yeni bir şey yok. Ancak bu kaynak soyutlamalarının somut uygulamaları potansiyel olarak kendi uygulamalarına özel istisnalar atabilir, değil mi?

Peki bu uygulamaya özel istisnaları kim ele alabilir? İş mantığında herhangi bir kaynağa özgü istisnalar işlenebilir mi? Hayır, değil. İş mantığı, bu uygulamaya özel istisna ayrıntılarının bilgisini dışlayan soyutlamalara karşı programlanmıştır.

“Aha!” Diyebilirsiniz: “ama bu yüzden istisnaları alt sınıflara koyabilir ve istisna hiyerarşileri oluşturabiliriz” ( Bay Bahar'a göz atın !). Sana söyleyeyim, bu bir yanlışlık. İlk olarak, OOP hakkındaki her makul kitap, somut kalıtımın kötü olduğunu, ancak bir şekilde JVM'nin bu istisnai durumun temel bileşeni olan somut kalıtımla yakından ilişkili olduğunu söylüyor. İronik olarak, Joshua Bloch, çalışan bir JVM ile deneyim kazanmadan önce Etkin Java kitabını yazamazdı, değil mi? Yeni nesil için "öğrenilen dersler" kitabından daha fazlası. İkincisi, ve daha önemlisi, üst düzey bir istisna yakalarsanız, nasıl HANDLE edeceksin?PatientNeedsImmediateAttentionException: Ona bir hap vermemiz veya bacaklarını kesmemiz gerekmiyor mu? Mümkün olan tüm alt sınıflar için bir switch ifadesine ne dersiniz? Polimorfizmine gidiyor, soyutlama gidiyor. Sen anladın.

Öyleyse, kaynağa özgü istisnaları kim ele alabilir? Betonu bilen kişi olmalı! Kaynağı hazırlayan kişi! Elbette "kablolama" kodu! Şuna bir bak:

İş mantığı soyutlamalara karşı kodlandı ... BETON KAYNAKLARI HATASI KULLANIMI YOK!

static interface InputResource {
    String fetchData();
}

static interface OutputResource {
    void writeData(String data);
}

static void doMyBusiness(InputResource in, OutputResource out, int times) {
    for (int i = 0; i < times; i++) {
        System.out.println("fetching data");
        String data = in.fetchData();
        System.out.println("outputting data");
        out.writeData(data);
    }
}

Bu arada başka bir yerde somut uygulamalar ...

static class ConstantInputResource implements InputResource {
    @Override
    public String fetchData() {
        return "Hello World!";
    }
}

static class FailingInputResourceException extends RuntimeException {
    public FailingInputResourceException(String message) {
        super(message);
    }
}

static class FailingInputResource implements InputResource {
    @Override
    public String fetchData() {
        throw new FailingInputResourceException("I am a complete failure!");
    }
}

static class StandardOutputResource implements OutputResource {
    @Override
    public void writeData(String data) {
        System.out.println("DATA: " + data);
    }
}

Ve nihayet kablolama kodu ... Somut kaynak istisnalarını kim ele alıyor? Onları bilen biri!

static void start() {
    InputResource in1 = new FailingInputResource();
    InputResource in2 = new ConstantInputResource();
    OutputResource out = new StandardOutputResource();

    try {
        ReusableBusinessLogicClass.doMyBusiness(in1, out, 3);
    }
    catch (FailingInputResourceException e)
    {
        System.out.println(e.getMessage());
        System.out.println("retrying...");
        ReusableBusinessLogicClass.doMyBusiness(in2, out, 3);
    }
}

Şimdi benimle kal. Yukarıdaki kod olan basit. Birden fazla IOC konteyner yönetilen kaynağı kapsamına sahip bir işletme uygulamanızın / web konteynerinizin olduğunu ve otomatik olarak yeniden denemeler yapmanız ve oturumun yeniden başlatılması veya kapsam kaynakları istemeniz vb. Olduğunu söyleyebilirsiniz. kaynaklar yaratın, bu nedenle gerçek uygulamaların farkında olmamak. Sadece yüksek seviye kapsamlar, bu düşük seviye kaynakların ne gibi istisnalar yaratabileceğini gerçekten bilecektir. Şimdi bekle!

Maalesef, istisnalar yalnızca çağrı yığını üzerinde yönlendirmeye izin verir ve farklı kardinallikleri olan farklı kapsamlar genellikle birden fazla farklı iş parçacığı üzerinde çalışır. İstisnalarla iletişim kurmanın bir yolu yok. Burada daha güçlü bir şeye ihtiyacımız var. Cevap: zaman uyumsuz mesaj geçiyor . Her istisnayı alt seviye kapsamının kökünden yakalayın. Hiçbir şeyi görmezden gelme, hiçbir şeyin kaymasına izin verme. Bu, mevcut kapsamın arama yığında oluşturulan tüm kaynakları kapatacak ve imha edecektir. Sonra, hataların bilindiği düzeye ulaşana kadar istisna işleme yordamındaki ileti kuyruklarını / kanallarını kullanarak hata mesajlarını daha geniş bir alana yayın. Bununla nasıl başa çıkacağını bilen adam.

SUMMA SUMMARUM

Yani benim yorumlama göre catch geç en uygun yerde istisnaları yakalamak için anlamı SİZ ARTIK soyutlama kırma değildir . Çok erken yakalama! Kaynak soyutlamaların örneklerini atarak , soyutlamaların somutluğunu bilen katman olan somut istisnayı oluşturduğunuz katmandaki istisnaları yakalayın . "Kablolama" katmanı.

HTH. Mutlu kodlama!


Arabirimi sağlayan kodun, arayüzü kullanan koddan daha neyin yanlış gidebileceği hakkında daha fazla şey bileceği doğru, ancak bir yöntemin aynı arabirim türündeki iki kaynağı kullandığını ve hataların farklı şekilde ele alınması gerektiğini varsayalım? Ya da bu kaynaklardan biri dahili olarak, yaratıcısı tarafından bilinmeyen bir uygulama detayı olarak, aynı türde diğer iç içe geçmiş kaynakları kullanıyorsa? İş katmanının atılması WrappedFirstResourceExceptionveya WrappedSecondResourceException"kablolama" katmanının soruna neden olan sorunun nedenini görmek için bu istisnanın içine
bakmasını istemek

... çok tehlikeli olabilir, ancak herhangi bir FailingInputResourceistisnanın bir operasyonun sonucu olacağını varsaymaktan daha iyi görünür in1. Aslında, çoğu durumda doğru yaklaşımın kablolama katmanının bir istisna işleme nesnesini geçmesi ve iş katmanının da catchbu nesnenin handleExceptionyöntemini çağıracağını içermesi olacağını düşünüyorum . Bu yöntem, varsayılan verileri yeniden değerlendirebilir veya sunabilir veya bir "İptal Et / Yeniden Dene / Başarısız" komutunu verebilir ve uygulamanın ne gerektirdiğine bağlı olarak operatörün ne yapacağına karar vermesine izin verebilir.
supercat

@supercat Ne dediğini anlıyorum. Somut bir kaynak uygulamasının, atabileceği istisnaları bilmekle sorumlu olduğunu söyleyebilirim. Her şeyi belirtmesi gerekmez ( tanımsız davranış var ), ancak belirsizlik olmamasını sağlamalıdır. Ayrıca, işaretlenmemiş çalışma zamanı istisnaları belgelenmelidir. Dokümantasyonla çelişiyorsa, o bir hatadır. Arayan kodunun bir istisna hakkında mantıklı bir şey yapması beklenirse, asgari kaynak, kaynak UnrecoverableInternalExceptionkodunu HTTP 500 hata koduna benzer şekilde sarmasıdır.
Daniel Dinnyes

@supercat Yapılandırılabilir hata işleyicileri hakkındaki öneriniz hakkında: tam olarak! Son örneğimde hata işleme mantığı kodlanmış, statik doMyBusinessyöntemi çağırıyor . Bu, kısalık uğruna idi ve onu daha dinamik hale getirmek tamamen mümkün. Böyle bir Handlersınıf, bazı girdi / çıktı kaynaklarıyla başlatılabilir ve handlea sınıfı uygulayan bir metoda sahip olur ReusableBusinessLogicInterface. Ardından, kablo döşemesinde farklı bir işleyici, kaynak ve iş mantığı uygulamaları kullanmak için üstlerindeki bir yerde kullanabilirsiniz.
Daniel Dinnyes

10

Bu soruyu doğru bir şekilde cevaplamak için bir adım geriye gidelim ve daha temel bir soru soralım.

Neden ilk etapta istisnalar var?

Metodumuzun arayanına yapmamızı istediğimiz şeyi yapamadığımızı bildirmesi için istisnalar atarız. İstisna türü, yapmak istediklerimizi neden yapamadığımızı açıklar .

Bazı koda bir göz atalım:

double MethodA()
{
    return PropertyA - PropertyB.NestedProperty;
}

Bu kod, eğer PropertyBnull ise boş bir referans istisnası atabilir . Bu durumda bu durumu düzeltmek için yapabileceğimiz iki şey var. Yapabiliriz:

  • Bizde yoksa otomatik olarak PropertyB oluşturun; veya
  • İstisna, çağrı yöntemine geçsin.

Burada PropertyB oluşturmak çok tehlikeli olabilir. PropertyB oluşturmak için bu yöntemin sebebi nedir? Elbette bu tek sorumluluk ilkesini ihlal eder. Her durumda, eğer PropertyB burada mevcut değilse, bir şeylerin ters gittiğini gösterir. Yöntem, kısmen inşa edilmiş bir nesne üzerinde çağrılıyor veya PropertyB yanlış boş olarak ayarlandı. Burada PropertyB oluşturarak, daha sonra bizi ısırtabilecek, örneğin veri bozulmasına neden olan bir hata gibi daha büyük bir hatayı gizliyor olabiliriz.

Bunun yerine, null referansın kabarcıklaşmasına izin verirsek, bu yöntemi arayan geliştiricinin en kısa sürede bir şeyin yanlış gittiğini bilmesine izin veriyoruz. Bu yöntemin çağrılmasında hayati bir ön koşul kaçırılmıştır.

Sonuç olarak, erken atıyoruz çünkü endişelerimizi daha iyi ayırıyor. Bir arıza meydana gelir gelmez, yukarı akış geliştiricilere bunu bildiririz.

Neden "geç satarız" farklı bir hikaye. Gerçekten geç kalmak istemiyoruz, konuyla nasıl başa çıkacağımızı bildiğimiz kadar erken yakalamak istiyoruz. Bu zamanın bir kısmı daha sonra onbeş soyutlama katmanı olacak ve bunun bir kısmı da yaratma noktasında olacak.

Mesele şu ki, istisnayı doğru şekilde ele almamız gereken tüm bilgilere sahip olduğumuz noktada istisnayı ele almamızı sağlayan soyutlama katmanında istisnayı yakalamak istiyoruz.


Sanırım yukarı yöndeki geliştiricileri yanlış anlamda kullanıyorsunuz. Ayrıca, tek bir sorumluluk ilkesini ihlal ettiğini söylediniz, ancak gerçekte çoğu talep başlatma ve değer önbelleğe alma konusunda bu şekilde uygulanır (elbette yerinde uygun eşzamanlılık kontrolleriyle)
Daniel Dinnyes

if(PropertyB == null) return 0;
Verdiğiniz

1
Son paragrafınızı, özellikle ' soyutlama katmanı ' ile neyi kastediyorsunuz ?
user1451111

Eğer bir GÇ çalışması yapıyorsak, bir GÇ İstisnasını yakalamak için bir soyutlama katmanı çalışmayı yaptığımız yer olacaktır. Bu noktada, kullanıcıya bir mesaj kutusu yeniden denemek veya atmak ya da bir dizi varsayılan kullanarak bir nesne oluşturmak isteyip istemediğimize karar vermemiz gereken tüm bilgilere sahibiz.
Stephen

"Verdiğiniz örnekte, çıkarma işleminden önce null değerini denetlemeye ne dersiniz, eğer böyle (PropertyB == null) 0 döndürürse;" Yuck. Bu, çağıran yönteme benim çıkarmam gereken geçerli şeylerin olduğunu söylüyor. Elbette bu bağlamsaldır ancak çoğu durumda burada hata kontrolümü yapmak kötü bir uygulama olacaktır.
Stephen

6

Nesneleri geçersiz bir duruma sokmamak için atmaya değer bir şey görür görmez at. Boş bir işaretçi geçtiyse, erken kontrol et ve düşük seviyeye düşme şansı vermeden NPE at .

Hatayı düzeltmek için ne yapacağınızı öğrenir öğrenmez yakalayın (bu genellikle attığınız yerde değil, sadece bir if-başka kullanabilirsiniz) .


1
Sen yazdın: Yakında at, ... Yakında yakala ...! Neden? Bu, "erken at, geç yakala" nın aksine, tamamen aykırı bir yaklaşım.
shylynx

1
@ shylynx "Erken at, geç yakala" nereden geliyor bilmiyorum, ama değeri sorgulanabilir. Tam olarak ne demek "geç" demek? Bir istisna yakalamanın mantıklı olduğu yer (eğer varsa) soruna bağlıdır. Açık olan tek şey, problemleri olabildiğince erken tespit etmek (ve atmak).
Doval

2
Sanırım "geç yakalamak", hatayı düzeltmek için ne yapabileceğinizi bilmeden önce yakalama uygulamasının kontrastı ifade etmekte olduğunu düşünmekteyim - örneğin, bazen her şeyi yakalayan işlevleri görürsünüz;

@Hurkyl: "Geç yakalamak" ile ilgili bir sorun, bir istisna hakkında hiçbir şey bilmeyen katmanlar arasında kabarması durumunda, durum hakkında bir şeyler yapabilecek durumda olabilecek bir kodun zor olabileceğidir. beklenen. Basit bir örnek olarak, bir kullanıcı belge dosyası için bir çözümleyici diskten bir CODEC yüklemeye ihtiyaç duyuyorsa ve bunu okurken bir disk hatası oluştuğunu varsayalım, kullanıcı okurken bir disk hatası olduğunu düşünürse, çözümleyiciyi çağıran kodun uygun davranmayacağını varsayalım. belgesi.
supercat

4

Geçerli bir iş kuralı 'düşük seviye yazılım bir değer hesaplayamazsa, o zaman ...'

Bu sadece daha yüksek seviyede ifade edilebilir, aksi takdirde düşük seviye yazılımı, sadece düğümde bitecek olan kendi doğruluğuna bağlı olarak davranışını değiştirmeye çalışıyor.


2

Her şeyden önce, istisnalar istisnai durumlar içindir. Örneğinizde, eğer yüklenmemiş olan ham veri mevcut değilse, hiçbir şekil hesaplanamaz.

Tecrübelerime göre, yığını yürürken istisnaları soyutlamak iyi bir pratik. Genelde bunu yapmak istediğiniz noktalar, bir istisna iki kat arasındaki sınırı geçtiğinde olur.

Ham verilerinizi veri katmanında toplama konusunda bir hata varsa, verileri isteyen kişiyi bilgilendirmek için bir istisna atın. Bu konuyu burada çözmeye çalışmayın. Kullanım kodunun karmaşıklığı çok yüksek olabilir. Ayrıca veri katmanı, veri yaparken meydana gelen hataları ele almaktan değil sadece veri talebinden sorumludur. Bu, "erken atma" ile kastettiğiniz şeydir .

Örneğinizde alıcı katman servis katmanıdır. Hizmetin kendisi, veri erişim katmanı üzerinde oturan yeni bir katmandır. Demek ki istisnayı orada yakalamak istiyorsun. Belki de hizmetiniz bazı yük devretme altyapısına sahiptir ve başka bir depodan veri istemeye çalışır. Bu da başarısız olursa, istisnayı, servisin arayanının anladığı bir şeyin içine sarın (eğer bir web servisi ise bu bir SOAP hatası olabilir). Orijinal istisnayı iç istisna olarak ayarlayın, böylece sonraki katmanlar tam olarak neyin yanlış gittiğini kaydedebilir.

Servis arızası servisi çağıran katman tarafından tespit edilebilir (örneğin, UI). Ve bu "geç yakalamak" ile kastedilen budur . İstisnaları daha düşük bir katmanda idare edemiyorsanız, tekrar deneyin. En üstteki katman istisnanın üstesinden gelemiyorsa, üstesinden gel! Bu, günlüğe kaydetmeyi veya sunmayı içerebilir.

İstisnaları yeniden yazmanızın nedeni (yukarıda daha genel istisnalar halinde silerek yukarıda açıklandığı gibi), kullanıcının geçersiz belleğe işaret ettiği için bir işaretçinin bir hata olduğunu anlayamaması çok muhtemeldir. Ve umrunda değil. Sadece rakamın hizmet tarafından hesaplanamamasına ve bu da kendisine gösterilmesi gereken bilgilerin önemine dikkat ediyor.

Detayli Eğer (ideal bir dünyada) tamamen bırakabilir gidiyor try/ catchUI kodu. Bunun yerine, muhtemelen alt katmanlar tarafından atılan istisnaları anlayabilen bir genel istisna işleyicisi kullanmak, bunları bir günlüğe yazar ve hatanın anlamlı (ve muhtemelen yerelleştirilmiş) bilgisini içeren hata nesnelerine sarar. Bu nesneler dilediğiniz herhangi bir şekilde kullanıcıya kolayca iletilebilir (mesaj kutuları, bildirimler, mesaj tostları vb.).


1

İstisnaları genel olarak erken atmak iyi bir uygulamadır, çünkü sözleşmelerde gereğinden fazla kırılan sözleşmelerin akmasını istemezsiniz. Örneğin, belirli bir işlev parametresinin pozitif bir tamsayı olmasını beklerseniz, bu değişkeni kod istifinde başka bir yerde kullanılana kadar beklemek yerine, işlev çağrısı noktasında zorlamanız gerekir.

Geç yakalama konusunda gerçekten yorum yapamam çünkü kendi kurallarım var ve projeden projeye değişiyor. Yapmaya çalıştığım tek şey istisnaları iki gruba ayırmak. Biri yalnızca dahili kullanım içindir, diğeri yalnızca harici kullanım içindir. İç istisna, kendi kodumla yakalanır ve ele alınır ve dış istisna, beni ne diye çağırırsa koduyla işlenmelidir. Bu temelde daha sonra bir şeyler yakalama şeklidir, ancak tam olarak değil, çünkü iç kodda gerektiğinde kuraldan sapma esnekliği sunar.

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.