Kiedy powinienem używać Debug.Assert ()?

220

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();
Nicholas Mancuso
źródło
2
Ustawiona dychotomia jest wskazówką. Nie jest to kwestia ani - ani wyjątków i zapewnień, ani ich, ani kodu obronnego. Kiedy to zrobić, co chcesz zrozumieć.
Casper Leon Nielsen
5
Kiedyś czytałem, że ktoś sugeruje, że wyjątek lub inna metoda awarii jest odpowiednia dla warunków, w których „Nie ma możliwości, aby rozsądnie wyzdrowieć po tym”, a dodatkowo twierdzenie jest odpowiednie dla warunków, w których „To nigdy nie powinno się zdarzyć”. Ale jakie realistyczne okoliczności spełniają te ostatnie warunki, a jednocześnie nie spełniają tych pierwszych? Wychodząc z tła Pythona, w którym twierdzenia pozostają w produkcji, nigdy nie rozumiałem podejścia Java / C # polegającego na wyłączeniu części weryfikacji podczas produkcji. Jedyny przypadek, który naprawdę mogę zobaczyć, to to, czy walidacja jest droga.
Mark Amery
2
Osobiście używam wyjątków dla metod publicznych i zapewnień dla metod prywatnych.
Fred

Odpowiedzi:

230

W debugowaniu aplikacji Microsoft .NET 2.0 John Robbins ma dużą sekcję dotyczącą asercji. Jego główne punkty to:

  1. Twierdzić swobodnie. Nigdy nie możesz mieć zbyt wielu twierdzeń.
  2. Asercje nie zastępują wyjątków. Wyjątki obejmują rzeczy, których wymaga Twój kod; twierdzenia obejmują to, co zakłada.
  3. Dobrze napisane stwierdzenie może powiedzieć nie tylko to, co się wydarzyło i gdzie (jak wyjątek), ale dlaczego.
  4. Komunikat o wyjątku może często być tajemniczy, co wymaga cofnięcia się przez kod w celu odtworzenia kontekstu, który spowodował błąd. Asercja może zachować stan programu w momencie wystąpienia błędu.
  5. Asercje podwojone jako dokumentacja, informujące innych programistów, od jakich domniemanych założeń zależy twój kod.
  6. Okno dialogowe pojawiające się, gdy asercja się nie powiedzie, umożliwia dołączenie debugera do procesu, dzięki czemu można przeszukiwać stos, tak jakbyś umieścił tam punkt przerwania.

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.

Rory MacLeod
źródło
3
+1 za zwięzłe i pomocne podsumowanie. Bardzo bezpośrednio stosowany. Najważniejsze, czego jednak brakuje mi, to kiedy użyć Trace.Assert vs. Trace.Assert. To znaczy, kiedy chcesz / nie chcesz ich w swoim kodzie produkcyjnym.
Jon Coombs
2
JonCoombs to „Trace.Assert vs. Trace.Assert” literówka?
thelem
1
@thelem Może Jon oznaczało Debug.AssertVs. Trace.Assert. Ten ostatni jest wykonywany zarówno w wersji Release, jak i w wersji Debug.
DavidRR
Dlaczego wolę Debug.Assert od zgłaszania wyjątku?
Barış Akkurt
86

Umieść Debug.Assert()wszędzie w kodzie, w którym chcesz mieć kontrolę poprawności, aby zapewnić niezmienniki. Podczas kompilacji kompilacji wydania (tj. Bez DEBUGstałej kompilatora) wywołania Debug.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.

