Jak uniknąć rzucania irytujących wyjątków?

21

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.
Mikrofon
źródło
1
Twój przykład zapisu, który może się nie powieść, nie jest tak naprawdę strasznie irytującym wyjątkiem. Jest to dość zwyczajne i prawdopodobnie powinno być po prostu wyjątkiem.
S.Lott,
@ S.Lott: Co masz na myśli mówiąc, że jest dość zwyczajny? Sama sytuacja czy wyjątek w tej sytuacji? W każdym razie zgadzam się z tobą, że nie jest oczywiste, czy w rzeczywistości jest to dokuczliwa sytuacja. Zaktualizuję pytanie.
Mike
„Sama sytuacja lub wyjątek w tej sytuacji” Oba.
S.Lott,

Odpowiedzi:

24

Załóżmy, że masz metodę Save, która może się nie powieść, 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?

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.

Eric Lippert
źródło
Wszystkie bardzo dobre punkty, dzięki. Oto retoryczne pytanie, które prawdopodobnie streszcza mój post: Gdybyś przeprojektował File.Open (), czy zamiast tego stworzyłbyś File.TryOpen ()? Jak poinformowałbyś konsumenta o przyczynie niepowodzenia? A może wprowadzenie egzogenicznego wyjątku jest naprawdę najlepszym kompromisem?
Mike
10
@Mike: Systemy plików są dobrym przykładem zastosowania egzogennych wyjątków. Rzadko zawodzą, więc porażka jest wyjątkowa. Zawodzą nieprzewidywalnie i z przyczyn całkowicie niezależnych od osoby dzwoniącej (nie ma „blokady”, którą można podłączyć, która utrzymuje kabel sieciowy podłączony), a awarie są zarówno różnorodne, jak i możliwe do działania (to jest awaria, ponieważ plik jest Nie znaleziono pliku kontra znaleziono plik, ale nie masz dostępu do zapisu. Obie czynności można wykonać na różne sposoby.) Wszystkie te powody stanowią wyjątek.
Eric Lippert,
Uważam, że na pytanie należy teraz odpowiedzieć, ale jestem po prostu ciekawy;) Jeśli metoda może zawieść z 2 lub więcej powodów, awarie są możliwe do wykonania, awarie są nietypowe, a awarii nie można wykryć z wyprzedzeniem ani zapobiec, co byś zrobił?
Mike
@Mike Nie mogę mówić w imieniu Erica, ale to brzmi jak dobre miejsce na kody błędów. Być może oddawanie członka wyliczenia.
Mateusz
1

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.

FrustratedWithFormsDesigner
źródło
0

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.

Tulains Córdova
źródło