İstisnalar, hata kodları ve ayrımcı sendikalar


80

Kısa bir süre önce bir C # programlama işine başladım, ancak Haskell'de epeyce bir geçmişe sahibim.

Fakat C # 'nın nesne yönelimli bir dil olduğunu anlıyorum, yuvarlak bir çiviyi kare bir deliğe zorlamak istemiyorum.

Aşağıdaki Microsoft'tan İstisna Fırlatma makalesini okudum :

YAPMAYIN hata kodlarını döndürür.

Ancak Haskell'e alışmak için, C # veri türünü kullanıyorum OneOf, sonucu "sağ" değer olarak ya da hatayı (en sık bir Numaralandırma) "sol" değer olarak döndürüyorum.

Bu EitherHaskell'in kongresine çok benziyor .

Bana göre bu istisnalardan daha güvenli. C # 'da, istisnaları dikkate almamak derleme hatası vermez ve eğer yakalanmazlarsa sadece kabarcıklanırlar ve programınızı çökertirler. Bu belki de bir hata kodunu görmezden gelmekten ve tanımsız davranışlar üretmekten daha iyidir, ancak müşterinizin yazılımını çökertmek, özellikle de arka plandaki birçok önemli ticari görevi yerine getirirken hala iyi bir şey değildir.

Bununla birlikte OneOf, paketten çıkarma ve geri dönüş değeri ile hata kodlarının ele alınması konusunda oldukça açık olmak gerekir. Kişi arama yığındaki bu aşamada nasıl idare edileceğini bilmiyorsa, mevcut işlevin dönüş değerine dahil edilmesi gerekir, böylece arayanlar bir hatanın ortaya çıkabileceğini bilir.

Ancak bu, Microsoft'un önerdiği yaklaşım gibi görünmüyor.

OneOf“Olağan” istisnaları (örneğin Dosya Bulunamadı vb. Gibi) işlemek için istisnalar yerine kullanmak makul bir yaklaşım mı yoksa korkunç bir uygulama mı?


Kontrol akışı olarak istisnaların ciddi bir antipattern olarak kabul edildiğini duyduğuma dikkat etmeliyim , yani "istisna" normalde programı sonlandırmadan halledeceğiniz bir şeyse , bu bir şekilde "kontrol akışı" değil mi? Anladığım kadarıyla burada biraz gri bir alan var.

Not OneOf"Bellek Dolu" gibi şeyler için kullanmıyorum , kurtarmayı beklemediğim koşullar hala istisnalar doğuracak. Ancak, ayrıştırılmayan kullanıcı girdisi gibi aslında "kontrol akışı" dır ve muhtemelen istisnalar atmamalıyım gibi oldukça makul sorunlar gibi hissediyorum.


Sonraki düşünceler:

Bu tartışmadan şu anda elimde olan şey şu şekilde:

  1. Anında arayan catchkişiden çoğu zaman istisnayı ele almasını ve işini sürdürmesini, belki başka bir yoldan devam etmesini beklerseniz, bu muhtemelen geri dönüş türünün bir parçası olmalıdır. Optionalveya OneOfburada faydalı olabilir.
  2. Anında arayanın çoğu zaman istisnayı yakalamamasını beklerseniz, bir yığın istisna atarak manuel olarak yığının üstünden el ile geçirme aptallığını kurtarın.
  3. Acil arayanın ne yapacağından emin değilseniz, ikisini de, gibi Parseve belki sağlayın TryParse.

13
F # Result;-) kullanın
Astrinus

8
Biraz konu dışı, ancak bakmakta olduğunuz yaklaşımın, geliştiricinin hatayı doğru şekilde işlemesini sağlamanın hiçbir yolu olmadığına dair genel bir güvenlik açığı olduğunu unutmayın. Elbette, yazım sistemi bir hatırlatıcı olarak hareket edebilir, ancak bir hatayı ele almamak için programı patlatma varsayılanını kaybedersiniz; Hatırlatmanın, bu temerrüde düşmeye değip değmeyeceği açık bir tartışmadır. Bir istisnanın bir noktada feshedileceğini varsayarak tüm kodunuzu yazmanın daha iyi olacağını düşünmeye meyilliyim; daha sonra hatırlatma daha az değere sahiptir.
jpmc26

9
İlgili makale: İstisnaların dört "kovası" na Eric Lippert . Dışardan dışavurumlarda verdiği örnek , istisnalarla başa çıkmanız gereken senaryolar olduğunu gösterir FileNotFoundException.
Søren D. Ptæus

7
Bu konuda yalnız olabilirim, fakat bence çökmenin iyi olduğunu düşünüyorum . Yazılımınızda, sorunların doğru şekilde izlendiğinden oluşan bir kilitlenme günlüğünden daha iyi bir kanıt yoktur. Bir yamayı 30 dakika veya daha kısa sürede düzeltip dağıtabilen bir ekibiniz varsa, bu çirkin arkadaşların ortaya çıkmasına izin vermek kötü bir şey olmayabilir.
T. Sar

9
Nasıl cevapların hiçbiri konusuna açıklık getirmiştir gelip monad bir hata kodu değil ve ne de bir ? Bunlar temelde farklıdır ve sonuçta soru bir yanlış anlaşılmaya dayanıyor gibi görünmektedir. (Her ne kadar değiştirilmiş bir formda olsa da hala geçerli bir soru.)EitherOneOf
Konrad Rudolph

