Amaçlanan davranışı yapmadan önce işlevlerin boş denetimler yapması gerekiyorsa, bu kötü tasarım mı?


67

Bu yüzden, bunun iyi mi yoksa kötü kod tasarımı mı olduğunu bilmiyorum, bu yüzden sormam daha iyi olur.

Sık sık, sınıfları içeren veri işleme yöntemlerini oluşturuyorum ve elimden önce boş referanslar veya başka hatalar alamadığımdan emin olmak için çoğu zaman yöntemler üzerinde birçok kontrol yapıyorum.

Çok basit bir örnek için:

// fields and properties
private Entity _someEntity;
public Entity SomeEntity => _someEntity;

public void AssignEntity(Entity entity){
    _someEntity = entity;
}
public void SetName(string name)
{
    if (_someEntity == null) return; //check to avoid null ref
    _someEntity.Name = name;
    label.SetText(_someEntity.Name);
}

Gördüğünüz gibi her seferinde null kontrol ediyorum. Fakat yöntem bu çeke sahip olmamalı mı?

Örneğin, harici kod verileri elden önce temizlemeli, böylece yöntemlerin aşağıdaki gibi doğrulaması gerekmez:

 if(entity != null) // this makes the null checks redundant in the methods
 {
     Manager.AssignEntity(entity);
     Manager.SetName("Test");
 }

Özetle, yöntemler "verilerin doğrulanması" olmalı ve daha sonra veriler üzerinde işlem yapmalı mı, yoksa yöntemi çağırmadan önce güvence altına alınmalı mı ve yöntemi çağırmadan önce doğrulamakta başarısız olursanız bir hata atması gerekir (veya hata)?


7
Birisi boş denetimi yapamadığında ve SetName yine de bir istisna atarsa ​​ne olur?
Robert Harvey

5
Aslında bir Varlığın Null olmasını istersem ne olur ? Ne istediğine karar verirsin, ya someEntity Null olabilir, ya da olamaz, ve sonra kodunu buna göre yaz. Asla Null olmamasını istiyorsanız, çekleri yinelemelerle değiştirebilirsiniz.
gnasher729

5
Gary Bernhardt'ın Sınırlarının , dış kabuktaki verilerin temizliği (örneğin, sıfırların kontrolü vb.) Ve dış kabukta temiz bir bölme yapmak ve bu şeyleri umursamayacak bir iç çekirdek sistemine sahip olmakla ilgili konuşmasını izleyebilirsiniz . iş.
mgarciaisaia

1
C # 8'de, doğru ayarların açık olmasıyla
JᴀʏMᴇᴇ

3
Bu C # dili ekibi null referans tiplerini (ve olmayan null olanlar) tanıtmak istiyor nedenlerinden biridir -> github.com/dotnet/csharplang/issues/36
Mafii

Yanıtlar:


168

Temel örneğinizle ilgili sorun boş çek değil , sessiz hata .

Boş gösterici / referans hataları, çoğu zaman değil, programcı hatalarıdır. Programcı hataları genellikle derhal ve yüksek sesle başarısızlıkla çözülür. Bu durumda sorunla başa çıkmanın üç genel yolu vardır:

  1. Denetimi zahmet etmeyin ve çalışma zamanının boş nokta istisnasını atmasına izin verin.
  2. Temel NPE mesajından daha iyi bir mesajla bir istisna olup olmadığını kontrol edin ve atın.

Üçüncü bir çözüm daha fazla iş ama çok daha sağlam:

  1. Sınıfınızı _someEntity, geçersiz bir durumda olmanın neredeyse imkansız olduğu şekilde yapılandırın . Bunu yapmanın bir yolu kurtulmak AssignEntityve bunu bir örnekleme parametresi olarak gerektirmektir. Diğer Bağımlılık Enjeksiyon teknikleri de faydalı olabilir.

