Wyjątki jak twierdzą czy jako błędy?

10

Jestem profesjonalnym programistą C i hobbystą programistą Obj-C (OS X). Ostatnio kusiło mnie, aby rozwinąć się do C ++, ze względu na bardzo bogatą składnię.

Do tej pory kodowania nie zajmowałem się zbytnio wyjątkami. Cel C ma je, ale polityka Apple jest dość surowa:

Ważne Należy zastrzec stosowanie wyjątków do programowania lub nieoczekiwanych błędów środowiska wykonawczego, takich jak dostęp do kolekcji poza zakresem, próby mutowania niezmiennych obiektów, wysyłanie niepoprawnych komunikatów i utrata połączenia z serwerem okien.

Wydaje się, że C ++ woli częściej używać wyjątków. Na przykład przykład wikipedii dotyczący RAII zgłasza wyjątek, jeśli pliku nie można otworzyć. Cel C byłby return nilz błędem wysłanym przez parametr wyjściowy. W szczególności wydaje się, że std :: ofstream można ustawić w dowolny sposób.

Tutaj, na programistach, znalazłem kilka odpowiedzi głoszących, że używają wyjątków zamiast kodów błędów lub wcale nie używają wyjątków . Te pierwsze wydają się bardziej rozpowszechnione.

Nie znalazłem nikogo, kto prowadziłby obiektywne badania dla C ++. Wydaje mi się, że ponieważ wskaźniki są rzadkie, musiałbym stosować wewnętrzne flagi błędów, jeśli zdecyduję się uniknąć wyjątków. Czy będzie to zbyt kłopotliwe w obsłudze, czy może będzie działało nawet lepiej niż wyjątki? Najlepszym rozwiązaniem byłoby porównanie obu przypadków.

Edycja: Chociaż nie do końca istotne, prawdopodobnie powinienem wyjaśnić, co niljest. Technicznie jest tak samo jak NULL, ale chodzi o to, że można wysłać wiadomość nil. Możesz więc zrobić coś takiego

NSError *err = nil;
id obj = [NSFileHandle fileHandleForReadingFromURL:myurl error:&err];

[obj retain];

nawet jeśli pierwsze połączenie powróciło nil. I jak nigdy nie robisz *objw Obj-C, nie ma ryzyka wyłuskiwania wskaźnika NULL.

Per Johansson
źródło
Imho, byłoby lepiej, gdybyś pokazał kawałek kodu Celu C, jak tam pracujesz z błędami. Wygląda na to, że ludzie mówią o czymś innym niż pytasz.
Gangnus
W pewnym sensie szukam uzasadnienia dla stosowania wyjątków. Ponieważ mam pojęcie o ich implementacji, wiem, jak drogie mogą być. Ale sądzę, że jeśli uda mi się zdobyć wystarczająco dobry argument za ich użyciem, pójdę tą drogą.
Per Johansson
Wydaje się, że dla C ++ potrzebujesz raczej uzasadnienia, aby ich nie używać . Zgodnie z reakcjami tutaj.
Gangnus
2
Być może, ale jak dotąd nikt nie wyjaśnił, dlaczego są lepsi (z wyjątkiem kodów błędów). Nie podoba mi się koncepcja używania wyjątków od rzeczy, które nie są wyjątkowe, ale jest bardziej instynktowna niż oparta na faktach.
Per Johansson
„Nie lubię… stosować wyjątków od rzeczy, które nie są wyjątkowe” - zgodził się.
Gangnus

Odpowiedzi:

1

Wydaje się, że C ++ woli częściej używać wyjątków.

Pod pewnymi względami zasugerowałbym mniej niż Objective-C, ponieważ standardowa biblioteka C ++ nie generowałaby generalnie błędów programistycznych, takich jak brak dostępu do sekwencji losowego dostępu w jej najczęstszej postaci ( operator[]np.) Lub próba wyłuskiwania nieprawidłowego iteratora. Język nie rzuca się na dostęp do tablicy poza granicami, dereferencję wskaźnika zerowego lub coś w tym rodzaju.

Usunięcie błędów programisty w dużej mierze z równania obsługi wyjątków faktycznie usuwa bardzo dużą kategorię błędów, na które często reagują inne języki throwing. C ++ ma tendencję do assert(która nie kompiluje się w kompilacjach wydania / produkcji, tylko kompilacje debugowania) lub po prostu wyładowuje (często ulega awarii) w takich przypadkach, prawdopodobnie częściowo dlatego, że język nie chce nakładać kosztów takich kontroli środowiska wykonawczego co byłoby wymagane do wykrycia takich błędów programisty, chyba że programista specjalnie chce ponieść koszty, pisząc kod, który sam dokonuje takich kontroli.