Yanıtlar:


131

ancak müşterinizin yazılımını çökertmek hala iyi bir şey değil

Kesinlikle çok iyi bir şey.

Tanımsız bir sistem bozuk veri gibi kötü şeyler yapabilir, sabit sürücüyü biçimlendirir ve cumhurbaşkanı tehdit edici e-postalar gönderir; Eğer sistemi kurtaramaz ve tekrar tanımlanmış bir duruma koyarsanız, kilitlenme işlemi yapmakla sorumludur. İşte bu yüzden, kendilerini sessizce parçalamak yerine çökecek sistemler inşa ediyoruz. Şimdi elbette, hepimiz asla çökmeyen istikrarlı bir sistem istiyoruz, ancak yalnızca sistemin tanımlanabilir bir öngörülebilir güvenli durumda kalmasını istiyoruz.

Kontrol akışı olarak istisnaların ciddi bir antipattern olarak kabul edildiğini duydum.

Bu kesinlikle doğru, ancak çoğu zaman yanlış anlaşılıyor. İstisna sistemini icat ettikleri zaman korkmuşlardı, yapılandırılmış programlamayı çiğniyorlardı. Elimizdeki neden Yapısal programlama olduğunu for, while, until, break, ve continuetüm ihtiyacımız olduğunda, bütün bunları yapmak için vardır goto.

Dijkstra bize goto'yu gayrı resmi olarak kullanmanın (dilediğiniz yere atlayarak) okuma kodunu bir kabus haline getirdiğini öğretti. İstisna sistemini bize verdiklerinde, yeniden yarattıklarından korkuyorlardı. Bu yüzden bize anlayacağımızı umarak "akış kontrolü için kullanmamamızı" söylediler. Ne yazık ki, çoğumuz yapmadık.

Garip, sık sık alıştığımız gibi spagetti kodu oluşturmak için istisnaları istismar etmiyoruz. Tavsiyenin kendisi daha fazla belaya neden olmuş gibi görünüyor.

Temel olarak istisnalar, bir varsayımı reddetmekle ilgilidir. Bir dosyanın kaydedilmesini istediğinizde, dosyanın kaydedilebileceğini ve kaydedileceğini varsayarsınız. Adının yasadışı olması, HD'nin dolu olması ya da bir kablonun veri kablonuzdan kemirilmiş olması nedeniyle olamayacağınız durumdaki istisna. Tüm bu hataları farklı şekilde halledebilir, hepsini aynı şekilde kaldırabilir veya sistemi durdurmalarına izin verebilirsiniz. Kodunuzda varsayımlarınızın gerçekleşmesi gereken mutlu bir yol var. Bir ya da başka istisnalar sizi bu mutlu yoldan uzaklaştırır. Açıkçası, evet, bu bir tür "akış kontrolü" ama sizi uyardıkları şey değildi. Böyle saçmalıktan bahsediyorlardı :

görüntü tanımını buraya girin

"İstisnalar olağanüstü olmalı". Bu küçük totoloji doğdu çünkü istisna sistem tasarımcılarının yığın izleri oluşturmak için zamana ihtiyaçları var. Etrafta atlamakla karşılaştırıldığında, bu yavaş. CPU zamanını yiyor. Ancak, sistemi günlüğe kaydetmek ve durdurmak ya da en azından bir sonraki sisteme başlamadan önce geçerli olan yoğun işlemi durdurmak istiyorsanız, o zaman öldürmek için biraz zamanınız var. Eğer insanlar "akış kontrolü için" istisnalar kullanmaya başlarlarsa, zaman hakkındaki bu varsayımların hepsi pencereden dışarı çıkar. Bu nedenle, "İstisnalar olağanüstü olmalı" gerçekten performans değerlendirmesi olarak bize verildi.

Bundan çok daha önemli, bizi şaşırtmıyor. Sonsuz döngüyü yukarıdaki kodda görmeniz ne kadar sürdü?

YAPMAYIN hata kodlarını döndürür.

... tipik olarak hata kodlarını kullanmayan bir kod tabanındaysanız iyi bir tavsiyedir. Neden? Çünkü kimse dönüş değerini kaydetmeyi ve hata kodlarını kontrol etmeyi hatırlamayacak. C'deyken hala iyi bir kongre.

OneOf

Başka bir kongre kullanıyorsun. Konvansiyonu ayarladığınız ve sadece başka biriyle savaşmadığınız sürece sorun değil. Aynı kod tabanında iki hata konvansiyonunun olması kafa karıştırıcı. Bir şekilde diğer sözleşmeyi kullanan tüm kodlardan kurtulduysanız, devam edin.

Kongreyi kendim seviyorum. Burada bulduğum en iyi açıklamalardan biri * :

görüntü tanımını buraya girin

Ama sevdiğim kadarıyla hala diğer sözleşmelerle karıştırmayacağım. Birini seç ve buna sadık kal. 1

1: Demek istediğim, aynı anda birden fazla kongre hakkında düşünmemi sağlama.


Sonraki düşünceler:

