İstisna at veya kodun başarısız olmasına izin ver


52

Bu stile karşı herhangi bir artıları ve eksileri olup olmadığını merak ediyorum:

private void LoadMaterial(string name)
{
    if (_Materials.ContainsKey(name))
    {
        throw new ArgumentException("The material named " + name + " has already been loaded.");
    }

    _Materials.Add(
        name,
        Resources.Load(string.Format("Materials/{0}", name)) as Material
    );
}

Bu yöntem, her biri için nameyalnızca bir kez çalıştırılmalıdır. _Materials.Add()Aynı şey için birden çok kez çağrılacaksa bir istisna atar name. Sonuç olarak, korumam tamamen gereksiz mi, yoksa daha az belirgin yararlar var mı?

Bu C #, Unity, eğer ilgilenen varsa.


3
Bir malzemeyi koruma olmadan iki kez yüklerseniz ne olur? _Materials.AddBir istisna atmak mı ?
user253751 23

2
Aslında çoktan bir istisna fırlattığından bahsettim: P
async

9
Yan notlar: 1) string.Formatİstisna mesajını oluşturmak için fazla dizge birleştirmeyi kullanmayı düşünün . 2) sadece asoyuncu kadrosunun başarısız olmasını bekler ve sonucu kontrol ederseniz kullanın null. Her zaman bir almayı bekliyorsanız Material, bir alçı kullanın (Material)Resources.Load(...). Açık bir döküm istisnası, daha sonra gerçekleşecek olan boş bir referans istisnasından daha kolay hata ayıklama için daha kolaydır.
KodlarInChaos

3
Bu özel durumda, arayanlar ayrıca bir yardımcı LoadMaterialIfNotLoadedveya ReloadMaterial(bu adı iyileştirme kullanabilir) yöntemini faydalı bulabilir.
jpmc26

1
Başka bir notta: Bu desen bazen listeye bir öğe eklemek için uygundur. Bununla birlikte, bu listeyi tüm listeyi doldurmak için kullanmayın, çünkü büyük listelerde performans sorunlarıyla karşılaşacağınız O (n ^ 2) durumuna gidersiniz. Bunu 100 girişte farketmeyeceksiniz ama 1000 girişte kesinlikle ve 10.000 girişte büyük bir tıkanıklık olacak.
Pieter B

Yanıtlar:


109

Bunun yararı, "özel" istisnalarınızın, nasıl uygulandığını (gelecekte hangisi olabilirsiniz!) Bilmeden bu işlevi çağıran herkes için anlamlı bir hata iletisine sahip olmasıdır .

Kabul edersek, bu durumda muhtemelen "standart" istisnanın ne anlama geldiğini tahmin edebileceklerdi, ancak yine de kodunuzdaki garip bir hataya rastlamak yerine sözleşmenizi ihlal ettiklerini açıkça belirtiyorsunuz.


3
Anlaşmak. Bir önkoşul ihlali için bir istisna oluşturmak neredeyse her zaman daha iyidir.
Frank Hileman,

22
"Bunun yararı," özel "istisnanızın, nasıl uygulandığını (gelecekte sizin olabilirsiniz!) Bu işlevi çağıran herkes için anlamlı bir hata iletisine sahip olmasıdır." En iyi tavsiye
asenkron

2
"Açık, örtük olmaktan iyidir." - Python'dan Zen
jpmc26

4
_Materials.AddArayan kişiye iletmek istediğiniz hata olmasa bile, hatayı yeniden etiketleme yönteminin yetersiz olduğunu düşünüyorum. Her başarılı çağrı LoadMaterial(normal işlem) için aynı sağlık testi iki kez , LoadMaterialtekrar ve tekrar olarak yapılır _Materials.Add. Her biri aynı stili kullanan daha fazla katman sarılırsa, aynı testten çok daha fazla kez yapabilirsiniz.
Marc van Leeuwen

15
... Bunun yerine _Materials.Addkoşulsuz bir şekilde dalmayı düşünün , daha sonra potansiyel bir hatayı yakalayın ve işleyicide farklı bir hata atın. Ekstra iş şimdi sadece hatalı durumlarda, gerçekten verimlilik konusunda hiç endişe etmediğiniz istisnai uygulama yolu olarak yapılır.
Marc van Leeuwen,

