Od około roku jestem profesjonalnym inżynierem oprogramowania, kończąc studia z tytułem CS. Przez jakiś czas wiedziałem o asercjach w C ++ i C, ale do niedawna nie miałem pojęcia, że istnieją w C # i .NET.
Nasz kod produkcyjny nie zawiera żadnych stwierdzeń, a moje pytanie brzmi:
Czy powinienem zacząć korzystać z Asserts w naszym kodzie produkcyjnym? A jeśli tak, to kiedy jest najbardziej odpowiednie? Czy miałoby to sens
Debug.Assert(val != null);
lub
if ( val == null )
throw new exception();
language-agnostic
exception
testing
assertions
defensive-programming
Nicholas Mancuso
źródło
źródło
Odpowiedzi:
W debugowaniu aplikacji Microsoft .NET 2.0 John Robbins ma dużą sekcję dotyczącą asercji. Jego główne punkty to:
PS: Jeśli podobało ci się Code Complete, polecam kontynuację tej książki. Kupiłem go, aby dowiedzieć się o korzystaniu z WinDBG i zrzutach plików, ale pierwsza połowa zawiera mnóstwo wskazówek, które pomogą uniknąć błędów.
źródło
Debug.Assert
Vs.Trace.Assert
. Ten ostatni jest wykonywany zarówno w wersji Release, jak i w wersji Debug.Umieść
Debug.Assert()
wszędzie w kodzie, w którym chcesz mieć kontrolę poprawności, aby zapewnić niezmienniki. Podczas kompilacji kompilacji wydania (tj. BezDEBUG
stałej kompilatora) wywołaniaDebug.Assert()
zostaną usunięte, aby nie wpływały na wydajność.Przed zadzwonieniem nadal powinieneś zgłaszać wyjątki
Debug.Assert()
. Aser tylko upewnia się, że wszystko jest zgodne z oczekiwaniami, gdy wciąż się rozwijasz.źródło
Z kodu ukończonego
źródło
FWIW ... Uważam, że moje metody publiczne używają
if () { throw; }
wzorca, aby upewnić się, że metoda jest wywoływana poprawnie. Moje prywatne metody zwykle używająDebug.Assert()
.Chodzi o to, że dzięki moim prywatnym metodom jestem pod kontrolą, więc jeśli zacznę wywoływać jedną z moich prywatnych metod z niepoprawnymi parametrami, to gdzieś złamałem własne założenie - nigdy nie powinienem był do tego stanu. W produkcji te prywatne twierdzenia powinny idealnie być niepotrzebną pracą, ponieważ mam utrzymywać stan wewnętrzny ważny i spójny. Porównaj z parametrami podanymi metodom publicznym, które mogą być wywoływane przez każdego w środowisku wykonawczym: nadal muszę egzekwować ograniczenia parametrów, wprowadzając wyjątki.
Ponadto moje prywatne metody mogą nadal zgłaszać wyjątki, jeśli coś nie działa w czasie wykonywania (błąd sieci, błąd dostępu do danych, złe dane pobrane z usługi strony trzeciej itp.). Moje twierdzenia są po to, aby upewnić się, że nie złamałem własnych wewnętrznych założeń dotyczących stanu obiektu.
źródło
Użyj twierdzeń, aby sprawdzić założenia programistów i wyjątki, aby sprawdzić założenia środowiskowe.
źródło
Gdybym był tobą, zrobiłbym:
Lub, aby uniknąć powtórnego sprawdzania stanu
źródło
Jeśli chcesz mieć Aserty w kodzie produkcyjnym (tj. Wersje kompilacji), możesz użyć Trace.Assert zamiast Debug.Assert.
To oczywiście dodaje koszty ogólne do pliku wykonywalnego produkcji.
Również, jeśli twoja aplikacja działa w trybie interfejsu użytkownika, domyślnie wyświetli się okno dialogowe Asercji, co może być nieco uciążliwe dla użytkowników.
Możesz zastąpić to zachowanie, usuwając DefaultTraceListener: spójrz do dokumentacji Trace.Listeners w MSDN.
W podsumowaniu,
Użyj Debug.Assert swobodnie, aby złapać błędy w kompilacjach debugowania.
Jeśli używasz Trace.Assert w trybie interfejsu użytkownika, prawdopodobnie chcesz usunąć DefaultTraceListener, aby uniknąć przeszkadzania użytkownikom.
Jeśli testowany przez Ciebie warunek nie jest w stanie obsłużyć Twojej aplikacji, prawdopodobnie lepiej jest zgłosić wyjątek, aby mieć pewność, że wykonanie nie będzie kontynuowane. Pamiętaj, że użytkownik może zignorować twierdzenie.
źródło
Aserty służą do wychwytywania błędu programisty (twojego), a nie błędu użytkownika. Powinny być używane tylko wtedy, gdy nie ma szansy, aby użytkownik uruchomił aser. Jeśli piszesz na przykład interfejs API, asercji nie należy używać do sprawdzania, czy argument nie jest pusty w żadnej metodzie, którą użytkownik API mógłby wywołać. Ale można go użyć w prywatnej metodzie, która nie jest ujawniona jako część interfejsu API, aby potwierdzić, że TWÓJ kod nigdy nie przekazuje argumentu zerowego, gdy nie powinien.
Zwykle wolę wyjątki niż stwierdzenia, gdy nie jestem pewien.
źródło
W skrócie
Asserts
są używane do ochrony i sprawdzania ograniczeń projektu według umowy, a mianowicie:Asserts
powinien być przeznaczony wyłącznie do debugowania i kompilacji nieprodukcyjnych. Aserty są zwykle ignorowane przez kompilator w kompilacjach wersji.Asserts
może sprawdzić błędy / nieoczekiwane warunki, które SĄ pod kontrolą twojego systemuAsserts
NIE są mechanizmem do pierwszego sprawdzania poprawności danych wejściowych lub reguł biznesowychAsserts
nie powinien być wykorzystywany do wykrywania nieoczekiwanych warunków środowiskowych (które są poza kontrolą kodu), np. braku pamięci, awarii sieci, awarii bazy danych itp. Chociaż rzadko, należy się spodziewać tych warunków (a kod aplikacji nie może rozwiązać problemów takich jak awaria sprzętu lub wyczerpanie zasobów). Zazwyczaj zgłaszane są wyjątki - aplikacja może wówczas podjąć działania naprawcze (np. Ponowić operację w bazie danych lub w sieci, spróbować zwolnić pamięć podręczną) lub przerwać z gracją, jeśli wyjątek nie może zostać obsłużony.Asserts
- twój kod działa na nieoczekiwanym terytorium. Stosy śladów i zrzutów awaryjnych można użyć do ustalenia, co poszło nie tak.Asercje mają ogromne zalety:
Debug
kompilacjach.... Więcej szczegółów
Debug.Assert
wyraża warunek, który został przyjęty o stanie przez pozostałą część bloku kodu pod kontrolą programu. Może to obejmować stan podanych parametrów, stan elementów instancji klasy lub zwrot z wywołania metody w zakontraktowanym / zaprojektowanym zakresie. Zazwyczaj aserty powinny zawiesić wątek / proces / program ze wszystkimi niezbędnymi informacjami (Trace stosu, Crash Dump itp.), Ponieważ wskazują na obecność błędu lub nieuwzględnionego warunku, który nie został zaprojektowany (tj. Nie próbuj złapać lub obsługiwać awarie asercji), z jednym możliwym wyjątkiem, gdy sama asercja może spowodować więcej szkód niż błąd (np. kontrolerzy ruchu lotniczego nie chcieliby YSOD, gdy statek powietrzny wchodzi na podwodny statek, choć sporne jest, czy kompilacja debugowania powinna zostać wdrożona w produkcja ...)Kiedy należy użyć
Asserts?
- w dowolnym momencie w systemie, bibliotece API lub usłudze, w której dane wejściowe do funkcji lub stanu klasy są uznawane za prawidłowe (np. Gdy weryfikacja została już wykonana na danych wejściowych użytkownika w warstwie prezentacji systemu , klasy biznesowe i warstwy danych zwykle zakładają, że kontrole zerowe, kontrole zakresu, kontrole długości łańcucha itp. na wejściu zostały już wykonane). - CzęsteAssert
kontrole obejmują przypadki, w których nieprawidłowe założenie spowodowałoby dereferencję zerowego obiektu, dzielnik zerowy, przepełnienie arytmetyczne liczbowe lub datowe oraz ogólne poza pasmem / nieprzeznaczone do zachowania (np. Gdyby 32-bitowy int był używany do modelowania wieku człowieka , byłoby rozsądne, abyAssert
wiek faktycznie wynosił od 0 do 125 lub mniej - wartości dla -100 i 10 ^ 10 nie zostały zaprojektowane dla).Zamówienia Kod NET
W NET Stack Umowy kodów może być stosowana , oprócz lub jako alternatywy dla korzystania
Debug.Assert
. Kontrakty kodowe mogą dodatkowo sformalizować sprawdzanie stanu i mogą pomóc w wykrywaniu naruszeń założeń w czasie ~ kompilacji (lub wkrótce potem, jeśli są uruchamiane jako sprawdzenie w tle w środowisku IDE).Dostępne kontrole według umowy (DBC) obejmują:
Contract.Requires
- Zakontraktowane warunki wstępneContract.Ensures
- Zakontraktowane warunki pocztoweInvariant
- Wyraża założenie o stanie obiektu we wszystkich punktach jego życia.Contract.Assumes
- uspokaja moduł sprawdzania statycznego, gdy wykonywane jest wezwanie do metod dekorowanych poza kontraktem.źródło
Głównie nigdy w mojej książce. W zdecydowanej większości przypadków, jeśli chcesz sprawdzić, czy wszystko jest zdrowe, rzuć, jeśli nie jest.
Nie podoba mi się fakt, że sprawia, że kompilacja debugowania funkcjonalnie różni się od kompilacji wydania. Jeśli potwierdzenie debugowania nie powiedzie się, ale funkcja działa w wersji, to jaki to ma sens? Jest jeszcze lepiej, gdy aserter już dawno opuścił firmę i nikt nie zna tej części kodu. Następnie musisz poświęcić trochę czasu na badanie problemu, aby zobaczyć, czy to naprawdę problem, czy nie. Jeśli jest to problem, to dlaczego osoba nie rzuca się w pierwszej kolejności?
Według mnie to sugeruje użycie Debugowania. Zapewnia, że odraczasz problem komuś innemu, sam sobie z nim poradzisz. Jeśli coś ma być, a nie jest, to rzuć.
Wydaje mi się, że istnieją scenariusze krytyczne pod względem wydajności, w których chcesz zoptymalizować swoje twierdzenia i są one tam przydatne, jednak nie mam jeszcze takiego scenariusza.
źródło
System.Diagnostics.Trace.Assert()
wykonuje się w kompilacji Release, a także w wersji Debug.Zgodnie ze standardem IDesign powinieneś
Jako wyłączenie odpowiedzialności należy wspomnieć, że wdrożenie tego IRL nie było dla mnie praktyczne. Ale to jest ich standard.
źródło
Używaj asercji tylko w przypadkach, w których chcesz usunąć czek dla kompilacji wersji. Pamiętaj, że twoje asercje nie będą działać, jeśli nie skompilujesz w trybie debugowania.
Biorąc pod uwagę twój przykład na zerowanie, jeśli jest w interfejsie API tylko do użytku wewnętrznego, mógłbym użyć asercji. Jeśli jest w publicznym interfejsie API, zdecydowanie użyłbym jawnego sprawdzania i rzucania.
źródło
System.Diagnostics.Trace.Assert()
do wykonania potwierdzenia w kompilacji wydania (produkcyjnej).null
gdy: „Widoczna z zewnątrz metoda usuwa jeden z argumentów referencyjnych bez sprawdzania, czy argument jest pusty ”. W takiej sytuacji metoda lub właściwość powinny wyrzucićArgumentNullException
.Wszystkie twierdzenia powinny być kodem, który można zoptymalizować w celu:
Ponieważ sprawdzanie, co już zakładałeś, jest prawdą. Na przykład:
Powyżej istnieją trzy różne podejścia do parametrów zerowych. Pierwszy akceptuje to jako dozwolone (po prostu nic nie robi). Drugi zgłasza wyjątek dla obsługi kodu wywołującego (lub nie, co powoduje komunikat o błędzie). Trzeci zakłada, że tak się nie stanie, i twierdzi, że tak jest.
W pierwszym przypadku nie ma problemu.
W drugim przypadku występuje problem z kodem wywołującym - nie powinien był zadzwonić
GetFirstAndConsume
z wartością NULL, więc otrzymano wyjątek.W trzecim przypadku występuje problem z tym kodem, ponieważ należało już było sprawdzić, czy
en != null
przed jego wywołaniem, aby nieprawda była błędem. Innymi słowy, powinien to być kod, który teoretycznie można zoptymalizowaćDebug.Assert(true)
, sicneen != null
zawsze powinno byćtrue
!źródło
en == null
w produkcji? Czy prawdopodobnie mówisz, że nieen == null
może się to zdarzyć podczas produkcji (ponieważ program został dokładnie debugowany)? Jeśli tak, to przynajmniej służy jako alternatywa dla komentarza. Oczywiście, jeśli zostaną wprowadzone przyszłe zmiany, nadal będzie miało wartość dla wykrycia możliwej regresji.Debug.Assert(en != null)
Debug.Assert()
są usuwane w kompilacji wydania. Tak więc, jeśli się mylisz, w trzecim przypadku nie będziesz o tym wiedział w produkcji (zakładając użycie wersji Release w produkcji). Jednak zachowanie pierwszego i drugiego przypadku jest identyczne w kompilacjach Debug i Release.Pomyślałem, że dodam jeszcze cztery przypadki, w których Debug.Assert może być właściwym wyborem.
1) Nie wspomniałem tutaj o dodatkowym zasięgu koncepcyjnym, który Asserts może zapewnić podczas testów automatycznych . Jako prosty przykład:
Gdy jakiś program wywołujący wyższego poziomu zostanie zmodyfikowany przez autora, który uważa, że rozszerzył zakres kodu w celu obsługi dodatkowych scenariuszy, najlepiej (!) Napisze testy jednostkowe, aby objąć ten nowy warunek. Może się zdarzyć, że w pełni zintegrowany kod będzie działał poprawnie.
Jednak w rzeczywistości wprowadzono subtelną wadę, ale nie została wykryta w wynikach testu. Odbiorca stał się w tym przypadku niedeterministyczny i zdarza się tylko , że zapewnia oczekiwany wynik. A może spowodował błąd zaokrąglenia, który nie został zauważony. Lub spowodował błąd, który został wyrównany w innym miejscu. Lub przyznany nie tylko żądany dostęp, ale dodatkowe uprawnienia, których nie należy przyznawać. Itp.
W tym momencie instrukcje Debug.Assert () zawarte w odbiorniku w połączeniu z nowym przypadkiem (lub przypadkiem krawędzi) sterowanym przez testy jednostkowe mogą dostarczyć bezcenne powiadomienie podczas testu, że założenia autora zostały unieważnione, a kod nie powinien zostanie wydany bez dodatkowej recenzji. Aserty z testami jednostkowymi są idealnymi partnerami.
2) Dodatkowo, niektóre testy są proste do napisania, ale są drogie i niepotrzebne, biorąc pod uwagę początkowe założenia . Na przykład:
Jeśli dostęp do obiektu można uzyskać tylko z pewnego bezpiecznego punktu wejścia, czy należy wykonać dodatkowe zapytanie do bazy danych praw sieciowych z każdej metody obiektowej, aby upewnić się, że osoba dzwoniąca ma uprawnienia? Na pewno nie. Być może idealne rozwiązanie obejmuje buforowanie lub inne rozszerzenia funkcji, ale projekt tego nie wymaga. Debug.Assert () natychmiast pokaże, kiedy obiekt zostanie dołączony do niepewnego punktu wejścia.
3) Następnie, w niektórych przypadkach, produkt może nie mieć żadnej użytecznej interakcji diagnostycznej dla wszystkich lub części swoich operacji, gdy zostanie wdrożony w trybie wydania . Na przykład:
Załóżmy, że jest to wbudowane urządzenie czasu rzeczywistego. Zgłaszanie wyjątków i restartowanie, gdy napotka zniekształcony pakiet, przynosi efekt przeciwny do zamierzonego. Zamiast tego urządzenie może czerpać korzyści z najlepszej pracy, nawet do poziomu generowania szumu na wyjściu. Może również nie mieć interfejsu człowieka, urządzenia rejestrującego, a nawet być fizycznie dostępny dla człowieka, gdy zostanie wdrożony w trybie zwolnienia, a świadomość błędów najlepiej zapewnić, oceniając ten sam wynik. W tym przypadku liberalne Asercje i dokładne testy przedpremierowe są cenniejsze niż wyjątki.
4) Wreszcie, niektóre testy są niepotrzebne tylko dlatego, że odbiorca jest postrzegany jako wyjątkowo niezawodny . W większości przypadków, im bardziej kod wielokrotnego użytku, tym więcej wysiłku włożono w uczynienie go niezawodnym. Dlatego często występuje wyjątek w przypadku nieoczekiwanych parametrów wywołujących, ale w przypadku nieoczekiwanych wyników wywoływanych - Asert. Na przykład:
Jeśli podstawowa
String.Find
operacja stwierdza, że zwróci a,-1
gdy kryteria wyszukiwania nie zostaną znalezione, możesz być w stanie bezpiecznie wykonać jedną operację zamiast trzech. Jeśli jednak rzeczywiście powróci-2
, możesz nie mieć rozsądnego sposobu działania. Przydałoby się zastąpienie prostszego obliczenia tym, które testuje osobno-1
wartość, i nieuzasadnione w większości środowisk wydania, aby zaśmiecić kod testami zapewniającymi, że biblioteki podstawowe działają zgodnie z oczekiwaniami. W tym przypadku Aserty są idealne.źródło
Cytat zaczerpnięty z The Pragmatic Programmer: Od Journeyman to Master
źródło
Zawsze należy stosować drugie podejście (rzucanie wyjątków).
Również jeśli jesteś w trakcie produkcji (i masz wersję kompilacji), lepiej rzucić wyjątek (i pozwolić aplikacji na awarię w najgorszym przypadku) niż pracować z nieprawidłowymi wartościami i być może zniszczyć dane klienta (co może kosztować tysiące dolarów).
źródło
Powinieneś użyć Debug.Assert do testowania błędów logicznych w swoich programach. Kompilator może jedynie informować o błędach składniowych. Dlatego zdecydowanie należy użyć instrukcji Assert do testowania błędów logicznych. Na przykład testowanie programu, który sprzedaje samochody, które tylko niebieskie BMW powinny otrzymać 15% zniżki. Kompilator nie może powiedzieć ci nic o tym, czy Twój program jest logicznie poprawny w wykonywaniu tego, ale instrukcja assert może.
źródło
Przeczytałem tutaj odpowiedzi i pomyślałem, że powinienem dodać ważne rozróżnienie. Istnieją dwa bardzo różne sposoby wykorzystywania twierdzeń. Jednym z nich jest tymczasowy skrót programisty do „To nie powinno się tak naprawdę wydarzyć, więc jeśli to da mi znać, żebym mógł zdecydować, co robić”, coś w rodzaju warunkowego punktu przerwania dla przypadków, w których Twój program jest w stanie kontynuować. Drugi to sposób na założenie w kodzie założeń dotyczących poprawnych stanów programu.
W pierwszym przypadku twierdzenia nawet nie muszą znajdować się w ostatecznym kodzie. Powinieneś używać
Debug.Assert
podczas programowania i możesz je usunąć, jeśli / kiedy nie są już potrzebne. Jeśli chcesz je zostawić lub zapomnisz je usunąć, nie ma problemu, ponieważ nie będą miały żadnych konsekwencji w kompilacjach wersji.Ale w drugim przypadku twierdzenia są częścią kodu. Zapewniają, że twoje założenia są prawdziwe, a także je dokumentują. W takim przypadku naprawdę chcesz zostawić je w kodzie. Jeśli program jest w niepoprawnym stanie, nie powinno być dozwolone kontynuowanie. Jeśli nie stać Cię na wydajność, nie będziesz używać C #. Z jednej strony przydatne może być dołączenie debuggera, jeśli tak się stanie. Z drugiej strony, nie chcesz, aby ślad stosu pojawiał się na użytkownikach i być może ważniejsze, aby nie mogli go zignorować. Poza tym, jeśli jest w usłudze, zawsze będzie ignorowany. Dlatego podczas produkcji poprawnym działaniem byłoby zgłoszenie wyjątku i użycie normalnej obsługi wyjątków w programie, co może pokazać użytkownikowi miły komunikat i zapisać szczegóły.
Trace.Assert
ma idealny sposób na osiągnięcie tego. Nie zostanie usunięty podczas produkcji i można go skonfigurować z różnymi odbiornikami za pomocą app.config. Tak więc w przypadku programowania domyślny moduł obsługi jest w porządku, a do produkcji można utworzyć prosty TraceListener, taki jak poniżej, który zgłasza wyjątek i aktywuje go w produkcyjnym pliku konfiguracyjnym.A w pliku konfiguracji produkcji:
źródło
Nie wiem, jak to jest w C # i .NET, ale w C będzie działać assert () tylko jeśli zostanie skompilowany z -DDEBUG - użytkownik końcowy nigdy nie zobaczy assert (), jeśli zostanie skompilowany bez. To jest tylko dla programistów. Używam go bardzo często, czasem łatwiej jest wyśledzić błędy.
źródło
Nie użyłbym ich w kodzie produkcyjnym. Zgłaszaj wyjątki, łap i rejestruj.
Trzeba także zachować ostrożność w asp.net, ponieważ aser może pojawić się na konsoli i zawiesić żądania.
źródło