Bu tartışmadan şu anda elimde olan şey şu şekilde:

  1. Anında arayanın çoğu zaman istisnayı yakalayıp işlemesini ve belki de başka bir yoldan çalışmalarına devam etmesini beklerseniz, bu muhtemelen geri dönüş türünün bir parçası olmalıdır. İsteğe bağlı veya OneOf burada yararlı olabilir.
  2. Anında arayanın çoğu zaman istisnayı yakalamamasını beklerseniz, bir yığın istisna atarak manuel olarak yığının üstünden el ile geçirme aptallığını kurtarın.
  3. Acil arayanın ne yapacağından emin değilseniz Parse ve TryParse gibi ikisini de sağlayın.

Gerçekten bu kadar basit değil. Anlamanız gereken temel şeylerden biri, sıfırın ne olduğudur.

Mayıs ayında kaç gün kaldı? 0 (çünkü Mayıs değil. Zaten Haziran).

İstisnalar, bir varsayımı reddetmenin bir yoludur, ancak tek yol değildir. Eğer varsayımı reddetmek için istisnalar kullanırsanız, mutlu yolu terk edersiniz. Ancak, olayların varsayıldığı kadar basit olmadığını gösteren mutlu yolu göndermek için değerler seçtiyseniz, o zaman bu değerlerle başa çıkabildiği sürece o yolda kalabilirsiniz. Bazen 0 zaten bir şey ifade etmek için kullanılmaktadır, bu nedenle varsayımınızı reddettiğiniz fikrinin haritasını çıkarmak için başka bir değer bulmanız gerekir. Bu fikri eski güzel cebirdeki kullanımından tanıyabilirsiniz . Monad'lar bu konuda yardımcı olabilirler, ancak her zaman bir monad olmak zorunda değildir.

Örneğin 2 :

IList<int> ParseAllTheInts(String s) { ... }

Bunun kasıtlı olarak herhangi bir şey atması için tasarlanması gereken herhangi bir iyi sebep olduğunu düşünüyor musunuz? Hiçbir int ayrıştırılamadığında ne elde edeceğini tahmin et? Sana söylememe bile gerek yok.

Bu iyi bir ismin işaretidir. Üzgünüm ama TryParse benim için iyi bir isim değil.

Çoğu zaman, cevap aynı anda birden fazla şey olduğunda hiçbir şey almamaya istisna atmaktan kaçınırız, ancak bazı nedenlerden dolayı, cevap ya bir şey ya da hiçbir şeyse, bize bir şey vermesi ya da atması konusunda ısrarla takıntılı hale gelirsek:

IList<Point> Intersection(Line a, Line b) { ... }

Paralel çizgilerin gerçekten burada bir istisna oluşturması gerekiyor mu? Bu listenin asla birden fazla nokta içermemesi gerçekten kötü mü?

Belki anlamsal olarak bunu alamazsın. Eğer öyleyse, üzücü. Ancak, keyfi bir boyuta sahip olmayan Monad'lar, Listsizi daha iyi hissetmenizi sağlayacaktır.

Maybe<Point> Intersection(Line a, Line b) { ... }

Monad'lar, test etmelerine gerek kalmayan belirli şekillerde kullanılması amaçlanan küçük özel amaçlı koleksiyonlardır. Ne içerdiklerine bakmaksızın onlarla başa çıkmanın yollarını bulmamız gerekiyor. Bu şekilde mutlu yol basit kalır. Çatlak açıp her Monad'ı test ederseniz dokunduğunuzda yanlış kullanıyorsunuzdur.

Biliyorum, çok garip. Ama bu yeni bir araç (bizim için). Bu yüzden biraz zaman ver. Çekiçler onları vidalarla kullanmayı bıraktığında daha anlamlı olur.


Beni şımartacaksanız, bu yorumu ele almak istiyorum:

Yanıtların hiçbiri, ya Monad'ın bir hata kodu olmadığını, ne de OneOf olmadığını açıklamaz. Bunlar temelde farklıdır ve sonuçta soru bir yanlış anlaşılmaya dayanıyor gibi görünmektedir. (Değiştirilmiş bir formda olmasına rağmen hala geçerli bir soru.) - Konrad Rudolph 4 Haz 18, 14:08

Bu kesinlikle doğru. Monadlar, istisnalar, bayraklar veya hata kodlarından çok koleksiyonlara daha yakındır. Akıllıca kullanıldığında böyle şeyler için ince kaplar yaparlar.


9
Kırmızı iz kutuların altında olsaydı , böylece onlar arasında çalışan olsaydı daha uygun olmaz mıydı ?
Flater

4
Mantıklı olarak haklısın. Bununla birlikte, bu özel şemadaki her kutu bir monadik bağlama işlemini temsil eder. bağlama kırmızı parçayı (belki de yaratır!) içerir. Sunumun tamamını izlemenizi öneririm. İlginç ve Scott harika bir konuşmacı.
Gusdor

7
GOTO'dan ziyade bir COMEFROM talimatı gibi istisnalar olduğunu düşünüyorum , çünkü throwaslında nereye atlayacağımızı bilmiyor / söylemiyor;)
Warbo

3
Sınırları belirleyebiliyorsanız, sözleşmelerin ne olduğu ile ilgili olarak, karma sözleşmelerde yanlış bir şey yoktur.
Robert Harvey,

