Arayandaki giriş parametresinin doğrulanması: kod çoğaltma?


16

Fonksiyonun giriş parametrelerini doğrulamak için en iyi yer neresidir: arayanda veya fonksiyonun kendisinde?

Kodlama tarzımı geliştirmek istediğim için, bu konuda en iyi uygulamaları veya bazı kuralları bulmaya çalışıyorum. Ne zaman ve daha iyi.

Önceki projelerimde, işlevin içindeki her giriş parametresini kontrol edip tedavi ederdik (örneğin, boş değilse). Şimdi, bazı cevaplarda ve Pragmatik Programcı kitabında, giriş parametresinin onaylanmasının arayanın sorumluluğu olduğunu okudum.

Bu, işlevi çağırmadan önce giriş parametrelerini doğrulamam gerektiği anlamına gelir. Her yerde fonksiyon denir. Ve bu bir soruyu gündeme getiriyor: işlevin çağrıldığı her yerde kontrol koşulunun bir kopyasını oluşturmuyor mu?

Sadece null koşullarla ilgilenmiyorum, ancak herhangi bir giriş değişkeninin validasyonu ( sqrtişleve negatif değer , sıfıra bölme, durum ve posta kodunun yanlış kombinasyonu veya başka bir şey)

Giriş koşulunun nerede kontrol edileceğine karar vermek için bazı kurallar var mı?

Bazı argümanlar düşünüyorum:

  • geçersiz değişkenin sqrt()davranışı değişebilirse, onu arayan tarafında doğrulamak iyidir (örneğin işlev - bazı durumlarda karmaşık sayı ile çalışmak isteyebilirim, bu yüzden arayandaki durumu tedavi ederim)
  • kontrol koşulu her arayanda aynı olduğunda, yinelemeleri önlemek için fonksiyonun içinde kontrol etmek daha iyidir
  • arayandaki giriş parametresinin doğrulanması, bu parametreyle birçok fonksiyon çağrılmadan önce sadece bir tane gerçekleşir. Bu nedenle, her işlevdeki bir parametrenin doğrulanması etkili değildir
  • doğru çözüm özel duruma bağlıdır

Umarım bu soru başkalarının kopyası değildir, bu sorunu araştırdım ve benzer sorular buldum ama tam olarak bu davadan bahsetmiyorlar.

Yanıtlar:


15

Değişir. Doğrulamanın nereye konulacağına karar verilmesi , yöntem tarafından ima edilen (veya belgelenen) sözleşmenin açıklamasına ve gücüne dayanmalıdır . Doğrulama, belirli bir sözleşmeye bağlılığı artırmak için iyi bir yoldur. Herhangi bir nedenle yöntemin çok katı bir sözleşmesi varsa, evet, aramadan önce kontrol etmek size bağlıdır.

Genel bir yöntem oluşturduğunuzda bu özellikle önemli bir kavramdır , çünkü temel olarak bazı yöntemlerin bir işlem yaptığını bildiriyorsunuz. Söylediklerini yapsan iyi olur!

Aşağıdaki yöntemi örnek olarak alalım:

public void DeletePerson(Person p)
{            
    _database.Delete(p);
}

Sözleşme ne ima ediyor DeletePerson? Programcı yalnızca herhangi Personbiri geçerse silineceğini varsayabilir . Ancak bunun her zaman doğru olmadığını biliyoruz. Ya pbir nulldeğer? pVeritabanında yoksa ne olur ? Veritabanının bağlantısı kesilirse ne olur? Bu nedenle, DeletePerson sözleşmesini iyi yerine getirmiş gibi görünmüyor. Bazen bir kişiyi siler ve bazen bir NullReferenceException veya DatabaseNotConnectedException oluşturur veya bazen hiçbir şey yapmaz (kişi zaten silinmiş gibi).

Bunun gibi API'lerin kullanımı son derece zordur, çünkü bir yöntemin bu "kara kutusu" olarak adlandırdığınızda, her türlü korkunç şey olabilir.

