İstisnalar nasıl tasarlanır?


11

Çok basit bir soru ile mücadele ediyorum:

Şimdi bir sunucu uygulaması üzerinde çalışıyorum ve istisnalar için bir hiyerarşi icat etmeliyim (bazı istisnalar zaten var, ancak genel bir çerçeve gerekli). Bunu yapmaya nasıl başlayabilirim?

Bu stratejiyi takip etmeyi düşünüyorum:

1) Sorun nedir?

  • İzin verilmeyen bir şey sorulur.
  • Bir şey sorulur, buna izin verilir, ancak yanlış parametreler nedeniyle çalışmaz.
  • Bir şey sorulur, buna izin verilir, ancak dahili hatalar nedeniyle çalışmaz.

2) Talebi kim başlatıyor?

  • İstemci uygulaması
  • Başka bir sunucu uygulaması

3) Mesaj teslimi: Bir sunucu uygulamasıyla uğraşırken, her şey mesaj almak ve göndermekle ilgilidir. Peki ya mesaj göndermek yanlış giderse?

Bu nedenle, aşağıdaki istisna türlerini alabiliriz:

  • ServerNotAllowedException
  • ClientNotAllowedException
  • ServerParameterException
  • ClientParameterException
  • InternalException (sunucunun isteğin nereden geldiğini bilmemesi durumunda)
    • ServerInternalException
    • ClientInternalException
  • MessageHandlingException

Bu, istisna hiyerarşisini tanımlamak için çok genel bir yaklaşımdır, ancak korkarım bazı bariz durumlardan yoksun olabilirim. Hangi alanları kapsamadığım konusunda fikirleriniz var mı, bu yöntemin herhangi bir dezavantajı var mı veya bu tür bir soruya daha genel bir yaklaşım var mı (ikinci durumda, nerede bulabilirim)?

Şimdiden teşekkürler


5
İstisna sınıfı hiyerarşinizle neyi başarmak istediğinizden bahsetmediniz (ve bu hiç de açık değil). Anlamlı günlük kaydı? Müşterilerin farklı istisnalara makul tepki vermesini sağlamak mı? Ya da ne?
Ralf Kleberhoff

2
Muhtemelen bazı hikayeleri incelemek ve ilk önce vakaları kullanmak ve neyin ortaya çıktığını görmek faydalı olacaktır. Örneğin: istemci X isteklerine izin verilmez. İstemci X isteğinde bulunur , ancak istek geçersiz. İstisnaları kimin ele alması gerektiğini, onunla ne yapabileceklerini (hızlı, tekrar dene, ne olursa olsun) ve bunu iyi yapmak için hangi bilgilere ihtiyaçları olduğunu düşünerek onların üzerinde çalışın. Ardından , somut istisnalarınızın ne olduğunu ve işleyicilerin bunları işlemek için ihtiyaç duydukları bilgileri öğrendikten sonra, bunları güzel bir hiyerarşiye dönüştürebilirsiniz.
Yararsız


1
Bu kadar farklı istisna türünü kullanma arzusunu hiçbir zaman gerçekten anlamadım. Genellikle kullandığım çoğu catchblok için, istisna için içerdiği hata mesajından daha fazla kullanmıyorum. Gerçekten bir dosya okuma işlemi sırasında bellek tahsis başarısız olarak bir dosya okumak için başarısız bir istisna için yapabileceğim farklı bir şey yok, bu yüzden sadece std::exceptioniçerdiği hata mesajını yakalamak ve rapor, belki de dekorasyon eğilimindedir ile bu "Failed to open file: %s", ex.what()yazdırmadan önce yığın tampon.

3
Ayrıca, ilk etapta atılacak tüm istisna türlerini tahmin edemiyorum. Onları şimdi tahmin edebilirim, ancak meslektaşları gelecekte yenilerini tanıtabilir, örneğin, bu nedenle, şu anda ve sonsuza dek, bir süreçte atılabilecek tüm farklı istisna türlerini bilmek benim için biraz umutsuz. operasyon. Bu yüzden sadece tek bir yakalama bloğu ile genellikle süper yakalarım. catchTek bir kurtarma sitesinde çok sayıda farklı blok kullanan kişilerin örneklerini gördüm , ancak genellikle istisna içindeki mesajı görmezden gelmek ve daha yerel bir mesaj yazdırmak ...

Yanıtlar:


5

Genel açıklamalar

(biraz önyargılı)

Genellikle ayrıntılı bir istisna hiyerarşisi için gitmezdim.

En önemli şey: bir istisna, arayanıza yönteminizin işini tamamlayamadığını bildirir. Ve Arayan zorunluluk bunun hakkında bilgi , bu yüzden devam etmez. Hangi istisna sınıfını seçerseniz seçin, herhangi bir istisna ile çalışır.