4
Bu cevabın ilk bölümünün tamamı alıntılanan cümlenin ardından soruyu okumayı bırakmışsınız gibi güçlü bir şekilde okunuyor. Soru, programı çökertmenin tanımsız davranışa geçmekten daha üstün olduğunu kabul ediyor: çalışma zamanı çökmelerinin devam eden, tanımlanmamış yürütmeyle değil, potansiyel hata düşünülmeden programı oluşturmanızı bile önleyen derleme zamanı hatalarıyla çelişiyor; ve onunla başa çıkmak (eğer yapılacak başka bir şey yoksa, bir kaza olabilir), ama bu bir kaza olacak çünkü bunun olmasını istedin, unuttuğun için değil).
KRyan

35

C # Haskell değildir ve C # topluluğunun uzman görüş birliğini takip etmelisiniz. Bunun yerine Haskell uygulamalarını bir C # projesinde takip etmeye çalışırsanız, ekibinizdeki herkesi yabancılaştırırsınız ve sonunda C # topluluğunun bazı şeyleri neden farklı yaptığının sebeplerini keşfedersiniz. Bunun büyük bir nedeni C # 'nın ayrımcı sendikaları rahatlıkla desteklememesidir.

Kontrol akışı olarak istisnaların ciddi bir antipattern olarak kabul edildiğini duydum.

Bu evrensel olarak kabul edilmiş bir gerçek değildir. İstisnaları destekleyen her dilde seçim, ya bir istisna atmak (ki arayanın kullanmaması serbesttir) ya da bir bileşik değer (arayanın kullanması gereken) geri döndürür.

Hata durumlarını çağrı yığını boyunca yukarı doğru yaymak, her seviyede bir koşul gerektirir, bu yöntemlerin döngüsel karmaşıklığını iki katına çıkarır ve sonuç olarak birim test durumlarının sayısını iki katına çıkarır. Tipik iş uygulamalarında, pek çok istisna, kurtarmanın ötesindedir ve en yüksek seviyeye yayılmaya bırakılabilir (örneğin, bir web uygulamasının servis giriş noktası).


9
Monadları yaymak hiçbir şey gerektirmez.
Basilevs

23
@Basilevs C # 'nın sahip olmadığı kodu okunabilir tutarken monadların yayılması için destek gerektirir. Ya if-return talimatlarını ya da lambda zincirlerini tekrar edersiniz.
Sebastian Redl,

4
@SebastianRedl lambdas C # içinde Tamam görünüyor.
Basilevs

@Basilevs Sözdizimi görünümünden evet, ancak performans açısından daha az çekici olabileceğinden şüpheleniyorum.
Pharap

5
Modern C # 'da, biraz daha hızlı geri dönmek için muhtemelen yerel işlevleri kullanabilirsiniz. Her durumda, çoğu iş yazılımı IO tarafından diğerlerinden çok daha önemli ölçüde yavaşlar.
Turksarama

26

C # 'ya ilginç bir zamanda geldiniz. Nispeten yakın zamana kadar, dil zorunlu programlama alanında sıkıca oturdu. Hataları iletmek için istisnalar kullanmak kesinlikle normdu. Dönüş kodlarının kullanılması, dil desteğinin olmamasından muzdaripti (örneğin, ayrımcı sendikalar ve örüntü eşleşmesi gibi). Oldukça makul bir şekilde, Microsoft'tan gelen resmi rehberlik, iade kodlarından kaçınmak ve istisnalar kullanmaktı.

Bununla birlikte, her zaman TryXXXbir booleanbaşarı sonucu döndürecek ve bir outparametre ile ikinci bir değer sonucu sağlayacak yöntemler biçiminde istisnalar olmuştur . Bunlar, işlevsel programlamadaki "deneme" modellerine çok benzer, sonucun bir Maybe<T>geri dönüş değeri yerine bir out parametresiyle geldiğini kaydeder .

Ancak işler değişiyor. İşlevsel programlama daha popüler hale geliyor ve C # gibi diller yanıt veriyor. C # 7.0, bazı temel desen eşleme özelliklerini dile getirdi; C # 8, bir anahtar ifadesi, özyinelemeli kalıplar vb. Dahil çok daha fazla şey tanıtacaktır. Bununla birlikte, C # için "işlevsel kütüphanelerde", ayrımcı sendikalara destek sağlayan kendi Succinc <T> kütüphanemde de bir büyüme oldu. .

Bu iki şey bir araya geldiğinde, aşağıdaki gibi bir kodun popülaritesinin yavaşça arttığı anlamına gelir.

static Maybe<int> TryParseInt(string source) =>
    int.TryParse(source, out var result) ? new Some<int>(result) : none; 

public int GetNumberFromUser()
{
    Console.WriteLine("Please enter a number");
    while (true)
    {
        var userInput = Console.ReadLine();
        if (TryParseInt(userInput) is int value)
        {
            return value;
        }
        Console.WriteLine("That's not a valid number. Please try again");
    }
}

Son iki yılda bile, C # geliştiricileri arasındaki genel düşmanlıktan bu tür kodlara, bu tür teknikleri kullanma konusundaki artan ilgiye kadar belirgin bir değişiklik olduğunu fark etsem de, şu anda hala oldukça uygun.