100

Ixrec'in cevabına katılıyorum . Bununla birlikte, üçüncü bir alternatifi düşünmek isteyebilirsiniz: İşlevin önemsiz hale getirilmesi. Başka bir deyişle, atmak yerine erken dönün ArgumentException. Aksi halde, LoadMaterialher zaman aramadan önce daha önce yüklenmiş olup olmadığını kontrol etmek zorunda kalırsanız, bu genellikle tercih edilir . Ne kadar az önkoşul varsa, programlayıcı üzerindeki bilişsel yük o kadar az olur.

Bir istisna atılması, eğer gerçekten bir programlayıcı hatasıysa, malzeme önceden yüklendiyse derleme zamanında açık ve bilinmesi gereken, aramadan önce çalışma zamanında kontrol etmek zorunda kalmayacaksa, tercih edilir.


2
Öte yandan, bir işlevin sessizce başarısız olması beklenmedik davranışlara neden olabilir ve daha sonra ortaya çıkan hataları bulmak zorlaşır.
Philipp

30
@Philipp işlevi sessizce başarısız olmaz. Başarılı olmak, bir çok kez çalıştırmanın, bir kez çalıştırmayla aynı etkiye sahip olduğu anlamına gelir. Başka bir deyişle, eğer malzeme zaten yüklendiyse, şartnamemiz ("malzeme yüklenmeli") çoktan tatmin olmuş demektir, dolayısıyla hiçbir şey yapmamız gerekmez. Sadece geri dönebiliriz.
Warbo

8
Aslında bunu daha çok seviyorum. Elbette, başvuru şartlarının verdiği kısıtlamalara bağlı olarak, bu yanlış olabilir. Sonra tekrar, idempotent yöntemlerin mutlak hayranıyım.
FP

6
@Philipp, bunun hakkında LoadMaterialşu kısıtlamaları olduğunu düşünürsek : " Aradıktan sonra malzeme her zaman yüklenir ve yüklenen malzeme sayısı azalmaz". Üçüncü bir kısıtlama için bir istisna atma "Malzeme iki kez eklenemez" aptalca ve benim için sezgisel görünüyor. İşlevin idempotent yapılması, kodun yürütme sırasına ve uygulama durumuna bağlılığını azaltır. Benim için çifte kazanç gibi görünüyor.
Benjamin Gruenbaum

7
Bu fikri sevdim, ancak küçük bir değişiklik önerin: bir şeyin yüklenip yüklenmediğini yansıtan bir boolean döndürün. Arayan, uygulama mantığını gerçekten önemserse, bunu kontrol edebilir ve bir istisna atabilir.
user949300

8

Sormanız gereken temel soru şudur: İşleviniz için arayüzün ne olmasını istiyorsunuz? Özellikle, _Materials.ContainsKey(name)koşul, işlevin bir önkoşulu mu?

Eğer öyleyse değil önkoşulu, işlev için iyi tanımlanmış bir sonuç vermelidir tüm olası vales name. Bu durumda, bir nameparçası değilse atılan istisna _Materials, fonksiyonun arayüzünün bir parçası olur. Bu, arayüz dokümantasyonunun bir parçası olması gerektiği anlamına gelir ve ileride bu istisnayı değiştirmeye karar verirseniz, bu bir ara yüz değişimi olacaktır .

Daha ilginç olan soru, eğer bir önkoşul ise ne olur. Bu durumda önkoşulun kendisi fonksiyon arayüzünün bir parçası olur, ancak bu koşul ihlal edildiğinde bir fonksiyonun nasıl davrandığı mutlaka arayüzün bir parçası değildir .