Mark Cidade
źródło
35
Czy możesz wyjaśnić, dlaczego warto zastosować twierdzenie, jeśli nadal rzucasz wyjątek przed wywołaniem go? A może źle zrozumiałem twoją odpowiedź?
Roman Starkov
2
@romkyns Nadal musisz je uwzględnić, ponieważ jeśli tego nie zrobisz, po zbudowaniu projektu w trybie Release wszystkie weryfikacje / sprawdzanie błędów znikną.
Oscar Mederos
28
@Oscar Myślałem, że o to właśnie chodziło w pierwszym rzędzie o asercje ... OK, więc stawiacie wyjątki przed nimi - po co więc położyć te twierdzenia?
Roman Starkov
4
@ superjos: Muszę się nie zgodzić: punkt 2 w odpowiedzi MacLeoda stwierdza, że ​​rzeczywiście potrzebujesz twierdzenia ORAZ wyjątków, ale nie w tym samym miejscu. Bezużyteczne jest rzucanie NullRefEx na zmienną i zaraz po wykonaniu na niej Assert (w tym przypadku metoda assert nigdy nie wyświetli okna dialogowego, co jest sednem assert). MacLeod oznacza, że ​​w niektórych miejscach potrzebujesz wyjątku, w innych wystarczy Asert.
David
1
To może być uciążliwe zinterpretować moją interpretację cudzego odpowiedź :) W każdym razie z tobą na te musisz oboje, i należy nie stawiać wyjątku przed assert. Nie jestem pewien znaczenia słowa „nie w tym samym miejscu”. Ponownie, odmawiając interpretacji, przedstawię tylko moje przemyślenia / preferencje: postawię jeden lub więcej stwierdzeń, aby sprawdzić warunki wstępne przed rozpoczęciem jakiejś operacji lub aby sprawdzić warunki końcowe po operacji. Oprócz twierdzeń, a po nich i tak sprawdź, czy coś poszło nie tak i trzeba zgodzić się na wyjątki.
superjos 30.01.12
52

Z kodu ukończonego

8 Programowanie defensywne

8.2 Twierdzenia

Asercja to kod, który jest używany podczas programowania - zwykle jest to procedura lub makro - który pozwala programowi sprawdzić się podczas działania. Gdy twierdzenie jest prawdziwe, oznacza to, że wszystko działa zgodnie z oczekiwaniami. Gdy ma wartość false, oznacza to, że wykrył nieoczekiwany błąd w kodzie. Na przykład, jeśli system zakłada, że ​​plik informacji o kliencie nigdy nie będzie zawierał więcej niż 50 000 rekordów, program może zawierać stwierdzenie, że liczba rekordów jest mniejsza lub równa 50 000. Dopóki liczba rekordów jest mniejsza lub równa 50 000, twierdzenie będzie milczące. Jeśli napotka więcej niż 50 000 rekordów, głośno „zapewni”, że w programie wystąpił błąd.

Asercje są szczególnie przydatne w dużych, skomplikowanych programach i programach o wysokiej niezawodności. Umożliwiają programistom szybsze usuwanie niezgodnych założeń interfejsu, błędów, które wkradają się podczas modyfikowania kodu itd.

Asercja zwykle przyjmuje dwa argumenty: wyrażenie logiczne, które opisuje założenie, które powinno być prawdziwe, i komunikat do wyświetlenia, jeśli nie jest.

(…)

Zwykle nie chcesz, aby użytkownicy widzieli komunikaty potwierdzające w kodzie produkcyjnym; twierdzenia są przede wszystkim do wykorzystania podczas opracowywania i konserwacji. Asercje są zwykle kompilowane w kodzie w czasie programowania i kompilowane z kodu do produkcji. Podczas opracowywania twierdzenia wypierają sprzeczne założenia, nieoczekiwane warunki, złe wartości przekazywane do procedur i tak dalej. Podczas produkcji są one kompilowane z kodu, dzięki czemu twierdzenia nie obniżają wydajności systemu.

juan
źródło
7
Co się stanie, jeśli plik informacji o kliencie napotkany podczas produkcji zawiera ponad 50 000 rekordów? Jeśli asercja jest skompilowana z kodu produkcyjnego i ta sytuacja nie zostanie rozwiązana w inny sposób, czy to nie oznacza kłopotów?
DavidRR
1
@DavidRR Tak, rzeczywiście. Ale gdy tylko produkcja zasygnalizuje problem i jakiś programista (który może nie znać dobrze tego kodu) debuguje problem, potwierdzenie zakończy się niepowodzeniem, a programista natychmiast dowie się, że system nie jest używany zgodnie z przeznaczeniem.
Marc
48

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.

