Yapıcı parametre doğrulama C # - En iyi yöntemler


34

Yapıcı parametre doğrulaması için en iyi yöntem nedir?

Diyelim ki basit bir C # biti:

public class MyClass
{
    public MyClass(string text)
    {
        if (String.IsNullOrEmpty(text))
            throw new ArgumentException("Text cannot be empty");

        // continue with normal construction
    }
}

Bir istisna atmak kabul edilebilir mi?

Karşılaştığım alternatif, başlatılmadan önce ön doğrulama yapıldı:

public class CallingClass
{
    public MyClass MakeMyClass(string text)
    {
        if (String.IsNullOrEmpty(text))
        {
            MessageBox.Show("Text cannot be empty");
            return null;
        }
        else
        {
            return new MyClass(text);
        }
    }
}

10
"istisna atmak kabul edilebilir mi?" Alternatif nedir?
S.Lott

10
@ S.Lott: Hiçbir şey yapmayın. Varsayılan bir değer ayarlayın. Konsol / günlük dosyasına bir mesasge yazdırın. Bir çan çalmak. Bir ışığı yakıp söndürün.
SinirliFormsDesigner ile

3
@FrustratedWithFormsDesigner: "Hiçbir şey yapmadın mı?" "Metin boş değil" kısıtlaması nasıl sağlanacak? "Varsayılan bir değer belirle"? Metin argümanı değeri nasıl kullanılacak? Diğer fikirler gayet iyi, ancak bu ikisi bir devlette bu sınıftaki kısıtlamaları ihlal eden bir nesneye yol açacak.
S.Lott

1
@ S.Lott Kendimi tekrarlamaktan korktuğum için, bilmediğim başka bir yaklaşım olma ihtimaline açıktım. Eminim ki hepsini bilmiyorum, o kadarını biliyorum.
MPelletier

3
@ MPelletier, zamanın önündeki parametreleri doğrulamak DRY'yi ihlal etmeye başlayacaktır. (Kendiniz Tekrar Etmeyin) Bu ön onaylamayı bu sınıfı başlatmaya çalışan herhangi bir koda kopyalamanız gerektiğinden.
CaffGeek

Yanıtlar:


26

Bütün onaylarımı kurucuda yerine getirme eğilimindeyim. Bu bir zorunluluktur çünkü neredeyse daima değişken nesneler yaratıyorum. Özel durumunuz için bunun kabul edilebilir olduğunu düşünüyorum.

if (string.IsNullOrEmpty(text))
    throw new ArgumentException("message", "text");

.NET 4 kullanıyorsanız, bunu yapabilirsiniz. Elbette bu, geçersiz olan sadece beyaz boşluk içeren bir dize olarak düşünmenize bağlıdır.

if (string.IsNullOrWhiteSpace(text))
    throw new ArgumentException("message", "text");

Onun "özel davasının" böyle bir tavsiye verecek kadar kesin olduğundan emin değilim. Bu bağlamda bir istisna atmanın veya nesnenin yine de var olmasına izin vermenin mantıklı olup olmadığını bilmiyoruz .
SinirliFormsDesigner'la

@FrustratedWithFormsDesigner - Bunun için bana oy verdiğini farz ediyorum? Elbette haklı çıkarım konusunda haklısın ama açık bir şekilde, eğer giriş metni boşsa bu teorik nesnenin geçersiz olması gerekir. InitBir istisna tam olarak işe yarayacak ve nesneyi daima geçerli bir durumu sürdürmeye zorlayacağı zaman bir tür hata kodu almak için çağrı yapmak ister misiniz ?
ChaosPandion

2
Bu ilk davayı iki ayrı istisnaya bölme eğilimindeyim: Biri boşluğu ArgumentNullExceptiontest eden ve birini boşaltan bir test yapan ve boşaltan bir ArgumentException. İstisna idaresinde isterlerse arayanın daha seçici olmasını sağlar.
Jesse C. Slicer

1
@Jesse - Bu tekniği kullandım ama hala genel kullanımının yararı için çit üzerindeyim.
ChaosPandion

3
Değişmez nesneler için +1! Onları mümkün olan her yerde kullanıyorum (şahsen neredeyse her zaman buluyorum).

22

Birçok insan, inşaatçıların istisnalar atmaması gerektiğini belirtiyor. Bu sayfadaki KyleG, örneğin, sadece bunu yapıyor. Açıkçası, neden olmasın bir sebep düşünemiyorum.