Henüz " Hata kodlarını döndürmeyin " demedik . antika ve emekliye ihtiyacı var. Ancak bu hedefe doğru giden yolda yürüdük. Öyleyse seçiminizi yapın: şeyleri yapmaktan hoşlanıyorsanız, eşcinsel terkiyle istisnaları atmanın eski yöntemlerine sadık kalın; veya fonksiyonel dillerden gelen sözleşmelerin C # 'da daha popüler hale geldiği yeni bir dünyayı keşfetmeye başlayın.


22
Bu yüzden C # 'dan nefret ediyorum :) Bir kedinin cildine yol katmaya devam ediyorlar ve tabii ki eski yollardan asla uzaklaşmıyorlar. Sonunda hiçbir şey için hiçbir sözleşme yoktur, mükemmeliyetçi bir programcı hangi yöntemin "daha iyi" olduğuna karar vermek için saatlerce oturabilir ve yeni bir programcının başkalarının kodlarını okuyabilmeden önce her şeyi öğrenmesi gerekir.
Aleksandr Dubinsky

24
@AleksandrDubinsky, Sadece C # ile değil, genel olarak programlama dilleri ile ilgili temel bir problemi vurdunuz. Yeni diller yaratılıyor, yalın, taze ve modern fikirlerle dolu. Ve çok az insan onları kullanır. Zamanla, var olan kodu kıracağından kaldırılamayan eski özelliklerin üstüne cıvatalanmış kullanıcılar ve yeni özellikler kazanırlar. Şişkinlik büyür. Böylece halk, yalın, taze ve modern fikirlerle dolu yeni diller yaratıyor ...
David Arno

7
@AleksandrDubinsky, şimdi 1960'lardan gelen özellikleri eklediklerinde bundan nefret etmiyorsunuz.
ctrl-alt-delor

6
@AleksandrDubinsky Evet. Java bir keresinde bir şey eklemeye çalıştığı için (jenerik) başarısız oldular, artık ortaya çıkan korkunç karışıklıkla yaşamak zorundayız, böylece dersi öğrendiler ve herhangi bir şey eklemeyi
bıraktılar:)

3
@Agent_L: Artık Java'nın eklendiğini java.util.Stream(yarı saygın bir IEnumerable-hem benzeri) ve şimdi de lambdalar biliyorsunuz , değil mi? :)
cHao

5

Hata döndürmek için DU kullanmanın tamamen mantıklı olduğunu düşünüyorum, ancak sonra şunu söylerim: OneOf'u yazdığım gibi :) Ama yine de şunu söyleyebilirim, F # etc de (örneğin Sonuç türü, diğerleri tarafından belirtildiği gibi). ).

Genelde istisnai durumlar olarak döndüğüm Hataları düşünmüyorum, bunun yerine normal, ancak umarım nadiren karşılaşılan veya ele alınması gerekebilecek, ancak modellenmesi gereken durumlar olabilir.

İşte proje sayfasından örnek.

public OneOf<User, InvalidName, NameTaken> CreateUser(string username)
{
    if (!IsValid(username)) return new InvalidName();
    var user = _repo.FindByUsername(username);
    if(user != null) return new NameTaken();
    var user = new User(username);
    _repo.Save(user);
    return user;
}

Bu hatalar için istisnalar atmak fazladan gözüküyor gibi görünüyor - yöntem imzasında açık olmamanın ya da ayrıntılı eşleştirme sağlamanın dezavantajları dışında, genel bir performansa sahipler.

OneOf'un c # dilinde ne kadar aptalca olduğu başka bir soru. Sözdizimi geçerli ve oldukça sezgisel. DU ve demiryolu odaklı programlama iyi bilinen kavramlardır.

Mümkün olan yerlerde bir şeyin kırıldığını gösteren şeyler için İstisnalar'ı kurtarırdım.


Ne diyorsun yani OneOfbir Nullable dönen gibi, dönüş değeri daha zengin sağlamakla ilgilidir. Başlamak için istisnai olmayan durumlar için istisnalar değiştirir.
Aleksandr Dubinsky

Evet - ve kolay bir yol sağlar, mirasa dayalı Sonuç hiyerarşisini kullanarak mümkün olmayan, arayanda ayrıntılı eşleştirme yapar.
mcintyre321

4

OneOfJava’daki işaretli istisnalara eşdeğerdir. Bazı farklılıklar var:

  • OneOfyan etkileri için çağrılan metotları üreten metotlarla çalışmaz ( anlamlı olarak adlandırılabildikleri ve sonuç sadece göz ardı edilebildiği için). Açıkçası, hepimiz saf işlevleri kullanmaya çalışmalıyız, ancak bu her zaman mümkün değildir.

  • OneOfsorunun nerede olduğu hakkında hiçbir bilgi vermez. Bu, performansı düşürdüğü için iyidir (Java'daki atılan istisnalar, yığın izini doldurma nedeniyle maliyetlidir ve C # 'da aynı olacaktır) ve sizi hata üyesinde yeterli bilgi vermeye zorlar OneOf. Bu bilgi yetersiz olabileceğinden ve sorunun kaynağını bulmakta zorlandığınız için de kötüdür.