İkinci yönü günlüğe kaydetmektir. Bir şeyler ters gittiğinde anlamlı günlük girişleri bulmak istersiniz. Bu aynı zamanda farklı istisna sınıflarına, sadece iyi tasarlanmış metin mesajlarına ihtiyaç duymaz (sanırım hata günlüklerinizi okumak için bir otomasyona ihtiyacınız yoktur ...).

Üçüncü yön, arayanın tepkisidir. Arayan kişi bir istisna aldığında ne yapabilir? Burada farklı istisna sınıflarına sahip olmak mantıklı olabilir, böylece arayan aynı çağrıyı yeniden denemek, farklı bir çözüm kullanmak (örneğin bunun yerine bir geri dönüş kaynağı kullanmak) veya pes etmek için karar verebilir.

Ve belki de istisnalarınızı son kullanıcıyı sorun hakkında bilgilendirmek için temel olarak kullanmak istersiniz. Bu, günlük dosyası için yönetici metninin yanı sıra kullanıcı dostu bir mesaj oluşturmak anlamına gelir, ancak farklı istisna sınıflarına ihtiyaç duymaz (belki de metin üretimini kolaylaştırabilir ...).

Günlüğe kaydetmenin (ve kullanıcı hata mesajlarının) önemli bir yönü, istisnayı bir katmanda yakalayarak, bazı bağlam bilgilerini, örneğin yöntem parametrelerini ekleyerek ve yeniden atayarak bağlam bilgisiyle değiştirebilmesidir.

Hiyerarşiniz

İsteği kim başlatıyor?İsteği başlatan bilgilere ihtiyacınız olacağını sanmıyorum. Bazı çağrı yığınlarının derinliklerinde nasıl bildiğinizi bile hayal bile edemiyorum.

Mesaj işleme : Bu farklı bir özellik değil, sadece "Neler yanlış gidiyor?"

Bir yorumda, " günlük kaydı yok " bir istisna oluştururken " " bayrağı . Bir istisna oluşturup attığınız yerde, bu istisnanın kaydedilip kaydedilmeyeceğine dair güvenilir bir karar verebileceğinizi düşünmüyorum.

Hayal edebileceğim tek durum, bazı daha yüksek katmanların API'nizi bazen istisnalar üretecek şekilde kullanması ve bu katmanın istisna ile herhangi bir yöneticiyi rahatsız etmesinin gerekmediğini bilmesi, bu nedenle sessizce istisnayı yutmasıdır. Ancak bu bir kod kokusu: beklenen bir istisna kendi içinde bir çelişki, API'yi değiştirmek için bir ipucu. Ve istisna üreten kod değil, karar vermesi gereken daha yüksek katman.


Deneyimlerime göre, kullanıcı dostu bir metinle birlikte bir hata kodu çok iyi çalışıyor. Yöneticiler ek bilgi bulmak için hata kodunu kullanabilir.
Sjoerd

1
Genel olarak bu cevabın arkasındaki fikri seviyorum. Deneyimlerime göre İstisnalar gerçekten bir canavarı çok karmaşık hale getirmemelidir. Bir istisnanın temel amacı, çağrı kodunun belirli bir sorunu ele almasına izin vermek ve işlevin yanıtıyla uğraşmadan ilgili hata ayıklama / yeniden deneme bilgilerini almaktır.
greggle138

2