Bazı durumlarda, yaptığınız çalışmadan önce tüm fonksiyon argümanlarının geçerliliği olup olmadığını kontrol etmek mantıklı olur ve diğerlerinde, arayanlara girdilerinin geçerli olduğundan ve kontrol etmemesinden sorumlu olduklarını söylemek mantıklı olur. Bulunduğunuz spektrumun hangi ucu sorun alanınıza bağlı olacak. Üçüncü seçeneğin, bir dereceye kadar derleyicinin arayanın her şeyi düzgün bir şekilde yapmasını sağlaması için önemli bir faydası vardır.

Üçüncü seçenek bir seçenek değilse, o zaman bence sessizce başarısız olmadığı sürece önemli değil. Boşluğu denetlememek programın anında patlamasına neden olursa, sorun olmaz, ancak bunun yerine verileri yolda sorunlara neden olmak için sessizce bozarsa, o zaman ve orada kontrol etmek ve onunla ilgilenmek en iyisidir.


10
@aroth: Herhangi bir tasarım, yazılım dahil, herhangi bir kısıtlama ile gelecek. Tasarım eylemi, bu kısıtlamaların nereye gittiğini seçmek ve benim sorumluluğum değil, ne olursa olsun herhangi bir girdiyi kabul etmem ve onunla yararlı bir şeyler yapmam bekleneceği gibi olmamalı. nullYanlış mı yoksa geçersiz mi olduğunu biliyorum , çünkü varsayımsal kütüphanemde veri tiplerini tanımladım ve neyin geçerli olup neyin olmadığını tanımladım. Sen buna "zorlama varsayımları" diyorsun ama tahmin et ne: var olan her yazılım kütüphanesinin yaptığı şey bu. Null öğesinin geçerli bir değer olduğu zamanlar vardır, ancak bu "her zaman" değildir.
whatsisname

4
"O kodunuzu o işlenmemesi nedeniyle bozuldu nulldüzgün" - Bu doğru kullanımı ne anlama geldiğini bir yanlış anlama. Çoğu Java programcılar için, için bir istisna atma anlamına herhangi geçersiz giriş. Kullanma @ParametersAreNonnullByDefault, nullargüman olarak işaretlenmediği sürece, herkes için de geçerlidir @Nullable. Başka bir şey yapmak, yolu başka bir yere üfleyene kadar uzatmak ve böylece hata ayıklama için harcanan zamanı uzatmak anlamına gelir . Sorunları gözardı ederek, asla patlamayacağını başarabilirsiniz, ancak yanlış davranış daha sonra garanti edilir.
maaartinus

4
@ aroth Sevgili Lord, yazdığınız herhangi bir kodla çalışmaktan nefret ediyorum. Patlamalar, saçma sapan bir şey yapmaktan kesinlikle daha iyidir, bu geçersiz giriş için tek seçenek. İstisnalar, beni arayanı zorlar, yöntem, neyi hedeflediğimi tahmin edemediğinde, bu durumlarda yapılacak doğru şeyin ne olduğuna karar verir. Kontrol edilen istisnalar ve gereksiz yere genişletme Exception, tamamen alakasız tartışmalardır. (Her ikisinin de sorunlu olduğuna katılıyorum.) Bu özel örnek için, nullsınıfın amacı nesne üzerinde yöntemler çağırmaksa, burada hiçbir anlamı yoktur.
jpmc26

3
"kodunuz, arayanın dünyasına varsayımları zorlamamalıdır" - Varsayımları arayana zorlamamak imkansızdır. Bu nedenle bu varsayımları mümkün olduğu kadar açık hale getirmemiz gerekiyor.
Diogo Kollross

3
@aroth sizin onun geçen çünkü kod bozuldu benim bir özellik olarak adı olan bir şey geçirerek gerekirken kodu bir null. Patlamadan başka herhangi bir davranış, dinamik tipte IMHO'nun bir tür arka kapı tuhaf dünya versiyonudur.
mbrig

56

Yana _someEntityherhangi bir aşamasında değiştirilebilir, o zaman bunu her zaman test etmek mantıklı SetNamedenir. Sonuçta, bu yöntemin son çağrılmasından bu yana değişmiş olabilir. Ancak kodun SetNameiş parçacığı için güvenli olmadığına dikkat edin, bu yüzden bir çekimi başarabilir, başka bir kişiye _someEntityayarlamış olabilirsiniz nullve sonra NullReferenceExceptionyine de kod başlayacaktır .