OneOf ve kontrol edilen istisnaların ortak olarak ortak bir önemli özelliği vardır:

  • her ikisi de sizi arama yığınında her yere koymaya zorlar

Bu korkunuzu önler

C # 'da, istisnaları dikkate almamak derleme hatası vermez ve eğer yakalanmazlarsa sadece kabarcıklanırlar ve programınızı çökertirler.

ama daha önce de söylediğim gibi, bu korku irrasyoneldir. Temel olarak, programınızda genellikle her şeyi yakalamanız gereken tek bir yer vardır (olasılıkla zaten bunu yapan bir çerçeve kullanmanız gerekir). Siz ve ekibiniz program üzerinde çalışarak haftalar veya yıllar geçirdikçe, unutmayacaksınız, değil mi?

İhmal edilebilecek istisnaların en büyük avantajı , yığın izlemesinde her yerde, daha sonra onları işlemek istediğiniz her yerde işlenebilmeleridir. Bu, tonlarca kazanı ortadan kaldırır ("atıyor ...." veya sarma istisnaları bildiren bazı Java kodlarına bakın) ve ayrıca kodunuzu daha az hata yapar: Herhangi bir yöntemin atabileceği gibi, bunun farkında olmanız gerekir. Neyse ki, doğru eylem genellikle hiçbir şey yapmamakta , yani makul bir şekilde idare edilebileceği bir yere kadar kabarmasını sağlamak.


+1 ama neden OneOf yan etkiler ile çalışmıyor? (Geri dönüş değerini atamazsanız bir onay vereceğinden sanırım, ancak OneOf, ne de çok fazla C # bilmiyorum çünkü emin değilim - açıklama yanıtınızı artıracaktır.)
dcorking

1
Döndünüz hata nesnesi yararlı bilgiler protein içerenler yaparak oneof ile hata bilgisi dönebilirsiniz hata ayrıntılarını sağlar. OneOf<SomeSuccess, SomeErrorInfo>SomeErrorInfo
mcintyre321

@dcorking Düzenlendi. Elbette, eğer dönüş değerini görmezden gelirseniz, o zaman ne olduğu hakkında hiçbir şey bilmiyorsunuzdur. Bunu saf bir işlevle yaparsanız, sorun değil (sadece zamanınızı boşa harcıyorsunuz).
maaartinus

2
@ mcintyre321 Tabii bilgi ekleyebilirsin, ancak manuel olarak yapmak zorundasın ve tembellik ve unutmaya tabi. Sanırım, daha karmaşık durumlarda, yığın izi daha yararlı olur.
maaartinus

4

"Olağan" istisnaları (örneğin Dosya Bulunamadı vb.) İşlemek için istisnalar yerine OneOf'u kullanmak makul bir yaklaşım mı yoksa korkunç bir uygulama mı?

Kontrol akışı olarak istisnaların ciddi bir antipattern olarak kabul edildiğini duyduğuma dikkat etmeliyim, yani "istisna" normalde programı sonlandırmadan halledeceğiniz bir şeyse, bu bir şekilde "kontrol akışı" değil mi?

Hataların onlara göre birçok boyutu vardır. Üç boyut belirlerim: engellenebilir mi, ne sıklıkta ortaya çıktıkları ve kurtarılıp kurtarılamayacakları. Bu arada, hata sinyallerinin temel olarak tek bir boyutu vardır: hatayı başa vurmaya ne kadar zorlayacağına veya hatayı bir istisna olarak "sessizce" kabarmaya bırakmaya karar vermek.

Önlenemeyen, sık ve kurtarılabilir hatalar, çağrı sitesinde gerçekten ele alınması gereken sorunlardır. Arayanı onlarla yüzleşmeye zorlayarak haklı çıkarırlar. Java'da onları istisnai durumları kontrol ettiririm ve onlara benzer Try*yöntemler yaparak veya ayrımcılığa uğramış bir birliği geri getirerek benzer bir etki elde ederim . Ayrımcı sendikalar, bir fonksiyonun bir geri dönüş değeri olduğunda özellikle iyidir. Dönenlerin çok daha rafine versiyonları null. try/catchBlokların sözdizimi harika değil (çok fazla parantez), alternatiflerin daha iyi görünmesini sağlar. Ayrıca, istisnalar biraz yavaştır çünkü yığın izlerini kaydederler.

Önlenebilir hatalar (programlayıcı hatası / ihmali nedeniyle önlenemeyen) ve kurtarılamayan hatalar olağan istisnalar gibi gerçekten işe yarar. Nadiren hatalar da sıradan istisnalar olarak daha iyi çalışır, çünkü bir programcı genellikle öngörme çabalarına değmeyebilir (programın amacına bağlıdır).

Önemli bir nokta, bir yöntemin hata modlarının bu boyutlara nasıl uyduğunu belirleyen ve aşağıdakileri göz önünde bulundururken akılda tutulması gereken kullanım alanı olmasıdır.

Tecrübelerime göre, arayanın hata koşullarıyla yüzleşmeye zorlanması, hataların önlenemez, sık ve kurtarılabilir olması durumunda iyidir, ancak arayanlar ihtiyaç duymadıklarında veya istemediklerinde çemberin içinden atlamak zorunda kaldıklarında çok can sıkıcı olur . Bunun nedeni, arayan kişinin hatanın olmayacağını bilmesi veya (henüz) kodu esnek hale getirmek istememesi nedeniyle olabilir. Java'da bu, kontrol edilen istisnaların sık kullanımına ve İsteğe bağlı olarak geri dönmeye yönelik açığı açıklar (bu, Belki'ye benzer ve yakın zamanda pek çok tartışmaya girmiştir). Şüphe duyduğunuzda, istisnalar atayın ve arayan kişinin onları nasıl ele almak istediğine karar vermesine izin verin.

