İstisna nesnelerini atmak yerine geri göndermenin meşru sebepleri var mı?


45

Bu soru, istisna işlemeyi destekleyen herhangi bir OO programlama diline uygulanmaya yöneliktir; C # yalnızca örnekleme amaçlı kullanıyorum.

İstisnalar, genellikle, kodun hemen ele alınamayacağı bir sorun ortaya çıktığında ortaya çıkar ve daha sonra catchfarklı bir yerde (genellikle bir dış yığın çerçevesi) bir maddede yakalanır .

S: İstisnaların atılmadığı ve yakalanmadığı, sadece bir yöntemden döndürüldüğü ve daha sonra hata nesnesi olarak geçirildiği meşru durumlar var mı?

Bu soru benim için ortaya çıktı, çünkü .NET 4'ün System.IObserver<T>.OnErrormetodu sadece şunu öneriyor: istisnalar hata nesnesi olarak aktarılıyor.

Başka bir senaryoya bakalım, doğrulama. Geleneksel bilgeliği takip ettiğimi ve bu nedenle bir hata nesnesi türü IValidationErrorile ValidationExceptionbeklenmeyen hataları bildirmek için kullanılan ayrı bir istisna türü arasında ayrım yaptığımı varsayalım :

partial interface IValidationError { }

abstract partial class ValidationException : System.Exception
{
    public abstract IValidationError[] ValidationErrors { get; }
}

( System.Component.DataAnnotationsAd alanı oldukça benzer bir şey yapar.)

Bu türler aşağıdaki gibi kullanılabilir:

partial interface IFoo { }  // an immutable type

partial interface IFooBuilder  // mutable counterpart to prepare instances of above type
{
    bool IsValid(out IValidationError[] validationErrors);  // true if no validation error occurs
    IFoo Build();  // throws ValidationException if !IsValid(…)
}

Şimdi merak ediyorum, yukarıdakileri basitleştiremedim mi:

partial class ValidationError : System.Exception { }  // = IValidationError + ValidationException

partial interface IFoo { }  // (unchanged)

partial interface IFooBuilder
{
    bool IsValid(out ValidationError[] validationErrors);
    IFoo Build();  // may throw ValidationError or sth. like AggregateException<ValidationError>
}

S: Bu iki farklı yaklaşımın avantajları ve dezavantajları nelerdir?


İki ayrı cevap arıyorsanız, iki ayrı soru sormalısınız. Onları birleştirmek sadece cevap vermeyi zorlaştırıyor.
Bobson

2
@Bobson: Bu iki soruyu tamamlayıcı olarak görüyorum: Önceki sorunun, konunun genel bağlamını geniş terimlerle tanımlaması beklenirken, ikincisi, okuyucuların kendi sonuçlarını çıkarmasına izin vermesi gereken özel bilgiler ister. Bir cevap, her iki soruya da ayrı ve açık cevaplar vermek zorunda değildir; "Arada" cevapları da aynı şekilde.
stakx

5
Bir şey hakkında net değilim: Atmayacaksanız, ValidationError'ı İstisna'dan çıkarmanın nedeni nedir?
pdr

Pdr: Bunun AggregateExceptionyerine atma durumunda demek istiyorsun ? İyi bir nokta.
stakx

3
Pek sayılmaz. Yani, onu atacaksan, bir istisna kullan. Geri döndürecekseniz, bir hata nesnesi kullanın. Doğası gereği gerçekten hata olan istisnalar varsa, onları yakalayın ve hata nesnesine koyun. Birinin birden fazla hataya izin verip vermeyeceği tamamen anlamsızdır.
pdr

Yanıtlar:


31

İstisnaların atılmadığı ve yakalanmadığı, sadece bir yöntemden döndürüldüğü ve daha sonra hata nesnesi olarak dolaştığı meşru durumlar var mı?

Asla atılmazsa o zaman bir istisna değildir. derivedDavranışı izlemese de, bir Exception sınıfından bir nesnedir . İstisna olarak adlandırmak bu noktada tamamen anlamlıdır, fakat atmama konusunda yanlış bir şey görmüyorum. Benim açımdan, bir istisna atmamak, onu yakalayan ve yayılmasını engelleyen bir iç işlev ile aynı şeydir.

Bir özel durum nesnesi döndüren bir işlev için geçerli mi?

Kesinlikle. Bunun uygun olabileceği kısa bir örnek liste:

  • Bir istisna fabrikası.
  • Kullanıma hazır bir istisna olarak önceki bir hata olup olmadığını bildiren bir içerik nesnesi.
  • Daha önce yakalanmış bir istisnayı tutan bir işlev.
  • İç tipte bir istisna oluşturan üçüncü taraf API.