Bu durumda, gönderdiğiniz yaklaşım, önkoşul ihlallerini kontrol etmek ve hatayı rapor etmek, savunma programlaması olarak bilinir . Defansif programlama, bir hata yaptığında ve işlevini sahte bir argümanla çağırdığında kullanıcıyı erkenden bilgilendirdiği için iyidir. Kullanıcı kodu, işlevin ön koşul ihlallerini belirli bir şekilde ele almasına bağlı olabileceğinden, bakım yükünü önemli ölçüde arttırması kötüdür. Özellikle, çalışma zamanı kontrolünün gelecekte hiç bir performans darboğazına dönüşmediğini tespit ederseniz (bu kadar basit bir durum için muhtemel değildir, ancak daha karmaşık ön koşullar için oldukça yaygındır), artık kaldıramayabilirsiniz.

Bu dezavantajların çok önemli olabileceği ortaya çıktı, bu da savunma programlamasına bazı çevrelerde kötü bir ün kazandı. Ancak, başlangıçtaki hedef hala geçerlidir: İşlevimizin kullanıcılarının bir hata yaptığında erken fark etmelerini istiyoruz.

Bu nedenle günümüzde birçok geliştirici bu tür sorunlar için biraz farklı bir yaklaşım önermektedir. Bir istisna atmak yerine, önkoşulları kontrol etmek için onay benzeri bir mekanizma kullanırlar. Diğer bir deyişle, ön koşullar kullanıcıların hatalarını erken yakalamalarına yardımcı olmak için hata ayıklama yapıları içinde kontrol edilebilir, ancak işlevler arayüzünün bir parçası değildir. Fark ilk bakışta ince görünebilir, ancak pratikte büyük bir fark yaratabilir.

Teknik olarak, ihlal edilmiş ön koşullara sahip bir işlevi çağırmak tanımsız davranıştır. Ancak uygulama, bu vakaları tespit etmeye ve bu durumda derhal kullanıcıyı bilgilendirmeye karar verebilir. İstisnalar ne yazık ki, kullanıcı kodu bunlara tepki gösterebileceği ve dolayısıyla varlıklarına güvenmeye başlayabileceğinden, bunu uygulamak için iyi bir araç değildir.

Klasik savunma yaklaşımı ile ilgili sorunların ayrıntılı bir şekilde açıklanmasının yanı sıra , eşik tarzı önkoşul kontrolleri için olası bir uygulama için, John Lakos'un CppCon 2014'ten Sağlanan Doğru Savunma Programları konuşmasına bakın ( Slaytlar , Video ).


4

Burada zaten birkaç cevap var, fakat işte Unity3D'yi hesaba katan cevap (işte Unity3D'ye çok özel bir cevap, çoğu durumda bunun çok farklı bir şekilde yapacağım):

Genel olarak, Unity3D geleneksel olarak istisnalar kullanmaz. Unity3D'de bir istisna atarsanız, ortalama .NET uygulamanızdaki gibi bir şey yapmaz, yani programı durdurmaz, çoğu editörü duraklatmak için yapılandırabilirsiniz. Bu sadece giriş yapacak. Bu, oyunu kolayca geçersiz bir duruma getirebilir ve hataları izlemeyi zorlaştıran kademeli bir etki yaratabilir. Bu yüzden, Birlik durumunda, Addbir istisna atmaya izin vermenin özellikle istenmeyen bir seçenek olduğunu söyleyebilirim .

Ancak istisnaların hızını incelemek, Mono'nun bazı platformlarda Unity'de nasıl çalıştığından dolayı bazı durumlarda erken bir optimizasyon durumu değildir. Aslında, iOS üzerindeki Unity3D bazı gelişmiş komut dosyası optimizasyonlarını destekler ve devre dışı istisnalar * bunlardan birinin yan etkisidir. Bu dikkate alınması gereken bir şey çünkü bu optimizasyonlar Unity3D'deki istisnaların kullanımını sınırlandırmayı düşünmek için gerçekçi bir durum gösteren birçok kullanıcı için çok değerli olduğunu kanıtladı. (* kodunuzdan değil, motordan gelen istisnalar hariç)

Birlik'te daha uzman bir yaklaşım benimsemek isteyebileceğinizi söyleyebilirim. İronik olarak, bu yazma sırasında çok aşağı olarak cevap , ben tek yön gösterir olabilir böyle bir şey uygulamak özellikle Unity3D bağlamında (böyle başka bir yerde bir şey gerçekten kabul edilemez olduğunu ve hatta Unity oldukça inelegant var).