Son olarak, hata koşulları hakkındaki en önemli şeyin, nasıl işaret edildikleri gibi diğer hususların çok ötesinde, onları tam olarak belgelemek olduğunu unutmayın .


2

Diğer cevaplar, istisnalar vs hata kodlarını yeterince ayrıntılı olarak tartıştı, bu yüzden soruya özel başka bir bakış açısı eklemek istiyorum:

OneOf bir hata kodu değildir, bir monad gibidir

Kullanım şekli OneOf<Value, Error1, Error2>ya gerçek sonucu temsil eden ya da bazı hata durumlarını gösteren bir kaptır. Bunun gibi Optional, hiçbir değer olmadığı zaman, bunun neden olduğu hakkında daha fazla ayrıntı sağlayabilir.

Hata kodlarıyla ilgili temel sorun, onları kontrol etmeyi unutmanızdır. Ancak burada, kelimenin tam anlamıyla, hataları denetlemeden sonuca erişemezsiniz.

Tek sorun, sonucu umursamadığın zaman. OneOf'un dokümantasyonundaki örnek şunları verir:

public OneOf<User, InvalidName, NameTaken> CreateUser(string username) { ... }

... ve sonra her sonuç için istisnalar kullanmaktan daha iyi olan farklı HTTP yanıtları yaratırlar. Ama böyle bir yöntemle çağırırsanız.

public void CreateUserButtonClick(String username) {
    UserManager.CreateUser(string username)
}

o zaman do bir sorun var ve istisnalar iyi bir seçim olacaktır. Rail karşılaştırın saveVS save!.


1

Düşünmeniz gereken bir şey, C # kodunun genellikle saf olmadığıdır. Yan etkilerle dolu bir kod tabanında ve bazı fonksiyonların geri dönüş değeriyle ne yaptığınızı umursamayan bir derleyiciyle, yaklaşımınız ciddi bir sıkıntıya neden olabilir. Örneğin, bir dosyayı silen bir yönteminiz varsa, "mutlu yoldaki" herhangi bir hata kodunu kontrol etmek için hiçbir neden olmamasına rağmen, yöntemin başarısız olduğu zaman uygulamanın fark ettiğinden emin olmak istersiniz. Bu, kullanmadığınız dönüş değerlerini açıkça göz ardı etmenizi gerektiren işlevsel dillerden önemli bir farktır. C # 'da, dönüş değerleri her zaman dolaylı olarak göz ardı edilir (özellik istisnası IIRC olan tek istisna).

MSDN'nin "hata kodlarını geri döndürmemeyi" söylemesinin nedeni tam olarak şudur - kimse sizi hata kodunu okumayı bile zorlamaz . Derleyici size hiç yardımcı olmuyor. Bununla birlikte, eğer fonksiyonunuz yan etkiye sahip değilse , güvenli bir şekilde güvenli bir şekilde kullanabilirsiniz Either- nokta, hata sonucunu (varsa) görmezden gelseniz bile, "uygun sonucu" kullanmazsanız bunu yapmanızdır. ya. Orada bunu gerçekleştirmek nasıl birkaç yolu var - örneğin, yalnızca başarı işlemek için bir temsilci geçirerek değerini "okuma" izin verebilir ve hata durumları ve kolayca gibi yaklaşımlar ile bu birleştirebilirsiniz Demiryolu Yönelik Programlama öyle ( sadece C # 'da çalışın.

int.TryParseortada bir yerlerde. Saf. İki sonucun değerlerinin her zaman ne olduğunu tanımlar - dönüş değeri ise false, çıkış parametresinin ayarlanacağını bilirsiniz 0. Yine de, dönüş değerini kontrol etmeden çıkış parametresini kullanmanızı engellemez, ancak en azından işlev başarısız olsa bile sonucun ne olduğunu garanti edersiniz.

Ancak burada kesinlikle çok önemli olan şey tutarlılık . Birisi bu işlevi yan etkiler yapacak şekilde değiştirirse, derleyici sizi kurtarmayacaktır. Veya istisnalar atmak için. Bu yüzden bu yaklaşım C # 'da gayet iyi olmasına rağmen (ve ben bunu kullanıyorum), takımınızdaki herkesin bunu anladığından ve kullandığından emin olmalısınız . Fonksiyonel programlamanın harika çalışması için gereken değişmezlerin çoğu, C # derleyicisi tarafından zorlanmaz ve onların da takip edilmesini sağlamalısınız. Haskell'in varsayılan olarak uyguladığı kurallara uymazsanız, kırılan şeylerin farkında olmalısınız - ve bu, kodunuza girdiğiniz herhangi bir fonksiyonel paradigma için aklınızda bulundurduğunuz bir şeydir.