Bu nedenle bu soruna bir yaklaşım daha da fazla savunma almak ve aşağıdakilerden birini yapmaktır:

  1. yerel bir kopyasını alın _someEntityve bu kopyayı bu yöntemle referans olarak alın,
  2. _someEntityyöntem yürütülürken değişmemesini sağlamak için bir kilit kullanın ,
  3. a kullanın try/catchve aynı boşluğu NullReferenceExceptionilk boş kontrolde yaptığınız gibi (bu durumda bir nopeylem) , yöntem içinde gerçekleşen herhangi bir işlem için uygulayın.

Fakat gerçekte yapmanız gereken şey, durup kendinize şu soruyu sormak: aslında _someEntityüzerine yazmak için izin vermeniz gerekiyor mu? Yapıcı aracılığıyla neden bir kez ayarlanmadı? Bu şekilde, yalnızca bir kez kontrol etmek için null değerine ihtiyacınız olacak:

private readonly Entity _someEntity;

public Constructor(Entity someEntity)
{
    if (someEntity == null) throw new ArgumentNullException(nameof(someEntity));
    _someEntity = someEntity;
}

public void SetName(string name)
{
    _someEntity.Name = name;
    label.SetText(_someEntity.Name);
}

Mümkünse, bu gibi durumlarda almanızı önerdiğim yaklaşım budur. Olası boş değerlerin nasıl kullanılacağını seçerken iplik emniyetine dikkat edemiyorsanız.

Bonus yorum olarak, kodunuzun garip olduğunu ve geliştirilebileceğini düşünüyorum. Hepsi:

private Entity _someEntity;
public Entity SomeEntity => _someEntity;

public void AssignEntity(Entity entity){
    _someEntity = entity;
}

ile değiştirilebilir:

public Entity SomeEntity { get; set; }

Aynı işleve sahip olan, farklı bir ada sahip bir yöntem yerine, alanı alan belirleyici ile ayarlayabilmek için tasarruf edin.


5
Bu neden soruyu cevaplıyor? Soru null kontrolü ele alıyor ve bu hemen hemen bir kod incelemesi.
IEatBagels

17
@TopinFrassi mevcut boş kontrol yaklaşımının iş parçacığının güvenli olmadığını açıklar. Daha sonra, bunun üstesinden gelmek için diğer savunma programlama tekniklerine değiniyor. Daha sonra bu savunma programlamasını ilk etapta yapmaktan kaçınmak için değişmezliği kullanma önerisi ile tamamlanır. Ek bir avantaj olarak, kodun daha iyi nasıl yapılandırılacağı konusunda da tavsiyelerde bulunur.
David Arno,

23

Fakat yöntem bu çeke sahip olmamalı mı?

Bu senin seçimin.

Bir publicyöntem oluşturarak , halka bunu söyleme fırsatı veriyorsunuz. Bu, her zaman bu yöntemin nasıl çağrılacağı ve bunu yaparken ne bekleyeceğiniz konusunda gizli bir sözleşmeyle birlikte gelir . Bu sözleşme, " evet, uhhm, eğer nullbir parametre değeri olarak geçerseniz, tam karşınıza çıkacak " ifadesini içerebilir (veya içermeyebilir) . Bu sözleşmenin diğer kısımları " ah, BTW: iki iplik bu yöntemi aynı anda uyguladığında , bir yavru kedi ölür " olabilir.

Ne tür bir sözleşme yapmak istediğiniz size kalmış. Önemli şeyler

  • kullanışlı ve tutarlı bir API oluşturun ve

  • Özellikle bu kenar davaları için iyi bir şekilde belgelendirmek için.

Yöntemin nasıl adlandırıldığı muhtemel durumlar nelerdir?


İş parçacığı güvenliği, eşzamanlı erişim olduğunda kural değil istisnadır. Bu nedenle, gizli / küresel durumun kullanımı belgelenmiştir, ayrıca her durumda eşzamanlılık yine de güvenlidir.
Deduplicator