Sözleşmeyi geliştirmenin birkaç yolu:

  • Doğrulama ekleyin ve sözleşmeye bir istisna ekleyin. Bu, sözleşmeyi daha güçlü hale getirir , ancak arayanın doğrulama yapmasını gerektirir. Ancak fark, şimdi onların gereksinimlerini biliyor olmalarıdır. Bu durumda, bunu bir C # XML yorumu ile iletiyorum, ancak bunun yerine bir throws(Java) ekleyebilir Assert, bir veya Kod Sözleşmeleri gibi bir sözleşme aracı kullanabilirsiniz.

    ///<exception>ArgumentNullException</exception>
    ///<exception>ArgumentException</exception>
    public void DeletePerson(Person p)
    {            
        if(p == null)
            throw new ArgumentNullException("p");
        if(!_database.Contains(p))
            throw new ArgumentException("The Person specified is not in the database.");
    
        _database.Delete(p);
    }
    

    Yan not: Bu stile karşı argüman genellikle tüm arama kodları tarafından aşırı ön doğrulamaya neden olmasıdır, ancak benim durumumda bu genellikle böyle değildir. Boş bir Kişiyi silmeye çalıştığınız bir senaryo düşünün. Bu nasıl oldu? Boş Kişi nereden geldi? Bu bir kullanıcı arayüzü ise, geçerli seçim yoksa neden Delete tuşu kullanıldı? Zaten silinmişse, zaten ekrandan kaldırılmamış mıydı? Açıkçası bunun istisnaları vardır, ancak bir proje büyüdükçe, hataların sistemin derinlerine nüfuz etmesini önlemek için genellikle böyle bir kod teşekkür edersiniz.

  • Doğrulama ve kodu defansif olarak ekleyin. Bu, sözleşmeyi gevşetir , çünkü şimdi bu yöntem sadece kişiyi silmekten daha fazlasını yapar. Yöntem adını bunu yansıtacak şekilde değiştirdim, ancak API'nızda tutarlıysanız gerekli olmayabilir. Bu yaklaşımın artıları ve eksileri vardır. Pro şu anda TryDeletePersongeçersiz girişlerin her türlü geçerek çağırabilirsiniz ve istisnalar hakkında asla endişelenmeyin. Elbette, kodunuzun kullanıcıları muhtemelen bu yöntemi çok fazla arayacaklar veya p'nin boş olduğu durumlarda hata ayıklamayı zorlaştırabilirler. Bu, Tek Sorumluluk İlkesi'nin hafif bir ihlali olarak düşünülebilir , bu nedenle bir alev savaşı patlarsa aklınızda bulundurun.

    public void TryDeletePerson(Person p)
    {            
        if(p == null || !_database.Contains(p))
            return;
    
        _database.Delete(p);
    }
    
  • Yaklaşımları birleştirin. Bazen, harici arayanların kuralları yakından izlemesini (onları sorumlu kodlamaya zorlamak için) istediğiniz her ikisinden de biraz istersiniz, ancak özel kodunuzun esnek olmasını istersiniz.

    ///<exception>ArgumentNullException</exception>
    ///<exception>ArgumentException</exception>
    public void DeletePerson(Person p)
    {            
        if(p == null)
            throw new ArgumentNullException("p");
        if(!_database.Contains(p))
            throw new ArgumentException("The Person specified is not in the database.");
    
        TryDeletePerson(p);
    }
    
    internal void TryDeletePerson(Person p)
    {            
        if(p == null || !_database.Contains(p))
            return;
    
        _database.Delete(p);
    }
    

Deneyimlerime göre, zor bir kural yerine ima ettiğiniz sözleşmelere odaklanmak en iyi sonucu verir. Defansif kodlamanın, arayan kişinin bir işlemin geçerli olup olmadığını belirlemesinin zor veya zor olduğu durumlarda daha iyi çalıştığı görülmektedir. Sıkı sözleşmeler, arayan kişinin yalnızca gerçekten, gerçekten mantıklı olduğunda yöntem çağrısı yapmasını beklediğiniz yerde daha iyi çalışıyor gibi görünüyor.


Örnekle çok güzel bir cevap için teşekkürler. "Savunma" ve "katı sözleşme" yaklaşımlarını seviyorum.
srnka