Sutter zachęca nawet do unikania wyjątków w takich przypadkach w standardach kodowania C ++:

Podstawową wadą używania wyjątku do zgłaszania błędu programowania jest to, że tak naprawdę nie chcesz, aby odwijanie stosu miało miejsce, gdy chcesz, aby debuger uruchomił się dokładnie w wierszu, w którym wykryto naruszenie, z nienaruszonym stanem linii. Podsumowując: istnieją błędy, o których wiesz, że mogą się zdarzyć (patrz pozycje 69–75). Jeśli chodzi o wszystko inne, co nie powinno, a jest to wina programisty, jeśli tak, jest assert.

Ta zasada niekoniecznie musi być ugruntowana. W niektórych bardziej krytycznych przypadkach może być wskazane użycie opakowania, powiedzmy, opakowania i standardu kodowania, który jednolicie rejestruje, gdzie występują błędy programisty i throww przypadku błędów programisty, takich jak próba uszanowania czegoś nieprawidłowego lub uzyskania dostępu poza granicami, ponieważ w tych przypadkach odzyskanie oprogramowania może być zbyt kosztowne, jeśli oprogramowanie ma na to szansę. Ale ogólnie rzecz biorąc, bardziej powszechne użycie języka sprzyja nie rzucaniu się w obliczu błędów programisty.

Wyjątki zewnętrzne

Tam, gdzie widzę wyjątki najczęściej wspierane w C ++ (zgodnie ze standardowym komitetem, np.) Dotyczy „wyjątków zewnętrznych”, ponieważ w nieoczekiwany sposób niektóre zewnętrzne źródła poza programem. Przykładem jest nieprzydzielenie pamięci. Innym jest to, że nie można otworzyć pliku krytycznego wymaganego do uruchomienia oprogramowania. Innym nie udało się połączyć z wymaganym serwerem. Innym jest użytkownik, który zacina przycisk przerwania, aby anulować operację, której wspólna ścieżka wykonywania spraw oczekuje, że zakończy się ona sukcesem bez tej zewnętrznej przerwy. Wszystkie te rzeczy są poza kontrolą bezpośredniego oprogramowania i programistów, którzy je napisali. Są to nieoczekiwane wyniki ze źródeł zewnętrznych, które uniemożliwiają operację (którą tak naprawdę należy uważać za niepodzielną transakcję w mojej książce *).

Transakcje

Często zachęcam do patrzenia na tryblok jako „transakcję”, ponieważ transakcje powinny odnieść sukces jako całość lub zawieść jako całość. Jeśli próbujemy coś zrobić, a to nie powiedzie się w połowie, wszelkie efekty uboczne / mutacje wprowadzone do stanu programu zazwyczaj muszą zostać wycofane, aby przywrócić system do prawidłowego stanu, tak jakby transakcja nigdy nie została wykonana, podobnie jak RDBMS, który nie przetwarza zapytania w połowie, nie powinien naruszać integralności bazy danych. Jeśli mutujesz stan programu bezpośrednio we wspomnianej transakcji, musisz „cofnąć mutację” po napotkaniu błędu (i tutaj osłony zakresu mogą być przydatne w RAII).

Znacznie prostszą alternatywą jest nie mutowanie stanu oryginalnego programu; możesz zmutować kopię, a następnie, jeśli się powiedzie, zamień kopię na oryginał (upewniając się, że zamiana nie będzie możliwa). Jeśli się nie powiedzie, odrzuć kopię. Dotyczy to również sytuacji, gdy nie używasz wyjątków do obsługi błędów w ogóle. „Transakcyjny” sposób myślenia jest kluczem do prawidłowego powrotu do zdrowia, jeśli przed wystąpieniem błędu wystąpiły mutacje stanu programu. Albo odnosi sukces jako całość, albo porażka jako całość. Nie jest w połowie w stanie dokonać mutacji.

