İstisnaları yakalamak / atmak, aksi halde saf bir yöntemi zorlaştırıyor mu?


27

Aşağıdaki kod örnekleri sorumu bağlamı sağlar.

Room sınıfı bir temsilci ile başlatıldı. Room sınıfının ilk uygulamasında, delegelere istisnalar atan hiçbir muhafız yoktur. Bu istisnalar, delegenin değerlendirildiği Kuzey mülkünde kabarcıklı olacaktır (not: Main () yöntemi, bir Oda örneğinin müşteri kodunda nasıl kullanıldığını gösterir):

public sealed class Room
{
    private readonly Func<Room> north;

    public Room(Func<Room> north)
    {
        this.north = north;
    }

    public Room North
    {
        get
        {
            return this.north();
        }
    }

    public static void Main(string[] args)
    {
        Func<Room> evilDelegate = () => { throw new Exception(); };

        var kitchen = new Room(north: evilDelegate);

        var room = kitchen.North; //<----this will throw

    }
}

North özelliğini okurken kullanmak yerine nesne oluşturma konusunda başarısız olmayı tercih edersem, yapıcıyı özel olarak değiştiririm ve Create () adında statik bir fabrika yöntemi sunar. Bu yöntem, temsilcinin attığı istisnayı yakalar ve anlamlı bir istisna iletisine sahip bir sarmalayıcı istisnası atar:

public sealed class Room
{
    private readonly Func<Room> north;

    private Room(Func<Room> north)
    {
        this.north = north;
    }

    public Room North
    {
        get
        {
            return this.north();
        }
    }

    public static Room Create(Func<Room> north)
    {
        try
        {
            north?.Invoke();
        }
        catch (Exception e)
        {
            throw new Exception(
              message: "Initialized with an evil delegate!", innerException: e);
        }

        return new Room(north);
    }

    public static void Main(string[] args)
    {
        Func<Room> evilDelegate = () => { throw new Exception(); };

        var kitchen = Room.Create(north: evilDelegate); //<----this will throw

        var room = kitchen.North;
    }
}

Try-catch bloğu Create () yönteminin zorlanmasına neden oluyor mu?


1
Oda Yaratma işlevinin faydası nedir; Bir temsilci kullanıyorsanız, müşteri 'Kuzey'i aramadan önce neden aramalısınız?
SH

1
@SH Temsilci bir istisna atarsa, Room nesnesinin oluşturulmasını öğrenmek istiyorum. İstemcinin kullanım yerine istisnayı bulmasını istemiyorum, bunun yerine yaratılış üzerine. Yaratma yöntemi, kötü delegeleri ortaya çıkarmak için mükemmel bir yerdir.
Rock Anthony Johnson

3
@RockAnthonyJohnson, Evet. Delege yürütülürken yalnızca ilk kez çalışabilir veya ikinci çağrıda farklı bir odaya geri dönebilir misiniz? Delegeyi aramak, bir yan etkinin kendisine neden olabilir mi?
SH

4
Bir safsızlık işlevini çağıran bir işlev bir safsızlık işlevidir; bu nedenle, temsilci safsızsa Createda geçersizdir, çünkü onu çağırır.
Idan

2
Sizin Createfonksiyon özelliğini alırken istisna almak sizi korumaz. Temsilciniz fırlatırsa, gerçek hayatta, sadece bazı şartlar altında fırlatılması muhtemeldir. Olasılıklar, atma koşullarının inşaat sırasında mevcut olmaması, ancak mülkiyeti alırken mevcut olmalarıdır.
Bart van Ingen Schenau

Yanıtlar:


26

Evet. Bu etkili bir şekilde saf olmayan bir fonksiyondur. Yan etki yaratır: program yürütme, işlevin geri dönmesi beklenen yerin dışında bir yerde devam eder.

Saf bir işlev yapmak için, işlevden returnbeklenen değeri kaplayan gerçek bir nesne ve bir Maybenesne veya bir İş Birimi nesnesi gibi olası bir hata durumunu gösteren bir değerdir .


16
Unutma, "saf" sadece bir tanımdır. Eğer atan bir işleve ihtiyacınız varsa, ancak her şekilde referans olarak şeffaftır, o zaman yazdığınız şeydir.
Robert Harvey

1
Haskell'de en azından herhangi bir fonksiyon atabilir fakat yakalamak için IO'da olmanız gerekir, bazen atanan saf bir fonksiyon normalde kısmi
jk

4
Yorumun yüzünden bir epifanim oldu. Entelektüel olarak saflıkla ilgiliyken, pratikte gerçekte ihtiyacım olan şey determinizm ve saygılı şeffaflık. Saflığa ulaşırsam, bu sadece bir avantaj. Teşekkür ederim. Bilgin olsun, beni bu yola sürükleyen şey, Microsoft IntelliTest'in yalnızca deterministic olan koda karşı etkili olması.
Rock Anthony Johnson