7

Bu bir kongre, dokümantasyon ve kullanım meselesidir.

Tüm işlevler eşit değildir. Tüm gereksinimler eşit değildir. Tüm doğrulama eşit değildir.

Örneğin, Java projeniz mümkün olduğunda boş göstericilerden kaçınmaya çalışırsa ( örneğin , Guava stili önerilere bakın ), yine de boş olmadığından emin olmak için her işlev bağımsız değişkenini doğrular mısınız? Muhtemelen gerekli değildir, ancak hata bulmayı kolaylaştırmak için hala yapma olasılığınız vardır. Ancak, daha önce bir NullPointerException oluşturduğunuzda bir iddia kullanabilirsiniz.

Proje C ++ 'da ise ne olur? C ++ 'da konvansiyon / gelenek ön koşulları belgelemektir, ancak sadece hata ayıklama yapılarında (varsa) doğrular.

Her iki durumda da, işlevinizde belgelenmiş bir önkoşulunuz vardır: hiçbir argüman boş olamaz. Bunun yerine, işlevin etki alanını tanımlanmış davranışa sahip null'ler içerecek şekilde genişletebilirsiniz, örneğin "herhangi bir bağımsız değişken boşsa, bir istisna atar". Tabii ki, yine burada konuşan C ++ mirasım - Java'da, önkoşulları bu şekilde belgelemek için yeterince yaygın.

Ancak tüm ön koşulların bile olabilir makul kontrol edilmelidir. Örneğin, bir ikili arama algoritması aranacak dizinin sıralanması gereken ön koşula sahiptir. Ancak bunun kesinlikle bir O (N) işlemi olduğunu doğrulamak, bu yüzden her çağrıda bunu yapmak bir O (log (N)) algoritması kullanma noktasını yener. Defansif olarak programlıyorsanız, daha az kontrol yapabilirsiniz (örneğin, aradığınız her bölüm için başlangıç, orta ve bitiş değerlerinin sıralandığını doğrulama), ancak bu tüm hataları yakalamaz. Genellikle, yerine getirilen ön koşula güvenmeniz gerekir.

Açık kontrollere ihtiyaç duyduğunuz gerçek yer sınırlardır. Projenize harici girdi? Doğrulayın, doğrulayın, doğrulayın. Gri alan API sınırlarıdır. Bu gerçekten müşteri koduna ne kadar güvenmek istediğinize, geçersiz girdinin ne kadar hasar verdiğine ve hataları bulmak için ne kadar yardım sağlamak istediğinize bağlıdır. Herhangi bir ayrıcalık sınırı, dışsal olarak sayılmalıdır, örneğin sistem çağrıları yüksek bir ayrıcalık bağlamında çalıştırılmalıdır ve bu nedenle doğrulamak için çok dikkatli olmalıdır. Bu tür herhangi bir doğrulama elbette sistem çağrısının içinde olmalıdır.


Cevabınız için teşekkürler. Lütfen, Guava tarzı tavsiyeye bağlantı verebilir misiniz? Google'ı kullanarak ne demek istediğini bulamıyorum. Sınırları doğrulamak için +1.
srnka

Bağlantı eklendi. Aslında tam bir stil kılavuzu değil, null olmayan yardımcı programların belgelerinin sadece bir kısmı.
Sebastian Redl

6

Parametre doğrulaması çağrılan işlevin kaygısı olmalıdır. İşlev neyin geçerli girdi olarak kabul edildiğini ve neyin dikkate alınmadığını bilmelidir. Arayanlar bunu bilemeyebilir, özellikle işlevin dahili olarak nasıl uygulandığını bilmediklerinde. Fonksiyonun, arayanlardan gelen parametre değerlerinin herhangi bir kombinasyonunu ele alması beklenmelidir.

Fonksiyon parametreleri doğrulamaktan sorumlu olduğu için, hem geçerli hem de geçersiz parametre değerlerinde amaçlandığı gibi çalışmasını sağlamak için bu işleve karşı birim testleri yazabilirsiniz.


