Kiedy programując na podstawie umowy, funkcja lub metoda najpierw sprawdza, czy spełnione są jej warunki wstępne, przed rozpoczęciem pracy nad swoimi obowiązkami, prawda? Dwa najbardziej znanych sposobów na te kontrole są przez assert
i przez exception
.
- assert kończy się niepowodzeniem tylko w trybie debugowania. Aby upewnić się, że kluczowe jest (jednostkowe) przetestowanie wszystkich oddzielnych warunków wstępnych umowy, aby sprawdzić, czy faktycznie zawodzą.
- wyjątek kończy się niepowodzeniem w trybie debugowania i zwalniania. Ma to tę zaletę, że testowane zachowanie debugowania jest identyczne z zachowaniem wydania, ale wiąże się to z obniżeniem wydajności w czasie wykonywania.
Jak myślisz, który z nich jest lepszy?
Zobacz opublikowane pytanie tutaj
exception
assert
design-by-contract
andreas buykx
źródło
źródło
Odpowiedzi:
Wyłączanie funkcji assert w kompilacjach wydań jest jak powiedzenie „Nigdy nie będę mieć żadnych problemów w kompilacji wydania”, co często nie ma miejsca. Dlatego asercja nie powinna być wyłączana w kompilacji wydania. Ale nie chcesz, aby kompilacja wydania ulegała awarii, gdy wystąpią błędy, prawda?
Dlatego używaj wyjątków i dobrze z nich korzystaj. Użyj dobrej, solidnej hierarchii wyjątków i upewnij się, że możesz złapać i umieścić hak na rzucaniu wyjątków w debugerze, aby go złapać, aw trybie zwolnienia możesz zrekompensować błąd, a nie zwykłą awarię. To bezpieczniejszy sposób.
źródło
Ogólna reguła jest taka, że powinieneś używać asercji, gdy próbujesz wyłapać własne błędy, a wyjątków, gdy próbujesz wyłapać błędy innych ludzi. Innymi słowy, powinieneś używać wyjątków do sprawdzania warunków wstępnych dla publicznych funkcji API i zawsze, gdy otrzymujesz jakiekolwiek dane, które są zewnętrzne w stosunku do twojego systemu. Powinieneś używać potwierdzeń dla funkcji lub danych, które są wewnętrzne w systemie.
źródło
Zasada, którą kieruję się jest następująca: jeśli można realistycznie uniknąć sytuacji poprzez kodowanie, użyj asercji. W przeciwnym razie użyj wyjątku.
Stwierdzenia mają na celu zapewnienie przestrzegania Umowy. Umowa musi być uczciwa, tak aby klient był w stanie zapewnić jej zgodność. Na przykład możesz określić w umowie, że adres URL musi być ważny, ponieważ reguły dotyczące tego, co jest, a co nie jest prawidłowym adresem URL, są znane i spójne.
Wyjątki dotyczą sytuacji, które są poza kontrolą zarówno klienta, jak i serwera. Wyjątek oznacza, że coś poszło nie tak i nic nie można było zrobić, aby tego uniknąć. Na przykład łączność sieciowa jest poza kontrolą aplikacji, więc nie można nic zrobić, aby uniknąć błędu sieci.
Chciałbym dodać, że rozróżnienie stwierdzenie / wyjątek nie jest najlepszym sposobem, aby o tym myśleć. To, o czym naprawdę chcesz myśleć, to umowa i sposób jej egzekwowania. W powyższym przykładzie adresu URL najlepiej jest mieć klasę, która zawiera adres URL i ma wartość Null lub prawidłowy adres URL. Jest to konwersja ciągu znaków na adres URL, który wymusza kontrakt, a wyjątek jest generowany, jeśli jest nieprawidłowy. Metoda z parametrem adresu URL jest znacznie bardziej przejrzysta niż metoda z parametrem typu String i asercją określającą adres URL.
źródło
Potwierdzenia służą do wychwytywania czegoś, co programista zrobił źle (nie tylko dla Ciebie - również innego programisty w Twoim zespole). Jeśli rozsądne jest, że błąd użytkownika może stworzyć ten warunek, powinien to być wyjątek.
Pomyśl też o konsekwencjach. Asercja zazwyczaj wyłącza aplikację. Jeśli istnieje realistyczne oczekiwanie, że stan można wyleczyć, prawdopodobnie powinieneś użyć wyjątku.
Z drugiej strony, jeśli problem może wynikać tylko z błędu programisty, użyj asercji, ponieważ chcesz wiedzieć o tym jak najszybciej. Wyjątek może zostać złapany i obsłużony, a nigdy się o nim nie dowiesz. I tak, powinieneś wyłączyć potwierdzenia w kodzie wersji, ponieważ tam chcesz, aby aplikacja została przywrócona, jeśli jest najmniejsza szansa, że może. Nawet jeśli stan twojego programu jest głęboko uszkodzony, użytkownik może po prostu zachować swoją pracę.
źródło
Nie jest do końca prawdą, że „assert kończy się niepowodzeniem tylko w trybie debugowania”.
W Object Oriented Software Construction, 2. wydanie Bertranda Meyera, autor zostawia otwarte drzwi do sprawdzenia warunków wstępnych w trybie wydania. W takim przypadku to, co się dzieje, gdy asercja się nie powiedzie, to ... zgłaszany jest wyjątek naruszenia asercji! W tym przypadku nie ma wyjścia z sytuacji: można by jednak zrobić coś pożytecznego, a jest to automatyczne wygenerowanie raportu o błędach oraz, w niektórych przypadkach, ponowne uruchomienie aplikacji.
Motywacją jest to, że testowanie warunków wstępnych jest zazwyczaj tańsze niż niezmienników i warunków końcowych, a w niektórych przypadkach poprawność i „bezpieczeństwo” w kompilacji wydania są ważniejsze niż szybkość. tzn. dla wielu aplikacji nie chodzi o szybkość, ale o solidność (zdolność programu do bezpiecznego zachowania, gdy jego zachowanie jest nieprawidłowe, tj. gdy umowa jest zerwana).
Czy zawsze należy pozostawić włączone sprawdzanie warunków wstępnych? To zależy. To zależy od Ciebie. Nie ma uniwersalnej odpowiedzi. Jeśli tworzysz oprogramowanie dla banku, może być lepiej przerwać wykonywanie alarmującym komunikatem niż przelać 1 000 000 USD zamiast 1 000 USD. Ale co, jeśli programujesz grę? Może potrzebujesz całej prędkości, jaką możesz uzyskać, a jeśli ktoś zdobędzie 1000 punktów zamiast 10 z powodu błędu, którego nie złapały warunki wstępne (ponieważ nie są włączone), pech.
W obu przypadkach idealnie byłoby, gdybyś złapał ten błąd podczas testowania, a znaczną część testów powinieneś wykonać z włączonymi asercjami. Omówiono tutaj najlepszą politykę dla tych rzadkich przypadków, w których warunki wstępne zawodzą w kodzie produkcyjnym w scenariuszu, który nie został wcześniej wykryty z powodu niepełnego testowania.
Podsumowując, możesz mieć potwierdzenia i nadal automatycznie uzyskiwać wyjątki , jeśli zostawisz je włączone - przynajmniej w Eiffel. Myślę, że aby zrobić to samo w C ++, musisz wpisać to sam.
Zobacz także: Kiedy asercje powinny pozostać w kodzie produkcyjnym?
źródło
Był duży wątek dotyczący włączania / wyłączania asercji w kompilacjach wydań na comp.lang.c ++. Moderated, który, jeśli masz kilka tygodni, możesz zobaczyć, jak różne są opinie na ten temat. :)
W przeciwieństwie do coppro uważam, że jeśli nie masz pewności, że asercja może zostać wyłączona w kompilacji wydania, to nie powinna była to być asercja. Asercje mają chronić przed zerwaniem niezmienników programu. W takim przypadku, jeśli chodzi o klienta twojego kodu, będzie jeden z dwóch możliwych wyników:
Nie ma różnicy dla użytkownika, jednak możliwe jest, że asercje dodają niepotrzebny koszt wydajności do kodu, który jest obecny w zdecydowanej większości uruchomień, w których kod nie zawodzi.
Odpowiedź na pytanie w rzeczywistości znacznie bardziej zależy od tego, kim będą klienci API. Jeśli piszesz bibliotekę udostępniającą API, potrzebujesz jakiejś formy mechanizmu, aby powiadomić klientów, że niewłaściwie korzystali z API. Jeśli nie dostarczysz dwóch wersji biblioteki (jedna z potwierdzeniami, druga bez), to assert jest bardzo mało prawdopodobne, aby był to właściwy wybór.
Osobiście jednak nie jestem pewien, czy wybrałbym wyjątki w tym przypadku. Wyjątki są lepiej dostosowane do sytuacji, w których może mieć miejsce odpowiednia forma zdrowienia. Na przykład może to oznaczać, że próbujesz przydzielić pamięć. Po złapaniu wyjątku „std :: bad_alloc” może być możliwe zwolnienie pamięci i spróbuj ponownie.
źródło
Przedstawiłem tutaj mój pogląd na stan rzeczy: Jak sprawdzić stan wewnętrzny obiektu? . Ogólnie rzecz biorąc, dochodź swoich roszczeń i rzucaj je za naruszenie przez innych. Aby wyłączyć potwierdzenia w kompilacjach wydań, możesz wykonać następujące czynności:
Oczywiście w kompilacjach wydań nieudane asercje i nieprzechwycone wyjątki powinny być obsługiwane w inny sposób niż w kompilacjach debugowania (gdzie można po prostu wywołać std :: abort). Zapisz gdzieś dziennik błędu (prawdopodobnie do pliku), powiedz klientowi, że wystąpił błąd wewnętrzny. Klient będzie mógł przesłać Ci plik dziennika.
źródło
pytasz o różnicę między błędami czasu projektowania i wykonywania.
potwierdzenia to powiadomienia „hej programisto, to jest zepsute”, mają przypominać o błędach, których nie zauważyłeś, kiedy się pojawiły.
wyjątkami są powiadomienia `` hej użytkowniku, coś poszło nie tak '' (oczywiście możesz kodować, aby je złapać, aby użytkownik nigdy nie został poinformowany), ale są one zaprojektowane tak, aby pojawiały się w czasie wykonywania, gdy użytkownik Joe korzysta z aplikacji.
Jeśli więc myślisz, że możesz usunąć wszystkie swoje błędy, używaj tylko wyjątków. Jeśli uważasz, że nie możesz ..... użyj wyjątków. Nadal można używać potwierdzeń debugowania, aby oczywiście zmniejszyć liczbę wyjątków.
Nie zapominaj, że wiele warunków wstępnych to dane dostarczone przez użytkownika, więc będziesz potrzebować dobrego sposobu poinformowania użytkownika, że jego dane nie były dobre. Aby to zrobić, często będziesz musiał zwrócić dane o błędach w dół stosu wywołań do bitów, z którymi ma do czynienia. Potwierdzenia nie będą wtedy przydatne - podwójnie, więc jeśli Twoja aplikacja jest n-warstwowa.
Na koniec nie użyłbym żadnego - kody błędów są znacznie lepsze w przypadku błędów, które Twoim zdaniem będą występować regularnie. :)
źródło
Wolę ten drugi. Chociaż twoje testy mogły działać dobrze, Murphy mówi, że coś nieoczekiwanego pójdzie nie tak. Tak więc, zamiast uzyskać wyjątek w rzeczywistym wywołaniu błędnej metody, w końcu śledzisz NullPointerException (lub równoważny) o 10 ramek stosu głębiej.
źródło
Poprzednie odpowiedzi są poprawne: używaj wyjątków dla publicznych funkcji API. Jedynym przypadkiem, w którym możesz chcieć nagiąć tę regułę, jest sytuacja, gdy sprawdzenie jest kosztowne obliczeniowo. W takim przypadku możesz umieścić to w asercie.
Jeśli uważasz, że naruszenie tego warunku wstępnego jest prawdopodobne, zachowaj go jako wyjątek lub zreformuj warunek wstępny.
źródło
Powinieneś użyć obu. Potwierdzenia są dla Twojej wygody jako programisty. Wyjątki obejmują rzeczy, które przeoczyłeś lub których się nie spodziewałeś w czasie działania.
Mam polubili funkcji raportowania błędów GLib za zamiast zwykły stary twierdzi. Zachowują się jak instrukcje assert, ale zamiast zatrzymywać program, po prostu zwracają wartość i pozwalają programowi kontynuować. Działa zaskakująco dobrze, a jako bonus możesz zobaczyć, co dzieje się z resztą programu, gdy funkcja nie zwraca „tego, co powinna”. Jeśli się zawiesza, wiesz, że sprawdzanie błędów jest luźne w innym miejscu.
W moim ostatnim projekcie użyłem tego stylu funkcji do zaimplementowania sprawdzania warunków wstępnych, a jeśli jedna z nich zawiodła, wydrukowałem ślad stosu w pliku dziennika, ale nadal działałem. Zaoszczędziło mi mnóstwo czasu na debugowanie, gdy inne osoby napotkałyby problem podczas uruchamiania mojej kompilacji debugowania.
Gdybym potrzebował sprawdzania argumentów w czasie wykonywania, zrobiłbym to:
źródło
Spróbowałem zsyntetyzować tutaj kilka innych odpowiedzi z własnymi poglądami.
Użyj asercji w przypadkach, w których chcesz wyłączyć je w środowisku produkcyjnym, błądząc w kierunku pozostawienia ich w środku. Jedynym prawdziwym powodem wyłączenia w środowisku produkcyjnym, ale nie w fazie rozwoju, jest przyspieszenie działania programu. W większości przypadków to przyspieszenie nie będzie znaczące, ale czasami kod jest krytyczny czasowo lub test jest kosztowny obliczeniowo. Jeśli kod ma kluczowe znaczenie dla misji, wyjątki mogą być najlepsze pomimo spowolnienia.
Jeśli istnieje realna szansa na odzyskanie, użyj wyjątku, ponieważ potwierdzenia nie są przeznaczone do odzyskiwania. Na przykład kod rzadko jest przeznaczony do odtwarzania po błędach programowania, ale jest przeznaczony do odzyskiwania po takich czynnikach, jak awarie sieci lub zablokowane pliki. Błędy nie powinny być traktowane jako wyjątki po prostu poza kontrolą programisty. Raczej przewidywalność tych błędów w porównaniu z błędami w kodowaniu sprawia, że łatwiej je naprawić.
Ponownie argument, że łatwiej jest debugować asercje: ślad stosu z prawidłowo nazwanego wyjątku jest tak łatwy do odczytania, jak asercja. Dobry kod powinien wychwytywać tylko określone typy wyjątków, więc wyjątki nie powinny pozostać niezauważone z powodu wychwycenia. Myślę jednak, że Java czasami zmusza cię do wyłapania wszystkich wyjątków.
źródło
Według mnie ogólną zasadą jest używanie wyrażeń asercji do znajdowania błędów wewnętrznych i wyjątków błędów zewnętrznych. Możesz wiele skorzystać z poniższej dyskusji Grega tutaj .
PS: możesz sprawdzić podobne pytanie: wyjątek kontra asercja .
źródło
Zobacz także to pytanie :
źródło