C ++ 'da, bir kurucudan bir istisna atmak kötü bir fikirdir, çünkü sizi referans almadığınız başlatılmamış bir nesne içeren ayrılmış bir bellekle bırakır (yani, klasik bir bellek sızıntısı). Muhtemelen stigmanın geldiği yer burasıdır - bir grup eski okul C ++ geliştiricisi, C # öğrenmelerini yarı yarıya arttırdı ve C ++ 'dan bildiklerini uyguladı. O dilde kurucular böylece tersine, Objective-C Apple, başlatma adımdaki tahsisi adımını ayrılmış olabilir istisna atar.

C # başarısız bir çağrıdan bir yapıcıya bellek sızdırmaz. .NET çerçevesindeki bazı sınıflar bile yapıcılarında istisnalar ortaya koyacaktır.


Bazı tüyleri seninle karıştırmaya başlayacaksın C ++ yorumunda. :)
ChaosPandion

5
Ehh, C ++ harika bir dil ama evcil hayvanlarımdan biri bir dili iyi öğrenen ve her zaman böyle yazan insanlar . C #, C ++ değil.
Karınca

10
C # 'daki bir tüccarın istisnalarını atmak hala tehlikeli olabilir çünkü ctor , o zaman asla imha edilmeyecek olan yönetilmeyen kaynakları tahsis etmiş olabilir . Sonlandırıcının, tamamen inşa edilmemiş bir nesnenin sonlandırıldığı senaryoya karşı savunması için yazılması gerekir. Ancak bu senaryolar nispeten nadirdir. C # Dev Center ile ilgili yeni bir makale bu senaryoları ve bunların etrafını nasıl savunacak şekilde kodlayacağını ele alacak, bu yüzden ayrıntılar için o alanı izleyin.
Eric Lippert

6
ha? ctor gelen istisna atan nesneyi oluşturmak ulaşamaması durumunda ++ c yapabileceği tek şey olduğunu ve bu hiçbir neden yoktur vardır bir bellek sızıntısına neden (gerçi belli ki onun kadar).
jk.

@jk: Hmm, haklısın! Bir alternatif gibi görünmekle birlikte, kötü nesneleri STL'nin istisnalar atmak yerine görünüşte yaptığı gibi "zombiler" olarak işaretlemektir: yosefk.com/c++fqa/exceptions.html#fqa-17.2 Amaç-C'nin çözümü çok daha düzenli.
Karınca

12

Bir istisna atmak IFF, sınıfın anlamsal kullanımı bakımından tutarlı bir duruma getirilemez. Aksi takdirde yapmayın. Bir nesnenin tutarsız bir durumda olmasına ASLA izin vermeyin. Bu, tam kurucular sağlamayı da içermez (nesneniz aslında tamamen oluşturulmadan önce boş bir kurucu + initialize () olması gibi) ... JUST SAY NO!

Bir tutam, herkes yapar. Geçen gün çok dar kullanılan bir nesne için sıkı bir kapsamda yaptım. Yolun aşağısındaki bir gün, ben veya başkası pratikte bu fişin bedelini ödeyecek.

"Oluşturucu" ile müşterinin nesneyi oluşturmak için aradığı şeyi kastediyorum. Bu, "Oluşturucu" adıyla geçen gerçek bir yapıdan başka bir şey olabilir. Örneğin, C ++ 'da böyle bir şey IMNSHO ilkesini ihlal etmeyecektir:

struct funky_object
{
  ...
private:
  funky_object();
  bool initialize(std::string);

  friend boost::optional<funky_object> build_funky(std::string);
};
boost::optional<funky_object> build_funky(std::string str)
{
  funky_object fo;
  if (fo.initialize(str)) return fo;
  return boost::optional<funky_object>();
}

A'yı yaratmanın tek yolu , gerçek bir “Yapıcı” işi bitirmese bile, geçersiz bir nesnenin var olmasına asla izin vermemek ilkesini funky_objectçağırmaktır build_funky.

Şüpheli kazanç için olsa bile çok fazladan iş var (belki de zarar). Yine de istisna yolunu tercih ederim.


9

Bu durumda fabrika yöntemini kullanırdım. Temel olarak, sınıfınızın yalnızca özel yapıcıları ve nesnenizin bir örneğini döndüren bir fabrika yöntemine sahip olmasını ayarlayın. İlk parametreler geçersizse, null değerini döndürün ve arama kodunun ne yapacağınıza karar vermesini isteyin.

public class MyClass
{
    private MyClass(string text)
    {
        //normal construction
    }

