2009-08-26 15 views
41

Co to jest dobry projekt dla zestawu klas wyjątków? Widzę różnego rodzaju rzeczy wokół tego, co klasy wyjątków powinny i nie powinny robić, ale nie prosty projekt, który jest łatwy w obsłudze i rozszerzeniu, który robi te rzeczy.C++ Projektowanie klasy wyjątków

  1. Klasy wyjątków nie należy rzucać wyjątków, ponieważ może to prowadzić prosto do zakończenia procesu bez możliwości zalogowania błąd itd
  2. To musi być możliwe, aby uzyskać przyjazne dla użytkownika ciąg znaków, preferowane zlokalizowane na ich język, tak aby było coś do powiedzenia, zanim aplikacja zakończy działanie, jeśli nie będzie w stanie naprawić błędu.
  3. Musi istnieć możliwość dodawania informacji podczas rozwijania stosu, np. Jeśli parser xml nie przeanalizuje strumienia wejściowego, aby można było dodać, że źródło pochodziło z pliku lub przez sieć itp.
  4. Programy obsługi wyjątków potrzebują łatwego dostępu do informacji potrzebnych do obsługi wyjątku.
  5. Zapisuj sformatowane informacje o wyjątku do pliku dziennika (w języku angielskim, więc nie ma tu tłumaczeń).

Pierwsze 1 i 4 do wspólnej pracy to największy problem, jaki mam, ponieważ wszelkie metody formatowania i plików mogą potencjalnie zawieść.

EDYCJA: Tak więc po zapoznaniu się z klasami wyjątków w kilku klasach, a także w pytaniu, z którym związany jest Neil, wydaje się, że powszechną praktyką jest po prostu całkowite zignorowanie punktu 1 (i tym samym zaleceń doładowania), który wydaje się być raczej zły pomysł dla mnie.

W każdym razie myślałem, że id również opublikuję klasę wyjątków, którą zamierzam użyć.

class Exception : public std::exception 
{ 
public: 
    //enum for each exception type, which can also be used to determin 
    //exception class, useful for logging or other localisation methods 
    //for generating a message of some sort. 
    enum ExceptionType 
    { 
     //shouldnt ever be thrown 
     UNKNOWN_EXCEPTION = 0, 
     //same as above but has a string that may provide some info 
     UNKNOWN_EXCEPTION_STR, 
     //eg file not found 
     FILE_OPEN_ERROR, 
     //lexical cast type error 
     TYPE_PARSE_ERROR, 
     //NOTE: in many cases functions only check and throw this in debug 
     INVALID_ARG, 
     //an error occured while trying to parse data from a file 
     FILE_PARSE_ERROR, 
    } 
    virtual ExceptionType getExceptionType()const throw() 
    { 
     return UNKNOWN_EXCEPTION; 
    } 
    virtual const char* what()throw(){return "UNKNOWN_EXCEPTION";} 
}; 
class FileOpenError : public Exception 
{ 
public: 
    enum Reason 
    { 
     FILE_NOT_FOUND, 
     LOCKED, 
     DOES_NOT_EXIST, 
     ACCESS_DENIED 
    }; 
    FileOpenError(Reason reason, const char *file, const char *dir)throw(); 
    Reason getReason()const throw(); 
    const char* getFile()const throw(); 
    const char* getDir()const throw(); 
private: 
    Reason reason; 
    static const unsigned FILE_LEN = 256; 
    static const unsigned DIR_LEN = 256; 
    char file[FILE_LEN], dir[DIR_LEN]; 
}; 

Punkt 1 jest skierowana ponieważ wszystkie ciągi są obsługiwane przez kopiowanie do wewnętrznego bufora, ustalonej wielkości (obcinanie, jeśli to konieczne, ale zawsze null zakończone).

Mimo że nie dotyczy to punktu 3, myślę, że punkt ten najprawdopodobniej ma ograniczone zastosowanie w realnym świecie i najprawdopodobniej zostanie rozwiązany przez zgłoszenie nowego wyjątku w razie potrzeby.

Odpowiedz

2

Dobry projekt nie polega na utworzeniu zestawu klas wyjątków - wystarczy utworzyć jedną na bibliotekę, na podstawie wyjątku std ::.

Dodanie informacji jest dość proste:

try { 
    ... 
} 
catch(const MyEx & ex) { 
    throw MyEx(ex.what() + " more local info here"); 
} 

I wyjątek treserzy potrzebnych informacji, ponieważ są one wyjątek teleskopowe - tylko funkcje w blokach try mogą powodować wyjątki, więc przewodnicy tylko trzeba brać pod uwagę błędy. I nie powinieneś tak naprawdę używać wyjątków do ogólnej obsługi błędów.

Zasadniczo, wyjątki powinny być tak proste, jak to możliwe - trochę jak logi, które nie powinny mieć bezpośredniego połączenia.

To pytanie zostało już wcześniej zadane, ale nie mogę go teraz znaleźć.

+1

Ten? http://stackoverflow.com/questions/1157591/c-exception-handling – GManNickG

+0

To był ten, o którym myślałem, chociaż ponownie go czytałem, wydaje się, że odpowiada na nieco inne pytania. –

+8

Co jest nie tak z różnymi klasami? Możesz obsługiwać je inaczej, a nawet obsługiwać niektóre i pozostawiać innych. –

7

Użyj dziedziczenia wirtualnego. Ten pogląd zawdzięczamy Andrew Koenigowi. Korzystanie z dziedziczenia wirtualnego z klasy podstawowej uniknięcia zapobiega problemom niejednoznaczności w miejscu przechwytywania w przypadku, gdy ktoś zgłasza wyjątek wywodzący się z wielu baz, które mają wspólną klasę podstawową.

Inne równie przydatne porady na boost site

+1

W pierwszej kolejności nie wyprowadzaj wyjątków z wielu baz? Nie mam nic przeciwko MI w ogóle, ale nie widzę żadnego powodu, by używać go do klas wyjątków. –

+0

Czy jest nie do pomyślenia, aby wyjątek mógł być spowodowany więcej niż jednym typem problemu? –

+5

Ktoś oczywiście pisze aplikacje, a nie biblioteki. Struktura jest legalna w C++ i jako taka będzie używana bez względu na to, że "nie widzimy żadnego powodu, aby używać jej dla klas wyjątków". Zawdzięczasz Jonowi uprowadzenie, jeśli nie przeprosiny - pgast 0 secs ago – pgast

7


2: No nie należy mieszać interfejs użytkownika (= zlokalizowane wiadomości) z logiką programu. Komunikacja z użytkownikiem powinna odbywać się na poziomie zewnętrznym, gdy aplikacja zdaje sobie sprawę, że nie może obsłużyć problemu. Większość informacji w wyjątku jest zbyt dużym szczegółem implementacji, aby mimo to pokazać użytkownika.
3: Użyj boost.exception dla tego
5: Nie, nie rób tego. Patrz 2. Decyzja o logowaniu powinna zawsze znajdować się na stronie obsługi błędów.

Nie używaj tylko jednego typu wyjątku. Użyj wystarczającej liczby typów, aby aplikacja mogła użyć oddzielnego programu obsługi catch dla każdego typu odzyskiwania po błędzie potrzebnego:

23

Użyj płytkiej hierarchii klas wyjątków. Zbyt głęboka hierarchia zwiększa złożoność niż wartość.

Wylicz klasy wyjątków ze std :: exception (lub jednego z innych standardowych wyjątków, takich jak std :: runtime_error). Umożliwia to generyczne funkcje obsługi wyjątków na najwyższym poziomie w celu radzenia sobie z wyjątkami, których nie używasz. Na przykład może istnieć procedura obsługi wyjątków, która rejestruje błędy.

Jeśli dotyczy to konkretnej biblioteki lub modułu, możesz potrzebować bazy właściwej dla twojego modułu (nadal pochodzącej z jednej ze standardowych klas wyjątków). Dzwoniący mogą zdecydować się na przechwycenie czegokolwiek z twojego modułu w ten sposób.

Nie zrobiłbym zbyt wielu klas wyjątków. Możesz spakować wiele szczegółów na temat wyjątku w klasie, więc niekoniecznie musisz stworzyć wyjątkową klasę wyjątków dla każdego rodzaju błędu. Z drugiej strony, chcesz unikalnych klas dla błędów, które spodziewają się obsłużyć. Jeśli tworzysz analizator składni, możesz mieć jeden wyjątek składni wyjątku z członkami opisującymi szczegóły problemu, a nie kilka wyjątków dla różnych typów błędów składniowych.