Kötü atma değil mi?

Bir istisna atmak da bir nevi şeye benziyor: "Hapse gir. Gitme! Git!" tahta oyunu Tekel. Bir derleyiciye, herhangi bir kaynak kodunu çalıştırmadan tüm kaynak kodlarını yakalamaya kadar atlamasını söyler. Hata, raporlama veya hataları durdurma ile ilgisi yoktur. Bir işlev için "süper getiri" ifadesi olarak bir istisna atmayı düşünüyorum. Kodun yürütülmesini orijinal arayandan çok daha yüksek bir yere döndürür.

Buradaki önemli şey, istisnaların gerçek değerinin try/catchistisna nesnesinin somutlaştırılması değil, kalıp içinde olduğunu anlamaktır . İstisna nesnesi sadece bir durum mesajıdır.

Sorunuzda, bu iki şeyin kullanımının kafasını karıştırıyor gibi görünüyorsunuz: istisnanın işleyicisine bir atlama ve istisnanın temsil ettiği hata durumu. Sadece bir hata durumu alıp bir istisna haline getirmiş olmanız, try/catchkalıbı veya faydalarını takip ettiğiniz anlamına gelmez .


4
“Asla atılmazsa, o zaman bir istisna değildir. Bir Exceptionsınıftan türetilmiş bir nesnedir ancak davranışı izlemez.” - Mesele tam da bu: Meselenin Exceptionüreticisi tarafından ya da herhangi bir çağrı yöntemiyle atılmayacağı bir durumda , onaylanmış bir nesneyi kullanmak meşru mudur? "süper geri dönüş" kontrol akışı olmadan, sadece etrafa iletilen bir "durum mesajı" olarak işlev görür? Yoksa söylenmemiş bir sözleşmeyi try/ catch(Exception)sözleşmeyi o kadar fena ihlal ediyorum ki, bunu asla yapmamalıyım ve bunun yerine ayrı, Exceptiontip dışı mı kullanmalıyım ?
stakx

10
@stakx programcıları, diğer programcıların kafasını karıştıran, ancak teknik olarak yanlış olmayan şeyleri yapmaya ve yapmaya devam edeceklerdir. Bu yüzden anlambilim dedim. Yasal cevap, Nohiçbir sözleşmeyi çiğnemediğinizdir. Bunu popular opinionyapmamalısın demek olurdu. Programlama, kaynak kodunuzun yanlış bir şey yapmadığı ancak herkesin beğenmediği örneklerle doludur. Kaynak kod her zaman niyetin ne olduğu belli olacak şekilde yazılmalıdır. Gerçekten böyle bir istisna kullanarak başka bir programcıya yardım ediyor musunuz? Eğer evet ise, o zaman yapın. Aksi halde beğenilmezse şaşırmayın.
22

@cgTag italik için satır içi kod bloklarını kullanmanız beynimi incitiyor ..
Frayt

51

Bunları atmak yerine istisnaları döndürmek, durumu analiz etmek ve daha sonra arayan tarafından atılan uygun bir istisna döndürmek için bir yardım yöntemine sahip olduğunuzda anlamsal olarak anlaşılabilir (buna "istisna fabrikası" diyebilirsiniz). Bu hata çözümleyici işlevinde bir istisna atmak, analiz sırasında bir şeyin ters gittiği anlamına gelirken, bir istisna döndürmek, hatanın türünün başarıyla analiz edileceği anlamına gelir.

Bir olası kullanım durumu, HTTP yanıt kodlarını istisnalara dönüştüren bir işlev olabilir :

Exception analyzeHttpError(int errorCode) {
    if (errorCode < 400) {
         throw new NotAnErrorException();
    }
    switch (errorCode) {
        case 403:
             return new ForbiddenException();
        case 404:
             return new NotFoundException();
        case 500:
             return new InternalServerErrorException();
        …
        default:
             throw new UnknownHttpErrorCodeException(errorCode);
     }
}

Not olduğunu atma yöntemi yanlış kullanılmış veya ederken, dahili bir hatayla olduğu bir istisna aracı dönen hata kodu başarıyla tespit edildiğini bir istisna aracı.


Bu aynı zamanda derleyicinin kod akışını anlamasına yardımcı olur: eğer bir yöntem hiçbir zaman doğru şekilde geri dönmezse (“istenen” istisnayı atarsa) ve derleyici bunu bilmiyorsa return, derleyicinin şikayet etmesini durdurmak için bazen gereksiz ifadelere ihtiyaç duyabilirsiniz .
Joachim Sauer,

