Tüm modül kullanımını mı yoksa sadece genel yöntem argümanlarını mı doğrulamamız gerekiyor?


9

Genel yöntemlerin argümanlarının doğrulanmasının önerildiğini duydum:

Motivasyon anlaşılabilir. Bir modül yanlış bir şekilde kullanılacaksa, öngörülemeyen davranışlar yerine hemen istisna atmak istiyoruz.

Beni rahatsız eden şey, yanlış argümanların bir modül kullanılırken yapılabilecek tek hata olmamasıdır. Önerileri takip edersek ve hata yükseltme istemiyorsak kontrol mantığı eklememiz gereken bazı hata senaryoları aşağıdadır:

  • Gelen çağrı - beklenmeyen argümanlar
  • Gelen çağrı - modül yanlış durumda
  • Harici arama - beklenmeyen sonuçlar döndürüldü
  • Harici çağrı - beklenmeyen yan etkiler (çağrı modülüne çift giriş, diğer bağımlılık durumlarını kırma)

Tüm bu koşulları hesaba katmaya çalıştım ve tek bir yöntemle basit bir modül yazdım (üzgünüm, değil C # çocuklar):

public sealed class Room
{
    private readonly IDoorFactory _doorFactory;
    private bool _entered;
    private IDoor _door;
    public Room(IDoorFactory doorFactory)
    {
        if (doorFactory == null)
            throw new ArgumentNullException("doorFactory");
        _doorFactory = doorFactory;
    }
    public void Open()
    {
        if (_door != null)
            throw new InvalidOperationException("Room is already opened");
        if (_entered)
            throw new InvalidOperationException("Double entry is not allowed");
        _entered = true;
        _door = _doorFactory.Create();
        if (_door == null)
            throw new IncompatibleDependencyException("doorFactory");
        _door.Open();
        _entered = false;
    }
}

Şimdi güvende =)