Jest to dziwnie jeden z najrzadziej omawianych tematów, gdy widzę, że programiści pytają, jak poprawnie obsługiwać błędy lub wyjątki, ale najtrudniej jest uzyskać wszystko w każdym oprogramowaniu, które chce bezpośrednio mutować stan programu w wielu jego operacje. Czystość i niezmienność mogą tutaj pomóc w osiągnięciu bezpieczeństwa wyjątkowego w takim samym stopniu, jak pomagają w zabezpieczeniu nici, ponieważ mutacja / zewnętrzny efekt uboczny, który nie występuje, nie musi być cofany.

Wydajność

Kolejnym czynnikiem decydującym o tym, czy stosować wyjątki, jest wydajność, i nie mam na myśli obsesyjnego, szczypiącego grosza, odwrotnego do zamierzonego efektu. Wiele kompilatorów C ++ implementuje tak zwaną „obsługę wyjątków zerowych kosztów”.

Zapewnia zerowe obciążenie środowiska wykonawczego dla wykonania bezbłędnego, co przewyższa nawet obsługę błędów zwracanych przez wartość C. Jako kompromis propagacja wyjątku ma duże koszty ogólne.

Zgodnie z tym, co o nim czytałem, sprawia, że ​​ścieżki wykonywania zwykłych spraw nie wymagają narzutu (nawet narzutu, który zwykle towarzyszy obsłudze i propagacji kodów błędów w stylu C), w zamian za znaczne przesunięcie kosztów w kierunku wyjątkowych ścieżek ( co oznacza, że throwingjest teraz droższy niż kiedykolwiek).

„Drogie” jest nieco trudne do oszacowania, ale na początek prawdopodobnie nie chcesz rzucać miliona razy w jakąś ciasną pętlę. Ten rodzaj projektu zakłada, że ​​wyjątki nie występują przez cały czas po lewej i po prawej stronie.

Brak błędów

I ten punkt wydajności prowadzi mnie do braku błędów, co jest zaskakująco niewyraźne, jeśli spojrzymy na wiele innych języków. Ale powiedziałbym, biorąc pod uwagę wspomniany powyżej projekt EH o zerowym koszcie, że prawie na pewno nie chcesz tego throww odpowiedzi na brak klucza w zestawie. Ponieważ nie tylko jest to prawdopodobnie błąd (osoba szukająca klucza mogła zbudować zestaw i oczekiwać, że będzie szukała kluczy, które nie zawsze istnieją), ale w tym kontekście byłaby niezwykle droga.

Na przykład funkcja przecinania zestawu może chcieć przejrzeć dwa zestawy i wyszukać klucze, które mają wspólne. Jeśli nie uda ci się znaleźć klucza threw, będziesz zapętlał się i możesz napotkać wyjątki w połowie lub więcej iteracji:

Set<int> set_intersection(const Set<int>& a, const Set<int>& b)
{
     Set<int> intersection;
     for (int key: a)
     {
          try
          {
              b.find(key);
              intersection.insert(other_key);
          }
          catch (const KeyNotFoundException&)
          {
              // Do nothing.
          }
     }
     return intersection;
}

Ten powyższy przykład jest absolutnie śmieszny i przesadzony, ale widziałem w kodzie produkcyjnym, że niektórzy ludzie pochodzący z innych języków używają wyjątków w C ++ w podobny sposób, i myślę, że jest to dość praktyczne stwierdzenie, że nie jest to właściwe użycie wyjątków w C ++. Inną wskazówką powyżej jest to, że zauważysz, że catchblok nie ma absolutnie nic do roboty i jest napisany, aby zignorować takie wyjątki, i zwykle jest to wskazówka (choć nie jest gwarantem), że wyjątki prawdopodobnie nie są używane odpowiednio w C ++.

W przypadku tego rodzaju przypadków pewien rodzaj wartości zwracanej wskazującej błąd (wszystko od powrotu falsedo nieprawidłowego iteratora lub nullptrcokolwiek innego, co ma sens w kontekście) jest zwykle o wiele bardziej odpowiedni, a także często bardziej praktyczny i produktywny, ponieważ nie zawiera błędów case zwykle nie wymaga jakiegoś procesu rozwijania stosu, aby dotrzeć do analogicznej catchstrony.

pytania

Musiałbym użyć wewnętrznych flag błędów, jeśli zdecyduję się uniknąć wyjątków. Czy będzie to zbyt kłopotliwe w obsłudze, czy może będzie działało nawet lepiej niż wyjątki? Najlepszym rozwiązaniem byłoby porównanie obu przypadków.

