Code Contracts

Code Contracts

data: 6 maja, 2013
czas czytania: 5 min
autor: Łukasz Ciura

Code Contracts to opracowany przez firmę Microsoft mechanizm, który ma celu ułatwienie programistom definiowania zasad określających, w jaki sposób mają być wykorzystywane stworzone przez nich klasy wraz z ich metodami.

Code Contracts pozwala określać:

  • Preconditions – warunki, jakie muszą być spełnione, żeby metoda mogła być wywołana; najczęściej dotyczą one argumentów przesyłanych do metody
  • Postconditions – warunki, którą muszą zostać spełnione po wykonaniu metody; najczęściej dotyczą one wartości zwracanej przez metodę
  • Object Invariants – warunki, jakie musi spełniać klasa zarówno przed jak i po wykonaniu każdej metody

Code Contracts należy zainstalować aby móc z niego korzystać, instalator znajdziecie tutaj.

Preconditions

Załóżmy, że stworzyliśmy klasę Developer, która posiada pola Name (string), Age (int) i Note (float).
Pola te są ustawiane wyłącznie w konstruktorze i muszą spełniać następujące wymagania:

  • pole Name – nie może mieć wartości null ani być puste
  • wartość w polu Age musi być większa bądź równa 18
  • wartość w polu Note musi mieścić się w przedziale od 1.0 do 6.0

Konstruktor, który miałby zapewnić spełnienie tych warunków wyglądałby mniej więcej tak:

public Developer(string name, int age, float note)
{
    if (string.IsNullOrWhiteSpace(name))
    {
        throw new ArgumentNullException();
    }

    if (age < 18) { throw new ArgumentException(); } if (note > 6.0f || note < 1.0f)
    {
        throw new ArgumentException(); 
    }

    Name = name;
    Age = age;
    Note = note;
}

Przy korzystaniu z Code Contracts możemy zostosować metodę statyczną Requires klasy Contract (System.Diagnostics.Contracts). Metoda Requires jako parametr przyjmuje warunek, jaki metoda musi spełnić zanim zostanie wykonana oraz (opcjonalnie) wiadomość, jaka zostanie wyświetlona, gdy warunek ten nie zostanie spełniony. Jeżeli warunek nie zostanie spełniony to w zależności od naszych ustawień Code Contracts, aplikacja rzuci wyjątek (rodzaj wyjątku możemy zdefiniować) lub assercję. Powyższy kod przy zostosowaniu metody Requires wyglądałby następująco:

public Developer(string name, int age, float note)
{
    Contract.Requires(string.IsNullOrWhiteSpace(name), "Name cannot be null or empty");
    Contract.Requires(age >= 18, "Age has to greater or equal than 18");
    Contract.Requires(note >= 1.0f && note <= 6.0f, "Note is out of range");

    Name = name;
    Age = age;
    Note = note;
}

Dzięki zastosowaniu metody Requires, kod nie tylko stał się czytelniejszy, ale dodatkowo – jeśli włączyliśmy opcję Runtime Checking dostępną w opcjach Code Contracts – na bieżąco jesteśmy powiadamiani o niespełnionych warunkach kontraktów:

code contracts1

Postconditions

Wykorzystując kolejną statyczną metody klasy Contract – Ensures, możemy określić warunki które muszą zostać spełnione po wykonaniu danej metody. Jako przykład weźmy prostą metodę, która ma zwracać wyliczoną estymację czasu wykonania zadania. Jako parametr będzie przyjmować czas estymowany przez developera. Jedynym warunkiem, który musi spełnić metoda, jest,
by zwrócona wartość była przynajmniej dwa razy większa niż wartość wejściowa ;).

Public int CalculateEstimationForTask(int estimationGivenByDeveloper)
{
    Contract.Ensures(Contract.Result() >= estimationGivenByDeveloper * 2, "Calculated estimation can't be real!");
    int calculatedEstimation = estimationGivenByDeveloper;
    // do magic calculations...
    return calculatedEstimation;
}

Podobnie jak w przypadku postconditions, jeżeli warunek podany jako parametr w metodzie Ensures nie zostanie spełniony to informację o tym dostaniemy na liście „Warnings”. Dodatkowo aplikacja rzuci nam wyjątkiem jeżeli ją odpalimy i wywołamy daną metodę, rodzaj wyjątku także możemy określić. Postconditions są niezwykle przydatne przy pisaniu metod, które zawierają obliczenia dla biznesu.

Object Invariants

Object Invariants służy do określenia warunków jakie klasa musi spełniać zarówno po, jak i przed wykonaniem każdej metody. Warunki Object Invariants w przeciwieństwie do Preconditions i PostConditions muszą zostać określone w osobnej metodzie klasy. Kilka słów o metodzie:

  • metoda ta musi zostać opatrzona atrybutem [ContractInvariantMethod]
  • metoda musi być prywatna oraz zwracać typ void
  • nazwa metody jest bez znaczenia
  • klasa może posiadać więcej niż jedną taką metodę

Wracając do przykładu z klasą Developer, za sprawdzenie warunku dotyczącego pola Note (wartość nie może być większa od 6.0
i mniejsza od 1.0) będzie teraz odpowiedzialne Object Invariants, konkretniej metoda Invariant.
Metoda zawierająca odpowiednie warunki wygląda następująco:

[ContractInvariantMethod]
private void Invariants()
{
    Contract.Invariant(Note > 1.0f, "Note is too small");
    Contract.Invariant(Note < 6.0f, "Note is too big");
}

Tak zdefiniowana metoda gwarantuje nam, że za każdym razem, gdy nowa wartość pola Note klasy Developer nie będzie spełniać określonych przez nas warunków – aplikacja rzuci wyjątkiem:

code contracts2

Dodatkowo

Code Contracts umożliwia także określanie warunków dla typów abstrakcyjnych oraz interfejsów, więcej na ten temat możecie przeczytać w dokumentacji.

Dołączam także zrzut ekranu z ustawień Code Contracts:

code contracts3

Newsletter IT leaks

Dzielimy się inspiracjami i nowinkami z branży IT. Szanujemy Twój czas - obiecujemy nie spamować i wysyłać wiadomości raz na dwa miesiące.

Subscribe to our newsletter

Administratorem Twoich danych osobowych jest Future Processing S.A. z siedzibą w Gliwicach. Twoje dane będziemy przetwarzać w celu przesyłania cyklicznego newslettera dot. branży IT. W każdej chwili możesz się wypisać lub edytować swoje dane. Więcej informacji znajdziesz w naszej polityce prywatności.

Subscribe to our newsletter

Administratorem Twoich danych osobowych jest Future Processing S.A. z siedzibą w Gliwicach. Twoje dane będziemy przetwarzać w celu przesyłania cyklicznego newslettera dot. branży IT. W każdej chwili możesz się wypisać lub edytować swoje dane. Więcej informacji znajdziesz w naszej polityce prywatności.