Cevap için teşekkürler. Yani, bu fonksiyon her durumda geçerli ve geçersiz giriş parametrelerini kontrol etmelidir. Pragmatik Programcı kitap doğrulamasından farklı bir şey: "giriş parametresinin onaylanması arayanın sorumluluğundadır". "İşlev neyin geçerli kabul edildiğini bilmelidir ... Arayanlar bunu bilmeyebilir" diye düşünülmüş. Yani ön koşulları kullanmak istemiyor musunuz?
srnka

1
İsterseniz ön koşulları kullanabilirsiniz ( Sebastian'ın cevabına bakın ), ancak savunma yapmayı ve olası her türlü girişi ele almayı tercih ediyorum.
Bernard

4

İşlev içinde. İşlev birden çok kez kullanılıyorsa, her işlev çağrısı için parametreyi doğrulamak istemezsiniz.

Ayrıca, işlev parametrenin doğrulanmasını etkileyecek şekilde güncellenirse, bunları güncellemek için arayan doğrulamasının her tekrarını aramanız gerekir. Çok hoş değil :-).

Guard Maddesine başvurabilirsiniz

Güncelleme

Verdiğiniz her senaryo için cevabımı görün.

  • geçersiz değişkenin sqrt()davranışı değişebilirse, onu arayan tarafında doğrulamak iyidir (örneğin işlev - bazı durumlarda karmaşık sayı ile çalışmak isteyebilirim, bu yüzden arayandaki durumu tedavi ederim)

    Cevap

    Programlama dillerinin çoğunluğu varsayılan olarak tam sayı ve gerçek sayıları destekler, karmaşık sayı değil, bu nedenle bunların uygulanması sqrtsadece negatif olmayan sayıları kabul eder. sqrtKarmaşık sayı döndüren bir işleve sahip olmanızın tek örneği Mathematica gibi matematiğe yönelik programlama dilini kullanmanızdır.

    Dahası, sqrtçoğu programlama dili için zaten uygulanmıştır, bu yüzden onu değiştiremezsiniz ve uygulamayı değiştirmeye çalışırsanız (maymun yamalarına bakın), ortak çalışanlarınız neden sqrtaniden negatif sayıları kabul ettiğine tamamen şok olacaktır .

    İsterseniz sqrt, negatif sayıyı işleyen ve karmaşık sayıyı döndüren özel işlevinizin etrafına sarabilirsiniz .

  • kontrol koşulu her arayanda aynı olduğunda, yinelemeleri önlemek için fonksiyonun içinde kontrol etmek daha iyidir

    Cevap

    Evet, bu, parametre doğrulamasını kodunuza dağılmasını önlemek için iyi bir uygulamadır.

  • arayandaki giriş parametresinin doğrulanması, bu parametreyle birçok fonksiyon çağrılmadan önce sadece bir tane gerçekleşir. Bu nedenle, her işlevdeki bir parametrenin doğrulanması etkili değildir

    Cevap

    Arayanın bir işlev olması hoş olacaktır, değil mi?

    Arayandaki işlevler başka bir arayan tarafından kullanılıyorsa, arayan tarafından çağrılan işlevler içindeki parametreyi doğrulamanızı engelleyen nedir?

  • doğru çözüm özel duruma bağlıdır

    Cevap

    Korunabilir kodu hedefleyin. Parametre doğrulamanızı taşımak, işlevin neyi kabul edip edemeyeceği konusunda bir doğru kaynağı sağlar.


Cevap için teşekkürler. Sqrt () sadece bir örnektir, giriş parametresiyle aynı davranış diğer birçok işlev tarafından kullanılabilir. "Eğer fonksiyon parametrenin validasyonunu etkileyecek şekilde güncellenirse, arayan validasyonunun her tekrarını aramalısınız" - Buna katılmıyorum. Daha sonra dönüş değeri için aynı şeyi söyleyebiliriz: işlev dönüş değerini etkileyecek şekilde güncellenirse, her arayanı düzeltmeniz gerekir ... Bence işlevin yapılacak iyi tanımlanmış bir görevi olması gerekir ... Aksi takdirde yine de arayandaki değişiklik gereklidir.
srnka

2

