Czytanie artykułu Erica Lipperta na temat wyjątków zdecydowanie przykuło uwagę na to, jak powinienem podejść do wyjątków, zarówno jako producenta, jak i konsumenta. Jednak wciąż staram się zdefiniować wytyczne dotyczące tego, jak uniknąć zgłaszania irytujących wyjątków.
Konkretnie:
- Załóżmy, że masz metodę Save, która może zawieść, ponieważ: a) ktoś zmodyfikował rekord przed tobą , lub b) wartość, którą próbujesz utworzyć, już istnieje . Tych warunków należy się spodziewać i nie są one wyjątkowe, dlatego zamiast zgłaszać wyjątek, decydujesz się utworzyć wersję Try, TrySave, która zwraca wartość logiczną wskazującą, czy zapis się powiódł. Ale jeśli zawiedzie, skąd konsument będzie wiedział, na czym polegał problem? A może najlepiej byłoby zwrócić wyliczenie wskazujące wynik, rodzaj Ok / RecordAlreadyModified / ValueAlreadyExists? Z integer.TryParse ten problem nie istnieje, ponieważ istnieje tylko jeden powód, dla którego metoda może zawieść.
- Czy poprzedni przykład jest naprawdę irytującą sytuacją? Czy może preferowanie byłoby w tym przypadku wyjątkiem? Wiem, że tak właśnie dzieje się w większości bibliotek i frameworków, w tym frameworku Entity.
- W jaki sposób decydujesz, kiedy utworzyć wersję próbną metody, a nie zapewniając wcześniej sposób przetestowania, czy metoda zadziała, czy nie? Obecnie stosuję się do tych wskazówek:
- Jeśli istnieje szansa na wyścig, stwórz wersję próbną. Zapobiega to konieczności wychwycenia przez konsumenta egzogenicznego wyjątku. Na przykład w opisanej wcześniej metodzie Save.
- Jeśli metoda przetestowania warunku właściwie zrobiłaby wszystko, co robi oryginalna metoda, należy utworzyć wersję próbną. Na przykład integer.TryParse ().
- W każdym innym przypadku utwórz metodę przetestowania warunku.
exceptions
Mikrofon
źródło
źródło
Odpowiedzi:
Dobre pytanie.
Pierwsze pytanie, jakie przychodzi mi do głowy, brzmi: jeśli dane już tam są, to w jakim sensie zapisywanie nie powiodło się ? Z pewnością brzmi to tak, jakby mi się udało. Ale załóżmy dla argumentu, że naprawdę masz wiele różnych powodów, dla których operacja może się nie powieść.
Drugie pytanie, jakie przychodzi mi do głowy, to: czy informacje, które chcesz zwrócić użytkownikowi, są wykonalne ? To znaczy, czy podejmą jakąś decyzję na podstawie tych informacji?
Kiedy zapala się lampka „sprawdź silnik”, otwieram maskę, sprawdzam, czy w samochodzie nie ma silnika, który się nie pali, i zabieram go do garażu. Oczywiście w garażu mają wszelkiego rodzaju sprzęt diagnostyczny specjalnego przeznaczenia, który mówi im, dlaczego lampka kontrolna silnika jest włączona, ale z mojej perspektywy system ostrzegania jest dobrze zaprojektowany. Nie dbam o to, czy problem polega na tym, że czujnik tlenu rejestruje nienormalny poziom tlenu w komorze spalania, czy też dlatego, że detektor prędkości biegu jałowego jest odłączony, czy coś w tym rodzaju. Mam zamiar podjąć to samo działanie, a mianowicie pozwolić komuś innemu to rozgryźć .
Czy dzwoniącemu zależy, dlaczego zapisywanie nie powiodło się? Czy zamierzają coś z tym zrobić, albo się poddać, albo spróbować ponownie?
Załóżmy dla argumentu, że osoba dzwoniąca naprawdę podejmie różne działania w zależności od przyczyny niepowodzenia operacji.
Trzecie pytanie, jakie przychodzi na myśl, to: czy tryb awaryjny jest wyjątkowy ? Myślę, że możesz mylić możliwe z wyjątkowymi . Pomyślałbym o dwóch użytkownikach próbujących jednocześnie zmodyfikować ten sam rekord jako sytuację wyjątkową, ale możliwą, a nie powszechną.
Załóżmy dla argumentu, że jest to wyjątkowe.
Czwarte pytanie, które przychodzi na myśl, brzmi: czy istnieje sposób, aby rzetelnie wykryć złą sytuację przed czasem?
Jeśli zła sytuacja jest w moim „egzogenicznym” segmencie, to nie. Nie ma sposobu, aby rzetelnie powiedzieć „czy inny użytkownik zmodyfikował ten rekord?” ponieważ mogą go zmodyfikować po zadaniu pytania . Odpowiedź jest nieaktualna, gdy tylko zostanie opublikowana.
Piąte pytanie, które przychodzi na myśl, brzmi: czy istnieje sposób zaprojektowania interfejsu API, aby można było zapobiec złej sytuacji?
Na przykład możesz sprawić, aby operacja „zapisz” wymagała dwóch kroków. Krok pierwszy: uzyskaj blokadę modyfikowanego rekordu. Ta operacja kończy się powodzeniem lub niepowodzeniem, dlatego może zwrócić wartość logiczną. Osoba dzwoniąca może mieć zasady dotyczące postępowania w przypadku niepowodzenia: poczekaj chwilę i spróbuj ponownie, zrezygnuj, cokolwiek. Krok drugi: po uzyskaniu blokady wykonaj zapis i zwolnij blokadę. Teraz zapis zawsze kończy się powodzeniem, więc nie trzeba się martwić o obsługę błędów. Jeśli zapis nie powiedzie się, jest to naprawdę wyjątkowe.
źródło
W twoim przykładzie, jeśli sytuację ValueAlreadyExists można łatwo sprawdzić, należy ją sprawdzić, a przed próbą zapisania można zgłosić wyjątek, nie sądzę, aby w tej sytuacji konieczne było wypróbowanie. Wyścig jest trudniejszy do sprawdzenia z wyprzedzeniem, więc w tym przypadku ominięcie Save in a Try jest prawdopodobnie bardzo dobrym pomysłem.
Ogólnie rzecz biorąc, jeśli istnieje warunek, który moim zdaniem jest bardzo prawdopodobny (taki jak NoDataReturned, DivideByZero itp.) LUB LUB jest bardzo łatwy do sprawdzenia (np. Pusta kolekcja lub wartość NULL), próbuję sprawdzić z wyprzedzeniem i poradzę sobie z tym, zanim dojdę do momentu, w którym będę musiał złapać wyjątek. Przyznaję, że nie zawsze łatwo jest poznać te warunki z wyprzedzeniem, czasem pojawiają się tylko wtedy, gdy kod jest poddawany rygorystycznym testom.
źródło
save()
Sposób należy wyrzucić wyjątek.Najwyższa warstwa powinna złapać i poinformować użytkownika , bez przerywania programu, chyba że jest to program wiersza poleceń podobny do Uniksa, w którym to przypadku można zakończyć.
Zwracane wartości nie są dobrym sposobem zarządzania wyjątkami.
źródło