Łańcuchy w wyjątkach służą do debugowania. Nie należy ich używać w interfejsie użytkownika. Chcesz, aby interfejs użytkownika i logika były jak najbardziej oddzielne, aby umożliwić takie rzeczy jak tłumaczenie na inne języki.

Twoje klasy wyjątków mogą mieć dodatkowe pola ze szczegółowymi informacjami na temat problemu. Na przykład wyjątek składniowy może mieć nazwę pliku źródłowego, numer wiersza itp. W miarę możliwości należy trzymać się podstawowych typów dla tych pól, aby zmniejszyć szansę na skonstruowanie lub skopiowanie wyjątku, aby uruchomić inny wyjątek. Na przykład, jeśli musisz zapisać nazwę pliku w wyjątku, możesz potrzebować tablicy znaków o stałej długości, zamiast std :: string. Typowe implementacje wyjątku std :: dynamicznie przydzielają ciąg znaków za pomocą funkcji malloc. Jeśli malloc nie powiedzie się, poświęcą ciąg powodu, zamiast rzucać zagnieżdżony wyjątek lub awarię.

Wyjątki w C++ powinny być dla "wyjątkowych" warunków. Tak więc przykłady analizowania mogą nie być dobre. Błąd składniowy napotkany podczas analizowania pliku może nie być wystarczająco wyjątkowy, aby zagwarantować obsługę wyjątków. Powiedziałbym, że coś jest wyjątkowe, jeśli program prawdopodobnie nie może być kontynuowany, chyba że warunek jest jawnie obsługiwany. Tak więc większość awarii alokacji pamięci jest wyjątkowa, ale złe dane wejściowe od użytkownika prawdopodobnie nie są.

+1

"Ciągi w wyjątkach służą do debugowania Nie powinieneś ich używać w interfejsie użytkownika Chcesz zachować interfejs i logikę jak najdalej, aby włączyć takie rzeczy jak tłumaczenie na inne języki. " więc gdzie i jak powinny być generowane takie łańcuchy, jak powiedziałeś, exceptiosn to użyteczne rzeczy, które powodują wyjście programu, lub przynajmniej nie robią tego, co chciał użytkownik, więc musi być teksturowana reprezentacja, która jest przyjazna dla użytkownika końcowego dla ogromnej większości wyjątków. –

+5

Blok catch, który obsługuje wyjątek, powinien załadować ciąg UI z zasobów. Coś jak: 'catch (const syntax_error & ex) {std :: cerr << ex.filename() <<" ("<< ex.line_number() <<"): "<< GetText (IDS_SYNTAXERROR) << std: : endl; } ', gdzie' GetText() 'ładuje ciąg z zasobów twojego programu. –

+0

czy C++ 11 lub C++ 17 zmieniłoby cokolwiek w twojej odpowiedzi? Bardziej interesuje mnie C++ 11 niż C++ 17. – Gizmo

4

nie są bezpośrednio związane z projektem hierarchii klasowej wyjątek, ale ważne (i związane z użyciem tych wyjątków) jest to, że powinien generalnie throw by value and catch by reference.

Pozwala to uniknąć problemów związanych z zarządzaniem pamięci rzucony wyjątek (jeśli rzucałeś wskaźniki) i potencjał do krojenia obiektów (jeśli łapiesz wyjątki według wartości).

0

Od std::nested_exception i std::throw_with_nested stały się dostępne z C++ 11, chciałbym zwrócić się do odpowiedzi na StackOverflow here i here

odpowiedzi te opisują w jaki sposób można uzyskać ślad na swoim wyjątkami wewnątrz Twój kod bez potrzeby debuggera lub uciążliwego logowania, poprzez napisanie odpowiedniego programu obsługi wyjątku, który ponownie wyrzuci wyjątki zagnieżdżone.

Konstrukcja istnieje wyjątek, moim zdaniem, również sugeruje, aby nie tworzyć hierarchie klasowe wyjątek, ale utworzyć tylko jedną klasę wyjątku za biblioteką (jak już wskazano w an answer to this question).

Powiązane problemy