İstisnalar veya yedeklilik olmadan giriş doğrulaması nasıl yapılır


11

Belirli bir program için bir arabirim oluşturmaya çalıştığımda genellikle doğrulanmamış girişe bağlı istisnalar atmaktan kaçınmaya çalışıyorum.

Yani sık sık böyle bir kod parçası düşündüm ki (bu sadece bir örnek uğruna bir örnek, gerçekleştirdiği işleve aldırma, örneğin Java):

public static String padToEvenOriginal(int evenSize, String string) {
    if (evenSize % 2 == 1) {
        throw new IllegalArgumentException("evenSize argument is not even");
    }

    if (string.length() >= evenSize) {
        return string;
    }

    StringBuilder sb = new StringBuilder(evenSize);
    sb.append(string);
    for (int i = string.length(); i < evenSize; i++) {
        sb.append(' ');
    }
    return sb.toString();
}

Tamam, evenSizeaslında kullanıcı girişinden türetilmiş olduğunu söyleyin . Bu yüzden bile olduğundan emin değilim. Ancak bu yöntemi bir istisna oluşması ihtimaliyle çağırmak istemiyorum. Bu yüzden aşağıdaki işlevi yaparım:

public static boolean isEven(int evenSize) {
    return evenSize % 2 == 0;
}

ama şimdi aynı giriş doğrulamasını gerçekleştiren iki kontrolüm var: ifadedeki ififade ve açık check-in isEven. Yinelenen kod, hoş değil, bu yüzden yeniden düzenleyelim:

public static String padToEvenWithIsEven(int evenSize, String string) {
    if (!isEven(evenSize)) { // to avoid duplicate code
        throw new IllegalArgumentException("evenSize argument is not even");
    }

    if (string.length() >= evenSize) {
        return string;
    }

    StringBuilder sb = new StringBuilder(evenSize);
    sb.append(string);
    for (int i = string.length(); i < evenSize; i++) {
        sb.append(' ');
    }
    return sb.toString();
}

Tamam, bu çözüldü, ama şimdi aşağıdaki duruma giriyoruz:

String test = "123";
int size;
do {
    size = getSizeFromInput();
} while (!isEven(size)); // checks if it is even
String evenTest = padToEvenWithIsEven(size, test);
System.out.println(evenTest); // checks if it is even (redundant)

artık yedekli bir kontrolümüz var: değerin eşit olduğunu zaten biliyoruz, ancak padToEvenWithIsEvenyine de bu işlevi çağırdığımız için her zaman true döndürecek parametre kontrolünü gerçekleştiriyoruz.

Şimdi isEvenelbette bir sorun oluşturmuyor, ancak parametre kontrolü daha hantalsa, bu çok fazla maliyete neden olabilir. Bunun yanı sıra, fazladan bir çağrı yapmak doğru gelmiyor.

Bazen "doğrulanmış bir tür" tanıtarak veya bu sorunun oluşamayacağı bir işlev oluşturarak bu sorunu çözebiliriz:

public static String padToEvenSmarter(int numberOfBigrams, String string) {
    int size = numberOfBigrams * 2;
    if (string.length() >= size) {
        return string;
    }

    StringBuilder sb = new StringBuilder(size);
    sb.append(string);
    for (int i = string.length(); i < size; i++) {
        sb.append('x');
    }
    return sb.toString();
}

ancak bu biraz akıllı düşünme ve oldukça büyük bir yeniden düzenleme gerektirir.

Gereksiz çağrıları önleyebilmemiz isEvenve çift parametre kontrolü gerçekleştirebilmemiz için (daha fazla) genel bir yol var mı? Çözümün aslında padToEvengeçersiz bir parametre ile çağrılmamasını ve istisnayı tetiklemesini istiyorum.


İstisnalar dışında, istisnasız programlama anlamına gelmez, yani kullanıcı girişi tasarım tarafından bir istisna tetiklemez, genel fonksiyonun kendisi hala parametre kontrolünü içerir (sadece programlama hatalarına karşı korumak için).


Sadece istisnayı kaldırmaya mı çalışıyorsunuz? Gerçek boyutun ne olması gerektiği hakkında bir varsayım yaparak bunu yapabilirsiniz. Örneğin, 13'ü geçerseniz, 12 veya 14'e geçin ve kontrolü tamamen önleyin. Bu varsayımlardan birini yapamazsanız, parametre kullanılamadığından ve işleviniz devam edemediğinden istisnada kalırsınız.
Robert Harvey