@JoachimSauer Evet. Bir kereden fazla, C # 'ya "asla" bir dönüş türü eklesinlerdi - bu fonksiyonun fırlatarak çıkması gerektiğini belirtirdi, bunun yerine bir geri dönüş koymak bir hatadır. Bazen tek işi bir istisna uyandıran kodun olabilir.
Loren Pechtel

2
@LorenPechtel Asla döndürmeyen bir işlev bir işlev değildir. Bu bir sonlandırıcı. Çağıran yığını temizleyen böyle bir işlevi çağırın. Aramaya benzer System.Exit(). İşlev çağrısından sonraki kod yürütülmez. Bir işlev için iyi bir tasarım deseni gibi gelmiyor.
Reactgular

5
@MathewFoscarini Benzer şekilde hata yapabilen birden fazla yönteme sahip bir sınıfı göz önünde bulundurun. Bazı devlet bilgilerini, patlarken neyin çiğnendiğini belirtmek için istisnaların bir parçası olarak bırakmak istiyorsunuz. DRY nedeniyle biçimlendirme kodunun bir kopyasını istiyorsunuz. Bu, ya güzelleştirmeyi yapan ve sonucu atma işleminizde kullanan bir işlev çağrısı yapar ya da hazır metni oluşturan ve sonra da fırlatan bir işlev anlamına gelir. Ben genellikle ikincisini üstün buluyorum.
Loren Pechtel

1
@LorenPechtel ah, evet ben de yaptım. Tamam, şimdi anlıyorum.
Tepki

5

Kavramsal olarak, istisna nesnesi işlemin beklenen sonucuysa, evet. Bununla birlikte, düşünebildiğim davalar her zaman bir noktada fırlama-yakalamayı içerir:

  • "Dene" desenindeki varyasyonlar (burada bir yöntem ikinci bir istisna atma yöntemini içerir, ancak istisnayı yakalar ve bunun yerine bir boole belirten başarı döndürür). Bir Boole döndürmek yerine, atılan herhangi bir İstisna'yı (başarılı olursa null) geri döndürerek son kullanıcının yakalanamayan başarı veya başarısızlık göstergesini koruyarak daha fazla bilgi edinmesini sağlayabilirsiniz.

  • İş akışı hata işleme. Pratikte "deneme kalıbı" yöntemini kapsülleme yöntemine benzer şekilde, bir komut zinciri düzeninde soyutlanmış bir iş akışı adımınız olabilir. Zincirdeki bir bağlantı bir istisna atarsa, soyutlama nesnesindeki bu istisnayı yakalamak genellikle daha temizdir, daha sonra söz konusu işlemin bir sonucu olarak geri gönderin, böylece iş akışı motoru ve bir iş akışı adımı olarak çalışan gerçek kod kapsamlı bir deneme gerektirmez -kendinin kendine has mantığı.


Dikkat çekici kimselerin "Dene" modelinde böyle bir varyasyonu savunmadıklarını görmüyorum, ancak bir Boole döndürmenin "önerilen" yaklaşımı oldukça anemik görünüyor. Soyut bir OperationResultsınıfa, bool"Başarılı" özelliğine, bir GetExceptionIfAnyyönteme ve bir AssertSuccessfulyönteme sahip olmanın daha iyi olabileceğini düşünüyorum GetExceptionIfAny. GetExceptionIfAnyBir özellik yerine bir yöntem olması , söylenecek çok yararlı olmadığı durumlar için statik değişmez hata nesnelerinin kullanılmasına izin verecektir.
supercat

5

Diyelim ki, bazı iş parçacığı havuzunda yürütülecek bazı görevleri zorunlu kıldık. Bu görev istisna atarsa, farklı bir iş parçacığıdır, böylece yakalayamazsınız. İdam edildiği konu sadece ölmek.