Bir hata yanıtı deseni tasarlarken akılda tutulması gereken en önemli şey, arayanlar için yararlı olduğundan emin olmaktır. Bu, istisnalar veya tanımlanmış hata kodları kullansanız da geçerlidir, ancak kendimizi istisnalarla göstermeyle sınırlayacağız.

  • Diliniz veya çerçeveniz zaten genel istisna sınıfları sağlıyorsa, bunları uygun olan yerlerde ve makul bir şekilde beklenen yerlerde kullanın . Kendi ArgumentNullExceptionveya ArgumentOutOfRangeistisna sınıflarınızı tanımlamayın . Arayanlar bunları yakalamayı beklemeyecek.

  • MyClientServerAppExceptionUygulamanız bağlamında benzersiz olan hataları kapsayacak bir temel sınıf tanımlayın . Asla temel sınıfın bir örneğini atmayın. Belirsiz bir hata yanıtı, EN KÖTÜ ŞEYDİR . Bir "dahili hata" varsa, o hatanın ne olduğunu açıklayın.

  • Çoğunlukla, temel sınıfın altındaki hiyerarşi derin değil geniş olmalıdır. Sadece arayan için yararlı olduğu durumlarda derinleştirmeniz gerekir. Örneğin, bir iletinin istemciden sunucuya başarısız olmasının 5 nedeni varsa, bir ServerMessageFaultözel durum tanımlayabilir, ardından bu 5 hatanın her biri için bir özel durum sınıfı tanımlayabilirsiniz. Bu şekilde arayan, ihtiyaç duyduğu veya istediği takdirde üst sınıfı yakalayabilir. Bunu belirli, makul durumlarla sınırlamaya çalışın.

  • Tüm istisna sınıflarınızı gerçekten kullanılmadan önce tanımlamaya çalışmayın. Çoğunu tekrar yaparak bitireceksin. Kod yazarken bir hata durumu ile karşılaştığınızda, o hatayı en iyi nasıl açıklayacağınıza karar verin. İdeal olarak, arayan kişinin ne yapmaya çalıştığı bağlamında ifade edilmelidir.

  • Önceki nokta ile ilgili olarak, hatalara yanıt vermek için istisnalar kullandığınızdan, bunun yalnızca hata durumları için istisnalar kullanmanız gerektiği anlamına gelmediğini unutmayın . İstisnaları atmanın genellikle pahalı olduğunu ve performans maliyetinin bir dilden diğerine değişebileceğini unutmayın. Bazı dillerde, arama yığınının derinliğine bağlı olarak maliyet daha yüksektir, bu nedenle bir çağrı yığınının içinde bir hata varsa, itmek için basit ilkel türleri (tamsayı hata kodları veya boole bayrakları) kullanıp kullanamayacağınızı kontrol edin. hatayı yığına yedekler, böylece arayanın çağrılmasına daha yakın olabilir.

  • Hata yanıtının bir parçası olarak günlüğe kaydetmeyi dahil ediyorsanız, arayanların bir istisna nesnesine bağlam bilgileri eklemesi kolay olmalıdır. Günlük kodunda bilgilerin kullanıldığı yerden başlayın. Günlüğün yararlı olması için ne kadar bilgi gerektiğine karar verin (dev bir metin duvarı olmadan). Ardından, istisna sınıflarının bu bilgilerle kolayca sağlanabildiğinden emin olmak için geriye doğru çalışın.

Son olarak, uygulamanız belleği yetersiz bir hatayla kesinlikle ele alamazsa, bunlarla veya diğer felaket çalışma zamanı istisnalarıyla uğraşmaya çalışmayın. İşletim sisteminin bununla başa çıkmasına izin verin, çünkü gerçekte yapabileceğiniz tek şey bu.


2
İkinci ila son merminiz hariç (istisnaların maliyeti hakkında), cevap iyidir. İstisnaların maliyeti hakkındaki bu mermi yanıltıcıdır, çünkü istisnaları düşük seviyede uygulamanın tüm yaygın yollarında, bir istisna atmanın maliyeti çağrı yığının derinliğinden tamamen bağımsızdır. Hatanın hemen arayan tarafından işleneceğini biliyorsanız, ancak bir istisna atmadan önce çağrı yığınından birkaç işlev almamak için alternatif hata raporlama yöntemleri daha iyi olabilir.
Bart van Ingen Schenau

@BartvanIngenSchenau: Bunu belirli bir dile bağlamamayı denedim. Bazı dillerde ( örneğin Java ), çağrı yığınının derinliği, örnekleme maliyetini etkiler. O kadar kesilmiş ve kurutulmuş olmadığını yansıtmak için düzenleyeceğim.
Mark Benningfield

0

İlk ve en önemlisi Exception, uygulamanız tarafından atılabilecek tüm kontrol edilen istisnalar için bir temel sınıf oluşturmanızı tavsiye ederim . Başvurunuz çağrıldıysa DuckType, bir DuckTypeExceptiontemel sınıf yapın.

Bu DuckTypeException, işleme için temel sınıfınızın istisnalarını yakalamanızı sağlar . Buradan, istisnalarınız sorunun türünü daha iyi vurgulayabilecek açıklayıcı adlarla dallanmalıdır. Örneğin, "DatabaseConnectionException".

Açık olalım, bunların hepsi muhtemelen programınızda incelikle işlemek isteyebileceğiniz durumlar için kontrol edilmelidir. Başka bir deyişle, veritabanına bağlanamazsınız, bu nedenle DatabaseConnectionExceptionbir süre sonra beklemek ve yeniden denemek için yakalayabileceğiniz bir a atılır.

Sen olur değil bu tür geçersiz SQL sorgusunun boş gösterici durum olarak bir çok beklenmedik sorun için bir kontrol istisna görmek ve ben bu istisnaları en yakalamak maddeleri aşmak (veya yakalanmış ve gerekli olarak rethrown) ana varmak kadar izin için teşvik edecek denetleyicinin kendisi, daha sonra RuntimeExceptionyalnızca günlük kaydı amacıyla yakalayabilir .