0

Doğru ifade şu olabilir : İstisnalar için hata kodları kullanmayın! İstisna olmayanlar için istisnalar kullanmayın.

Kodunuzun kurtarılamadığı (yani çalışmasını sağlayan) bir şey olursa, bu bir istisnadır. Acil kodunuzun bir günlüğe kaydetmesi veya kullanıcıya bir şekilde kod vermesi dışında bir hata kodu ile yapacağı hiçbir şey yoktur. Ancak bu tam olarak istisnalar tam da budur - etrafındaki tüm işlerle uğraşır ve gerçekte ne olduğunu analiz etmeye yardımcı olurlar.

Bununla birlikte, otomatik olarak yeniden biçimlendirerek veya kullanıcıdan verileri düzeltmesini isteyerek (ve bu durumu düşündünüz), girebileceğiniz, geçersiz giriş gibi bir şey olursa, bu sizin için bir istisna değildir. bekle. Bu vakaları hata kodlarıyla veya başka bir mimariyle işlemekte özgürsünüz. Sadece istisnalar değil.

(C # konusunda çok tecrübeli olmadığımı unutmayın, ancak cevabım, istisnaların kavramsal seviyesine dayanarak, genel davada yanıtlamalıdır.)


7
Bu cevabın temeline kesinlikle katılmıyorum. Dil tasarımcıları kurtarılabilir hatalar için kullanılmak üzere istisnalar niyet etmediyse, catchanahtar kelime mevcut olmazdı. Ve bir C # bağlamında konuştuğumuz için, burada benimsenen felsefenin .NET çerçevesi tarafından takip edilmediğine dikkat etmek önemlidir; belki de "ele alabileceğiniz geçersiz girdi" nin en basit akla gelebilecek örneği, sayının beklendiği bir alanda sayı olmayan bir kullanıcı yazmasıdır ... ve int.Parsebir istisna atar.
Mark Amery

@MarkAmery int.Parse için ondan kazanabileceği bir şey değil, bekleyeceği bir şey de değil. Catch yan tümcesi, dış görünümden tamamen başarısızlıktan kaçınmak için kullanılır, kullanıcıdan yeni Veritabanı kimlik bilgileri veya benzeri bir şey istemez, programın çökmesini önler. Uygulama kontrol akışı değil teknik bir arıza.
Frank Hopkins,

3
Bir koşul oluştuğunda bir fonksiyonun bir istisna oluşturmasının daha iyi olup olmadığı meselesi, fonksiyonun hemen arayan tarafının bu koşulu yararlı bir şekilde idare edip etmemesine bağlıdır . Olabilecek durumlarda, hemen arayan kişinin yakalaması gereken bir istisna atmak, arayan kodun yazarı için ekstra iş ve onu işleyen makine için ekstra iş anlamına gelir. Bir arayan, bir koşulu yerine getirmeye hazırlanmayacaksa, istisnalar, arayan kişiyi açıkça kodlama ihtiyacını hafifletir.
supercat,

@Darkwing "Bu vakaları hata kodlarıyla veya başka bir mimariyle ele almakta özgürsünüz". Evet, bunu yapmakta özgürsünüz. Ancak, .NET CL'nin kendisi hata kodları yerine istisnalar kullandığından, bunu yapmak için .NET'ten herhangi bir destek alamazsınız. Bu nedenle, yalnızca istisnaların kabarmasına izin vermek yerine .NET istisnalarını yakalamak ve karşılık gelen hata kodunu geri göndermek zorunda kalmazsınız, aynı zamanda ekibinizdeki diğer kişileri de kullanmaları gereken başka bir hata işleme kavramına zorlarsınız. istisnalara paralel olarak standart hata işleme kavramı.
Aleksander

-2

Bu bir 'Korkunç Fikir'.

Bu korkunç, çünkü en azından .net'te, olası istisnalar hakkında ayrıntılı bir listeniz yok. Herhangi bir kod muhtemelen herhangi bir istisna atabilir. Özellikle OOP yapıyorsanız ve belirtilen tür yerine bir alt tür üzerinde geçersiz kılınan yöntemleri çağırabilirseniz

Bu nedenle, tüm yöntemleri ve fonksiyonları değiştirmelisiniz OneOf<Exception, ReturnType>, o zaman farklı istisna türlerini ele almak isteseydiniz, If(exception is FileNotFoundException)vb . Türünü incelemeniz gerekirdi.

try catch eşdeğeri OneOf<Exception, ReturnType>

Düzenle ----

İade etmeyi önerdiğini OneOf<ErrorEnum, ReturnType>biliyorum ama bunun farksız bir şekilde bir ayrım olduğunu düşünüyorum.

İstisna ile dönüş kodlarını, beklenen istisnaları ve dallanmayı birleştiriyorsunuz. Ancak genel etki aynıdır.


2
'Normal' İstisnalar ayrıca korkunç bir fikir. ipucu adında
Ewan

4
Geri dönüş Exceptions teklif etmiyorum .
Clinton,

Birinin alternatifinin istisnalar yoluyla kontrol akışı olduğunu mu teklif ediyorsunuz
Ewan
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.