Şimdi bir şeyin (o görevdeki kod veya iş parçacığı uygulaması) ya da istisnanın yakaladığı ve işin bittiğini (başarısız olarak) tamamladığını düşünün. Artık görevin bitip bitmediğini sorabilirsiniz (ya da fırlatılıp atılmadığını sorabilir ya da ipliğinize tekrar atılabilir (veya daha iyi, neden olarak orijinal olan yeni istisna).

Manuel olarak yaparsanız, yeni istisna oluşturduğunuzu, fırlatıp yakaladığınızı, sonra farklı iplik alma, fırlatma, yakalama ve tepki verme işlemlerini fark ettiğinizi fark edebilirsiniz. Bu yüzden fırlatmayı atla ve yakala ve sakla ve işi bitir ve sonra istisna olup olmadığını sor ve bunun üzerinde tepki olup olmadığını sor. Ancak, aynı yerde gerçekten istisnalar olabilirse, bu daha karmaşık bir kodda yol açabilir.

Not: Bu, istisna oluşturulduğunda yığın izleme bilgisinin yaratıldığı Java ile yapılan deneyimle yazılmıştır (atma sırasında yarattığı C # 'dan farklı olarak). Bu nedenle, Java'daki atılmayan istisna yaklaşımı C # 'dan daha yavaş olacaktır (önceden kullanılmadığı ve kullanılmadığı sürece), ancak yığın izleme bilgisi mevcut olacaktır.

Genel olarak, istisna yaratmaktan ve asla fırlatmaktan uzak dururum (profil oluşturduktan sonra performans optimizasyonu, darboğaz olarak göstermediği sürece). En azından istisna oluşturmanın pahalı olduğu Java'da (yığın izi). C # 'da olasılıktır, ancak IMO şaşırtıcıdır ve bundan kaçınılmalıdır.


Bu genellikle hata dinleyici nesnelerinde de olur. Bir sıra görevi yürütür, herhangi bir istisnayı yakalar, sonra bu istisnayı sorumlu hata dinleyicisine iletir. Bu durumda işle ilgili fazla bir şey bilmeyen görev dizisini öldürmek genellikle mantıklı değildir. Bu tür bir fikir, bir iş parçacığının diğerlerinden
Lance Nanek

1

Bu, tasarımınıza bağlı olarak, genellikle bir arayan kişiye istisnalar döndürmem, aksine onları fırlatıp yakalar ve üzerinde bırakırdım. Genellikle kod erken ve hızlı bir şekilde başarısız olmak için yazılır. Örneğin, bir dosyayı açıp işleme koymayı düşünün (Bu, C # PsuedoCode'dur):

        private static void ProcessFileFailFast()
        {
            try
            {
                using (var file = new System.IO.StreamReader("c:\\test.txt"))
                {
                    string line;
                    while ((line = file.ReadLine()) != null)
                    {
                        ProcessLine(line);
                    }
                }
            }
            catch (Exception ex) 
            {
                LogException(ex);
            }
        }

        private static void ProcessLine(string line)
        {
            //TODO:  Process Line
        }

        private static void LogException(Exception ex)
        {
            //TODO:  Log Exception
        }

Bu durumda, hatalı bir kayıtla karşılaşır karşılaşmaz dosyayı işlemeyi durdurur ve durdururuz.

Ancak, şartın, bir veya daha fazla satırda bir hata olsa bile dosyayı işlemeye devam etmek istediğimizi söylediğini söyleyin. Kod şöyle görünmeye başlayabilir:

    private static void ProcessFileFailAndContinue()
    {
        try
        {
            using (var file = new System.IO.StreamReader("c:\\test.txt"))
            {
                string line;
                while ((line = file.ReadLine()) != null)
                {
                    Exception ex = ProcessLineReturnException(line);
                    if (ex != null)
                    {
                        _Errors.Add(ex);
                    }
                }
            }

            //Do something with _Errors Here
        }
        //Catch errors specifically around opening the file
        catch (System.IO.FileNotFoundException fnfe) 
        { 
            LogException(fnfe);
        }    
    }

    private static Exception ProcessLineReturnException(string line)
    {
        try
        {
            //TODO:  Process Line
        }
        catch (Exception ex)
        {
            LogException(ex);
            return ex;
        }

        return null;
    }

Bu durumda, istisnayı arayana geri göndeririz, bununla birlikte istisna yakalandı ve zaten ele alındığından beri bir istisna geri döndürmüyordum, bunun yerine bir çeşit hata nesnesi döndürdüm. Bu bir istisnayı geri döndürmenin zararı olmaz, ancak diğer arayanlar istisna nesnesinin bu davranışa sahip olması nedeniyle istenmeyebilir olabilecek istisnayı tekrar atabilir. Arayanların yeniden atma yeteneğine sahip olmasını istiyorsanız, bir istisna döndürün, aksi takdirde istisna nesnesinden bilgileri alın ve daha küçük bir hafif nesne oluşturun ve bunun yerine geri gönderin. Hızlı başarısız genellikle temiz koddur.

Doğrulama örneğiniz için, doğrulama hataları oldukça yaygın olabileceğinden, muhtemelen bir istisna sınıfından miras alamaz veya istisnalar alamazdım. Kullanıcılarımın% 50'si ilk denemede bir formu doğru doldurmazsa, bir istisna atmak yerine bir nesneyi döndürmek daha az olur.


1

S: İstisnaların atılmadığı ve yakalanmadığı, sadece bir yöntemden döndürüldüğü ve daha sonra hata nesnesi olarak geçirildiği meşru durumlar var mı?

Evet. Örneğin, yakın zamanda bir ASMX Web Hizmeti bağışında atılan İstisnaların, ortaya çıkan SOAP mesajında ​​bir öğe içerdiğini bulduğum bir durumla karşılaştım, bu yüzden onu üretmek zorunda kaldım.

Örnek kod:

Public Sub SomeWebMethod()
    Try
        ...
    Catch ex As Exception
        Dim soapex As SoapException = Me.GenerateSoapFault(ex)
        Throw soapex
    End Try
End Sub

Private Function GenerateSoapFault(ex As Exception) As SoapException
    Dim document As XmlDocument = New XmlDocument()
    Dim faultDetails As XmlNode = document.CreateNode(XmlNodeType.Element, SoapException.DetailElementName.Name, SoapException.DetailElementName.Namespace)
    faultDetails.InnerText = ex.Message
    Dim exceptionType As String = ex.GetType().ToString()
    Dim soapex As SoapException = New SoapException("SoapException", SoapException.ClientFaultCode, Context.Request.Url.ToString, faultDetails)
    Return soapex
End Function

1

Çoğu dilde (afaik), istisnalar bazı eklenmiş güzelliklerle birlikte gelir. Çoğunlukla, geçerli yığın izinin anlık görüntüsü nesnede saklanır. Bu hata ayıklama için güzel, aynı zamanda bellek etkileri olabilir.

@ThinkingMedia'nın daha önce de söylediği gibi, istisnayı gerçekten bir hata nesnesi olarak kullanıyorsunuz.

Kod örneğinizde, çoğunlukla kodu yeniden kullanmak ve kompozisyonu önlemek için yapıyorsunuz. Şahsen bunun bunu yapmak için iyi bir neden olduğunu sanmıyorum.

Başka bir olası neden, yığın izlemeli bir hata nesnesi vermek için dili kandırmak olacaktır. Bu, hata işleme kodunuza ek bağlamsal bilgi verir.

Öte yandan, yığın izini tutmanın bir hafıza maliyetinin olduğunu varsayabiliriz. Örneğin, bu nesneleri bir yerde toplamaya başlarsanız, hoş olmayan bir hafıza etkisi görebilirsiniz. Elbette bu büyük ölçüde istisna yığın izlerinin ilgili dilde / motorda nasıl uygulandığına bağlıdır.

Peki bu iyi bir fikir mi?

Şimdiye kadar kullanıldığını veya tavsiye edildiğini görmedim. Ancak bu fazla bir şey ifade etmiyor.

Soru şudur: Yığın izi, hata işlemeniz için gerçekten yararlı mı? Ya da daha genel olarak, geliştiriciye veya yöneticiye hangi bilgileri vermek istersiniz?

Tipik fırlatma / deneme / yakalama düzeni, yığın izlemenin gerekli olmasını sağlar, çünkü aksi halde nereden geldiği hakkında hiçbir fikriniz yoktur. Döndürülen bir nesne için her zaman çağrılan işlevden gelir. Yığın izi, geliştiricinin ihtiyaç duyduğu tüm bilgileri içerebilir, ancak daha az ağır ve daha özel bir şey yeterli olacaktır.


-4

Cevap Evet. Http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Exceptions adresini ziyaret edin ve istisnalarla ilgili Google'ın C ++ stil kılavuzu için oku açın. Orada karar vermek için kullandıkları argümanları görebiliyorsunuz, ancak tekrar yapmak zorunda kalırlarsa büyük olasılıkla karar vereceklerini söyleyebilirsiniz.

Bununla birlikte, Go dilinin benzer nedenlerle deyimsel olarak istisnalar kullanmadığını da belirtmek gerekir.


1
Maalesef, bu yönergelerin soruyu yanıtlamaya nasıl yardımcı olduğunu anlamıyorum: İstisna işlemlerinin tamamen önlenmesini önlüyorlar. İstediğim şey bu değil; Sorum, ortaya çıkan istisna nesnesinin hiç atılmayacağı bir durumda bir hata durumunu tanımlamak için bir istisna tipi kullanmanın yasal olup olmadığıdır. Bu kılavuzda bununla ilgili hiçbir şey görünmüyor.
stakx
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.