Dlaczego miałbym używać kontraktów kodowych

26

Niedawno natknąłem się na platformę Microsoft dotyczącą kontraktów kodowych.

Przeczytałem trochę dokumentacji i ciągle pytałem: „Dlaczego miałbym kiedykolwiek chcieć to robić, ponieważ nie wykonuje i często nie może przeprowadzić analizy statycznej”.

Teraz mam już pewien rodzaj defensywnego stylu programowania, z takimi wyjątkami:

if(var == null) { throw new NullArgumentException(); }

Używam również dużo NullObject Pattern i rzadko mam problemy. Dodaj do niego testy jednostkowe i wszystko gotowe.

Nigdy nie użyłem twierdzeń i nigdy ich nie przegapiłem. Wręcz przeciwnie. Naprawdę nienawidzę kodu, który ma wiele bezsensownych zapewnień, co jest dla mnie tylko hałasem i odciąga mnie od tego, co naprawdę chcę zobaczyć. Kontrakty kodowe, przynajmniej sposób Microsoftu, są prawie takie same - a nawet gorzej. Dodają dużo szumu i złożoności do kodu. W 99% i tak zostanie zgłoszony wyjątek - więc nie dbam o to, czy wynika to z twierdzenia / kontraktu, czy z faktycznego problemu. Pozostaje tylko bardzo niewiele przypadków, w których stany programu naprawdę ulegają uszkodzeniu.

Szczerze mówiąc, jaka jest korzyść ze stosowania umów kodowych? Czy jest w ogóle coś takiego? Jeśli używasz już testów jednostkowych i kodu w obronie, uważam, że wprowadzenie umów nie jest warte kosztów i powoduje hałas w kodzie, który opiekun przeklnie, gdy aktualizuje tę metodę, podobnie jak ja, gdy nie widzę, co robi kod z powodu bezużytecznych twierdzeń. Nie widziałem jeszcze dobrego powodu, aby zapłacić tę cenę.

Sokół
źródło
1
Dlaczego mówisz „nie wykonuje i często nie może przeprowadzić analizy statycznej”? Myślałem, że to był jeden z punktów tego projektu (w przeciwieństwie do zwykłego korzystania z Asserts lub rzucania wyjątków obronnych)?
Carson63000,
@ Carson63000 Przeczytaj uważnie dokumentację, a przekonasz się, że moduł sprawdzania statycznego wyświetli wiele ostrzeżeń, najbardziej niepotrzebnych (programiści przyznają, że tak jest) i że większość zdefiniowanych umów po prostu wyrzuci wyjątek i nic poza tym nie zrobi .
Falcon
@ Falcon: o dziwo, moje doświadczenie jest zupełnie inne. Analiza statyczna nie działa bezbłędnie, ale całkiem nieźle i rzadko widzę niepotrzebne ostrzeżenia, nawet podczas pracy na poziomie ostrzeżenia 4 (najwyższym).
Arseni Mourzenko
Chyba będę musiał spróbować, żeby dowiedzieć się, jak się zachowuje.
Falcon

Odpowiedzi:

42

Równie dobrze możesz zapytać, kiedy pisanie statyczne jest lepsze niż pisanie dynamiczne. Debata szalona od lat bez końca. Spójrz więc na pytania takie jak dynamiczne i statyczne badania języków

Ale podstawowym argumentem byłby potencjał wykrycia i rozwiązania problemów w czasie kompilacji, które w przeciwnym razie mogłyby trafić do produkcji.

Kontrakty kontra Strażnicy

Nawet ze strażnikami i wyjątkami nadal masz problem z tym, że system nie wykona zamierzonego zadania w niektórych przypadkach. Możliwe, że wystąpienie, które będzie bardzo krytyczne i kosztowne, nie powiedzie się.

Kontrakty a testy jednostkowe

Typowym argumentem w tym przypadku jest to, że testy dowodzą obecności błędów, podczas gdy typy (kontrakty) dowodzą braku. F.ex. używając typu, który wiesz, że żadna ścieżka w programie prawdopodobnie nie może dostarczyć nieprawidłowych danych wejściowych, podczas gdy test może jedynie stwierdzić, że ścieżka objęta zawierała prawidłowe dane wejściowe.