Unikanie wyjątków wprost w C ++ wydaje mi się wyjątkowo przeciwne do zamierzonego, chyba że pracujesz w jakimś systemie wbudowanym lub w konkretnym rodzaju skrzynki, która zabrania ich używania (w takim przypadku musisz również zrobić wszystko, aby uniknąć wszystkich biblioteka i funkcjonalność językowa, która w innym przypadku byłaby throwściśle używana nothrow new).

Jeśli absolutnie musisz unikać wyjątków z jakiegokolwiek powodu (np. Praca nad granicami C API modułu, którego eksportujesz C API), wielu może się ze mną nie zgodzić, ale w rzeczywistości sugerowałbym użycie globalnego modułu obsługi błędów / statusu, takiego jak OpenGL glGetError(). Możesz użyć pamięci lokalnej, aby mieć unikalny status błędu dla każdego wątku.

Uzasadniam to tym, że nie jestem przyzwyczajony do patrzenia na zespoły w środowisku produkcyjnym, dokładnie sprawdzające wszystkie możliwe błędy, niestety, gdy zwracane są kody błędów. Gdyby były dokładne, niektóre interfejsy API C mogą napotkać błąd przy prawie każdym pojedynczym wywołaniu API C, a dokładne sprawdzenie wymagałoby:

if ((err = ApiCall(...)) != success)
{
     // Handle error
}

... przy prawie każdym wierszu kodu wywołującym interfejs API wymagającym takich kontroli. Jednak nie miałem szczęścia pracować z tak dokładnymi zespołami. Często ignorują takie błędy w połowie, a czasem nawet w większości przypadków. To dla mnie największy apel wyjątków. Jeśli opakowujemy ten interfejs API i ujednolicamy go throwpo napotkaniu błędu, wyjątku nie można zignorować , a moim zdaniem i doświadczenia, właśnie tam leży przewaga wyjątków.

Ale jeśli nie można zastosować wyjątków, to globalny status błędu według wątków ma przynajmniej tę zaletę (ogromną w porównaniu do zwracania mi kodów błędów), że może mieć szansę złapać poprzedni błąd nieco później niż wtedy, gdy wystąpił w jakiejś niechlujnej bazie kodu, zamiast go całkowicie pominąć i pozostawiając nas całkowicie nieświadomymi co się stało. Błąd mógł pojawić się kilka linii wcześniej lub w poprzednim wywołaniu funkcji, ale pod warunkiem, że oprogramowanie jeszcze się nie zawiesiło, możemy zacząć pracować wstecz i dowiedzieć się, gdzie i dlaczego.

Wydaje mi się, że ponieważ wskaźniki są rzadkie, musiałbym stosować wewnętrzne flagi błędów, jeśli zdecyduję się uniknąć wyjątków.

Niekoniecznie powiedziałbym, że wskaźniki są rzadkie. Istnieją nawet metody w C ++ 11 i nowsze, aby uzyskać dostęp do bazowych wskaźników danych kontenerów i nowego nullptrsłowa kluczowego. Generalnie uważa się za nierozsądne stosowanie surowych wskaźników do posiadania / zarządzania pamięcią, jeśli można użyć czegoś podobnego, unique_ptrbiorąc pod uwagę, jak krytyczne jest to, aby być zgodnym z RAII w przypadku wyjątków. Jednak surowe wskaźniki, które nie posiadają pamięci / nie zarządzają pamięcią, niekoniecznie są uważane za tak złe (nawet od ludzi takich jak Sutter i Stroustrup), a czasem bardzo praktyczne jako sposób wskazywania na rzeczy (wraz ze wskaźnikami wskazującymi na rzeczy).

Są one prawdopodobnie nie mniej bezpieczne niż standardowe iteratory kontenerów (przynajmniej w wersji, nieobecne sprawdzone iteratory), które nie wykryją, jeśli spróbujesz je wyrenderować po ich unieważnieniu. Powiedziałbym, że C ++ wciąż jest trochę niebezpiecznym językiem, chyba że twoje specyficzne użycie chce opakować wszystko i ukryć nawet nieposiadające surowych wskaźników. Jest prawie krytyczne z wyjątkami, że zasoby są zgodne z RAII (co generalnie nie wiąże się z żadnymi kosztami środowiska uruchomieniowego), ale poza tym niekoniecznie stara się być najbezpieczniejszym językiem w celu uniknięcia kosztów, których deweloper wyraźnie nie chce wymienić na coś innego. Zalecane użycie nie próbuje chronić cię przed takimi zwisającymi wskaźnikami i unieważnionymi iteratorami, że tak powiem (w przeciwnym razie zachęcamy do korzystaniashared_ptrw całym miejscu, któremu Stroustrup gwałtownie się sprzeciwia). Stara się chronić cię przed niepowodzeniem w prawidłowym uwolnieniu / zwolnieniu / zniszczeniu / odblokowaniu / oczyszczeniu zasobu, gdy coś throws.