Nicholas Piasecki
źródło
3
Jest to bardzo jasny opis dobrej praktyki i daje bardzo rozsądną odpowiedź na zadane pytanie.
Casper Leon Nielsen
42

Użyj twierdzeń, aby sprawdzić założenia programistów i wyjątki, aby sprawdzić założenia środowiskowe.

Justin R.
źródło
31

Gdybym był tobą, zrobiłbym:

Debug.Assert(val != null);
if ( val == null )
    throw new exception();

Lub, aby uniknąć powtórnego sprawdzania stanu

if ( val == null )
{
    Debug.Assert(false,"breakpoint if val== null");
    throw new exception();
}
Mark Ingram
źródło
5
Jak to rozwiązuje problem? Dzięki temu debug.assert staje się bezcelowy.
Quibblesome
43
Nie, nie robi - włamuje się do kodu w momencie tuż przed zgłoszeniem wyjątku. Jeśli próbujesz / złapiesz gdzie indziej w swoim kodzie, możesz nawet nie zauważyć wyjątku!
Mark Ingram,
2
+1 Miałem wiele problemów, w których ludzie po prostu próbowali
wychwytywać
5
Sądzę, że są przypadki, w których możesz chcieć to zrobić, ale nigdy nie powinieneś łapać ogólnego wyjątku!
Casebash,
8
@ MarkIngram -1 do twojej odpowiedzi i +1 do twojego komentarza, uzasadniając to. Jest to niezła sztuczka w pewnych szczególnych okolicznościach, ale wydaje się, że jest to dziwna rzecz do zrobienia dla całej weryfikacji.
Mark Amery
24

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.

Joe
źródło
1
+1 za wskazanie kluczowej różnicy między Debug.Assert i Trace.Assert, ponieważ OP konkretnie zapytał o kod produkcyjny.
Jon Coombs
21

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.

użytkownik19113
źródło
11

W skrócie

Asserts są używane do ochrony i sprawdzania ograniczeń projektu według umowy, a mianowicie:

  • Assertspowinien 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 systemu
  • Asserts NIE są mechanizmem do pierwszego sprawdzania poprawności danych wejściowych lub reguł biznesowych
  • Assertsnie 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.
  • Niepowodzenie asercji powinno mieć fatalny wpływ na twój system - tzn. W przeciwieństwie do wyjątku, nie próbuj wychwytywać ani obsługiwać awarii 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:

  • Aby pomóc w znalezieniu brakującej weryfikacji danych wejściowych użytkownika lub błędów poprzedzających w kodzie wyższego poziomu.
  • Twierdzenia w podstawie kodu wyraźnie przekazują czytelnikowi założenia poczynione w kodzie
  • Assert będzie sprawdzany w czasie wykonywania w Debugkompilacjach.
  • Po wyczerpującym przetestowaniu kodu, przebudowanie kodu w wersji Release usunie narzut związany z weryfikacją założenia (z tą korzyścią, że późniejsza kompilacja Debugowania zawsze cofnie kontrole, jeśli zajdzie taka potrzeba).

... Więcej szczegółów

Debug.Assertwyraż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ęste Assertkontrole 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, aby Assertwiek 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 korzystaniaDebug.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ępne
  • Contract.Ensures - Zakontraktowane warunki pocztowe
  • Invariant - 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.
StuartLC
źródło
Niestety, Kontrakty Kodowe są prawie martwe, odkąd MS przestało je rozwijać.
Mike Lowery,
10

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.

