Język Go Google nie ma wyjątków jako wybór projektowy, a Linus z Linuxa nazwał wyjątki bzdurą. Czemu?
Język Go Google nie ma wyjątków jako wybór projektowy, a Linus z Linuxa nazwał wyjątki bzdurą. Czemu?
Odpowiedzi:
Wyjątki bardzo ułatwiają pisanie kodu, w którym zgłoszenie wyjątku spowoduje przerwanie niezmienników i pozostawienie obiektów w niespójnym stanie. Zasadniczo zmuszają cię do zapamiętania, że prawie każde twoje oświadczenie może potencjalnie rzucić i poprawnie sobie z tym poradzić. Może to być trudne i sprzeczne z intuicją.
Rozważ coś takiego jako prosty przykład:
class Frobber
{
int m_NumberOfFrobs;
FrobManager m_FrobManager;
public:
void Frob()
{
m_NumberOfFrobs++;
m_FrobManager.HandleFrob(new FrobObject());
}
};
Zakładając, że FrobManager
będzie delete
się FrobObject
, to wygląda OK, prawda? A może nie ... Wyobraź sobie, że albo FrobManager::HandleFrob()
albo operator new
rzuca wyjątek. W tym przykładzie przyrost wartości m_NumberOfFrobs
nie jest wycofywany. Dlatego każdy, kto korzysta z tej instancji programu, Frobber
będzie miał prawdopodobnie uszkodzony obiekt.
Ten przykład może wydawać się głupi (ok, musiałem się trochę rozciągnąć, aby go skonstruować :-)), ale na wynos jest to, że jeśli programista nie myśli ciągle o wyjątkach i upewniając się, że każda permutacja stanu zostanie zmieniona gdy są rzuty, wpadasz w kłopoty.
Na przykład możesz o tym myśleć tak, jak o muteksach. W sekcji krytycznej polegasz na kilku instrukcjach, aby upewnić się, że struktury danych nie są uszkodzone i że inne wątki nie widzą twoich wartości pośrednich. Jeśli którekolwiek z tych stwierdzeń po prostu przypadkowo się nie powiedzie, znajdziesz się w świecie bólu. Teraz usuń blokady i współbieżność i pomyśl o każdej metodzie w ten sposób. Pomyśl o każdej metodzie jak o transakcji permutacji stanu obiektu, jeśli wolisz. Na początku wywołania metody obiekt powinien mieć stan czysty, a na końcu również stan czysty. W międzyczasie zmienna foo
może być niespójna zbar
, ale twój kod w końcu to naprawi. Wyjątki oznaczają, że każde z Twoich wypowiedzi może Ci przeszkodzić w dowolnym momencie. W każdej indywidualnej metodzie spoczywa na tobie ciężar, aby to naprawić i wycofać, gdy tak się stanie, lub tak uporządkować operacje, aby rzuty nie wpływały na stan obiektu. Jeśli zrobisz to źle (i łatwo jest popełnić taki błąd), wtedy dzwoniący zobaczy Twoje wartości pośrednie.
Metody takie jak RAII, o którym programiści C ++ uwielbiają wspominać jako ostateczne rozwiązanie tego problemu, bardzo dobrze chronią przed tym problemem. Ale to nie jest srebrna kula. Zapewni to natychmiastowe zwolnienie zasobów, ale nie zwalnia cię od konieczności myślenia o uszkodzeniu stanu obiektu i wywołujących widzenie wartości pośrednich. Tak więc wielu ludziom łatwiej powiedzieć, zgodnie z zasadami stylu kodowania, bez wyjątków . Jeśli ograniczysz rodzaj pisanego kodu, trudniej będzie wprowadzić te błędy. Jeśli tego nie zrobisz, łatwo popełnić błąd.
Napisano całe książki o kodowaniu bezpiecznym dla wyjątków w C ++. Wielu ekspertów pomyliło się. Jeśli jest naprawdę tak złożony i ma tak wiele niuansów, może to dobry znak, że musisz zignorować tę funkcję. :-)
Powód, dla którego w Go nie ma wyjątków, wyjaśniono w często zadawanych pytaniach dotyczących projektowania języka Go:
Wyjątki to podobna historia. Zaproponowano wiele projektów wyjątków, ale każdy z nich znacznie komplikuje język i czas wykonywania. Ze swej natury wyjątki obejmują funkcje, a być może nawet gorutynę; mają daleko idące konsekwencje. Istnieją również obawy co do wpływu, jaki wywrą one na biblioteki. Są z definicji wyjątkowe, ale doświadczenie z innymi językami, które je obsługują, pokazuje, że mają one głęboki wpływ na specyfikację biblioteki i interfejsu. Byłoby miło znaleźć projekt, który pozwala im być naprawdę wyjątkowymi bez zachęcania do zwykłych błędów do przekształcenia się w specjalny przepływ sterowania, który wymaga od każdego programisty kompensacji.
Podobnie jak produkty generyczne, wyjątki pozostają otwartą kwestią.
Innymi słowy, jeszcze nie wymyślili, jak obsługiwać wyjątki w Go w sposób, który ich zdaniem jest zadowalający. Nie mówią, że wyjątki są złe same w sobie ;
AKTUALIZACJA - maj 2012
Projektanci Go zeszli teraz z ogrodzenia. Ich FAQ mówi teraz tak:
Uważamy, że łączenie wyjątków ze strukturą kontrolną, jak w idiomie try-catch-last, skutkuje zawikłaniem kodu. Ma również tendencję do zachęcania programistów do oznaczania zbyt wielu zwykłych błędów, takich jak niepowodzenie otwarcia pliku, jako wyjątkowych.
Go ma inne podejście. W przypadku zwykłej obsługi błędów zwroty wielowartościowe Go ułatwiają zgłoszenie błędu bez przeciążania zwracanej wartości. Kanoniczny typ błędu w połączeniu z innymi funkcjami Go sprawia, że obsługa błędów jest przyjemna, ale zupełnie inna niż w innych językach.
Go ma również kilka wbudowanych funkcji do sygnalizowania i przywracania sprawności po naprawdę wyjątkowych warunkach. Mechanizm przywracania jest wykonywany tylko wtedy, gdy stan funkcji jest niszczony po błędzie, co jest wystarczające do obsługi katastrofy, ale nie wymaga żadnych dodatkowych struktur sterujących, a jeśli jest dobrze używane, może skutkować czystym kodem obsługi błędów.
Szczegółowe informacje można znaleźć w artykule Odraczanie, panika i odzyskiwanie.
Krótka odpowiedź jest taka, że mogą to zrobić inaczej, używając zwrotu z wielu wartości. (I tak mają formę obsługi wyjątków).
... a Linus znany z Linuksa nazwał wyjątki bzdurą.
Jeśli chcesz wiedzieć, dlaczego według Linusa wyjątki to bzdury, najlepiej poszukaj jego prac na ten temat. Jedyną rzeczą, którą do tej pory wyśledziłem, jest ten cytat, który jest osadzony w kilku e-mailach w C ++ :
„Cała obsługa wyjątków w C ++ jest zasadniczo zepsuta. Jest szczególnie zepsuta w przypadku jądra”.
Zauważysz, że mówi on w szczególności o wyjątkach C ++, a nie ogólnie o wyjątkach. (A wyjątki C ++ mają najwyraźniej pewne problemy, które utrudniają ich prawidłowe użycie).
Mój wniosek jest taki, że Linus w ogóle nie nazwał wyjątków (ogólnie) „bzdurami”!
Wyjątki same w sobie nie są złe, ale jeśli wiesz, że będą się często zdarzać, mogą być kosztowne pod względem wydajności.
Ogólna zasada jest taka, że wyjątki powinny oznaczać wyjątkowe warunki i nie należy ich używać do sterowania przepływem programu.
Nie zgadzam się z zasadą „wyrzucaj wyjątki tylko w wyjątkowych sytuacjach”. Chociaż ogólnie jest to prawdą, jest to mylące. Wyjątki dotyczą warunków błędu (niepowodzenia wykonania).
Niezależnie od używanego języka, pobierz kopię Wytycznych dotyczących projektowania platformy: konwencje, idiomy i wzorce dla bibliotek .NET wielokrotnego użytku (wydanie drugie). Rozdział o rzucaniu wyjątków jest bez peera. Kilka cytatów z pierwszej edycji (druga w mojej pracy):
Istnieją strony z uwagami na temat zalet wyjątków (spójność interfejsu API, wybór lokalizacji kodu obsługi błędów, ulepszona niezawodność itp.). Sekcja dotycząca wydajności zawiera kilka wzorców (Tester-Doer, Try-Parse).
Wyjątki i obsługa wyjątków nie są złe. Jak każda inna funkcja, mogą być nadużywane.
Z punktu widzenia golanga, myślę, że brak obsługi wyjątków sprawia, że proces kompilacji jest prosty i bezpieczny.
Z perspektywy Linusa rozumiem, że kod jądra jest WSZYSTKIM związany z przypadkami narożnymi. Dlatego warto odmawiać wyjątków.
Wyjątki mają sens w kodzie, w których można upuścić bieżące zadanie na podłogę i gdzie typowy kod przypadku ma większe znaczenie niż obsługa błędów. Ale wymagają generowania kodu przez kompilator.
Na przykład sprawdzają się w większości kodu wysokiego poziomu dla użytkownika, takiego jak kod aplikacji internetowej i komputerowej.
Wyjątki same w sobie nie są „złe”, to sposób, w jaki czasami obsługiwane są wyjątki, jest zwykle zły. Istnieje kilka wskazówek, które można zastosować podczas obsługi wyjątków, aby złagodzić niektóre z tych problemów. Niektóre z nich obejmują (ale z pewnością nie są ograniczone do):
Option<T>
zamiast null
obecnie. Właśnie zostałem wprowadzony na przykład w Javie 8, czerpiąc wskazówkę z guawy (i innych).
Typowe argumenty są takie, że nie ma sposobu, aby stwierdzić, jakie wyjątki wyjdą z określonego fragmentu kodu (w zależności od języka) i że są one zbyt podobne do goto
s, co utrudnia myślowe śledzenie wykonania.
http://www.joelonsoftware.com/items/2003/10/13.html
Zdecydowanie nie ma konsensusu w tej kwestii. Powiedziałbym, że z punktu widzenia twardego programisty C, takiego jak Linus, wyjątki są zdecydowanie złym pomysłem. Jednak typowy programista Java jest w zupełnie innej sytuacji.
setjmp
/ longjmp
rzecz, która jest dość zła.
Wyjątki nie są złe. Dobrze pasują do modelu RAII w C ++, który jest najbardziej elegancką cechą C ++. Jeśli masz już kilka kodów, które nie są bezpieczne pod względem wyjątków, są one złe w tym kontekście. Jeśli piszesz oprogramowanie naprawdę niskiego poziomu, takie jak system operacyjny Linux, to jest złe. Jeśli lubisz zaśmiecać swój kod kilkoma sprawdzeniami zwrotów błędów, to nie są one pomocne. Jeśli nie masz planu kontroli zasobów, gdy zostanie zgłoszony wyjątek (który zapewnia destruktory C ++), to są złe.
Świetnym przykładem użycia wyjątków jest zatem ...
Powiedzmy, że pracujesz nad projektem i każdy kontroler (około 20 różnych głównych) rozszerza pojedynczy kontroler nadklasy o metodę akcji. Wtedy każdy kontroler wykonuje różne rzeczy, wywołując obiekty B, C, D w jednym przypadku i F, G, D w innym. Wyjątki przychodzą tutaj na ratunek w wielu przypadkach, gdy było mnóstwo kodu zwrotnego i KAŻDY kontroler obsługiwał go inaczej. Uderzyłem cały ten kod, wrzuciłem odpowiedni wyjątek z "D", złapałem go w metodzie akcji kontrolera nadklasy i teraz wszystkie nasze kontrolery są spójne. Poprzednio D zwracał wartość null dla WIELU różnych przypadków błędów, o których chcemy poinformować użytkownika końcowego, ale nie mogłem, a ja nie.
tak, musimy się martwić o każdy poziom i wszelkie czyszczenie / wycieki zasobów, ale ogólnie żaden z naszych kontrolerów nie miał żadnych zasobów do wyczyszczenia.
dzięki Bogu mieliśmy wyjątki, bo inaczej byłbym narażony na ogromny refaktor i straciłbym zbyt dużo czasu na coś, co powinno być prostym problemem programistycznym.
Teoretycznie są naprawdę złe. W idealnym matematycznym świecie nie można znaleźć sytuacji wyjątkowych. Spójrz na języki funkcjonalne, nie mają skutków ubocznych, więc praktycznie nie mają źródła nietypowych sytuacji.
Ale rzeczywistość to inna historia. Zawsze mamy sytuacje, które są „nieoczekiwane”. Dlatego potrzebujemy wyjątków.
Myślę, że możemy myśleć o wyjątkach jako o cukrze składni dla ExceptionSituationObserver. Otrzymujesz tylko powiadomienia o wyjątkach. Nic więcej.
Myślę, że wraz z Go wprowadzą coś, co poradzi sobie z „nieoczekiwanymi” sytuacjami. Domyślam się, że postarają się, aby brzmiało to mniej destrukcyjnie jako wyjątki, a bardziej jako logika aplikacji. Ale to tylko moje przypuszczenie.
Paradygmat obsługi wyjątków w C ++, który stanowi częściową podstawę dla języka Java, a co za tym idzie .net, wprowadza kilka dobrych koncepcji, ale ma też pewne poważne ograniczenia. Jedną z kluczowych intencji projektowych obsługi wyjątków jest umożliwienie metodom zapewnienia, że spełnią one swoje warunki końcowe lub wyrzucą wyjątek, a także zapewnienie, że nastąpi czyszczenie, które musi nastąpić przed zakończeniem metody. Niestety, wszystkie paradygmaty obsługi wyjątków w językach C ++, Java i .net nie zapewniają żadnego dobrego sposobu radzenia sobie z sytuacją, w której nieoczekiwane czynniki uniemożliwiają wykonanie oczekiwanego czyszczenia. To z kolei oznacza, że trzeba albo ryzykować, że wszystko zatrzyma się z piskiem, jeśli wydarzy się coś nieoczekiwanego (podejście C ++ do obsługi wyjątku występuje podczas rozwijania stosu),
Nawet jeśli obsługa wyjątków byłaby ogólnie dobra, nie jest nierozsądne uznanie za niedopuszczalny paradygmatu obsługi wyjątków, który nie zapewnia dobrych środków do obsługi problemów, które pojawiają się podczas czyszczenia po innych problemach. Nie oznacza to, że frameworku nie można zaprojektować z paradygmatem obsługi wyjątków, który mógłby zapewnić rozsądne zachowanie nawet w scenariuszach wielu niepowodzeń, ale żaden z najlepszych języków ani frameworków nie może tego zrobić.
Nie przeczytałem wszystkich innych odpowiedzi, więc już o tym wspominałem, ale jedną z zarzutów jest to, że powodują one zrywanie programów w długie łańcuchy, co utrudnia wyśledzenie błędów podczas debugowania kodu. Na przykład, jeśli Foo () wywołuje Bar (), która wywołuje Wah (), która wywołuje ToString (), to przypadkowe przesłanie niewłaściwych danych do ToString () kończy się wyglądaniem jak błąd w Foo (), prawie całkowicie niepowiązanej funkcji.
OK, nudna odpowiedź tutaj. Myślę, że tak naprawdę zależy to od języka. Tam, gdzie wyjątek może pozostawić przydzielone zasoby, należy ich unikać. W językach skryptowych po prostu porzucają lub zastępują części przepływu aplikacji. Samo w sobie jest to nie do przyjęcia, ale unikanie błędów prawie krytycznych z wyjątkami jest akceptowalnym pomysłem.
Generalnie preferuję sygnały błędów do sygnalizacji błędów. Wszystko zależy od interfejsu API, przypadku użycia i ważności lub tego, czy wystarczy logowanie. Próbuję też przedefiniować zachowanie i throw Phonebooks()
zamiast tego. Chodzi o to, że „Wyjątki” są często ślepymi uliczkami, ale „Książka telefoniczna” zawiera przydatne informacje na temat usuwania błędów lub alternatywnych tras wykonywania. (Nie znalazłem jeszcze dobrego przypadku użycia, ale próbuj dalej).
Dla mnie sprawa jest bardzo prosta. Wielu programistów niewłaściwie używa obsługi wyjątków. Więcej zasobów językowych jest lepsze. Umiejętność obsługi wyjątków jest dobra. Jednym z przykładów złego użycia jest wartość, która musi być liczbą całkowitą, której nie można zweryfikować, lub inne dane wejściowe, które mogą dzielić i nie być sprawdzane pod kątem dzielenia zer ... obsługa wyjątków może być łatwym sposobem na uniknięcie dodatkowej pracy i ciężkiego myślenia, może chcieć zrobić nieprzyjemny skrót i zastosować obsługę wyjątków ... Stwierdzenie: „profesjonalny kod NIGDY nie zawodzi” może być iluzoryczne, jeśli niektóre z problemów przetwarzanych przez algorytm są z natury niepewne. Być może w nieznanych z natury sytuacjach dobrze jest wejść do gry z obsługą wyjątków. Dobre praktyki programowania są przedmiotem dyskusji.