Dragon Energy
źródło
14

Oto rzecz: z powodu wyjątkowej historii i elastyczności C ++ można znaleźć kogoś, kto głosi praktycznie każdą opinię na temat dowolnej funkcji, którą chcesz. Jednak ogólnie rzecz biorąc, im bardziej to, co robisz, wygląda na C, tym gorszy jest pomysł.

Jednym z powodów, dla których C ++ jest znacznie luźniejszy, jeśli chodzi o wyjątki, jest to, że nie możesz dokładnie, return nilkiedy masz na to ochotę. Nie ma czegoś takiego jak nilw zdecydowanej większości przypadków i typów.

Ale oto prosty fakt. Wyjątki działają automatycznie. Po zgłoszeniu wyjątku RAII przejmuje kontrolę i wszystko jest obsługiwane przez kompilator. Kiedy używasz kodów błędów, musisz pamiętać o ich sprawdzeniu. To z natury sprawia, że ​​wyjątki są znacznie bezpieczniejsze niż kody błędów - same się sprawdzają. Ponadto są bardziej wyraziste. Zgłaszając wyjątek, możesz uzyskać ciąg informujący o błędzie, a nawet może zawierać określone informacje, takie jak „Zły parametr X, który miał wartość Y zamiast Z”. Uzyskaj kod błędu 0xDEADBEEF i co dokładnie poszło nie tak? Mam nadzieję, że dokumentacja jest kompletna i aktualna, a nawet jeśli pojawi się „Zły parametr”, nigdy nie powie ci, któryparametr, jaką wartość miał i jaką wartość powinien mieć. Jeśli złapiesz również przez odniesienie, mogą być polimorficzne. Wreszcie wyjątki mogą być zgłaszane z miejsc, w których kody błędów nigdy nie mogą, takich jak konstruktory. A co powiesz na ogólne algorytmy? Jak std::for_eachsobie poradzisz z kodem błędu? Porada od specjalistów: tak nie jest.

Pod każdym względem wyjątki są znacznie lepsze niż kody błędów. Prawdziwe pytanie dotyczy wyjątków vs. twierdzeń.

To jest ta rzecz. Nikt nie może z góry wiedzieć, jakie warunki wstępne ma Twój program do działania, jakie warunki awarii są niezwykłe, które można wcześniej sprawdzić, a które są błędami. Ogólnie oznacza to, że nie można wcześniej zdecydować, czy dana awaria powinna być twierdzeniem, czy wyjątkiem, nie znając logiki programu. Ponadto operacja, która może być kontynuowana, gdy jedna z jej operacji podrzędnych zawiedzie, jest wyjątkiem , a nie regułą.

Moim zdaniem wyjątki są do złapania. Niekoniecznie natychmiast, ale w końcu. Wyjątkiem jest problem, z którego oczekuje się, że program będzie w stanie odzyskać w pewnym momencie. Ale operacja, o której mowa, nigdy nie wyjdzie z problemu, który uzasadnia wyjątek.

Niepowodzenia asercji są zawsze fatalnymi, nieodwracalnymi błędami. Takie uszkodzenie pamięci.

Więc jeśli nie możesz otworzyć pliku, to czy jest to twierdzenie czy wyjątek? Cóż, w bibliotece ogólnej istnieje wiele scenariuszy, w których można sobie poradzić z błędem , na przykład ładując plik konfiguracyjny, zamiast tego możesz po prostu użyć domyślnej wersji, więc powiedziałbym, że wyjątek.

Jako przypis chciałbym wspomnieć, że krąży wokół coś „Null Object Pattern”. Ten wzór jest okropny . Za dziesięć lat będzie to następny Singleton. Liczba przypadków, w których można wytworzyć odpowiedni obiekt zerowy, jest niewielka .