Kontrakty a wzorzec obiektu zerowego

Teraz jest to przynajmniej w tym samym parku. Języki takie jak Scala i Haskell odniosły wielki sukces dzięki temu podejściu do całkowitego wyeliminowania zerowych referencji z programów. (Nawet jeśli Scala formalnie dopuszcza wartości zerowe, konwencja nigdy ich nie używa)

Jeśli zastosujesz już ten wzorzec do wyeliminowania NRE, w zasadzie usunąłeś największe źródło awarii środowiska uruchomieniowego, w zasadzie sposób pozwala na to umowy.

Różnica może być taka, że ​​kontrakty mają opcję automatycznego żądania całego kodu, aby uniknąć wartości zerowej, a tym samym zmusić cię do korzystania z tego wzorca w większej liczbie miejsc do przekazania kompilacji.

Oprócz tego umowy zapewniają również elastyczność w kierowaniu na cele wykraczające poza zero. Więc jeśli nie widzisz już NRE w swoich błędach, możesz użyć kontraktów, aby zdławić następny najczęstszy problem, jaki możesz mieć. Wyłączone przez jednego? Indeks poza zakresem?

Ale...

Wszystko to powiedziane. Zgadzam się, że umowy dotyczące szumu syntaktycznego (a nawet szumu strukturalnego) zwiększające kod są dość znaczące i nie należy lekceważyć wpływu analizy na czas kompilacji. Więc jeśli zdecydujesz się dodać kontrakty do swojego systemu, rozsądnie byłoby zrobić to bardzo ostrożnie, z wąskim naciskiem na to, którą klasę błędów próbuje się rozwiązać.

John Nilsson
źródło
6
To świetna pierwsza odpowiedź dla programistów. Cieszę się, że masz swój udział w społeczności.
13

Nie wiem, skąd pochodzi twierdzenie, że „nie wykonuje i często nie może przeprowadzić analizy statycznej”. Pierwsza część tego twierdzenia jest po prostu błędna. Drugi zależy od tego, co rozumiesz przez „często”. Wolałbym powiedzieć, że często wykonuje analizę statyczną, a rzadko zawodzi. W zwykłej aplikacji biznesowej rzadko staje się o wiele bliżej nigdy .

Oto pierwsza korzyść:

Korzyści 1: analiza statyczna

Zwykłe twierdzenia i sprawdzanie argumentów mają wadę: są one odraczane do momentu wykonania kodu. Z drugiej strony, umowy kodowe przejawiają się na znacznie wcześniejszym poziomie, albo na etapie kodowania, albo podczas kompilacji aplikacji. Im wcześniej błąd zostanie wykryty, tym taniej go naprawić.

Korzyści 2: rodzaj zawsze aktualnej dokumentacji

Umowy kodeksowe dostarczają również pewnego rodzaju dokumentacji, która jest zawsze aktualna. Jeśli komentarz XML tej metody SetProductPrice(int newPrice)mówi, że newPricepowinna być lepsza lub równa zero, możesz mieć nadzieję, że dokumentacja jest aktualna, ale możesz również odkryć, że ktoś zmienił metodę, aby newPrice = 0rzucić ArgumentOutOfRangeException, ale nigdy nie zmieniła odpowiedniej dokumentacji. Biorąc pod uwagę korelację między umowami kodu a samym kodem, nie masz problemu z brakiem synchronizacji dokumentacji.

Dokumentacja dostarczana przez kontrakty kodowe jest również cenna, ponieważ często komentarze XML nie wyjaśniają dobrze dopuszczalnych wartości. Ile razy ja zastanawiałem się, czy nulllub string.Emptyczy \r\njest autoryzowanym wartość dla metody i komentarze XML milczeli na ten temat!

Podsumowując, bez umów kodowych wiele fragmentów kodu wygląda tak:

Zaakceptuję niektóre wartości, ale nie inne, ale będziesz musiał zgadnąć lub przeczytać dokumentację, jeśli taka istnieje. W rzeczywistości nie czytaj dokumentacji: jest nieaktualna. Wystarczy przejrzeć wszystkie wartości, a zobaczysz te, które zmuszają mnie do rzucania wyjątków. Musisz także odgadnąć zakres wartości, które mogą zostać zwrócone, ponieważ nawet gdybym powiedział ci coś więcej na ich temat, może to nie być prawda, biorąc pod uwagę setki zmian wprowadzonych w ciągu ostatnich lat.

Dzięki umowom kodowym staje się:

Argument tytułowy może być łańcuchem innym niż null o długości 0..500. Poniższa liczba całkowita jest wartością dodatnią, która może wynosić zero tylko wtedy, gdy łańcuch jest pusty. Wreszcie zwrócę IDefinitionobiekt, nigdy nie będzie zerowy.

Korzyści 3: umowy dotyczące interfejsów

Trzecią korzyścią jest to, że umowy kodowe wzmacniają interfejsy. Powiedzmy, że masz coś takiego:

public interface ICommittable
{
    public ICollection<AtomicChange> PendingChanges { get; }

    public void CommitChanges();

    ...
}

W jaki sposób, korzystając wyłącznie z twierdzeń i wyjątków, gwarantujesz, że CommitChangesmożna je wywołać tylko wtedy, gdy PendingChangesnie jest puste? Jak byś zagwarantował, że PendingChangesnigdy tak nie jest null?

Korzyści 4: egzekwowanie wyników metody

Wreszcie czwartą korzyścią jest możliwość Contract.Ensureuzyskania wyników. Co jeśli, pisząc metodę zwracającą liczbę całkowitą, chcę mieć pewność, że wartość nigdy nie jest gorsza ani równa zero? W tym pięć lat później, po wielu zmianach od wielu programistów? Gdy tylko metoda ma wiele punktów powrotu, Assertstaje się koszmarem konserwacyjnym.


Rozważ umowy o kod nie tylko jako sposób na poprawność kodu, ale także na bardziej rygorystyczny sposób pisania kodu. W podobny sposób osoba, która używa wyłącznie języków dynamicznych, może zapytać, dlaczego wymuszać typy na poziomie językowym, podczas gdy możesz zrobić to samo w asercjach, gdy jest to potrzebne. Możesz, ale pisanie statyczne jest łatwiejsze w użyciu, mniej podatne na błędy w porównaniu do szeregu twierdzeń i samo dokumentowania.

Różnica między pisaniem dynamicznym a pisaniem statycznym jest bardzo zbliżona do różnicy między zwykłym programowaniem a programowaniem na podstawie umów.

MainMa
źródło
3

Wiem, że to trochę za późno na pytanie, ale jest jeszcze jedna korzyść z Code Contracts, o której należy wspomnieć

Kontrakty są sprawdzane poza metodą

Kiedy w naszej metodzie mamy warunki ochrony, jest to miejsce, w którym odbywa się sprawdzanie i gdzie zgłaszane są błędy. Być może będziemy musieli zbadać ślad stosu, aby dowiedzieć się, gdzie jest rzeczywiste źródło błędu.

Kontrakty kodu znajdują się w obrębie metody, ale warunki wstępne są sprawdzane poza metodą, gdy cokolwiek próbuje wywołać tę metodę. Kontrakty stają się częścią metody i określają, czy można ją wywołać, czy nie. Oznacza to, że otrzymujemy zgłoszony błąd znacznie bliżej faktycznego źródła problemu.

Jeśli masz metodę chronioną umową, która jest wymagana dla wielu miejsc, może to być realna korzyść.

Alex White
źródło
2

Dwie rzeczy naprawdę:

  1. Wsparcie narzędziowe, VS może tworzyć dane kontraktowe oprócz inteligencji, dzięki czemu jest to część automatycznej pomocy.
  2. Gdy podklasa zastępuje metodę, tracisz wszystkie czeki (które powinny być nadal ważne, jeśli postępujesz zgodnie z LSP) z umowami automatycznie podążają za swoimi klasami potomnymi.
kamieniste
źródło