@RobertHarvey - Benim görüşüme göre - doğrudan en az sürpriz ilkesine karşı olduğu kadar hızlı başarısız olma ilkesine de karşı çıkıyor. Giriş parametresi null ise null döndürmek gibidir (ve sonra sonucu doğru bir şekilde işlemeyi unutmak elbette, açıklanamayan NPE'yi merhaba).
Maarten Bodewes

Ergo, istisna. Sağ?
Robert Harvey

2
Bir dakika ne? Buraya tavsiye için geldiniz, değil mi? Söylediğim (görünüşe göre çok ince olmayan bir şekilde), bu istisnaların tasarım gereğidir, bu yüzden onları ortadan kaldırmak istiyorsanız, muhtemelen iyi bir nedene sahip olmalısınız. Bu arada, amon ile aynı fikirdeyim: muhtemelen bir parametre için tek sayıları kabul edemeyen bir fonksiyonunuz olmamalıdır.
Robert Harvey

1
@MaartenBodewes: Bunun kullanıcı girişi doğrulaması padToEvenWithIsEven yapmadığını hatırlamanız gerekir . Arama kodundaki programlama hatalarına karşı kendini korumak için girişinde bir geçerlilik kontrolü yapar . Bu doğrulamanın ne kadar kapsamlı olması gerektiği, arama maliyetini yazan kişinin yanlış parametrede geçme riskine karşı çek maliyetini koyduğunuz bir maliyet / risk analizine bağlıdır.
Bart van Ingen Schenau

Yanıtlar:


7

Örneğinizde, en iyi çözüm daha genel bir dolgu işlevi kullanmaktır; arayan kişi eşit bir boyuta geçmek isterse bunu kendileri kontrol edebilir.

public static String padString(int size, String string) {
    if (string.length() >= size) {
        return string;
    }

    StringBuilder sb = new StringBuilder(size);
    sb.append(string);
    for (int i = string.length(); i < size; i++) {
        sb.append(' ');
    }
    return sb.toString();
}

Bir değer üzerinde sürekli olarak aynı doğrulamayı gerçekleştirirseniz veya yalnızca bir türdeki değerlerin alt kümesine izin vermek istiyorsanız, mikrotipler / küçük türler yardımcı olabilir. Doldurma gibi genel amaçlı araçlar için bu iyi bir fikir değildir, ancak değeriniz alan adı modelinizde belirli bir rol oynarsa, ilkel değerler yerine özel bir tür kullanmak ileriye doğru büyük bir adım olabilir. Burada şunları tanımlayabilirsiniz:

final class EvenInteger {
  public final int value;

  public EvenInteger(int value) {
    if (!(value % 2 == 0))
      throw new IllegalArgumentException("EvenInteger(" + value + ") is not even");
    this.value = value;
  }
}

Şimdi beyan edebilirsiniz

public static String padStringToEven(EvenInteger evenSize, String string)
    ...

içeriden doğrulama yapmak zorunda değilsiniz. Bu kadar basit bir test için bir int'i bir nesnenin içine sarmak, çalışma süresi performansı açısından daha pahalı olacaktır, ancak yazım sistemini kendi yararınıza kullanmak hataları azaltabilir ve tasarımınızı netleştirebilir.

Bu tür küçük türlerin kullanılması, hiçbir doğrulama yapmadıklarında bile, örneğin FirstNamea LastName. Bu kalıbı sık sık statik olarak yazılmış dillerde kullanıyorum.


İkinci fonksiyonunuz bazı durumlarda hala bir istisna oluşturmuyor mu?
Robert Harvey

1
@RobertHarvey Evet, örneğin boş göstergelerde. Ancak şimdi ana kontrol - numaranın çift olduğu - işlevden arayanın sorumluluğuna zorlanır. Daha sonra istisna ile uygun gördükleri şekilde başa çıkabilirler. Yanıtın doğrulama kodunda kod çoğaltmasından kurtulmaktan çok tüm istisnalardan kurtulmaya odaklandığını düşünüyorum.
amon

1
Mükemmel cevap. Elbette Java'da veri sınıfları oluşturmanın cezası oldukça yüksektir (birçok ek kod), ancak bu, sunduğunuz fikirden daha fazla bir dil sorunudur. Sorunumun meydana geleceği tüm kullanım durumları için bu çözümü kullanmayacağınızı tahmin ediyorum, ancak aşırı primerlere karşı veya parametre kontrolünün maliyetinin son derece zor olduğu uç durumlar için iyi bir çözüm.
Maarten Bodewes

1
@MaartenBodewes Doğrulama yapmak istiyorsanız, istisnalar gibi bir şeyden kurtulamazsınız. Alternatif olarak, doğrulanmış bir nesneyi veya hata durumunda null döndüren, ancak temelde hiçbir avantajı olmayan statik bir işlev kullanmaktır. Ancak, doğrulamayı işlevin dışına yapıcıya taşıyarak, artık bir doğrulama hatası olasılığı olmadan işlevi çağırabiliriz. Bu arayan kişiye tüm kontrolü verir. Örn:EvenInteger size; while (size == null) { try { size = new EvenInteger(getSizeFromInput()); } catch(...){}} String result = padStringToEven(size,...);
amon

1
@MaartenBodewes Yinelenen doğrulama her zaman gerçekleşir. Örneğin, bir kapıyı kapatmaya çalışmadan önce kapının kapalı olup olmadığını kontrol edebilirsiniz, ancak kapının kendisi de kapalıysa yeniden kapatılmasına izin vermez. Arayanın herhangi bir geçersiz işlem gerçekleştirmediğine her zaman güveniyorsanız, bunun dışında bir yol yoktur. Sizin durumunuzda unsafePadStringToEven, herhangi bir kontrol yapmayan bir işleminiz olabilir , ancak doğrulamadan kaçınmak kötü bir fikir gibi görünüyor.
plalx

9

@ Amon'un cevabının bir uzantısı olarak EvenInteger, işlevsel programlama topluluğunun "Akıllı Yapıcı" olarak adlandırabileceği şeyle birleştirebiliriz - aptal yapıcıyı saran ve nesnenin geçerli bir durumda olduğundan emin olan bir işlev (dilsiz kurucu sınıfı veya sınıf dışı dillerdeki modül / paket özel olarak yalnızca akıllı kurucuların kullanıldığından emin olun). İşin püf noktası Optional, işlevi daha karmaşık hale getirmek için bir (veya eşdeğeri) döndürmektir .

public final class EvenInteger {
    private final int value;

    private EvenInteger(value) {
        this.value = value;
    }

    public static Optional<EvenInteger> of(final int value) {
        if (value % 2 == 0) {
            return Optional.of(new EvenInteger(value));
        }
        return Optional.empty();
    }

    public int getValue() {
        return this.value;
    }
}

Daha sonra Optionalgiriş mantığını yazmak için standart yöntemleri kolayca kullanabiliriz :

class GetEvenInput {
    public Optional<EvenInteger> askOnce() {
        int size = getSizeFromInput();
        return EvenInteger.of(size);
    }

    public EvenInteger keepAsking() {
        return askOnce().orElseGet(() -> keepAsking());
    }
}

keepAsking()Do-while döngüsü ile daha deyimsel bir Java stili de yazabilirsiniz .

Optional<EvenInteger> result;
do {
    result = askOnce();
} while (!result.isPresent());

return result.get();

Daha sonra kodunuzun geri kalanında EvenIntegergerçekten eşit olacağına ve hatta çekimizin sadece bir kez yazıldığı belgelerine güvenebilirsiniz EvenInteger::of.


İsteğe bağlı olarak, geçersiz olabilecek verileri oldukça iyi temsil eder. Bu, hem verileri hem de girdi geçerliliğini gösteren bir bayrağı döndüren çok sayıda TryParse yöntemi ile benzerdir. Complie zaman kontrolleri ile.
Basilevs

1

Doğrulamanın sonucu aynıysa ve doğrulama aynı sınıfta yapılıyorsa, iki kez doğrulama yapmak bir sorundur. Bu senin örneğin değil. Yeniden düzenlenmiş kodunuzda yapılan ilk isEven denetimi giriş doğrulamasıdır, başarısızlık yeni bir giriş istenir. İkinci kontrol, sınıfın dışından çağrılabilen ve farklı bir sonuca (istisna) sahip genel bir padToEvenWithEven yönteminde olduğu gibi ilkinden tamamen bağımsızdır .

Sorununuz, yanlışlıkla aynı kodun kuru olmama nedeniyle karıştırılması sorununa benzer. Uygulamayı tasarımla karıştırıyorsunuz. Aynı değildirler ve aynı olan bir veya bir düzine çizginiz olması, aynı amaca hizmet ettikleri ve sonsuza kadar değiştirilebilir olarak kabul edilebilecekleri anlamına gelmez. Ayrıca, muhtemelen sınıfınızı çok fazla yapmak, ancak atlayın, çünkü bu muhtemelen sadece bir oyuncak örneği ...

Bu bir performans sorunuysa, genel padToEvenWithEven uygulamanızın doğrulama işlemini yaptıktan sonra çağırdığı ve bunun yerine diğer yöntemin de arayacağı özel bir yöntem oluşturarak sorunu çözebilirsiniz. Bu bir performans sorunu değilse, farklı yöntemlerinizin atanmış görevlerini gerçekleştirmeleri için gereken denetimleri yapmasına izin verin.


OP, fonksiyonun atanmasını önlemek için giriş doğrulamasının yapıldığını belirtti. Böylece kontroller tamamen bağımlı ve kasıtlı olarak aynıdır.
D Drmmr

@DDrmmr: hayır, bağımlı değiller. Fonksiyon atar, çünkü bu sözleşmenin bir parçasıdır. Dediğim gibi, cevap özel bir yöntem oluşturmak dışında istisna atmak dışında her şeyi yapar ve sonra özel yöntem özel yöntemi çağırmaktır. Genel yöntem, onaylanmamış girdileri işlemek için farklı bir amaca hizmet ettiği kontrolü korur.
jmoreno
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.