    public static MyClass MakeMyClass(string text)
    {
        if (String.IsNullOrEmpty(text))
            return null;
        else
            return new MyClass(text);
    }
}
public class CallingClass
{
    public MyClass MakeMyClass(string text)
    {
        var cls = MyClass.MakeMyClass(text);
        if(cls == null)
             //show messagebox or throw exception
        return cls;
    }
}

Koşullar istisnai durumlar dışında asla istisnalar atmayın. Bu durumda boş bir değerin kolayca geçilebileceğini düşünüyorum. Bu durumda, bu kalıbı kullanmak istisnalardan ve Sınıf sınıfını geçerli tutarken ortaya çıkan performans cezalarından kaçınır.


1
Avantaj göremiyorum. Neden bir fabrikanın sonucunu, kurucu istisnayı yakalayabilecek bir yerde bir deneme-denemesi yapmak için tercih edilen bir test sonucudur? Yaklaşımınız "istisnaları dikkate almayın, hataların geri dönüş değerini açıkça kontrol etmek için geri dönün" gibi görünüyor. Uzun zaman önce, istisnaların genellikle her yöntemin hata değerini açık olarak test etmek zorunda kalmanın üstün olduğunu kabul ettik, bu nedenle tavsiyeniz şüpheli görünüyor. Genel kuralın burada geçerli olmadığını düşündüğünüz için bir gerekçe görmek istiyorum.
DW

istisnaların geri dönüş kodlarından daha üstün olduğunu asla kabul etmedik, çok sık atılırlarsa sert bir performans olabilir. Ancak, bir kurucudan bir istisna atmak, yönetilmeyen bir şey tahsis ettiyseniz, .NET'te bile bellek sızdırıyor. Bu davayı telafi etmek için sonlandırıcınızda çok fazla çalışma yapmanız gerekir. Her neyse, buradaki fabrika iyi bir fikir ve TBH boş değer vermek yerine istisna atmalı. Yalnızca birkaç 'kötü' sınıf oluşturduğunuzu varsayarsak, fabrika çıkışını yapmak tercih edilir.
gbjbaanb

2
  • Bir yapıcının herhangi bir yan etkisi olmamalıdır.
    • Özel alan başlatma işleminden daha fazlası, yan etki olarak görülmelidir.
    • Yan etkileri olan bir kurucu, Tek Sorumluluk İlkesini (SRP) ihlal eder ve nesne yönelimli programlama (OOP) ruhuna aykırı hareket eder.
  • Bir yapıcı hafif olmalı ve asla başarısız olmamalıdır.
    • Örneğin, bir yapıcı içinde bir try-catch bloğu gördüğümde her zaman titriyorum. Bir yapıcı istisnalar veya günlük hataları atmamalıdır.

Bir kişi bu yönergeleri makul bir şekilde sorgulayabilir ve “Ama bu kuralları izlemiyorum ve kodum iyi çalışıyor!” Diyebilir. Buna göre, "Olmazsaya dek, bu doğru olabilir" cevabını veririm.

  • Bir yapıcının içindeki istisnalar ve hatalar çok beklenmeyen bir durumdur. Bunu yapmaları söylenmedikçe, gelecekteki programcıların bu yapıcı çağrılarını savunma koduyla çevrelemeye meyilli olmayacaklardır.
  • Üretimde herhangi bir şey başarısız olursa, oluşturulan yığın izinin ayrıştırılması zor olabilir. Yığın izlemesinin tepesi yapıcı çağrısına işaret edebilir, ancak yapıcıda birçok şey olur ve başarısız olan gerçek LOC'ye işaret etmeyebilir.
    • Bunun olduğu birçok .NET yığını izini ayrıştırdım.

0

Sınıfımın ne yaptığına bağlı. MyClass aslında bir veri havuzu sınıfıysa ve parametre metni bir bağlantı dizesiyse, en iyi uygulama bir ArgumentException oluşturmaktır. Ancak, eğer MyClass StringBuilder sınıfı ise (örneğin), boş bırakabilirsiniz.

Bu nedenle, parametre metninin yönteme ne kadar temel olduğuna bağlıdır - nesne boş veya boş bir değere sahip mi?


2
Bence soru, sınıfın aslında dizginin boş olmamasını gerektirdiğini ima ediyor. StringBuilder örneğinizde durum böyle değil.
Sergio Acosta

O zaman cevap evet olmalı - bir istisna atmak kabul edilebilir. Bu hatayı denemek / yakalamak zorunda kalmaktan kaçınmak için, sizin için nesne oluşturmayı işlemek için boş bir dize durumu içeren bir fabrika deseni kullanabilirsiniz.
Steven Striga

