To pytanie ma na celu zastosowanie do dowolnego języka programowania OO, który obsługuje obsługę wyjątków; Używam C # tylko w celach ilustracyjnych.
Wyjątki są zwykle zgłaszane, gdy pojawia się problem, którego kod nie może natychmiast obsłużyć, a następnie przechwytywane w catch
klauzuli w innym miejscu (zwykle zewnętrznej ramce stosu).
P: Czy istnieją uzasadnione sytuacje, w których wyjątki nie są zgłaszane i wychwytywane, ale po prostu wracają z metody, a następnie przekazywane jako obiekty błędów?
To pytanie pojawiło się dla mnie, ponieważ System.IObserver<T>.OnError
metoda .NET 4 sugeruje właśnie to: wyjątki są przekazywane jako obiekty błędów.
Spójrzmy na inny scenariusz, sprawdzanie poprawności. Powiedzmy, że postępuję zgodnie z konwencjonalną mądrością i dlatego rozróżniam typ obiektu błędu IValidationError
od osobnego typu wyjątku, ValidationException
który służy do zgłaszania nieoczekiwanych błędów:
partial interface IValidationError { }
abstract partial class ValidationException : System.Exception
{
public abstract IValidationError[] ValidationErrors { get; }
}
(Przestrzeń System.Component.DataAnnotations
nazw robi coś całkiem podobnego).
Te typy można zastosować w następujący sposób:
partial interface IFoo { } // an immutable type
partial interface IFooBuilder // mutable counterpart to prepare instances of above type
{
bool IsValid(out IValidationError[] validationErrors); // true if no validation error occurs
IFoo Build(); // throws ValidationException if !IsValid(…)
}
Zastanawiam się, czy nie mógłbym uprościć powyższego:
partial class ValidationError : System.Exception { } // = IValidationError + ValidationException
partial interface IFoo { } // (unchanged)
partial interface IFooBuilder
{
bool IsValid(out ValidationError[] validationErrors);
IFoo Build(); // may throw ValidationError or sth. like AggregateException<ValidationError>
}
P: Jakie są zalety i wady tych dwóch różnych podejść?
AggregateException
zamiast tego rzucanie ? Słuszna uwaga.Odpowiedzi:
Jeśli nigdy nie zostanie wyrzucony, nie jest to wyjątkiem. Jest to obiekt
derived
z klasy Exception, chociaż nie jest zgodny z zachowaniem. Nazywanie tego Wyjątkiem jest w tym momencie czysto semantyką, ale nie widzę nic złego w tym, że go nie rzucam. Z mojego punktu widzenia nie rzucanie wyjątku jest dokładnie tym samym, co funkcja wewnętrzna przechwytująca go i uniemożliwiająca jego propagację.Czy funkcja może zwrócić obiekty wyjątków?
Absolutnie. Oto krótka lista przykładów, w których może to być odpowiednie:
Czy nie rzuca się źle?
Zgłoszenie wyjątku przypomina: „Idź do więzienia. Nie przechodź Idź!” w grze planszowej Monopoly. Mówi kompilatorowi, aby przeskoczył cały kod źródłowy aż do połowu bez wykonywania żadnego z tego kodu źródłowego. Nie ma to nic wspólnego z błędami, zgłaszaniem ani zatrzymywaniem błędów. Lubię myśleć o zgłoszeniu wyjątku jako instrukcji „super return” dla funkcji. Zwraca wykonanie kodu gdzieś znacznie wyżej niż pierwotny program wywołujący.
Ważne jest, aby zrozumieć, że prawdziwą wartością wyjątków jest
try/catch
wzorzec, a nie instancja obiektu wyjątku. Obiekt wyjątku jest tylko komunikatem stanu.W twoim pytaniu wydajesz się mylić użycie tych dwóch rzeczy: przeskoczenie do modułu obsługi wyjątku i stan błędu, który reprezentuje wyjątek. To, że przyjąłeś stan błędu i umieściłeś go w wyjątku, nie oznacza, że podążasz za
try/catch
wzorem lub jego zaletami.źródło
Exception
klasy, ale nie podążający za zachowaniem”. - I o to właśnie chodzi: czy uzasadnione jest użycieException
obiektu pochodzącego z wyprowadzenia w sytuacji, gdy nie zostanie on w ogóle wyrzucony ani przez producenta obiektu, ani przez żadną metodę wywoływania; gdzie służy jedynie jako „komunikat stanu” przekazywany bez przepływu kontroli „super powrotu”? Czy też tak bardzo naruszam niewypowiedzianą umowętry
/catch(Exception)
umowę, że nigdy nie powinienem tego robić, a zamiast tego używam osobnego, innego niżException
typ?No
nie zrywasz żadnych umów.popular opinion
Byłoby to, że nie powinno się tego robić. Programowanie jest pełne przykładów, w których kod źródłowy nie robi nic złego, ale wszyscy go nie lubią. Kod źródłowy powinien być zawsze napisany, aby było jasne, o co chodzi. Czy naprawdę pomagasz innemu programistowi, wykorzystując w ten sposób wyjątek? Jeśli tak, to zrób to. W przeciwnym razie nie zdziw się, jeśli się nie spodoba.Zwracanie wyjątków zamiast ich zgłaszania może mieć sens semantyczny, gdy masz metodę pomocniczą do analizy sytuacji i zwracania odpowiedniego wyjątku, który jest następnie zgłaszany przez wywołującego (można nazwać to „fabryką wyjątków”). Zgłoszenie wyjątku w tej funkcji analizatora błędów oznaczałoby, że coś poszło nie tak podczas samej analizy, a zwrócenie wyjątku oznacza, że rodzaj błędu został pomyślnie przeanalizowany.
Jednym z możliwych przypadków użycia może być funkcja, która zamienia kody odpowiedzi HTTP w wyjątki:
Zauważ, że rzucanie e środki różnicą, że metoda została zastosowana błędne lub wystąpił błąd wewnętrzny, natomiast wracając e środki różnicą, że kod błędu został zidentyfikowany pomyślnie.
źródło
return
instrukcje, aby kompilator przestał narzekać.System.Exit()
. Kod po wywołaniu funkcji nie jest wykonywany. To nie brzmi jak dobry wzór dla funkcji.Koncepcyjnie, jeśli obiekt wyjątku jest oczekiwanym wynikiem operacji, to tak. Przypadki, o których mogę myśleć, zawsze wiążą się z pewnym rzutem:
Wariacje na temat wzorca „Try” (gdzie metoda zawiera drugą metodę zgłaszania wyjątków, ale przechwytuje wyjątek i zamiast tego zwraca wartość logiczną wskazującą powodzenie). Zamiast zwracać wartość logiczną można zwrócić zgłoszony wyjątek (zerowy, jeśli się powiedzie), umożliwiając użytkownikowi końcowemu więcej informacji, zachowując bezdotykowy wskaźnik sukcesu lub niepowodzenia.
Przetwarzanie błędu przepływu pracy. Podobnie w praktyce do enkapsulacji metody „próbuj wzorzec”, możesz mieć etap przepływu pracy wyodrębniony we wzorcu łańcucha poleceń. Jeśli łącze w łańcuchu zgłasza wyjątek, często łatwiej jest złapać ten wyjątek w obiekcie abstrakcyjnym, a następnie zwrócić go w wyniku wspomnianej operacji, aby silnik przepływu pracy i rzeczywisty kod działający jako krok przepływu pracy nie wymagały obszernej próby -chwytaj własną logikę.
źródło
OperationResult
klasę abstrakcyjną zbool
właściwością „Successful”,GetExceptionIfAny
metodą iAssertSuccessful
metodą (która wyrzuciłaby wyjątek,GetExceptionIfAny
jeśli nie jest zerowy). PosiadanieGetExceptionIfAny
metody zamiast właściwości pozwoliłoby na użycie statycznych niezmiennych obiektów błędów w przypadkach, w których nie było wiele użytecznych do powiedzenia.Powiedzmy, że zleciłeś wykonanie zadania w puli wątków. Jeśli to zadanie generuje wyjątek, jest to inny wątek, więc go nie złapiesz. Wątek, w którym został wykonany, po prostu umrzyj.
Teraz zastanów się, że coś (kod w tym zadaniu lub implementacja puli wątków) łapie ten wyjątek, zapisz go wraz z zadaniem i uważaj zadanie za ukończone (bez powodzenia). Teraz możesz zapytać, czy zadanie zostało zakończone (albo zapytać, czy zostało rzucone, czy może zostać ponownie wrzucone do wątku (lub lepiej, nowy wyjątek z oryginałem jako przyczyną)).
Jeśli robisz to ręcznie, możesz zauważyć, że tworzysz nowy wyjątek, rzucasz go i łapiesz, a następnie przechowujesz, rzucając, chwytając i reagując na różne wątki. Dlatego warto pominąć rzut i złapać, po prostu przechowywać i zakończyć zadanie, a następnie po prostu zapytać, czy jest wyjątek i zareagować na to. Ale może to prowadzić do bardziej skomplikowanego kodu, jeśli w tym samym miejscu mogą istnieć wyjątki.
PS: jest to napisane z doświadczeniem w Javie, gdzie informacje śledzenia stosu są tworzone, gdy tworzony jest wyjątek (w przeciwieństwie do C #, gdzie jest tworzony podczas rzucania). Podejście nie w przypadku wyjątków zgłaszanych w Javie będzie wolniejsze niż w języku C # (chyba że zostanie wstępnie przetworzone i ponownie użyte), ale będą dostępne informacje o śledzeniu stosu.
Ogólnie rzecz biorąc, unikałbym tworzenia wyjątku i nigdy go nie rzucałbym (chyba że optymalizacja wydajności po profilowaniu wskazuje to jako wąskie gardło). Przynajmniej w java, gdzie tworzenie wyjątków jest drogie (śledzenie stosu). W C # jest to możliwe, ale IMO jest zaskakujące i dlatego należy tego unikać.
źródło
To zależy od twojego projektu, zwykle nie zwracam wyjątków dzwoniącemu, raczej rzucam je, łapię i zostawiam. Zazwyczaj kod jest zapisywany tak, aby nie działał wcześnie i szybko. Na przykład rozważmy przypadek otwarcia pliku i przetworzenia go (jest to C # PsuedoCode):
W takim przypadku pomylilibyśmy się i przestaliśmy przetwarzać plik, gdy tylko napotkał zły rekord.
Ale powiedzmy, że wymaganie polega na tym, że chcemy kontynuować przetwarzanie pliku, nawet jeśli jeden lub więcej wierszy zawiera błąd. Kod może zacząć wyglądać mniej więcej tak:
Tak więc w tym przypadku zwracamy wyjątek z powrotem do programu wywołującego, chociaż prawdopodobnie nie zwróciłbym wyjątku z powrotem, ale zamiast tego jakiś obiekt błędu, ponieważ wyjątek został wychwycony i już rozwiązany. Nie jest to szkodliwe ze zwróceniem wyjątku z powrotem, ale inni wywołujący mogą ponownie wygenerować wyjątek, co może nie być pożądane, ponieważ obiekt wyjątku ma takie zachowanie. Jeśli chcesz, aby osoby dzwoniące miały możliwość ponownego wyrzucenia, a następnie zwróć wyjątek, w przeciwnym razie wyjmij informacje z obiektu wyjątku i zbuduj mniejszy obiekt o niewielkiej masie i zwróć go. Błąd szybki to zazwyczaj czystszy kod.
W twoim przykładzie sprawdzania poprawności prawdopodobnie nie odziedziczyłbym po klasie wyjątków ani nie wyrzucał wyjątków, ponieważ błędy sprawdzania poprawności mogą być dość powszechne. Zwrócenie obiektu byłoby mniej obciążeniem niż rzucanie wyjątku, jeśli 50% moich użytkowników nie wypełni poprawnie formularza przy pierwszej próbie.
źródło
Tak. Na przykład ostatnio spotkałem się z sytuacją, w której zauważyłem, że zgłoszone wyjątki w donocie usługi sieciowej ASMX zawierają element w wynikowym komunikacie SOAP, więc musiałem go wygenerować.
Kod ilustracyjny:
źródło
W większości języków (afaik) wyjątki zawierają dodatkowe dodatki. Zwykle migawka bieżącego śladu stosu jest przechowywana w obiekcie. Jest to przydatne do debugowania, ale może mieć również wpływ na pamięć.
Jak już powiedział @ThinkingMedia, naprawdę używasz wyjątku jako obiektu błędu.
W twoim przykładzie kodu wydaje się, że robisz to głównie w celu ponownego użycia kodu i uniknięcia kompozycji. Osobiście nie uważam, że to dobry powód, aby to zrobić.
Innym możliwym powodem może być oszukiwanie języka w celu uzyskania obiektu błędu ze śledzeniem stosu. Daje to dodatkowe informacje kontekstowe w kodzie obsługi błędów.
Z drugiej strony możemy założyć, że utrzymanie śladu stosu ma koszt pamięci. Na przykład, jeśli zaczniesz gdzieś zbierać te obiekty, możesz zauważyć nieprzyjemny wpływ na pamięć. Oczywiście w dużej mierze zależy to od tego, jak ślady stosu wyjątków są implementowane w odpowiednim języku / silniku.
Czy to dobry pomysł?
Do tej pory nie widziałem, aby był używany lub zalecany. Ale to niewiele znaczy.
Pytanie brzmi: czy śledzenie stosu jest naprawdę przydatne do obsługi błędów? Lub bardziej ogólnie, jakie informacje chcesz przekazać deweloperowi lub administratorowi?
Typowy wzorzec rzucania / próbowania / łapania wymaga pewnego rodzaju śledzenia stosu, ponieważ w przeciwnym razie nie masz pojęcia, skąd pochodzi. W przypadku zwracanego obiektu zawsze pochodzi on z wywołanej funkcji. Śledzenie stosu może zawierać wszystkie informacje potrzebne deweloperowi, ale może wystarczyłoby coś mniej ciężkiego i bardziej szczegółowego.
źródło
Odpowiedź brzmi tak. Zobacz http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Exceptions i otwórz strzałkę w przewodniku po stylu C ++ Google dotyczącym wyjątków. Widać tam argumenty, przed którymi się zdecydowali, ale powiedz, że gdyby musieli to zrobić od nowa, prawdopodobnie by zdecydowali.
Warto jednak zauważyć, że język Go również nie używa idiomatycznie wyjątków z podobnych powodów.
źródło