Jestem wielkim fanem pisania assert
czeków w kodzie C ++ jako sposobu na złapanie przypadków podczas programowania, które nie mogą się zdarzyć, ale zdarzają się z powodu błędów logicznych w moim programie. Jest to ogólnie dobra praktyka.
Zauważyłem jednak, że niektóre funkcje, które piszę (które są częścią złożonej klasy) mają 5+ twierdzeń, co wydaje się być potencjalną złą praktyką programistyczną, jeśli chodzi o czytelność i łatwość konserwacji. Myślę, że nadal jest świetny, ponieważ każdy wymaga ode mnie myślenia o warunkach wstępnych i końcowych funkcji, a one naprawdę pomagają wykrywać błędy. Chciałem jednak po prostu to przedstawić i zapytać, czy istnieją lepsze paradygmaty wychwytywania błędów logicznych w przypadkach, gdy konieczna jest duża liczba kontroli.
Komentarz Emacsa : Ponieważ Emacs jest moim IDE do wyboru, mam nieco wyszarzyć stwierdzenia, które pomagają zmniejszyć poczucie bałaganu, które mogą zapewnić. Oto, co dodaję do mojego pliku .emacs:
; gray out the "assert(...)" wrapper
(add-hook 'c-mode-common-hook
(lambda () (font-lock-add-keywords nil
'(("\\<\\(assert\(.*\);\\)" 1 '(:foreground "#444444") t)))))
; gray out the stuff inside parenthesis with a slightly lighter color
(add-hook 'c-mode-common-hook
(lambda () (font-lock-add-keywords nil
'(("\\<assert\\(\(.*\);\\)" 1 '(:foreground "#666666") t)))))
źródło
Odpowiedzi:
Widziałem setki błędów, które zostałyby rozwiązane szybciej, gdyby ktoś napisał więcej twierdzeń, i ani jednego, który można by rozwiązać szybciej, pisząc mniej .
Być może problemem może być czytelność - chociaż z mojego doświadczenia wynika, że ludzie, którzy piszą dobre stwierdzenia, również piszą czytelny kod. I nigdy nie przeszkadza mi, że początek funkcji zaczyna się od bloku twierdzeń, aby sprawdzić, czy argumenty nie są śmieciami - po prostu wstaw za nim pustą linię.
Z mojego doświadczenia wynika, że łatwość konserwacji jest zawsze poprawiana przez twierdzenia, tak jak w testach jednostkowych. Aserty zapewniają sprawdzanie poprawności, czy kod jest używany zgodnie z jego przeznaczeniem.
źródło
Oczywiście, że tak. [Wyobraź sobie tutaj nieznośny przykład.] Jednak stosując się do poniższych wskazówek, nie powinieneś mieć problemów z przekraczaniem tego limitu w praktyce. Jestem także wielkim fanem twierdzeń i używam ich zgodnie z tymi zasadami. Wiele z tych rad nie dotyczy w szczególności twierdzeń, ale dotyczyło ich jedynie ogólnej dobrej praktyki inżynierskiej.
Pamiętaj o czasie pracy i obciążeniu binarnym
Asercje są świetne, ale jeśli spowodują, że twój program będzie zbyt wolny, będzie albo bardzo denerwujący, albo wcześniej lub później je wyłączysz.
Chciałbym oszacować koszt asercji w stosunku do kosztu funkcji, w której się ona zawiera. Rozważ następujące dwa przykłady.
Sama funkcja jest operacją O (1), ale twierdzenia uwzględniają obciążenie ogólne O ( n ). Nie sądzę, byś chciał, aby takie kontrole były aktywne, chyba że w bardzo szczególnych okolicznościach.
Oto kolejna funkcja z podobnymi twierdzeniami.
Sama funkcja jest operacją O ( n ), więc znacznie mniej boli dodanie dodatkowego obciążenia O ( n ) dla asercji. Spowolnienie funkcji o mały (w tym przypadku prawdopodobnie mniejszy niż 3) stały czynnik jest czymś, na co zwykle możemy sobie pozwolić w kompilacji debugowania, ale może nie w kompilacji wydania.
Teraz rozważ ten przykład.
Chociaż wiele osób prawdopodobnie będzie o wiele wygodniej z tym twierdzeniem O (1) niż z dwoma twierdzeniami O ( n ) z poprzedniego przykładu, są one moim zdaniem moralnie równoważne. Każdy z nich dodaje narzut w kolejności złożoności samej funkcji.
Wreszcie istnieją „naprawdę tanie” twierdzenia, które są zdominowane przez złożoność funkcji, w których się znajdują.
Tutaj mamy dwie asercje O (1) w funkcji O ( n ). Zapewne nie będzie problemu z utrzymaniem tego obciążenia nawet w kompilacjach wersji.
Należy jednak pamiętać, że asymptotyczne złożoności nie zawsze dają odpowiednie oszacowanie, ponieważ w praktyce zawsze mamy do czynienia z wielkościami wejściowymi ograniczonymi przez pewne skończone stałe i stałe czynniki ukryte przez „Big- O ”, które mogą być bardzo nieistotne.
Więc teraz zidentyfikowaliśmy różne scenariusze, co możemy z nimi zrobić? (Prawdopodobnie zbyt) łatwym podejściem byłoby przestrzeganie zasady, takiej jak: „Nie używaj twierdzeń, które dominują w funkcji, w której są zawarte”. Chociaż może to działać w przypadku niektórych projektów, inne mogą wymagać bardziej zróżnicowanego podejścia. Można to zrobić za pomocą różnych makr asercji dla różnych przypadków.
Teraz można korzystać z trzech makr
MY_ASSERT_LOW
,MY_ASSERT_MEDIUM
aMY_ASSERT_HIGH
zamiast standardowej biblioteki za „jeden rozmiar dla wszystkich”assert
makro dla twierdzeń, które są zdominowane przez ani zdominowanych przez nie dominuje i dominuje złożoność ich funkcji zawierającej odpowiednio. Podczas budowania oprogramowania można wstępnie zdefiniować symbol preprocesora,MY_ASSERT_COST_LIMIT
aby wybrać, jakie twierdzenia powinny uczynić go plikiem wykonywalnym. StałeMY_ASSERT_COST_NONE
iMY_ASSERT_COST_ALL
nie odpowiadają żadnym makrom asercyjnym i powinny być używane jako wartości dlaMY_ASSERT_COST_LIMIT
w celu odpowiednio włączenia lub włączenia wszystkich asercji.Opieramy się na założeniu, że dobry kompilator nie wygeneruje żadnego kodu
i przekształcić
w
które moim zdaniem jest obecnie bezpiecznym założeniem.
Jeśli masz zamiar dostosować powyższy kod, należy rozważyć adnotacje kompilatora specyficzne jak
__attribute__ ((cold))
namy::assertion_failed
lub__builtin_expect(…, false)
na!(CONDITION)
zmniejszenie narzutu minęły twierdzeń. W kompilacjach wersji można również rozważyć zamianę wywołania funkcji namy::assertion_failed
coś, na przykład w__builtin_trap
celu zmniejszenia obciążenia, co utrudnia utratę komunikatu diagnostycznego.Tego rodzaju optymalizacje są tak naprawdę istotne tylko w wyjątkowo tanich asercjach (takich jak porównywanie dwóch liczb całkowitych, które już podano jako argumenty) w funkcji, która sama w sobie jest bardzo zwarta, nie biorąc pod uwagę dodatkowego rozmiaru pliku binarnego nagromadzonego przez włączenie wszystkich ciągów komunikatów.
Porównaj jak ten kod
jest wkompilowany w następujący zestaw
podczas gdy następujący kod
daje to zgromadzenie
z czym czuję się znacznie bardziej komfortowo. (Przykłady testowano GCC 5.3.0 użyciu
-std=c++14
,-O3
i-march=native
flagi 4.3.3-2-Arch x86_64 GNU / Linux. Nie pokazane na powyższych fragmentów jest zadeklarowanetest::positive_difference_1st
, atest::positive_difference_2nd
które dodaje się__attribute__ ((hot))
do.my::assertion_failed
Uznano z__attribute__ ((cold))
).Zapewnij warunki wstępne w funkcji, która zależy od nich
Załóżmy, że masz określoną funkcję z określoną umową.
Zamiast pisać
w każdej witrynie wywołującej umieść tę logikę raz w definicji
count_letters
i nazywaj to bez zbędnych ceregieli.
Ma to następujące zalety.
assert
instrukcji w kodzie.Oczywistą wadą jest to, że nie dostaniesz lokalizacji źródłowej strony wywołującej w komunikacie diagnostycznym. Uważam, że jest to drobny problem. Dobry debugger powinien być w stanie w wygodny sposób prześledzić pochodzenie naruszenia umowy.
To samo dotyczy „specjalnych” funkcji, takich jak przeciążone operatory. Kiedy piszę iteratory, zwykle - jeśli pozwala na to charakter iteratora - nadaję im funkcję członka
pozwala to zapytać, czy można bezpiecznie odrzucić iterator. (Oczywiście w praktyce prawie zawsze można jedynie zagwarantować, że nie będzie bezpiecznie wyłapywać iteratora. Ale uważam, że dzięki tej funkcji nadal można złapać wiele błędów.) Zamiast zaśmiecać cały mój kod który używa iteratora z
assert(iter.good())
instrukcjami, wolałbym umieścić jedenassert(this->good())
jako pierwszy wierszoperator*
implementacji iteratora.Jeśli używasz biblioteki standardowej, zamiast ręcznie sprawdzać jej warunki wstępne w kodzie źródłowym, włącz ich sprawdzanie w kompilacjach debugowania. Mogą wykonywać nawet bardziej skomplikowane kontrole, takie jak testowanie, czy kontener, do którego odwołuje się iterator, nadal istnieje. (Aby uzyskać więcej informacji, zobacz dokumentację libstdc ++ i libc ++ (prace w toku)).
Uwzględnij wspólne warunki
Załóżmy, że piszesz pakiet algebry liniowej. Wiele funkcji będzie miało skomplikowane warunki wstępne, a ich naruszenie często spowoduje nieprawidłowe wyniki, których nie można natychmiast rozpoznać. Byłoby bardzo dobrze, gdyby te funkcje spełniały swoje warunki wstępne. Jeśli zdefiniujesz kilka predykatów, które mówią ci pewne właściwości dotyczące struktury, te twierdzenia stają się znacznie bardziej czytelne.
Daje także bardziej przydatne komunikaty o błędach.
pomaga o wiele bardziej niż, powiedzmy
gdzie najpierw trzeba spojrzeć na kod źródłowy w kontekście, aby dowiedzieć się, co faktycznie zostało przetestowane.
Jeśli masz
class
nietrywialne niezmienniki, prawdopodobnie dobrym pomysłem jest od czasu do czasu twierdzenie, że popsułeś stan wewnętrzny i chcesz upewnić się, że pozostawiasz obiekt w prawidłowym stanie po powrocie.W tym celu uznałem za użyteczne zdefiniowanie
private
funkcji składowej, którą konwencjonalnie nazywamclass_invaraiants_hold_
. Załóżmy, że dokonałeś ponownej implementacjistd::vector
(ponieważ wszyscy wiemy, że to nie wystarczy). Może mieć taką funkcję.Zwróć uwagę na kilka rzeczy na ten temat.
const
inoexcept
zgodnie z wytycznymi, które twierdzenia nie mają skutków ubocznych. Jeśli ma to sens, również to zadeklarujconstexpr
.assert(this->class_invariants_hold_())
. W ten sposób, jeśli asercje zostaną skompilowane, możemy być pewni, że nie zostaną narzucone żadne nakłady czasu wykonywania.if
instrukcji zawierających wczesnereturn
s zamiast dużego wyrażenia. Ułatwia to przejście przez funkcję w debuggerze i sprawdzenie, która część niezmiennika została uszkodzona, jeśli asercja zostanie uruchomiona.Nie przejmuj się głupotami
Niektóre rzeczy po prostu nie mają sensu potwierdzać.
Te twierdzenia nie czynią kodu nawet odrobinę bardziej czytelnym lub łatwiejszym do uzasadnienia. Każdy programista C ++ powinien być wystarczająco pewny, jak
std::vector
działa, aby mieć pewność, że powyższy kod jest poprawny, po prostu patrząc na niego. Nie twierdzę, że nigdy nie powinieneś twierdzić o rozmiarze pojemnika. Jeśli dodałeś lub usunąłeś elementy za pomocą nietrywialnego przepływu sterowania, takie twierdzenie może być przydatne. Ale jeśli tylko powtórzy to, co zostało napisane powyżej w kodzie niepotwierdzającym, nie zyska żadnej wartości.Nie twierdzę również, że funkcje biblioteczne działają poprawnie.
Jeśli tak mało ufasz bibliotece, lepiej zamiast tego użyj innej biblioteki.
Z drugiej strony, jeśli dokumentacja biblioteki nie jest w 100% przejrzysta i zyskujesz pewność co do jej umów poprzez czytanie kodu źródłowego, sensowne jest twierdzenie o tej „wywnioskowanej umowie”. Jeśli zostanie zepsuty w przyszłej wersji biblioteki, szybko to zauważysz.
Jest to lepsze niż poniższe rozwiązanie, które nie powie ci, czy twoje założenia były prawidłowe.
Nie nadużywaj twierdzeń do implementacji logiki programu
Asercje powinny być zawsze wykorzystywane do wykrywania błędów, które są warte natychmiastowego zabicia twojej aplikacji. Nie należy ich używać do weryfikacji żadnego innego warunku, nawet jeśli odpowiednia reakcja na ten warunek byłaby również natychmiastowa.
Dlatego napisz to ...
…zamiast tego.
Również nigdy nie używać do sprawdzania poprawności twierdzeń wejście niezaufane lub sprawdzić, czy
std::malloc
niereturn
jesteśnullptr
. Nawet jeśli wiesz, że nigdy nie wyłączysz asercji, nawet w kompilacjach wersji, asercja informuje czytelnika, że sprawdza coś, co jest zawsze prawdziwe, biorąc pod uwagę, że program jest wolny od błędów i nie ma widocznych efektów ubocznych. Jeśli nie jest to komunikat, który chcesz przekazać, użyj alternatywnego mechanizmu obsługi błędów, takiego jakthrow
zgłoszenie wyjątku. Jeśli uznasz, że wygodnie jest mieć opakowanie makr do kontroli niepotwierdzania, napisz je. Po prostu nie nazywaj tego „twierdzeniem”, „zakładaniem”, „wymaganiem”, „zapewnieniem” lub czymś takim. Jego wewnętrzna logika może być taka sama jakassert
, z tym wyjątkiem, że oczywiście nigdy nie jest skompilowana.Więcej informacji
Znalazłem John Lakos' talk Defensive Programowanie zrobione dobrze , zważywszy na CppCon'14 ( 1 st strony , 2 nd części ) bardzo wiecania. Pomysł dostosowywania włączanych asercji i reagowania na nieudane wyjątki jest jeszcze większy niż w tej odpowiedzi.
źródło
Assertions are great, but ... you will turn them off sooner or later.
- Mam nadzieję, że wcześniej, tak jak przed wysłaniem kodu. Rzeczy, które muszą spowodować, że program zginie w produkcji, powinny być częścią „prawdziwego” kodu, a nie twierdzeń.Uważam, że z czasem piszę mniej stwierdzeń, ponieważ wiele z nich to „działa kompilator”, a „działa biblioteka”. Gdy zaczniesz myśleć o tym, co dokładnie testujesz, podejrzewam, że napiszesz mniej stwierdzeń.
Na przykład metoda, która (powiedzmy) dodaje coś do kolekcji, nie powinna wymagać stwierdzenia, że kolekcja istnieje - jest to na ogół albo warunek wstępny klasy, która jest właścicielem komunikatu, albo błąd krytyczny, który powinien wrócić do użytkownika . Więc sprawdź to raz, bardzo wcześnie, a następnie załóż to.
Zapewnienia są dla mnie narzędziem do debugowania i zazwyczaj używam ich na dwa sposoby: znajdowanie błędu na moim biurku (i nie są one sprawdzane. Cóż, być może ten jeden klucz może być); i znajdowanie błędu na biurku klienta (i oni się meldują). Za każdym razem używam asercji głównie do generowania śladu stosu po wymuszeniu wyjątku jak najwcześniej. Należy pamiętać, że asercje używane w ten sposób mogą łatwo prowadzić do heisenbugs - błąd może nigdy nie wystąpić w kompilacji debugowania z włączonymi asercjami .
źródło
Za mało twierdzeń: powodzenia w zmianie tego kodu, który jest pełen ukrytych założeń.
Zbyt wiele stwierdzeń: może prowadzić do problemów z czytelnością i potencjalnie zapachu kodu - czy klasa, funkcja i interfejs API są zaprojektowane, gdy mają tak wiele założeń zawartych w instrukcjach asercji?
Mogą istnieć również twierdzenia, które tak naprawdę nie sprawdzają niczego ani nie sprawdzają rzeczy takich jak ustawienia kompilatora w każdej funkcji: /
Celuj w najsłodsze miejsce, ale nie mniej (jak ktoś już powiedział: „więcej” twierdzeń jest mniej szkodliwych niż posiadanie ich za mało lub niech Bóg nam pomoże - nic).
źródło
Byłoby wspaniale, gdybyś mógł napisać funkcję Assert, która odwoływałaby się tylko do boolowskiej metody CONST, w ten sposób masz pewność, że twoje aserty nie wywołują skutków ubocznych, zapewniając, że do testowania assert zostanie użyta metoda boolean const
wyciągnęłoby to trochę z czytelności, zwłaszcza, że nie sądzę, że nie można przypisać lambda (w c ++ 0x) jako stałej do jakiejś klasy, co oznacza, że nie można do tego używać lambda
przesada, jeśli mnie zapytasz, ale jeśli zacznę widzieć pewien poziom zanieczyszczenia z powodu twierdzeń, będę ostrożny wobec dwóch rzeczy:
źródło
Pisałem w C # o wiele więcej niż w C ++, ale oba języki nie są zbyt daleko od siebie. W .Net używam Asserts dla warunków, które nie powinny się zdarzyć, ale często rzucam wyjątki, gdy nie ma możliwości kontynuacji. Debuger VS2010 pokazuje mi wiele dobrych informacji na temat wyjątku, bez względu na to, jak zoptymalizowana jest wersja Release. Dobrym pomysłem jest także dodanie testów jednostkowych, jeśli możesz. Czasami rejestrowanie jest również dobrym pomysłem na pomoc w debugowaniu.
Czy może być więc zbyt wiele twierdzeń? Tak. Wybór pomiędzy Przerwij / Zignoruj / Kontynuuj 15 razy w ciągu jednej minuty staje się denerwujący. Wyjątek zostaje zgłoszony tylko raz. Trudno jest oszacować punkt, w którym jest zbyt wiele twierdzeń, ale jeśli twoje twierdzenia spełniają rolę twierdzeń, wyjątków, testów jednostkowych i rejestrowania, coś jest nie tak.
Zastrzegłbym twierdzenia dla scenariuszy, które nie powinny się zdarzyć. Możesz początkowo nadmiernie potwierdzać, ponieważ asercje są szybsze do napisania, ale później ponownie uwzględnij kod - zamień niektóre z nich w wyjątki, niektóre w testy itp. Jeśli masz wystarczająco dużo dyscypliny, aby wyczyścić każdy komentarz do zrobienia, pozostaw komentuj obok każdego, który planujesz przerobić, i NIE ZAPOMNIJ, aby zająć się TODO później.
źródło
Chcę z tobą pracować! Ktoś, kto dużo pisze,
asserts
jest fantastyczny. Nie wiem, czy istnieje coś takiego jak „za dużo”. O wiele bardziej powszechne są dla mnie osoby, które piszą za mało i ostatecznie wpadają na sporadyczny śmiertelny problem UB, który pojawia się tylko podczas pełni księżyca, który można łatwo odtworzyć wielokrotnie za pomocą prostegoassert
.Komunikat o błędzie
Jedyną rzeczą, o której mogę myśleć, jest osadzenie informacji o awarii w
assert
przypadku, gdy jeszcze tego nie robisz, na przykład:W ten sposób możesz już nie mieć poczucia, że masz ich zbyt wiele, jeśli jeszcze tego nie robiłeś, ponieważ teraz twoje twierdzenia odgrywają większą rolę w dokumentowaniu założeń i warunków wstępnych.
Skutki uboczne
Oczywiście
assert
można faktycznie nadużywać i wprowadzać błędy, takie jak:... jeśli
foo()
wywołuje skutki uboczne, powinieneś być bardzo ostrożny, ale jestem pewien, że już jesteś tym, który zapewnia bardzo liberalnie („doświadczony aserter”). Mamy nadzieję, że twoja procedura testowania jest równie dobra, jak staranna dbałość o założenia.Szybkość debugowania
Podczas gdy szybkość debugowania powinna zasadniczo znajdować się na dole naszej listy priorytetów, pewnego razu skończyłem tak dużo w bazie kodu, zanim uruchomienie kompilacji debugowania za pomocą debugera było ponad 100 razy wolniejsze niż wydanie.
Było tak przede wszystkim dlatego, że miałem takie funkcje:
... gdzie każde pojedyncze wezwanie
operator[]
wykonałoby stwierdzenie sprawdzające granice. Skończyło się na tym, że niektóre z tych krytycznych pod względem wydajności zostały zastąpione niebezpiecznymi ekwiwalentami, które nie zapewniają drastycznego przyspieszenia debugowania kompilacji przy niewielkim koszcie tylko bezpieczeństwa na poziomie szczegółowości implementacji, i tylko dlatego, że zaczynała się jego szybkość bardzo zauważalnie obniża produktywność (sprawienie, że szybsze debugowanie przeważa nad kosztem utraty kilku twierdzeń, ale tylko dla funkcji takich jak ta funkcja produktu krzyżowego, która była używana na najbardziej krytycznych, mierzonych ścieżkach, a nieoperator[]
ogólnie).Zasada pojedynczej odpowiedzialności
Chociaż nie sądzę, abyś naprawdę mógł pomylić się z większą liczbą twierdzeń (przynajmniej daleko, o wiele lepiej jest pomylić się po stronie zbyt wielu niż za mało), same twierdzenia mogą nie stanowić problemu, ale mogą wskazywać na jeden.
Jeśli na przykład masz 5 asercji do pojedynczego wywołania funkcji, może to oznaczać zbyt wiele. Jego interfejs może mieć zbyt wiele warunków wstępnych i parametrów wejściowych, np. Uważam, że nie ma to związku z samym tematem tego, co stanowi zdrową liczbę twierdzeń (na które ogólnie odpowiadam, „im więcej, tym lepiej!”), Ale może to być możliwa czerwona flaga (lub bardzo możliwe, że nie).
źródło
Dodanie kontroli do kodu jest bardzo rozsądne. W przypadku zwykłego asertu (wbudowanego w kompilator C i C ++) mój wzorzec użytkowania jest taki, że nieudane asercja oznacza błąd w kodzie, który należy naprawić. Trochę to interpretuję; jeśli spodziewam prośba internetowej, aby powrócić do stanu 200 i dochodzić do niego bez obsługi innych przypadków potem udało twierdzenie jest rzeczywiście pokazać błąd w kodzie, więc assert jest uzasadnione.
Więc kiedy ludzie mówią, że twierdzenie, że tylko sprawdza to, co robi kod, jest zbędne, to nie do końca tak. To twierdzenie sprawdza, co według nich robi kod, a sednem tego stwierdzenia jest sprawdzenie, czy założenie o braku błędu w kodzie jest prawidłowe. Aserta może również służyć jako dokumentacja. Jeśli założę, że po wykonaniu pętli i == n i nie jest to w 100% oczywiste z kodu, pomocne będzie „assert (i == n)”.
Lepiej mieć w swoim repertuarze coś więcej niż „zapewnić”, aby poradzić sobie z różnymi sytuacjami. Na przykład sytuacja, w której sprawdzam, czy coś się nie dzieje, co wskazywałoby na błąd, ale nadal pracuję nad tym warunkiem. (Na przykład, jeśli użyję pamięci podręcznej, mogę sprawdzić, czy nie wystąpiły błędy, a jeśli błąd wystąpi nieoczekiwanie, bezpiecznym rozwiązaniem może być wyrzucenie pamięci podręcznej. Chcę czegoś, co jest prawie twierdzeniem, które mówi mi podczas programowania i nadal pozwala mi kontynuować.
Innym przykładem jest sytuacja, w której nie spodziewam się, że coś się wydarzy, mam ogólne obejście, ale jeśli tak się stanie, chcę o tym wiedzieć i to zbadać. Znowu coś prawie jak twierdzenie, które powinno mi powiedzieć podczas rozwoju. Ale niezupełnie twierdzenie.
Zbyt wiele asercji: jeśli aser powoduje awarię programu, gdy jest on w rękach użytkownika, nie możesz mieć żadnych asercji, które ulegają awarii z powodu fałszywych negatywów.
źródło
To zależy. Jeśli wymagania dotyczące kodu są jasno udokumentowane, to stwierdzenie powinno zawsze odpowiadać wymaganiom. W takim przypadku jest to dobra rzecz. Jeśli jednak nie ma wymagań lub źle napisanych wymagań, nowym programistom byłoby trudno edytować kod bez konieczności odwoływania się do testu jednostkowego za każdym razem, aby dowiedzieć się, jakie są wymagania.
źródło