wtorek, 15 września 2009

Design by contract i wstępna walidacja danych, część 1

Jednym z założeń wzorca Design by contract jest sprawdzanie poprawności przekazanych do funkcji argumentów. Zapewne w wielu miejscach Twojego kodu możesz spotkać konstrukcje podobne do tej poniżej:
public void PerformAction(string actionName, Data data, int id)
{
  if (string.IsNullOrEmpty(actionName))
  {
    throw new ArgumentNullException("actionName");
  }
  if (data == null)
  {
    throw new ArgumentNullException("data");
  }
  if (id >= 50)
  {
    throw new ArgumentOutOfRangeException("id", "Must be less than 50");
  }
   
  ...
}

W powyższym przykładzie dostrzegam następującego zagrożenia:
  • powtórzenie nazw argumentów - raz w deklaracji argumentów funkcji, drugi raz w sprawdzeniu warunku null, trzeci raz w rzucaniu wyjątku
  • rozwlekłość, a przez to zmniejszona czytelność kodu
  • przy rosnącej ilości funkcji da się zaobserwować praktycznie identyczne sprawdzenia
Z ostatnim punktem związane jest utrudnione zarządzanie kodem w przyszłości - wystarczy sobie zadać pytanie - a jeśli zamiast ArgumentNullException będę chciał rzucić inny wyjątek? A jeśli będę potrzebował innej walidacji? A jeśli dodatkowo chciałbym logować wartości przekazanych argumentów?

Jako rozwiązanie problemu proponuję skorzystać z nowych funkcji C# 3.0 jakim jest lambda expression oraz starego, znanego mechanizmu Reflection. Cel, jaki będę chciał na początek uzyskać przedstawia się następująco:

public void PerformAction(string actionName, Data data, int id)
{
  this.CheckArg(x => x.PerformAction(actionName, data, id));
 
  if (id >= 50)
  {
    throw new ArgumentOutOfRangeException("id", "Must be less than 50");
  }
 
  ...
}

Widać tutaj znaczące skrócenie ilości kodu oraz pełną kontrolę typów, chociaż nie jest to jeszcze coś, co nazwałbym ideałem (chodzi o dodatkowe sprawdzanie wartości id, ale i na to znajdziemy sposób :)). Założenie jest takie, że funkcja this.CheckArg sprawdzi każdy z argumentów pod kątem wystąpienia null - jeśli wystąpi, to rzuci wyjątek ArgumentNullException z podaniem nazwy argumentu oraz funkcji, w której to wystąpiło (czyli w tym przypadku PerformAction). Jeśli argument jest stringiem, to warunkiem do sprawdzenia będzie string.IsNullOrEmpty().

Jak widać, wykorzystam następujące elementy języka:
  • klasy rozszerzające
  • wyrażenia lambda - to one zapewnią kontrolę typów i odpowiednie przekazanie argumentów
  • mechanizm Reflection

W kolejnych postach opiszę jak tego dokonać. Następnie spróbuję przedstawić podobne zagadnienie, tym razem związane z testowaniem funkcji pod kątem tego, czy sprawdza i rzuca odpowiednie wyjątki przy wystąpieniu null jako jednego z jej argumentów.

Brak komentarzy:

Prześlij komentarz