4
@RockAnthonyJohnson: Tüm kodlar deterministik veya en azından mümkün olduğunca deterministik olmalıdır. Referans şeffaflık, bu determinizmi elde etmenin sadece bir yoludur. İş parçacığı gibi bazı tekniklerin bu determinizme karşı çalışma eğilimi vardır, bu nedenle iş parçacığı modelinin deterministik olmayan eğilimlerini geliştiren Görevler gibi başka teknikler de vardır. Rastgele sayı üreteçleri ve Monte-Carlo simülatörleri gibi şeyler de belirleyicidir, fakat olasılıklara göre farklı bir şekilde.
Robert Harvey

3
@zzzzBov: "Saf" ın gerçek, kesin tanımını bu kadar önemli görmüyorum. Bkz yukarıda, ikinci yorumunu.
Robert Harvey,

12

Şey, evet ... ve hayır.

Saf bir işlev Referans Saydamlığına sahip olmalıdır - yani, program davranışını değiştirmeden, olası bir çağrıyı olası değere döndürülen değerle değiştirebilmelisiniz. * İşlevin her zaman belirli argümanlara atma garantisi vardır, bu nedenle işlev çağrısını değiştirecek bir geri dönüş değeri yoktur, bunun yerine onu görmezden gelelim . Bu kodu düşünün:

{
    var ignoreThis = func(arg);
}

Eğer funcsaf bir iyileştirici karar verebilir func(arg)'s sonuçla değiştirilebilir. Sonucun ne olduğunu henüz bilmiyor, ancak kullanılmadığını söyleyebilir - bu ifadenin hiçbir etkisi olmadığını çıkartabilir ve kaldırabilir.

Fakat eğer func(arg)atılırsa, bu ifade bir şey yapar - bir istisna atar! Bu nedenle, optimize edici bunu kaldıramaz - fonksiyonun çağrılıp çağrılmaması önemlidir.

Fakat...

Uygulamada bu çok az önemli. Bir istisna - en azından C # - istisnai bir durumdur. Düzenli kontrol akışınızın bir parçası olarak kullanmamanız gerekiyor - denemeniz ve yakalamanız gerekiyor ve eğer bir şeyi yakalarsanız, yaptığınız işlemi geri almak ya da bir şekilde hala başarmak için hatayı giderirsiniz. Programınız başarısız olacak bir kodun en iyi duruma getirilmiş olması nedeniyle düzgün çalışmıyorsa, istisnaları yanlış kullanıyorsunuz (test kodu olmadığı sürece ve testler için oluşturduğunuzda istisnalar optimize edilmemelidir).

Söyleniyor ki...

Saf işlevlerden istisnaları, yakalanmaları niyetiyle atmayın - işlevsel dillerin yığın çözme-istisnalar yerine monadları kullanmayı tercih etmelerinin iyi bir nedeni vardır.

C #, ErrorJava (ve diğer birçok dil) gibi bir sınıfa sahip olsaydı, Errorbunun yerine bir tane atmayı önerebilirdim Exception. İşlev kullanıcısının yanlış bir şey yaptığını (atan bir işlevi aştığını) ve bu gibi şeylere saf işlevlerde izin verildiğini gösterir. Ancak C # 'nın bir Errorsınıfı yoktur ve kullanım hatası istisnaları türev gibi görünmektedir Exception. Benim önerim, ArgumentExceptionfonksiyonun kötü bir argüman ile çağrıldığını açıkça ortaya koyan bir şey atmak .


* Hesaplamalı konuşma. Naif özyineleme kullanılarak uygulanan bir Fibonacci işlevi büyük sayılar için uzun zaman alacaktır ve makinenin kaynaklarını tüketebilir, ancak bunlar sınırsız zaman ve hafızaya sahip olduklarından fonksiyon her zaman aynı değeri döndürür ve yan etkileri olmaz (tahsisat dışında) hafıza ve bu hafızayı değiştirerek) - hala saf olarak kabul edilir.


1
+1 Önerilen ArgumentException kullanımınız için ve öneriniz için "yakalanma niyetiyle saf işlevlerden istisnalar atmamaya" öneriniz.
Rock Anthony Johnson

İlk argümanınız ("Ama ..." a kadar) bana sağlam geliyor. İstisna, fonksiyon sözleşmesinin bir parçasıdır. Kavramsal olarak, herhangi bir işlevi yeniden yazabilir (value, exception) = func(arg)ve bir istisna atma olasılığını kaldırabilirsiniz (bazı dillerde ve bazı istisna türleri için, onu tanımlarla, argümanlar veya dönüş değeriyle aynı şekilde listelemeniz gerekir). Şimdi daha önce olduğu gibi saf olurdu ( her zaman döner, aynı argümana verilen bir istisna atarsa). Bir istisna atmak , eğer bu yorumu kullanırsanız, bir yan etki değildir .
AnoE