Oldukça ürpertici. Ancak düzinelerce yöntem, karmaşık durum ve birçok dış çağrı (gerçek, bağımlılık enjeksiyon sevenler!) İle gerçek bir modülde ne kadar ürkütücü olabileceğini hayal edin. Hangi davranışı geçersiz kılan bir modülü (C # 'da mühürlü olmayan sınıf) çağırıyorsanız, harici bir arama yaptığınızı ve sonuçların arayanın kapsamında tahmin edilemeyeceğini unutmayın.

Özetle, doğru yol nedir ve neden? Aşağıdaki seçeneklerden birini seçerseniz, lütfen ek soruları yanıtlayın.

Tüm modül kullanımını kontrol edin. Birim testlerine ihtiyacımız var mı? Böyle bir kod örneği var mı? Bağımlılık enjeksiyonu kullanımda sınırlandırılmalı mı (daha fazla kontrol mantığına neden olacağından)? Bu kontrolleri hata ayıklama zamanına taşımak pratik değil mi (sürümde yer almayın)?

Yalnızca bağımsız değişkenleri kontrol edin. Deneyimlerime göre, argüman kontrolü - özellikle de null kontrol - en az etkili kontroldür, çünkü argüman hatası nadiren karmaşık hatalara ve hata artışlarına yol açar. Çoğu zaman bir NullReferenceExceptionsonraki satırda bir alacaksınız . Peki neden argüman kontrolleri bu kadar özel?

Modül kullanımını kontrol etmeyin. Oldukça popüler olmayan bir görüş, nedenini açıklayabilir misiniz?


Alan ataması sırasında değişmezlerin tutulduğundan emin olmak için kontroller yapılmalıdır.
Basilevs

@Basilevs İlginç ... Kod Sözleşmeleri ideolojisinden mi yoksa daha eski bir şeyden mi? Okumak için bir şeyler önerebilir misiniz (yorumunuzla ilgili)?
astef

Endişelerin temel bir ayrımı. Kod çoğaltması asgari düzeyde ve sorumluluklar iyi tanımlanmışken, tüm durumlarınız kapsanmaktadır.
Basilevs

@Basilevs Yani, diğer modüllerin davranışlarını hiç kontrol etmeyin, kendi durum değişmezlerini kontrol edin. Kulağa makul geliyor. Ama neden bu basit makbuzu argüman kontrolleri ile ilgili sorularda görmüyorum?
astef

Yine de, bazı berovorial kontrollere ihtiyaç vardır, ancak bunlar başka yerlerde iletilenler üzerinde değil, sadece gerçekten kullanılan değerlerde yapılmalıdır. Örneğin, istemci kodundaki dizini denetlemek yerine OOB hatalarını denetlemek için Liste uygulamasına güvenirsiniz. Genellikle düşük seviyeli çerçeve hatalarıdır ve manuel olarak gönderilmeleri gerekmez.
Basilevs

Yanıtlar:


2

TL; DR: Durum değişikliğini doğrulayın, mevcut durumun [geçerliliğine] güvenin.

Aşağıda yalnızca yayın özellikli doğrulamaları ele alacağım. Yalnızca hata ayıklama etkin iddiaları, kendi yolunda yararlı olan ve bu sorunun kapsamı dışında kalan bir belge türüdür.

Aşağıdaki ilkeleri göz önünde bulundurun:

  • Sağduyu
  • Hızlı başarısız
  • KURU
  • SRP

Tanımlar

  • Bileşen - API sağlayan bir birim
  • İstemci - bileşen API'sının kullanıcısı

Değişebilir durum

Sorun

Zorunlu dillerde, hata belirtisi ve nedeni saatlerce ağır kaldırma ile ayrılabilir. Devlet yolsuzluğu kendini gizleyebilir ve açıklanamaz bir başarısızlıkla sonuçlanacak şekilde değişebilir, çünkü mevcut durumun incelenmesi tam yolsuzluk sürecini ve dolayısıyla hatanın kaynağını ortaya çıkaramaz.

Çözüm

Devletin her değişikliği özenle hazırlanmalı ve doğrulanmalıdır. Değişken durumla başa çıkmanın bir yolu, onu minimumda tutmaktır. Bu şu şekilde sağlanır:

  • tip sistemi (sabit ve son üye beyanları)
  • değişmezleri tanıtmak
  • herkese açık API'lar aracılığıyla bileşenin durumundaki her değişikliği doğrulama

Bir bileşenin durumunu genişletirken, derleyicinin yeni verilerin değişmezliğini zorunlu kılmasına izin vererek bunu düşünün. Ayrıca, olası sonuç durumlarını mümkün olan en küçük iyi tanımlanmış kümeyle sınırlandırarak her makul çalışma zamanı kısıtlamasını uygulayın.

Misal

// Wrong
class Natural {
    private int number;
    public Natural(int number) {
        this.number = number;
    }
    public int getInt() {
      if (number < 1)
          throw new InvalidOperationException();
      return number;
    }
}

// Right
class Natural {
    private readonly int number;
    /**
     * @param number - positive number
     */
    public Natural(int number) {
      // Going to modify state, verification is required
      if (number < 1)
        throw new ArgumentException("Natural number should be  positive: " + number);
      this.number = number;
    }
    public int getInt() {
      // State is guaranteed by construction and compiler
      return number;
    }
}

Tekrarlama ve sorumluluk uyumu

Sorun

Ön koşulların ve işlem sonrası koşulların kontrol edilmesi, hem istemci hem de bileşende doğrulama kodunun çoğaltılmasına yol açar. Bileşen çağrısını doğrulamak genellikle istemciyi bileşenin sorumluluklarından bazılarını almaya zorlar.

Çözüm

Mümkünse durum doğrulaması yapmak için bileşene güvenin. Bileşenler, bileşen durumunun iyi tanımlanmış olmasını sağlamak için özel kullanım doğrulaması gerektirmeyen bir API (örneğin, doğrulama doğrulaması veya işlem sırası zorlaması) sağlayacaktır. API çağırma bağımsız değişkenlerini gerektiği gibi doğrulamak, başarısızlıkları gerektiği şekilde bildirmek ve durumlarının bozulmasını önlemek için çaba gösterirler.

İstemciler API'larının kullanımını doğrulamak için bileşenlere güvenmelidir. Sadece tekrardan kaçınmakla kalmaz, istemci artık bileşenin ek uygulama ayrıntılarına da bağlı değildir. Bir bileşen olarak çerçeveyi düşünün. Özel doğrulama kodunu yalnızca bileşen değişmezleri yeterince katı olmadığında veya bileşen istisnasını uygulama ayrıntısı olarak kapsüllediğinde yazın.

Bir işlem durumu değiştirmezse ve durum değişikliği doğrulamalarının kapsamında değilse, her bağımsız değişkeni mümkün olan en derin düzeyde doğrulayın.

Misal

class Store {
  private readonly List<int> slots = new List<int>();
  public void putToSlot(int slot, int data) {
    if (slot < 0 || slot >= slots.Count) // Unnecessary, validated by List, only needed for custom error message
      throw new ArgumentException("data");
    slots[slot] = data;
  }
}

class Natural {
   int _number;
   public Natural(int number) {
       if (number < 1)
          number = 1;  //Wrong: client can't rely on argument verification, additional state uncertainity is introduced.  Right: throw new ArgumentException(number);
       _number = number;
   }
}

Cevap

Bahsedilen örneğe tarif edilen prensipler uygulandığında:

public sealed class Room
{
    private bool _entered = false;
    // Do not use lazy instantiation if not absolutely necessary, this introduces additional mutable state
    private readonly IDoor _door;
    public Room(IDoorFactory doorFactory)
    {
        // Rely on system null check
        IDoor door = _doorFactory.Create();
        // Modifying own state, verification is required
        if (door == null)
           throw new ArgumentNullException("Door");
        _door = door;
    }
    public void Enter()
    {
        // Room invariants do not guarantee _entered value. Door state is indirectly a part of our state. Verification is required to prevent second door state change below.
        if (_entered)
           throw new InvalidOperationException("Double entry is not allowed");
        _entered = true;     
        // rely on immutability for _door field to be non-null
        // rely on door implementation to control resulting door state       
        _door.Open();            
    }
}

özet

Müşterinin durumu, kendi alan değerlerinden ve bileşen durumunun kendi değişmezleri tarafından kapsanmayan kısımlarından oluşur. Doğrulama, yalnızca müşterinin gerçek durum değişikliğinden önce yapılmalıdır.


1

Bir sınıf kendi durumundan sorumludur. Bu nedenle, işleri kabul edilebilir bir duruma getirdiği veya koyduğu ölçüde doğrulayın.

Bir modül yanlış bir şekilde kullanılacaksa, öngörülemeyen davranışlar yerine hemen istisna atmak istiyoruz.

Hayır, istisna atmayın, bunun yerine öngörülebilir davranışlar sağlayın. Devlet sorumluluğunun sonucu, sınıfı / uygulamayı mümkün olduğunca hoşgörülü kılmaktır. Örneğin, geçen nulletmek aCollection.Add()? Sadece eklemeyin ve devam edin. nullNesne oluşturmak için girdi alıyor musunuz? Boş bir nesne veya varsayılan bir nesne oluşturun. Yukarıda, doorzaten open? Ne olmuş yani, devam et. DoorFactoryargüman null? Yeni bir tane oluşturun. Bir yarattığımda enumhep bir Undefinedüyem var. Ben Dictionarys'nin liberal kullanımı ve enumsşeyleri açıkça tanımlamak; ve bu öngörülebilir davranışlar sunma yolunda uzun bir yol kat ediyor.

(merhaba, bağımlılık enjeksiyon sevenler!)

Evet, parametre vadisinin gölgesinden geçmeme rağmen hiçbir argümandan korkmayacağım. Öncekine ayrıca olabildiğince varsayılan ve isteğe bağlı parametreleri de kullanıyorum.

Yukarıdakilerin tümü dahili işlemenin devam etmesini sağlar. Belirli bir uygulamada, bir istisnanın atıldığı tek bir yerde birden fazla sınıfta onlarca yöntem var. O zaman bile, null argümanları yüzünden değil ya da kodun "işlevsel olmayan" / "null" bir nesne yaratmasıyla sonuçlandığı için işlemeye devam edemedim.

Düzenle

Yorumumu bütünüyle aktarıyorum. Bence tasarım 'null' ile karşılaştığında “pes etmemeli”. Özellikle bileşik bir nesne kullanmak.

Burada kilit kavramları / varsayımları unutuyoruz - encapsulation& single responsibility. İstemci ile etkileşen ilk katmandan sonra neredeyse hiç boş denetim yoktur. Kod toleranslı sağlamdır. Sınıflar varsayılan durumlarla tasarlanmıştır ve bu nedenle etkileşimde bulunan kodlar hatalıdır, hileli önemsiz gibi yazılmadan çalışır. Kompozit bir ebeveynin geçerliliği değerlendirmek için alt katmanlara ulaşması gerekmez (ve dolayısı ile tüm köşelerde ve çatlaklarda null olup olmadığını kontrol edin). Ebeveyn, çocuğun varsayılan durumunun ne anlama geldiğini bilir

düzenlemeyi bitir


1
Geçersiz bir toplama öğesi eklememek çok öngörülemez bir davranıştır.
Basilevs

1
Tüm arayüzler böyle bir toleranslı bir şekilde tasarlanacaksa, bir gün, banal hatası nedeniyle, programlar yanlışlıkla uyanır ve insanlığı yok eder.
astef

Burada kilit kavramları / varsayımları unutuyoruz - encapsulation& single responsibility. nullİlk, istemci ile etkileşen katmandan sonra neredeyse hiçbir denetim yoktur . Kod <strike> toleranslı </strike> sağlamdır. Sınıflar varsayılan durumlarla tasarlanmıştır ve bu nedenle etkileşimde bulunan kodlar hatalıdır, hileli önemsiz gibi yazılmadan çalışır. Kompozit bir ebeveynin geçerliliği değerlendirmek için alt katmanlara ulaşması gerekmez (ve nulldolayısı ile tüm köşe ve çatlaklarda kontrol edin ). Ebeveyn, bir çocuğun varsayılan durumunun ne anlama geldiğini bilir
radarbob
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.