Doğrulama kontrolleri ile kontrol akışı için stil


27

Kendimi böyle bir çok kod yazarken buluyorum:

int myFunction(Person* person) {
  int personIsValid = !(person==NULL);
  if (personIsValid) {
     // do some stuff; might be lengthy
     int myresult = whatever;
     return myResult;
  }
  else {
    return -1;
  }
}

Özellikle birden fazla kontrol yapıldığında oldukça karışık olabilir. Bu gibi durumlarda, bunun gibi alternatif stilleri denedim:

int netWorth(Person* person) {
  if (Person==NULL) {
    return -1;
  }
  if (!(person->isAlive))  {
    return -1;
  }
  int assets = person->assets;
  if (assets==-1)  {
    return -1;
  }
  int liabilities = person->liabilities;
  if (liabilities==-1) {
    return -1;
  }
  return assets - liabilities;
}

Buradaki stilistik seçimlerle ilgili yorumlarla ilgileniyorum. [Bireysel ifadelerin detayları hakkında çok fazla endişelenmeyin; beni ilgilendiren genel kontrol akışı.]


8
Örnekte oldukça ciddi bir belirtim hatası olduğunu belirtmeme izin verin. Örneğin, varlıklar == 42 ve borçlar == 43 ise, var olmayan kişiyi ilan edersiniz.
John R. Strohm

Bir istisna atmak ve müşteri kodunun onaylamaları yönetmesine izin vermek daha iyi olmaz mıydı?
Tulains Córdova

@ TulainsCórdova İstisnalar mevcut olmayabilir veya belki de geçersiz veriler, yığın izlemesini vb. Oluşturmanın performans etkisinin kabul edilebilir olduğu kadar istisnai değildir.
Hulk

Yanıtlar:


27

Bu tür sorunlar için Martin Fowler, Şartname modelini önerdi :

... tasarım kuralları, böylece iş kuralları, boolean mantığı kullanılarak birlikte iş kuralları zincirlenerek yeniden birleştirilebilir.
 
Bir teknik özellik deseni, diğer işletme kuralları ile birleştirilebilen bir işletme kuralını ana hatlarıyla belirtir. Bu düzende, bir iş mantığı birimi işlevselliğini soyut toplam Kompozit Spesifikasyon sınıfından miras alır. Kompozit Spesifikasyon sınıfı, bir boolean değeri döndüren IsSatisfiedBy adlı bir işleve sahiptir. Örneklemeden sonra, özellik diğer özelliklerle "zincirlenir", böylece yeni özelliklerin kolayca bakımı yapılabilir, ancak özelleştirilebilir iş mantığı. Dahası, başlatmanın ardından iş mantığı, yöntem çağırma ya da kontrolün tersine çevrilmesi yoluyla, kalıcılık havuzu gibi diğer sınıfların delegesi olmak üzere devletini değiştirebilir ...

Yukarıda biraz kaş gibi geliyor (en azından benim için), ama kodumda denediğimde oldukça sorunsuz geçti ve uygulanması ve okunması kolaylaştı.

Gördüğüm gibi, ana fikir, kontrolleri özel yöntem (ler) / nesneler haline getiren kodu "çıkarmak".

Senin ile netWorthörneğin, bu şöyle hakkında görünebilir:

int netWorth(Person* person) {
  if (isSatisfiedBySpec(person)) {
    return person->assets - person->liabilities;
  }
  log("person doesn't satisfy spec");
  return -1;
}

#define BOOLEAN int // assuming C here
BOOLEAN isSatisfiedBySpec(Person* person) {
  return Person != NULL
      && person->isAlive
      && person->assets != -1
      && person->liabilities != -1;
}

Durumunuz oldukça basit görünüyor, böylece tüm kontroller tek bir yöntemle düz bir listeye sığacak şekilde OK'a bakıyor. Daha iyi okumak için daha fazla yönteme ayrılmam gerekir.

Ayrıca, tipik olarak "spec" ile ilgili yöntemleri özel bir nesnede gruplandırıyorum / bununla birlikte, durumunuz tam olarak iyi görünmüyor.

  // ...
  Specification s, *spec = initialize(s, person);
  if (spec->isSatisfied()) {
    return person->assets - person->liabilities;
  }
  log("person doesn't satisfy spec");
  return -1;
  // ...

Yığın Taşması'ndaki bu soru yukarıda belirtilenlere ek olarak birkaç bağlantı önerir: Teknik Özellik Örneği Örnek . Özellikle, cevaplar , bir örnek için adım adım “Spesifikasyon modelini öğrenme” konusunda Dimecasts'ı önermekte ve Eric Evans ve Martin Fowler tarafından hazırlanan “Spesifikasyonlar” belgesinden bahsetmektedir .


8

Doğrulamayı kendi işlevine taşımayı daha kolay buluyorum, diğer işlevlerin amacını daha temiz tutmaya yardımcı oluyor, bu nedenle örneğiniz şöyle olacaktır.

int netWorth(Person* person) { 
    if(validPerson(person)) {
        int assets = person->assets;
        int liabilities = person->liabilities;
        return assets - liabilities;
    }
    else {
        return -1;
    }
}

bool validPerson(Person* person) { 
    if(person!=NULL && person->isAlive
      && person->assets !=-1 && person->liabilities != -1)
        return true;
    else
        return false;
}

2
Neden var mı ifin validPerson? person!=NULL && person->isAlive && person->assets !=-1 && person->liabilities != -1Bunun yerine basitçe geri dönün .
David Hammen,

3

Çok iyi çalıştığım bir şey, kodunuza bir doğrulama katmanı eklemektir. Öncelikle tüm dağınık doğrulamayı yapan ve bir -1şeyler ters gittiğinde hataları ( yukarıdaki örneklerde olduğu gibi) veren bir yönteme sahipsiniz . Doğrulama tamamlandığında, işlev gerçek işi yapmak için başka bir işlev çağırır. Şimdi bu işlevin tüm bu doğrulama adımlarını gerçekleştirmesi gerekmiyor çünkü zaten yapılması gerekiyor. Diğer bir deyişle, iş işlevi girişin geçerli olduğunu varsayar . Varsayımlarla nasıl başa çıkmalısınız? Onları kodda iddia ediyorsun.

Bence bu kodu okumayı çok kolaylaştırıyor. Doğrulama yöntemi, kullanıcı tarafındaki hatalarla başa çıkmak için tüm karışık kodları içerir. Çalışma metodu, varsayımlarını varsayımlarla açıkça belgelemektedir ve daha sonra potansiyel olarak geçersiz verilerle çalışmak zorunda değildir.

Örneğinizin bu yeniden değerlendirmesini yapın:

int myFunction(Person* person) {
  int personIsValid = !(person==NULL);
  if (personIsValid) {
     return myFunctionWork(person)
  }
  else {
    return -1;
  }
}

int myFunction(Person *person) {
  assert( person != NULL);  
  // Do work and return
}
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.