@Hayır Bu funcşekilde yazmış olsaydınız, verdiğim blok uzaklaştırabilirdi, çünkü yığını açmak yerine atılan istisna ignoreThis, görmezden geldiğimiz kodlanmış olurdu . Yığını çözen istisnaların aksine, dönüş türünde kodlanan istisna, saflığı ihlal etmez - ve bu nedenle birçok işlevsel dil onları kullanmayı tercih eder.
Idan Arye,

Kesinlikle ... bu hayal ürünü dönüşümün istisnasını görmezden gelmezdin. Sanırım bunların hepsi bir anlambilim / tanım meselesidir, sadece istisnaların varlığının ya da oluşmasının tüm saflık kavramlarını geçersiz kılmadığını belirtmek istedim.
AnoE

1
@AnoE Ama writted ettik kod olurdu bu hayal dönüşüm için istisna görmezden. func(arg)demeti geri döndürürdü (<junk>, TheException()), ve böylece ignoreThisdemeti içerecektir (<junk>, TheException()), ancak ignoreThisistisnayı hiç kullanmadığımız için hiçbir şey üzerinde bir etkisi olmaz.
Idan Arye

1

Bir değerlendirme, try - catch bloğunun sorun olmamasıdır. (Yukarıdaki soruya yaptığım yorumları dikkate alarak).

Asıl sorun, Kuzey özelliğinin bir G / Ç çağrısı olmasıdır .

Kodun yürütülmesinde bu noktada, programın müşteri kodu tarafından sağlanan G / Ç'yi kontrol etmesi gerekir. (Girişin bir delege şeklinde olması ya da girişin nominal olarak önceden iletilmiş olması uygun değildir).

Girişin kontrolünü kaybettiğinizde, fonksiyonun saf olduğundan emin olamazsınız. (Özellikle işlev atabilirse).


Taşı [Check] Room'u neden aramayı kontrol etmek istemediğinizi anlamadım. Soruya benim yorumum göre:

Evet. Delege yürütülürken yalnızca ilk kez çalışabilir veya ikinci çağrıda farklı bir odaya geri dönebilirsiniz? Delegeyi aramak, bir yan etkinin kendisine neden olabilir mi?

Bart van Ingen Schenau'nun yukarıda söylediği gibi

Oluşturma işleviniz, mülkü alırken sizi bir istisna almaktan korumaz. Temsilciniz fırlatırsa, gerçek hayatta, sadece bazı şartlar altında fırlatılması muhtemeldir. Olasılıklar, atma koşullarının inşaat sırasında mevcut olmaması, ancak mülk edinirken mevcut olmalarıdır.

Genel olarak, herhangi bir tembel yükleme türü dolaylı olarak hataları o noktaya kadar savunur .


Move [Check] Room yöntemini kullanmanızı öneririm. Bu, saf olmayan G / Ç yönlerini tek bir yere ayırmanıza izin verir.

Robert Harvey'in cevabına benzer :

Saf bir işlev yapmak için, işlevden beklenen değeri kaplayan gerçek bir nesneyi ve bir Belki nesnesi veya bir İş Birimi nesnesi gibi olası bir hata durumunu gösteren bir değer döndürün.

Girdideki (olası) istisnanın nasıl ele alınacağını belirlemek kod yazara bağlı olacaktır. Daha sonra yöntem, bir Room nesnesini veya Null Room nesnesini döndürebilir veya istisna dışında olabilir.

Bu noktaya bağlı:

  • Oda etki alanı, Oda İstisnalarına Boş veya Daha Kötü Bir Şey olarak mı davranıyor?
  • Null / İstisna Odasında North'u arayarak müşteri kodu nasıl bildirilir. (Kefalet / Durum Değişkeni / Küresel Durum / Monad / Ne Olursa Olsun; bazıları diğerlerinden daha saftır :)).

-1

Hmm ... Bu konuda doğru hissetmedim. Bence yapmaya çalıştığın şey, diğer insanların ne yaptıklarını bilmeden işlerini doğru yaptıklarından emin olmak.

Fonksiyon, sizin tarafınızdan ya da başkaları tarafından yazılabilecek olan tüketici tarafından geçtiğinden.

İşlev geçişi, Room nesnesinin oluşturulduğu sırada çalıştırmak için geçerli değilse ne olur?

Koddan, Kuzey'in ne yaptığından emin olamadım.