Bir fonksiyonun ön ve son koşullarını belirtmesi gerekir.
Ön koşullar , işlevi doğru bir şekilde kullanmadan ve giriş parametrelerinin geçerliliğini içerebilmesi (ve çoğu zaman yapabilmesi) için arayan tarafından karşılanması gereken koşullardır .
Post-koşullar, fonksiyonun arayanlara verdiği vaatlerdir.

Bir fonksiyonun parametrelerinin geçerliliği ön koşulların bir parçası olduğunda, bu parametrelerin geçerli olmasını sağlamak arayanın sorumluluğundadır. Ancak bu, her arayanın aramadan önce her parametreyi açıkça kontrol etmesi gerektiği anlamına gelmez. Çoğu durumda, dahili mantık ve arayanın ön koşulları parametrelerin geçerli olduğundan emin olduğu için açık testlere gerek yoktur.

Programlama hatalarına (hatalar) karşı bir güvenlik önlemi olarak, bir işleve iletilen parametrelerin gerçekten belirtilen ön koşulları karşılayıp karşılamadığını kontrol edebilirsiniz. Bu testler maliyetli olabileceğinden, sürüm sürümleri için bunları kapatabilmek iyi bir fikirdir. Bu testler başarısız olursa, programın bir hataya yol açtığı için program sonlandırılmalıdır.

Her ne kadar ilk bakışta arayan kontrol kodu çoğaltmaya davet gibi görünse de, aslında tam tersi. Callee içindeki kontrol kodu çoğaltmaya ve bir sürü gereksiz işin yapılmasına neden olur.
Sadece düşünün, parametreleri birkaç işlev katmanından ne sıklıkta geçirirsiniz, yol boyunca bazılarında sadece küçük değişiklikler yaparsınız. Sürekli olarak check-in-callee yöntemini uygularsanız, bu ara işlevlerin her birinin parametrelerin her biri için denetimi yeniden yapması gerekecektir.
Ve şimdi bu parametrelerden birinin sıralı bir liste olması gerektiğini hayal edin.
Arayan kişiyi kontrol ederken listenin gerçekten sıralandığından emin olmak için yalnızca ilk işlev gerekir. Diğerleri listenin zaten sıralandığını biliyorlar (ön koşullarında belirttikleri gibi) ve daha fazla kontrol yapmadan iletebilirler.


+1 Cevabınız için teşekkürler. Güzel yansıma: "Callee kontrol kod çoğaltılması ve bir sürü gereksiz iş yapılmasına neden olur". Ve cümleyle: "Çoğu durumda, açık bir teste gerek yoktur, çünkü iç mantık ve arayanın ön koşulları zaten" "iç mantık" ifadesi ile ne demek istersiniz? DBC işlevselliği?
srnka

@srnka: "İç mantık" ile bir fonksiyondaki hesaplamaları ve kararları kastediyorum. Temelde işlevin uygulanmasıdır.
Bart van Ingen Schenau

0

Çoğu zaman, yazdığınız işlevi kim, ne zaman ve nasıl arayacağını bilemezsiniz. En kötüsünü varsaymak en iyisidir: işleviniz geçersiz parametrelerle çağrılır. Yani kesinlikle bunu örtmelisin.

Bununla birlikte, kullandığınız dil istisnaları destekliyorsa, belirli hataları kontrol edemeyebilir ve bir istisnanın atılacağından emin olabilirsiniz, ancak bu durumda vakayı dokümantasyonda açıkladığınızdan emin olmalısınız (dokümantasyona ihtiyacınız vardır). İstisna, arayan kişiye neler olduğu hakkında yeterli bilgi verir ve ayrıca geçersiz argümanlara dikkat çeker.


Aslında, parametreyi doğrulamak daha iyi olabilir ve parametre geçersizse, kendinize bir istisna atın. Nedeni: Geçerli verileri verdiklerinden emin olmak için rutini çağıran palyaçolar, geçersiz verileri geçtiklerini gösteren hata dönüş kodunu kontrol etmekle uğraşmayacak olanlarla aynıdır. İstisna atmak sorunu düzeltmeye zorlar.
John R. Strohm
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.