Przeczytałem wiele artykułów (i kilka innych podobnych pytań, które zostały opublikowane w StackOverflow) o tym, jak i kiedy używać asercji i dobrze je zrozumiałem. Ale nadal nie rozumiem, jakiego rodzaju motywacja powinna mnie skłonić do korzystania, Debug.Assert
zamiast rzucać zwykły wyjątek. Chodzi mi o to, że w .NET domyślną odpowiedzią na niepowodzenie asercji jest „zatrzymanie świata” i wyświetlenie okna komunikatu użytkownikowi. Chociaż tego rodzaju zachowanie można zmodyfikować, uważam to za bardzo irytujące i zbędne, podczas gdy zamiast tego mógłbym po prostu zgłosić odpowiedni wyjątek. W ten sposób mógłbym łatwo zapisać błąd w dzienniku aplikacji tuż przed rzuceniem wyjątku, a poza tym moja aplikacja niekoniecznie się zawiesza.
Dlaczego więc, jeśli w ogóle, miałbym używać Debug.Assert
zamiast zwykłego wyjątku? Umieszczenie asercji tam, gdzie nie powinno, mogłoby po prostu spowodować wszelkiego rodzaju „niepożądane zachowanie”, więc z mojego punktu widzenia tak naprawdę nic nie zyskuję, używając asercji zamiast rzucać wyjątek. Zgadzasz się ze mną, czy coś mi tu brakuje?
Uwaga: w pełni rozumiem, jaka jest różnica „w teorii” (debugowanie a wydanie, wzorce użycia itp.), Ale jak widzę, lepiej byłoby rzucić wyjątek zamiast wykonywać asercję. Ponieważ w przypadku wykrycia błędu w wydaniu produkcyjnym nadal chciałbym, aby „asercja” zawiodła (w końcu „narzut” jest absurdalnie mały), więc lepiej będzie, jeśli zamiast tego rzucę wyjątek.
Edycja: Tak jak ja to widzę, jeśli asercja się nie powiodła, oznacza to, że aplikacja weszła w jakiś zepsuty, nieoczekiwany stan. Więc dlaczego miałbym chcieć kontynuować wykonanie? Nie ma znaczenia, czy aplikacja działa w wersji do debugowania czy wydania. To samo dotyczy obu
źródło
Odpowiedzi:
Chociaż zgadzam się, że twoje rozumowanie jest wiarygodne - to znaczy, jeśli twierdzenie zostanie naruszone nieoczekiwanie, sensowne jest wstrzymanie wykonania przez rzucanie - osobiście nie użyłbym wyjątków zamiast twierdzeń. Dlatego:
Jak powiedzieli inni, twierdzenia powinny dokumentować sytuacje, które są niemożliwe , w taki sposób, aby w przypadku zaistnienia rzekomo niemożliwej sytuacji informowany był o tym deweloper. Wyjątki natomiast zapewniają mechanizm kontroli przepływu w sytuacjach wyjątkowych, mało prawdopodobnych lub błędnych, ale nie w sytuacjach niemożliwych. Dla mnie kluczowa różnica jest taka:
ZAWSZE powinno być możliwe utworzenie przypadku testowego, który ćwiczy daną instrukcję throw. Jeśli nie jest możliwe stworzenie takiego przypadku testowego, oznacza to, że w programie znajduje się ścieżka kodu, która nigdy się nie wykonuje i powinna zostać usunięta jako martwy kod.
NIGDY nie powinno być możliwe stworzenie przypadku testowego, który spowoduje pożar asercji. Jeśli asercja zostanie odpalona, albo kod jest błędny, albo potwierdzenie jest błędne; tak czy inaczej, coś musi się zmienić w kodzie.
Dlatego nie zamieniłbym stwierdzenia na wyjątek. Jeśli potwierdzenie nie może faktycznie zostać uruchomione, zastąpienie go wyjątkiem oznacza, że w programie znajduje się niemożliwa do przetestowania ścieżka kodu . Nie lubię niesprawdzalnych ścieżek kodu.
źródło
Asercje służą do sprawdzania zrozumienia świata przez programistę. Asercja powinna zawieść tylko wtedy, gdy programista zrobił coś złego. Na przykład nigdy nie używaj potwierdzenia do sprawdzania danych wejściowych użytkownika.
Test potwierdzeń dla warunków, które „nie mogą wystąpić”. Wyjątki dotyczą warunków, które „nie powinny się zdarzyć, ale mają”.
Asercje są przydatne, ponieważ w czasie kompilacji (lub nawet w czasie wykonywania) można zmienić ich zachowanie. Na przykład często w kompilacjach wydania potwierdzenia nie są nawet sprawdzane, ponieważ wprowadzają niepotrzebne obciążenie. Jest to również coś, na co należy uważać: Twoje testy mogą nawet nie zostać wykonane.
Jeśli zamiast potwierdzeń użyjesz wyjątków, stracisz pewną wartość:
Kod jest bardziej rozwlekły, ponieważ testowanie i zgłaszanie wyjątku to co najmniej dwa wiersze, podczas gdy potwierdzenie to tylko jeden.
Twój kod testowy i rzutowy będzie zawsze działał, podczas gdy potwierdzenia można skompilować.
Tracisz komunikację z innymi programistami, ponieważ potwierdzenia mają inne znaczenie niż kod produktu, który sprawdza i generuje. Jeśli naprawdę testujesz asercję programistyczną, użyj asercji.
Więcej tutaj: http://nedbatchelder.com/text/assert.html
źródło
EDYCJA: W odpowiedzi na edycję / notatkę, którą zrobiłeś w swoim poście: Wygląda na to, że używanie wyjątków jest właściwą rzeczą do używania zamiast używania asercji dla rodzaju rzeczy, które próbujesz osiągnąć. Myślę, że mentalną przeszkodą, którą uderzasz, jest to, że rozważasz wyjątki i stwierdzenia, aby spełnić ten sam cel, a więc próbujesz dowiedzieć się, który z nich byłby „właściwy” w użyciu. Chociaż mogą występować pewne nakładania się w sposobie używania asercji i wyjątków, nie należy mylić tego, że są to różne rozwiązania tego samego problemu - tak nie jest. Każde stwierdzenie i wyjątki ma swój własny cel, mocne i słabe strony.
Miałem zamiar wpisać odpowiedź własnymi słowami, ale ta koncepcja jest bardziej sprawiedliwa niż ja:
Stacja C #: asercje
Zasadniczo używaj wyjątków dla rzeczy, które muszą być przechwytywane / obsługiwane w aplikacji produkcyjnej, używaj asercji do wykonywania logicznych kontroli, które będą przydatne podczas programowania, ale zostaną wyłączone w produkcji.
źródło
Myślę, że (wymyślony) praktyczny przykład może pomóc wyjaśnić różnicę:
(zaadaptowane z rozszerzenia Batch MoreLinq )
// 'public facing' method public int DoSomething(List<string> stuff, object doohickey, int limit) { // validate user input and report problems externally with exceptions if(stuff == null) throw new ArgumentNullException("stuff"); if(doohickey == null) throw new ArgumentNullException("doohickey"); if(limit <= 0) throw new ArgumentOutOfRangeException("limit", limit, "Should be > 0"); return DoSomethingImpl(stuff, doohickey, limit); } // 'developer only' method private static int DoSomethingImpl(List<string> stuff, object doohickey, int limit) { // validate input that should only come from other programming methods // which we have control over (e.g. we already validated user input in // the calling method above), so anything using this method shouldn't // need to report problems externally, and compilation mode can remove // this "unnecessary" check from production Debug.Assert(stuff != null); Debug.Assert(doohickey != null); Debug.Assert(limit > 0); /* now do the actual work... */ }
Tak więc, jak powiedzieli Eric Lippert i inni, potwierdzasz tylko rzeczy, których oczekujesz, że będą poprawne, na wypadek, gdybyś (programista) przypadkowo użył go niewłaściwie gdzie indziej, abyś mógł naprawić swój kod. Zasadniczo rzucasz wyjątki, gdy nie masz kontroli lub nie możesz przewidzieć tego, co się pojawi, np. W przypadku danych wejściowych użytkownika , aby cokolwiek przekazało złe dane, mogło odpowiednio zareagować (np. Użytkownik).
źródło
Kolejny samorodek z Code Complete :
Następnie dodaje kilka wskazówek dotyczących tego, czego należy, a czego nie należy się domagać.
Z drugiej strony wyjątki:
Jeśli nie masz tej książki, powinieneś ją kupić.
źródło
Debug.Assert domyślnie działa tylko w kompilacjach debugowania, więc jeśli chcesz wychwycić jakiekolwiek złe nieoczekiwane zachowanie w kompilacjach wydania, musisz użyć wyjątków lub włączyć stałą debugowania we właściwościach projektu (co jest uwzględnione w generalnie nie jest to dobry pomysł).
źródło
Użyj asercji do rzeczy, które SĄ możliwe, ale nie powinny się wydarzyć (gdyby to było niemożliwe, dlaczego miałbyś wstawiać asercję?).
Czy to nie brzmi jak przypadek do użycia
Exception
? Dlaczego miałbyś używać asercji zamiastException
?Ponieważ powinien istnieć kod, który zostanie wywołany przed Twoim potwierdzeniem, który zatrzymałby parametr potwierdzenia jako fałszywy.
Zwykle nie ma kodu,
Exception
który gwarantuje, że nie zostanie on wyrzucony.Dlaczego to dobrze, że
Debug.Assert()
jest kompilowany w wersji produkcyjnej? Jeśli chcesz wiedzieć o tym w debugowaniu, czy nie chciałbyś wiedzieć o tym w wersji produkcyjnej?Chcesz tego tylko podczas programowania, ponieważ gdy znajdziesz
Debug.Assert(false)
sytuacje, piszesz kod, aby zagwarantować, żeDebug.Assert(false)
to się nie powtórzy. Po zakończeniu programowania, zakładając, że znalazłeśDebug.Assert(false)
sytuacje i naprawiłeś je,Debug.Assert()
można je bezpiecznie skompilować, ponieważ są teraz zbędne.źródło
Załóżmy, że jesteś członkiem dość dużego zespołu i kilka osób pracuje nad tym samym ogólnym kodem, w tym nad nakładaniem się klas. Możesz utworzyć metodę, która jest wywoływana przez kilka innych metod i aby uniknąć rywalizacji o blokady, nie dodajesz do niej oddzielnej blokady, ale raczej „zakładasz”, że została ona wcześniej zablokowana przez metodę wywołującą z określoną blokadą. Na przykład Debug.Assert (RepositoryLock.IsReadLockHeld || RepositoryLock.IsWriteLockHeld); Inni programiści mogą przeoczyć komentarz, który mówi, że metoda wywołująca musi używać blokady, ale nie mogą tego zignorować.
źródło