14

Diğer cevaplar iyidir; Erişilebilirlik düzenleyicileri hakkında bazı yorumlar yaparak bunları genişletmek istiyorum. İlk olarak, eğer nullhiçbir zaman geçerli değilse ne yapmalı :

  • Genel ve korunan yöntemler, arayanı kontrol etmediğiniz yöntemlerdir. Onlar gerektiğini atmak boş geçerken. Bu şekilde arayanları size asla boş vermemeleri için eğitiyorsunuz çünkü programlarının çarpışmamasını istiyorlar.

  • İç ve özel yöntemler nerede olanlardır yok arayan kontrol eder. Boş geçtiklerinde iddiada bulunmalılar ; Daha sonra muhtemelen daha sonra çarpışacaklar, ama en azından ilk önce iddiayı ve hata ayıklayıcısını kırmak için bir fırsat buldunuz. Yine, sizi arayanları incitmeksizin inciterek sizi doğru şekilde aramaları için eğitmek istiyorsunuz.

O Şimdi, eğer ne yaparsınız belki boş geçilmesi için geçerli? Bu durumu null nesne modeliyle önlerdim . Yani: türün özel bir örneğini yapın ve geçersiz bir nesnenin gerekli olduğu her zaman kullanılmasını gerektirir . Şimdi null'un her kullanımında atma / iddia etme dünyasına geri döndünüz.

Örneğin, bu durumda olmak istemezsiniz:

class Person { ... }
...
class ExpenseReport { 
  public void SubmitReport(Person approver) { 
    if (approver == null) ... do something ...

ve çağrı sitesinde:

// I guess this works but I don't know what it means
expenses.SubmitReport(null); 

Çünkü (1) arayanları boş değerin iyi bir değer olduğu konusunda eğitiyorsunuz ve (2) bu değerin anlamının ne olduğu belirsiz. Aksine, bunu yapın:

class ExpenseReport { 
  public static readonly Person ApproverNotRequired = new Person();
  public static readonly Person ApproverUnknown = new Person();
  ...
  public void SubmitReport(Person approver) { 
    if (approver == null) throw ...
    if (approver == ApproverNotRequired) ... do something ...
    if (approver == ApproverUnknown) ... do something ...

Ve şimdi çağrı sitesi şöyle görünür:

expenses.SubmitReport(ExpenseReport.ApproverNotRequired);

ve şimdi kodun okuyucusu ne anlama geldiğini biliyor.


Daha da iyisi, ifboş nesneler için s zincirine sahip değil, doğru davranmalarını sağlamak için polimorfizm kullanın.
Konrad Rudolph

@KonradRudolph: Gerçekten, bu genellikle iyi bir tekniktir. Bunu veri yapıları için kullanmayı seviyorum, ki burada EmptyQueuene zaman boşaltılır, ne zaman fırlatıp atıyorsun.
Eric Lippert,

@EricLippert - Beni burada affet çünkü hala burada öğreniyorum, ama Personsınıfın değişmez olduğunu ve sıfır argüman kurucusu içermediğini varsayalım , kendinizi ApproverNotRequiredveya ApproverUnknownörneklerinizi nasıl kurarsınız ? Başka bir deyişle, hangi değerleri yapıcıya iletirsiniz?

2
SE'deki cevaplarınıza / yorumlarınıza girmeyi seviyorum, her zaman anlayışlılar. İçsel / özel yöntemler için
iddiada bulunduğumda

4
Siz benim verdiğim belirli örneği fazla düşünüyorsunuz. Boş nesne deseni fikrini hızlı bir şekilde elde etmek için düşünülmüştü. Kağıt ve maaş çekleri otomasyon sisteminde iyi bir tasarım örneği olması amaçlanmamıştır. Gerçek bir sistemde, "onaylayıcı" nın "kişi" tipinde olması, iş süreci ile uyuşmadığından muhtemelen kötü bir fikirdir; onaylayan olmayabilir, birkaç taneye ihtiyacınız olabilir, vb. Bu alandaki soruları yanıtlarken sıkça dikkat ettiğim gibi, gerçekten istediğimiz bir politika nesnesidir. Örneğe takılma.
Eric Lippert

8

Diğer cevaplar, kodunuzun, bulunduğunuz yerde boş bir kontrole ihtiyaç duymayacağı için temizlenebileceğini, ancak yararlı bir boş denetlemenin aşağıdaki örnek kodu göz önünde bulundurmak için ne olabileceğine dair genel bir cevap için:

public static class Foo {
    public static void Frob(Bar a, Bar b) {
        if (a == null) throw new ArgumentNullException(nameof(a));
        if (b == null) throw new ArgumentNullException(nameof(b));

        a.Something = b.Something;
    }
}

Bu, bakım için size bir avantaj sağlar. Kodunuzda bir şeyin boş bir nesneyi yönteme geçirdiği bir hata ortaya çıkarsa, hangi parametrenin boş olduğu yönteminden faydalı bilgiler alırsınız. Çekler olmadan, hatta NullReferenceException elde edersiniz:

a.Something = b.Something;

Ve (Something özelliğinin bir değer türü olduğu göz önüne alındığında) a veya b boş olabilir ve hangisinin yığın izinden bildiğini bilemezsiniz. Çeklerle, hangi nesnenin boş olduğunu tam olarak bileceksiniz, bu da bir kusuru gidermeye çalışırken çok yararlı olabilir.

Orijinal kodunuzdaki kalıbı not edeceğim:

if (x == null) return;

Belirli durumlarda kullanımlarına sahip olabilir, ayrıca (muhtemelen daha sık) kod tabanınızdaki temel sorunları maskelemenin çok iyi bir yolunu sağlayabilir.


4

Hayır, hiç de değil, ama buna bağlı.

Tepki vermek istiyorsanız sadece boş bir referans kontrolü yapın.

Eğer varsa boş referans kontrol yapmak ve bir kontrol istisna , üzerinde tepki ve ona iyileşmesi için verilen yöntemin zorlar. Program hala beklendiği gibi çalışıyor.

Eğer varsa boş referans kontrol yapmayın (ya da kontrolsüz istisna) bir kontrolsüz atmak olabilir NullReferenceExceptiongenellikle yöntemin kullanıcı tarafından ele almayan ve hatta uygulamayı çalışamaz duruma gelebilir. Bu, işlevin tamamen yanlış anlaşıldığı ve bu nedenle uygulamanın hatalı olduğu anlamına gelir.


4

@ Null'ın cevabına bir uzantısı olan bir şey, ancak deneyimlerime göre, ne olursa olsun boş denetimler yapın.

Savunmasız programlama, mevcut rutini izolasyonda kodladığınız, programın geri kalanının tamamen çökertmeye çalıştığı varsayımına dayanan tutumdur. Başarısızlığın bir seçenek olmadığı kritik görev kodunu yazarken, bu hemen hemen gider.

Şimdi, aslında bir nulldeğerle nasıl başa çıkacağınız , kullanım durumunuza büyük ölçüde bağlı.

Eğer nullMSVC rutin _splitpath (beşinci parametre üzerinden ikinci bir kabul edilebilir bir değerdir, örneğin), daha sonra sessizce ile ilgili doğru bir davranıştır.

Eğer nullAPI sözleşme gerektirir OP'ın durumunda yani söz konusu rutin, çağrılırken hiç olmamalı AssignEntity()geçerli olan denilen edilmiş Entityo zaman sürecinde çok fazla gürültü yapmak sağlanması başarısız aksi bir istisna ya.

Hiçbir zaman boş bir çekin maliyeti hakkında endişelenmeyin, eğer olurlarsa ucuzdurlar ve modern derleyicilerde, derleyici gerçekleşmeyeceğini belirlediğinde statik kod analizi bunları tamamen ortadan kaldırabilir ve seçebilir.


Ya da tamamen önleyin (48 dakika 29 saniye: "Programınızın yakınında hiçbir zaman boş bir işaretçi kullanmayın veya kullanılmasına izin vermeyin" ).
Peter Mortensen
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.