Kişisel tercihim, kontrolsüz RuntimeExceptionbir istisnanın doğası gereği, kontrol edilmemiş bir istisna olarak yeniden kontrol etmemek, siz bunu beklemezsiniz ve bu nedenle başka bir istisna altında yeniden düşünmek bilgileri gizler. Bu tercihinizi Ancak, hala bir yakalayabilirsiniz RuntimeExceptionve atmak DuckTypeInternalExceptionhangi aksine DuckTypeExceptiondan türemiştir RuntimeExceptionve bu nedenle işaretli değildir.

İsterseniz, istisnalarınızı DatabaseExceptionveritabanı ile ilgili herhangi bir şey gibi örgütsel amaçlar için alt kategorilere ayırabilirsiniz , ancak yine de bu tür istisnaları temel istisnanızdan türetmeye DuckTypeExceptionve soyut olmaya ve dolayısıyla açıklayıcı adlarla türetmeye teşvik ederim .

Genel bir kural olarak, istisnaları işlemek için arayanların çağrılarını yükselttikçe deneme yakalamalarınız gittikçe daha genel olmalıdır ve yine ana denetleyicinizde, kontrol edilen istisnalarınızın hepsinin türettiği DatabaseConnectionExceptionbasit bir işlemeyi ele DuckTypeExceptionalmazsınız.


2
Sorunun "C ++" olarak etiketlendiğini unutmayın.
Martin Ba

0

Bunu basitleştirmeye çalışın.

Başka bir stratejide düşünmenize yardımcı olacak ilk şey: yakalanan birçok istisna Java'dan kontrol edilen istisnaların kullanımına çok benzer (üzgünüm, C ++ geliştiricisi değilim). Bu pek çok nedenden dolayı iyi değil, bu yüzden onları her zaman kullanmamaya çalışıyorum ve hiyerarşi istisna stratejiniz beni çok hatırlıyor.

Bu nedenle, size başka ve esnek bir strateji öneriyorum: denetlenmeyen özel durumlar ve kod hataları kullanın.

Örneğin, şu Java koduna bakın:

public class SystemErrorCode implements ErrorCode {

    INVALID_NAME(101),
    ORDER_NOT_FOUND(102),
    PARAMETER_NOT_FOUND(103),
    VALUE_TOO_SHORT(104);

    private final int number;

    private ErrorCode(int number) {
        this.number = number;
    }

    @Override
    public int getNumber() {
        return number;
    }
}

Ve benzersiz istisnanız:

public class SystemException extends RuntimeException {

    private ErrorCode errorCode;

    public SystemException(ErrorCode errorCode) {
        this.errorCode = errorCode;
    }

}

Bu bağlantıda bulduğum bu strateji ve Java uygulamasını burada bulabilirsiniz , burada daha fazla ayrıntı görebilirsiniz, çünkü yukarıdaki kod basitleştirilmiştir.

"İstemci" ve "başka bir sunucu" uygulamaları arasındaki farklı özel durumları ayırmanız gerektiğinden, ErrorCode arabirimini uygulayan birden çok hata kodu sınıfına sahip olabilirsiniz.


2
Sorunun "C ++" olarak etiketlendiğini unutmayın.
Sjoerd

Fikri uyarlamak için düzenledim.
Dherik

0

İstisnalar sınırsız goto'dur ve dikkatli kullanılmalıdır. Onlar için en iyi strateji onları kısıtlamaktır. Arama işlevi, çağırdığı veya programın öldüğü işlevler tarafından atılan tüm özel durumları işlemelidir. Yalnızca çağrı işlevi, özel durumu işlemek için doğru içeriğe sahiptir. Çağrı ağacının üstündeki fonksiyonlara sahip olmak, onları sınırsız gotolardır.

İstisnalar hata değildir. Koşullar, programın kodun bir dalını tamamlamasını engellediğinde ve takip etmesi için başka bir dal gösterdiğinde ortaya çıkar.

İstisnalar, çağrılan işlev bağlamında olmalıdır. Örneğin: ikinci dereceden denklemleri çözen bir fonksiyon . İki istisnayı ele almalıdır: division_by_zero ve square_root_of_negative_number. Ancak bu istisnalar, bu ikinci dereceden denklem çözücüsü olarak adlandırılan işlevler için anlamlı değildir. Denklemleri çözmek için kullanılan yöntem nedeniyle olurlar ve basitçe onları yeniden atmak içselleri açığa çıkarır ve kapsüllemeyi keser. Bunun yerine division_by_zero, not_quadratic ve square_root_of_negative_number ve no_real_roots olarak yeniden oluşturulmalıdır.

İstisnalar hiyerarşisine gerek yoktur. Bir işlev tarafından atılan istisnaların (numaralarını tanımlayan) bir sıralaması yeterlidir çünkü çağıran işlev bunları işlemek zorundadır. Çağrı ağacını işlemelerine izin vermek, bağlam dışı (kısıtlanmamış) bir goto.

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.