0

Tercihim varsayılan bir ayar yapmaktır, ancak Java’nın böyle bir şey yapan "Apache Commons" kütüphanesine sahip olduğunu biliyorum ve bence bu da oldukça iyi bir fikir. Geçersiz değer nesneyi kullanılamaz duruma getirirse, bir istisna atma sorunu görmüyorum; bir dize iyi bir örnek değil, peki ya zavallı adamın DI'siydi? nullBir ICustomerRepositoryarabirim yerine bir değeri geçtiyse işlem yapamazsınız . Böyle bir durumda, bir istisna atmak, olayları ele almanın doğru yolu olduğunu söyleyebilirim.


-1

Eskiden c ++ 'da çalıştığımda, kurucuda ortaya çıkarabileceği bellek sızıntısı probleminden dolayı istisna atamadım, zor yoldan öğrendim. C ++ ile çalışan herkes hafıza sızıntısının ne kadar zor ve sorunlu olduğunu bilir.

Fakat eğer c # / Java kullanıyorsanız, o zaman bu probleminiz yoktur, çünkü çöp toplayıcı belleği toplayacaktır. C # kullanıyorsanız, veri sınırlamalarının garanti altına alındığından emin olmak için mülkün güzel ve tutarlı bir şekilde kullanılması tercih edilir.

public class MyClass
{
    private string _text;
    public string Text 
    {
        get
        {
            return _text;
        } 
        set
        {
            if (string.IsNullOrWhiteSpace(value))
                throw new ArgumentException("Text cannot be empty");
            _text = value;
        } 
    }

    public MyClass(string text)
    {
        Text = text;
        // continue with normal construction
    }
}

-3

Bir istisna atmak, sınıfınızdaki kullanıcılar için gerçek bir acı olacaktır. Doğrulama bir sorun ise, genellikle nesneyi oluştururken 2 adımlı bir işlem yaparım.

1 - Örnek oluştur

2 - Geri dönüş sonucu olan bir Initialize yöntemini çağırın.

VEYA

Doğrulamayı yapabilen sizin için sınıfı oluşturan bir yöntem / işlev çağırın.

VEYA

Özellikle sevmediğim ama bazılarının sevdiği isal bayrağı var.


3
@Dunk, bu sadece yanlış.
CaffGeek

4
@Chad, bu senin fikrin, ama benim fikrim yanlış değil. Sadece sizinkinden farklı. Try-catch kodunu okumayı çok daha zor buluyorum ve insanlar önemsiz hata yönetimi için geleneksel yöntemlerden daha fazla denediklerinde yaratılan hataların çok daha fazla olduğunu gördüm. Bu benim deneyimim oldu ve yanlış değil. Neyse ne.
Dunk

5
NOKTA, bir kurucu çağırdıktan sonra, bir giriş geçersiz olduğu için yaratmazsa, bu bir istisnadır. Sadece nesneyi oluşturup bir bayrak diyerek ayarlarsam, uygulamanın verileri artık tutarsız / geçersiz bir durumda isValid = false. Eğer bir sınıf oluşturulduktan sonra test etmek zorunluluğu geçerliyse, korkunç, hataya açık bir tasarım. Ve, sen bir kurucu var dedin, sonra çağrı başlat ... Ya çağrı başlatmazsam? Sonra ne? Peki ya Initialize hatalı veri alırsa, şimdi bir istisna atabilir miyim? Sınıfınızın kullanımı zor olmamalıdır.
CaffGeek

5
@Dunk, istisnaları zor kullanırsanız, yanlış kullanırsınız. Basitçe söylemek gerekirse, bir kurucuya hatalı girdi sağlandı. Bu bir istisna. Uygulama, düzeltilmedikçe çalışmaya devam etmemelidir. Dönemi. Olursa, daha kötü bir problemle sonuçlanır, kötü veriler. İstisna ile nasıl başa çıkılacağını bilmiyorsanız, konuşmaya gerek kalmadan, çağrı yığınını işlenene kadar kabarmasına izin vermiş olursunuz, bu basit bir şekilde oturum açmak ve yürütmeyi durdurmak demektir. Basitçe söylemek gerekirse, istisnaları ele almanıza veya test etmenize gerek yoktur. Onlar sadece çalışırlar.
CaffGeek

3
Üzgünüz, bu dikkate alınması gereken çok fazla bir anti-kalıptır.
MPelletier
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.