Düşündüğüm bir başka yaklaşım aslında arayan ile ilgili olarak bir hatayı göstermek değil, Debug.LogXXişlevleri kullanmak . Bu şekilde, işlenmemiş bir istisna (aynı Unity3D'nin onları nasıl idare ettiğinden dolayı) bir şeyleri garip bir duruma sokma riski olmadan atma ile aynı davranışı elde edersiniz. Ayrıca bunun gerçekten bir hata olup olmadığını göz önünde bulundurun (Aynı materyali iki kez mutlaka yüklemeniz mi gerekiyor? Durumunuzda mı? Yoksa bu Debug.LogWarningdaha uygulanabilir bir durum olabilir ).

Debug.LogXXİstisnalar yerine işlevler gibi şeylerin kullanılmasıyla ilgili olarak, bir değere neden olan bir şeyden (GetMaterial gibi) bir istisna atıldığında ne olacağını düşünmelisiniz. Bu hatayı günlüğe kaydetmeyle birlikte null ile birlikte geçme eğilimindeyim ( yine, yalnızca Birlik'te ). Daha sonra malzeme gibi herhangi bir bağımlılığın boş değer olmadığından emin olmak için MonoBehaviors'de boş kontroller kullanıyorum ve eğer öyleyse MonoBehavior'ı devre dışı bırakıyorum. Birkaç bağımlılık gerektiren basit bir davranış örneği, şuna benzer:

    public void Awake()
    {
        _inputParameters = GetComponent<VehicleInputParameters>();
        _rigidbody = GetComponent<Rigidbody>();
        _rigidbodyTransform = _rigidbody.transform;
        _raycastStrategySelector = GetComponent<RaycastStrategySelectionBehavior>();

        _trackParameters =
            SceneManager.InstanceOf.CurrentSceneData.GetValue<TrackParameters>();

        this.DisableIfNull(() => _rigidbody);
        this.DisableIfNull(() => _raycastStrategySelector);
        this.DisableIfNull(() => _inputParameters);
        this.DisableIfNull(() => _trackParameters);
    }

SceneData.GetValue<>Örneğinize benzer şekilde bir istisna atan bir sözlükteki bir işlevi çağırır. Ancak, bir istisna atmak yerine, Debug.LogErrornormal bir istisna gibi bir yığın izlemesi veren kullanır ve boş döndürür. Takip eden * kontrolleri geçersiz bir durumda varlığını sürdürmesine izin vermek yerine davranışı devre dışı bırakacaktır.

* Çekler, oyun nesnesini devre dışı bıraktığında biçimlendirilmiş bir mesaj basan kullandığım küçük bir yardımcı yüzünden öyle görünüyor. ifBurada çalışılan basit boş kontroller (** yardımcının çekleri yalnızca Hata Ayıklama yapıları içinde derlenir (iddialar gibi).


1
Yığına gerçekten yeni bilgiler eklemek için +1. Bunu beklemiyordum.
Ixrec

3

İki ana cevabı beğendim ancak fonksiyon isimlerinizin iyileştirilebileceğini önermek istiyorum. Java'ya alışkınım, bu nedenle YMMV, ancak "add" yöntemi, öğe zaten varsa IMO'ya bir İstisna atmamalı. Öğeyi tekrar eklemeli veya hedef bir Set ise, hiçbir şey yapmamalı . Materials.Add davranışı olmadığı için, TryPut veya AddOnce veya AddOrThrow veya benzeri bir ad verilmelidir.

Aynı şekilde, LoadMaterial'iniz, # 1 veya # 2 cevabını kullanmanıza bağlı olarak (örneğin # 1 veya # 2'yi kullanmanıza bağlı olarak) LoadIfAbsent veya Put veya TryLoad veya LoadOrThrow olarak yeniden adlandırılmalıdır.

Ne olursa olsun, C # Unity adlandırma kurallarını izleyin.

Bu, özellikle aynı şeyi iki kez yüklemenize izin verilen diğer AddFoo ve LoadBar işlevleriniz varsa yararlı olacaktır. Net isimler olmadan geliştiriciler sinirli olacak.


C # Dictionary.Add () anlambiliminde (bkz. Msdn.microsoft.com/en-us/library/k7z0zy8k%28v=vs.110%29.aspx ) ve Java Collection.add () semantiği arasında bir fark vardır . oracle.com/javase/8/docs/api/java/util/… ). C # boşluğu döndürür ve istisna atar. Oysa Java, bool değerini döndürür ve önceden saklanan değeri yeni değerle değiştirir veya yeni öğe eklenemediğinde bir istisna atar.
Kasper van den Berg,

Kasper - teşekkürler ve ilginç. C # sözlüğündeki bir değeri nasıl değiştirirsiniz? (Bir Java'ya eşdeğer put ()). Bu sayfada yerleşik bir yöntem görmeyin.
user949300,


@ user949300 dictionary [key] = değer zaten varsa girişi değiştirir
Rupe

@ Rupi ve eğer değilse bir şey atar. Hepsi bana çok garip geliyor. Bir giriş wheteher dicti kurma Çoğu hasta umurumda değil idi orada onlar sadece kendi kurulum kodundan sonra, bu umurunda, bir orada. Java Koleksiyonlar API'sı (IMHO) için bir puan verin. PHP de dikte ile garip, bu emsal takip etmek C # fot bir hataydı.
user949300,

3

Tüm cevaplar değerli fikirler ekliyor, bunları birleştirmek istiyorum:

LoadMaterial()Operasyonun amaçlanan ve beklenen anlamına karar verin . En azından aşağıdaki seçenekler var:

  • nameYüklü Malzemeler üzerine bir önkoşul : →

    Önkoşulu ihlal edildiğinde, etkisi LoadMaterial()(olduğu gibi belirtilmemiş olan cevap ile ComicSansMS ). Bu uygulama ve gelecekteki değişikliklerin çoğunda özgürlüğe izin verir LoadMaterial(). Veya,

  • çağrı etkileri LoadMaterial(name)ile nameLoadedMaterials belirtildi; ya:

Anlambilim üzerine karar verirken bir uygulama seçmelisin. Aşağıdaki seçenekler ve önerildiği yerler:

  • (Gibi özel bir istisna önerilen tarafından Ixrec ) →

    Bunun yararı, "özel" istisnalarınızın bu işlevi çağıran herkes için anlamlı bir hata iletisine sahip olmasıdır - Ixrec ve User16547

    • Tekrar tekrar kontrol nameetmenin maliyetini önlemek için ∉ Yüklenmiş Malzemeler Marc van Leeuwen'in tavsiyelerini takip edebilirsiniz :

      ... Bunun yerine, _Malzemelere dalmayı düşünün. Koşulsuzca ekleyin, daha sonra olası bir hatayı yakalayın ve işleyicide farklı bir tane atın.

  • Let Dictionay.Add → istisna

    İstisna atma kodu gereksizdir. - Jon Raynor

    Yine de çoğu seçmen Ixrec ile daha fazla fikir birliğine varıyor

    Bu uygulamayı seçmek için ek sebepler şunlardır:

    • arayanlar zaten ArgumentExceptionistisnaları kaldırabilir ve
    • yığın bilgisini kaybetmemek için.

    Ancak bu iki neden önemliyse, özel istisnalarınızı türetebilir ArgumentExceptionve orijinali zincirleme istisna olarak kullanabilirsiniz.

  • Make LoadMaterial()olarak İdempotent anwser tarafından Karl Bielefeldt ve en sık (75 kez) upvoted.

    Bu davranış için uygulama seçenekleri:

    • Dictionary.ContainsKey () ile kontrol edin

    • Her zaman diyoruz Dictionary.Add () yakalamak ArgumentExceptionanahtarının varolduğunu zaten eklemek ve bu durum göz ardı etmek ne zaman atar. İstisnaları görmezden gelmenin ne yapmak istediğinizi ve neden olduğunu belgeleyin.

      • Her LoadMaterials()biri için (neredeyse) her zaman bir kez çağrıldığında name, bu, nameLoadedMaterials cf. Marc van Leeuwen . Ancak,
      • LoadedMaterials()çoğu zaman aynı şey için defalarca çağrıldığında name, bu, ArgumentExceptionistifin çözülmesinin ve atılmasının (pahalı) maliyetini doğurur .
    • TryGet () ' a bir TryAdd-metod analogu olduğunu düşündüm, bu da pahalı istisnaları atmamaktan ve Sözlük'te başarısız çağrıların çözülmesinden istifade etmekten kaçınmanıza izin vereceğini düşündüm .

      Ancak bu TryAdd-metod var görünmüyor.


1
En kapsamlı cevap olduğu için +1. Bu muhtemelen OP'nin başlangıçta neyin peşinde olduğunun çok ötesine geçiyor, ancak tüm seçeneklerin faydalı bir özeti.
Ixrec

1

İstisna atma kodu gereksizdir.

Eğer ararsan:

Malzeme olarak _Materials.Add (name, Resources.Load (string.Format ("Materials / {0}", name))

aynı anahtarla bir

System.ArgumentException

Mesaj şöyle olacaktır: "Aynı anahtara sahip bir öğe zaten eklenmiş."

ContainsKeyTemelde aynı davranış onsuz elde edildiğinden onay gereksiz olduğunu. Farklı olan tek madde istisnadaki gerçek mesajdır. Özel bir hata iletisine sahip olmanın gerçek yararı varsa, o zaman güvenlik kodunun bazı yararları vardır.

Aksi takdirde, muhtemelen bu durumda gardiyanı yeniden yönlendireceğim.


0

Bunun API tasarımıyla ilgili bir kodlama kuralı sorusundan daha fazla bir soru olduğunu düşünüyorum.

Aramanın beklenen sonucu nedir (sözleşme):

LoadMaterial("wood");

Arayan, bu yöntemi çağırdıktan sonra, "tahta" malzemesinin yüklendiğini bekleyebilir / garanti altına girerse, o zaman malzeme yüklendiğinde bir istisna atmak için hiçbir neden göremiyorum.

Malzemeyi yüklerken bir hata meydana gelirse, örneğin, veritabanı bağlantısı açılamadı veya depoda malzeme "tahta" bulunmuyorsa, bu sorunu arayan kişiye bildirmek için bir istisna atmak doğru olur.


-2

Neden metodu değiştirmiyorsunuz, böylece 'doğru' olarak geri dönüyor, eğer gerekli ise eklenmiş ve 'yanlış' ise?

private bool LoadMaterial(string name)
{
   if (_Materials.ContainsKey(name))

    {

        return false; //already present
    }

    _Materials.Add(
        name,
        Resources.Load(string.Format("Materials/{0}", name)) as Material
    );

    return true;

}

15
Hata değerleri döndüren işlevler, tam olarak istisnaların eskimiş olması beklenen anti-kalıptır.
Philipp

Durum böyle mi? Bunun sadece yarı yarıya olduğu bir durum olduğunu düşündüm ( en.wikipedia.org/wiki/Semipredicate_problem ) Çünkü OP kodu örneğinde, sisteme bir malzeme eklememekte sorun yok. Bu yöntem, onu çalıştırdığınızda malzemenin sistemde olduğundan emin olmak için kullanılır ve bu tam olarak bu yöntemin yaptığı şeydir. (başarısız olsa bile) Dönen boolean, sadece yöntemin bu maddeyi eklemeye çalışıp çalışmadığını gösterir. ps: Aynı isimde birden fazla madde olabileceğini dikkate almıyorum.
Bay

1
Sanırım çözümü kendim buldum: en.wikipedia.org/wiki/… Bu, Ixrec'in cevabının en doğru olduğuna işaret ediyor
Bay

@Philipp Kodlayıcı / teknik özellik , iki kez Yüklemeyi denediğinizde hiçbir hata olmadığına karar verdiğinde . Dönüş değeri, bu anlamda, bir hata değeri değil bilgi niteliğindedir.
user949300
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.