DeadMG
źródło
Alternatywą niekoniecznie jest prosta int. Bardziej prawdopodobne byłoby użycie czegoś podobnego do NSError, do którego jestem przyzwyczajony z Obj-C.
Per Johansson,
Porównuje nie do C, ale do Celu C. To duża różnica. A jego kody błędów wyjaśniają wiersze. Wszystko, co mówisz, jest poprawne, ale jakoś nie odpowiada na to pytanie. Bez obrazy.
Gangnus
Każdy, kto używa Objective-C, powie ci oczywiście, że całkowicie się mylisz.
gnasher729,
5

Wyjątki zostały wymyślone z jakiegoś powodu, aby uniknąć sytuacji, w której cały kod wyglądałby tak:

bool success = function1(&result1, &err);
if (!success) {
    error_handler(err);
    return;
}

success = function2(&result2, &err);
if (!success) {
    error_handler(err);
    return;
}

Zamiast tego otrzymujesz coś, co wygląda bardziej jak poniżej, z jedną procedurą obsługi wyjątków w górę mainlub w innej dogodnej lokalizacji:

result1 = function1();
result2 = function2();

Niektóre osoby twierdzą, że nie ma wyjątku, korzyści w zakresie wydajności, ale moim zdaniem problemy z czytelnością przeważają nad niewielkimi przyrostami wydajności, szczególnie gdy uwzględnisz czas wykonania dla całej if (!success)płyty kotła, którą musisz posypać wszędzie, lub ryzyko trudniejszego debugowania błędów segregujących, jeśli nie „ uwzględnij go, a biorąc pod uwagę szanse wystąpienia wyjątków, są stosunkowo rzadkie.

Nie wiem, dlaczego Apple odradza stosowanie wyjątków. Jeśli próbują uniknąć propagowania nieobsługiwanych wyjątków, wszystko, co naprawdę osiągasz, to ludzie używający wskaźników zerowych w celu wskazania wyjątków, więc błąd programisty powoduje wyjątek wskaźnika zerowego zamiast o wiele bardziej przydatnego wyjątku nie znalezionego pliku lub cokolwiek innego.

Karl Bielefeldt
źródło
1
Byłoby to bardziej sensowne, gdyby kod bez wyjątków faktycznie tak wyglądał, ale zwykle tak nie jest. Ten wzorzec pojawia się, gdy moduł obsługi błędów nigdy nie powraca, ale rzadko.
Per Johansson,
1

W poście, do którego się odwołujesz (wyjątki kontra kody błędów), myślę, że toczy się nieco inna dyskusja. Wydaje się, że istnieje pytanie, czy masz globalną listę #definicznych kodów błędów, prawdopodobnie wraz z nazwami takimi jak ERR001_FILE_NOT_WRITEABLE (lub w skrócie, jeśli masz pecha). I myślę, że głównym punktem tego wątku jest to, że jeśli programujesz w języku polimorficznym, używając instancji obiektów, taka procedura nie jest konieczna. Wyjątki mogą wyrażać, jaki błąd występuje po prostu ze względu na ich typ, i mogą zawierać informacje, takie jak komunikat do wydrukowania (i inne rzeczy). Tak więc wziąłbym tę rozmowę za to, czy powinieneś programować proceduralnie w języku obiektowym, czy nie.

Ale rozmowa o tym, kiedy i jak polegać na wyjątkach dotyczących obsługi sytuacji pojawiających się w kodzie, jest inna. Kiedy wyjątki są zgłaszane i wychwytywane, wprowadzasz zupełnie inny paradygmat przepływu sterowania niż tradycyjny stos wywołań. Zgłaszanie wyjątków jest w zasadzie instrukcją goto, która wykreśla cię ze stosu wywołań i wysyła do jakiejś nieokreślonej lokalizacji (gdziekolwiek kod klienta decyduje się obsłużyć twój wyjątek). To sprawia, że ​​logika wyjątków jest bardzo trudna do uzasadnienia .

I tak istnieje sentyment wyrażony przez Jheriko, że należy je zminimalizować. To znaczy, powiedzmy, że czytasz plik, który może, ale nie musi istnieć na dysku. Jheriko i Apple oraz ci, którzy myślą tak samo, jak ja (w tym ja), twierdzą, że zgłoszenie wyjątku nie jest właściwe - brak tego pliku nie jest wyjątkowy , należy się spodziewać. Wyjątki nie powinny zastępować normalnego przepływu sterowania. Równie łatwo jest zwrócić metodę ReadFile (), powiedzmy, wartość logiczną, a kod klienta widzi z wartości zwracanej fałsz, że plik nie został znaleziony. Kod klienta może wtedy stwierdzić, że plik użytkownika nie został znaleziony, lub po cichu obsłużyć tę sprawę lub cokolwiek chce zrobić.