Quibblesome
źródło
4
Odpowiedź zasługuje na pewną zasługę, ponieważ podkreślasz niektóre często podnoszone obawy dotyczące nich, fakt, że przerywają sesję debugowania i szansę na fałszywie pozytywne. Brakuje jednak niektórych subtelności i piszesz „optymalizuj z dala od twierdzeń” - co może opierać się tylko na tym, że zgłoszenie wyjątku i wykonanie debug.assert jest tym samym. Nie są, służą one różnym celom i cechom, jak widać w niektórych z wysoko ocenianych odpowiedzi. Dw
Casper Leon Nielsen
+1 za „To, co mi się nie podoba, to fakt, że sprawia, że ​​kompilacja debugowania funkcjonalnie różni się od kompilacji wersji. Jeśli potwierdzenie debugowania nie powiedzie się, ale funkcjonalność działa w wersji, to jaki to ma sens?” W .NET System.Diagnostics.Trace.Assert()wykonuje się w kompilacji Release, a także w wersji Debug.
DavidRR
7

Zgodnie ze standardem IDesign powinieneś

Potwierdź każde założenie. Średnio co piąta linia to stwierdzenie.

using System.Diagnostics;

object GetObject()
{...}

object someObject = GetObject();
Debug.Assert(someObject != null);

Jako wyłączenie odpowiedzialności należy wspomnieć, że wdrożenie tego IRL nie było dla mnie praktyczne. Ale to jest ich standard.

władca
źródło
Wygląda na to, że Juval Lowy lubi się zacytować.
devlord
6

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.

Derek Park
źródło
W .NET można użyć System.Diagnostics.Trace.Assert()do wykonania potwierdzenia w kompilacji wydania (produkcyjnej).
DavidRR
Reguła analizy kodu CA1062: Sprawdzanie poprawności argumentów metod publicznych wymaga sprawdzenia argumentu, nullgdy: „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.
DavidRR
6

Wszystkie twierdzenia powinny być kodem, który można zoptymalizować w celu:

Debug.Assert(true);

Ponieważ sprawdzanie, co już zakładałeś, jest prawdą. Na przykład:

public static void ConsumeEnumeration<T>(this IEnumerable<T> source)
{
  if(source != null)
    using(var en = source.GetEnumerator())
      RunThroughEnumerator(en);
}
public static T GetFirstAndConsume<T>(this IEnumerable<T> source)
{
  if(source == null)
    throw new ArgumentNullException("source");
  using(var en = source.GetEnumerator())
  {
    if(!en.MoveNext())
      throw new InvalidOperationException("Empty sequence");
    T ret = en.Current;
    RunThroughEnumerator(en);
    return ret;
  }
}
private static void RunThroughEnumerator<T>(IEnumerator<T> en)
{
  Debug.Assert(en != null);
  while(en.MoveNext());
}

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 != nullprzed 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), sicne en != nullzawsze powinno być true!

Jon Hanna
źródło
1
A więc, w trzecim przypadku, co się stanie, jeśli będzie en == nullw 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)
DavidRR
@DavidRR, rzeczywiście twierdzę, że nigdy nie może być zerowy, podobnie jak twierdzenie w kodzie, stąd nazwa. Mógłbym oczywiście się mylić lub popełniłem błąd przez zmianę, i taka jest wartość wywołania assert.
Jon Hanna
1
Połączenia z 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.
DavidRR
@DavidRR, co czyni to stosownym tylko wtedy, gdy uważam, że tak się nie stanie, ponieważ znowu jest to stwierdzenie faktu, a nie sprawdzenie stanu. Oczywiście nie ma również sensu mieć twierdzenia, mieć błąd, który mógłby złapać, a jednak nigdy nie trafił w ten przypadek podczas testowania.
Jon Hanna,
4

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.Findoperacja stwierdza, że ​​zwróci a, -1gdy 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 -1wartość, 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.

Shannon
źródło
4

Cytat zaczerpnięty z The Pragmatic Programmer: Od Journeyman to Master

Pozostaw włączone asercje

Istnieje powszechne nieporozumienie dotyczące twierdzeń, rozpowszechniane przez ludzi piszących kompilatory i środowiska językowe. Wygląda to mniej więcej tak:

Asercje dodają trochę narzutu do kodu. Ponieważ sprawdzają rzeczy, które nigdy nie powinny się wydarzyć, zostaną wyzwolone tylko przez błąd w kodzie. Po przetestowaniu i wysłaniu kodu nie są one już potrzebne i należy je wyłączyć, aby przyspieszyć działanie kodu. Asercje są narzędziem do debugowania.

Istnieją tutaj dwa ewidentnie błędne założenia. Po pierwsze, zakładają, że testowanie wykrywa wszystkie błędy. W rzeczywistości w przypadku jakiegokolwiek złożonego programu jest mało prawdopodobne, aby przetestować nawet niewielki procent permutacji, przez które będzie przechodził Twój kod (patrz: Bezwzględne testowanie).

Po drugie, optymiści zapominają, że Twój program działa w niebezpiecznym świecie. Podczas testów szczury prawdopodobnie nie będą gryźć kablem komunikacyjnym, ktoś grający w grę nie wyczerpie pamięci, a pliki dziennika nie zapełnią dysku twardego. Te rzeczy mogą się zdarzyć, gdy Twój program działa w środowisku produkcyjnym. Twoja pierwsza linia obrony sprawdza ewentualne błędy, a druga używa twierdzeń, aby spróbować wykryć te, które przegapiłeś.

Wyłączanie twierdzeń podczas dostarczania programu do produkcji jest jak przekraczanie wysokiego drutu bez siatki, ponieważ kiedyś udało się to w praktyce . Ma to dramatyczną wartość, ale trudno jest uzyskać ubezpieczenie na życie.

Nawet jeśli masz problemy z wydajnością, wyłącz tylko te stwierdzenia, które naprawdę Cię uderzyły .

Teoman Shipahi
źródło
2

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).

Thomas Danecker
źródło
1
Nie, tak samo jak niektóre inne odpowiedzi tutaj: tak naprawdę nie rozumiesz różnicy, więc rezygnujesz z jednej z ofert, ustanawiając fałszywą dychotomię między nimi w tym procesie. Dw
Casper Leon Nielsen
3
To jedyna poprawna odpowiedź na tej liście IMO. Nie zwlekaj tak łatwo Casper. Debugowanie Assert to anty-wzorzec. Jeśli jest niezmienny w czasie debugowania, jest niezmienny w czasie wykonywania. Zezwolenie aplikacji na kontynuację z uszkodzonym niezmiennikiem pozostawia Cię w stanie niedeterministycznym i potencjalnie gorszych problemach niż awaria. IMO lepiej jest mieć ten sam kod w obu kompilacjach, które szybko kończą się niepowodzeniem w przypadku zerwanych umów, a następnie wdrażają niezawodną obsługę błędów na najwyższym poziomie. np. Izoluj komponenty i zaimplementuj możliwość ich ponownego uruchomienia (np. awaria karty w przeglądarce nie powoduje awarii całej przeglądarki).
justin.m.chase
1
Pomocne może być uwzględnienie Trace.Assert w dyskusji tutaj, ponieważ nie można go odrzucić za pomocą tego samego argumentu.
Jon Coombs
0

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.

Orlando Calresian
źródło
2
przepraszam, ale wyjątki robią te same rzeczy, więc ta odpowiedź nie odnosi się do prawdziwego pytania.
Roman Starkov,
0

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.Assertpodczas 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.Assertma 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.

using System.Diagnostics;

public class ExceptionTraceListener : DefaultTraceListener
{
    [DebuggerStepThrough]
    public override void Fail(string message, string detailMessage)
    {
        throw new AssertException(message);
    }
}

public class AssertException : Exception
{
    public AssertException(string message) : base(message) { }
}

A w pliku konfiguracji produkcji:

<system.diagnostics>
  <trace>
    <listeners>
      <remove name="Default"/>
      <add name="ExceptionListener" type="Namespace.ExceptionTraceListener,AssemblyName"/>
    </listeners>
  </trace>
 </system.diagnostics>
AlexDev
źródło
-1

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.

nieistniejący
źródło
-1

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.

mattlant
źródło