Diyelim ki, eğer işlev oda rezervasyonu yapacaksa ve rezervasyonun zamanına ve rezervasyon periyoduna ihtiyaç duyuyorsa, hazır bilgiye sahip değilseniz istisna alırsınız.

Ya uzun süren bir işlevse? Bir Oda nesnesi oluştururken programınızı engellemek istemeyeceksiniz, ya 1000 oda nesnesi oluşturmanız gerekiyorsa?

Bu sınıfı tüketen tüketiciyi yazacak olan kişi olsaydınız, girilen işlevin doğru yazıldığından emin olursunuz, değil mi?

Tüketiciyi yazan başka bir kişi varsa, bir Room nesnesi yaratmanın "özel" koşulunu bilemeyebilir ve bu durum yürütmeye neden olabilir ve nedenini bulmaya çalışırken başlarını çizebilir.

Başka bir şey, yeni bir istisna atmak her zaman iyi bir şey değildir. Sebep, orijinal istisna bilgisini içermeyecek olmasıdır. Bir hata aldığınızı biliyorsunuz (genel istisna için arkadaşça bir mesaj), ancak hatanın ne olduğunu bilemezsiniz (null referans, sınır dışı indeks, sıfırdan sapma vb.). Soruşturma yapmamız gerektiğinde bu bilgi çok önemlidir.

İstisna, yalnızca neyi ve nasıl idare edeceğini bildiğiniz zaman halletmelisiniz.

Orijinal kodunuzun yeterince iyi olduğunu düşünüyorum, tek şey "Ana" (veya nasıl işleneceğini bilen herhangi bir katmanda) istisnasıyla başa çıkmak isteyebilirsiniz.


1
2. kod örneğine tekrar bakın; Yeni istisnayı düşük istisna ile başlatıyorum. Tüm yığın izi korunmuştur.
Rock Anthony Johnson,

Hata. Benim hatam.
user251630

-1

Diğer cevaplara katılmıyorum ve Hayır diyeceğim . İstisnalar, aksi halde saf bir fonksiyonun saflaşmamasına neden olmaz.

İstisnaların herhangi bir kullanımı, hata sonuçları için açık kontroller kullanmak üzere yeniden yazılabilir. Bunun dışında istisnalar sadece sözdizimsel şeker olarak kabul edilebilir. Uygun sözdizimsel şeker saf kod saflaştırmaz.


1
Safsızlığı tekrar yazabilmeniz, işlevi saf kılmaz. Haskell, saf olmayan işlevlerin kullanımının saf işlevlerle yeniden yazılabileceğinin bir kanıtıdır - dönüş saf türlerindeki saf işlevlerin herhangi bir safsızlık davranışını kodlamak için monadlar kullanır. GÇ monadlarının varlığı, normal GÇ'nin saf olduğu anlamına gelmez - istisnalar monadlarının varlığı neden normal istisnaların saf olduğu anlamına gelir?
Idan Arye,

@IdanArye: İlk etapta kirlilik olmadığını savunuyorum. Yeniden yazma örneği sadece bunu göstermek içindi. Düzenli IO, gözlemlenebilir yan etkilere neden olduğu için saf değildir veya bazı harici girdiler nedeniyle aynı girdiyle farklı değerler döndürebilir. Bir istisna atmak bunların hiçbirini yapmaz.
JacquesB 27:16

Yan etki serbest kod yeterli değil. Referans Şeffaflık da işlev saflığı için bir gerekliliktir.
Idan Arye,

1
Determinizm referanssal şeffaflık için yeterli değildir - yan etkiler de deterministik olabilir. Referanssal şeffaflık, ifadenin sonuçlarıyla değiştirilebileceği anlamına gelir - bu nedenle referans niteliğindeki şeffaf ifadeleri kaç kez değerlendirirseniz, hangi sırayla ve ne şekilde değerlendirseniz de değerlendirmeseniz de sonuç aynı olmalıdır. Bir istisna deterministik olarak atılırsa, "kaç kere olursa olsun" kriterleri konusunda iyiyiz, ancak diğerleriyle değil. Kendi
cevabımdaki

1
(... devamı) ve "hangi sırayla" için olduğu gibi - eğer fve ghem saf fonksiyonlar, o zaman f(x) + g(x)ne olursa olsun değerlendirmek düzenin aynı sonucu olmalıdır f(x)ve g(x). Ama eğer f(x)atar Fve g(x)atar G, o zaman bu ifadeden atılan istisna olacağını Feğer f(x)ilk değerlendirilir ve Geğer g(x)ilk değerlendirilir - Bu fonksiyonlar davranması gerektiği değil nasıl saftır! İstisna, geri dönüş türünde kodlandığında, bu sonuç Error(F) + Error(G)değerlendirme sırasından bağımsız olarak sonuçlanır .
Idan Arye
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.