Rzucając wyjątek, obciążasz swoich klientów. Dajesz im kod, który czasami wykręci ich z normalnego stosu wywołań i zmusi do napisania dodatkowego kodu, aby zapobiec awarii aplikacji. Taki potężny i wstrząsający ciężar powinien być na nich narzucony tylko wtedy, gdy jest to absolutnie konieczne w czasie wykonywania lub w interesie filozofii „wczesnego upadku”. Tak więc w pierwszym przypadku możesz zgłaszać wyjątki, jeśli celem twojej aplikacji jest monitorowanie niektórych operacji sieciowych i ktoś odłącza kabel sieciowy (sygnalizujesz katastrofalną awarię). W tym drugim przypadku możesz zgłosić wyjątek, jeśli twoja metoda oczekuje parametru innego niż null i otrzymujesz wartość null (sygnalizujesz, że jesteś niewłaściwie używany).

Jeśli chodzi o to, co robić zamiast wyjątków w świecie zorientowanym obiektowo, masz opcje poza konstrukcyjnym kodem błędu proceduralnego. Przypomina mi się, że możesz tworzyć obiekty o nazwie OperationResult lub coś w tym rodzaju. Metoda może zwrócić wartość OperationResult, a obiekt ten może zawierać informacje o tym, czy operacja się powiodła, i dowolne inne informacje, jakie ma ona mieć (na przykład komunikat o stanie, ale także, bardziej subtelnie, może zawierać strategie odzyskiwania błędów ). Zwrócenie tego zamiast zgłaszania wyjątku pozwala na polimorfizm, zachowuje przepływ kontroli i znacznie ułatwia debugowanie.

Erik Dietrich
źródło
Zgadzam się z tobą, ale wydaje się, że powodem, dla którego wydaje się, że nie, jest to, że zadałem to pytanie.
Per Johansson
0

W Clean Code znajduje się kilka rozdziałów, które mówią o tym właśnie temacie - bez punktu widzenia Apple.

Podstawową ideą jest to, że nigdy nie zwracasz zera żadnych funkcji, ponieważ:

  • Dodaje dużo zduplikowanego kodu
  • Robi bałagan w twoim kodzie - Tzn .: Trudniej jest go odczytać
  • Może często powodować błędy wskaźnika zerowego lub naruszać prawa dostępu w zależności od konkretnej „religii” programowania

Innym towarzyszącym pomysłem jest stosowanie wyjątków, ponieważ pomaga to jeszcze bardziej ograniczyć bałagan w kodzie, a nie tworzyć szeregu kodów błędów, które ostatecznie stają się trudne do śledzenia w zarządzaniu i utrzymywaniu (szczególnie na wielu modułach i platformach) oraz jest to dla klientów trudne do rozszyfrowania, zamiast tego tworzysz sensowne komunikaty, które identyfikują dokładną naturę problemu, upraszczają debugowanie i mogą zredukować kod obsługi błędów do czegoś tak prostego, jak proste try..catchstwierdzenie.

W czasach programowania COM miałem projekt, w którym każda awaria powodowała wyjątek, a każdy wyjątek wymagał użycia unikalnego identyfikatora kodu błędu opartego na rozszerzeniu standardowych wyjątków COM systemu Windows. Było to zawieszenie wcześniejszego projektu, w którym wcześniej stosowano kody błędów, ale firma chciała zorientować się na obiekt i wskoczyć na modę COM bez zmiany sposobu, w jaki robili zbyt wiele. Zabrakło im tylko 4 miesięcy, by zabrakło numerów kodów błędów, i wpadłem na siebie, aby przefakturować duże obszary kodu, aby zamiast tego zastosować wyjątki. Lepiej oszczędzić sobie wysiłku i po prostu rozsądnie korzystać z wyjątków.

S.Robins
źródło
Dzięki, ale nie rozważam zwrotu wartości NULL. W każdym razie nie wydaje się to możliwe w obliczu konstruktorów. Jak wspomniałem, prawdopodobnie musiałbym użyć flagi błędu w obiekcie.
Per Johansson,