W przypadku niektórych języków (np. C ++) wyciek zasobów nie powinien być przyczyną
C ++ jest oparty na RAII.
Jeśli masz kod, który może zawieść, zwrócić lub wyrzucić (czyli większość normalnego kodu), to powinieneś mieć swój wskaźnik zawinięty wewnątrz inteligentnego wskaźnika (zakładając, że masz bardzo dobry powód, aby nie tworzyć obiektu na stosie).
Kody zwrotne są bardziej szczegółowe
Są gadatliwe i mają tendencję do przekształcania się w coś takiego:
if(doSomething())
{
if(doSomethingElse())
{
if(doSomethingElseAgain())
{
// etc.
}
else
{
// react to failure of doSomethingElseAgain
}
}
else
{
// react to failure of doSomethingElse
}
}
else
{
// react to failure of doSomething
}
W końcu twój kod jest zbiorem zidentyfikowanych instrukcji (widziałem ten rodzaj kodu w kodzie produkcyjnym).
Ten kod można z powodzeniem przetłumaczyć na:
try
{
doSomething() ;
doSomethingElse() ;
doSomethingElseAgain() ;
}
catch(const SomethingException & e)
{
// react to failure of doSomething
}
catch(const SomethingElseException & e)
{
// react to failure of doSomethingElse
}
catch(const SomethingElseAgainException & e)
{
// react to failure of doSomethingElseAgain
}
Który wyraźnie oddziela kod i przetwarzanie błędów, co może być dobrą rzeczą.
Kody zwrotne są bardziej kruche
Jeśli nie jakieś niejasne ostrzeżenie z jednego kompilatora (zobacz komentarz "phjr"), można je łatwo zignorować.
W powyższych przykładach załóżmy, że ktoś zapomni zająć się możliwym błędem (to się dzieje ...). Błąd jest ignorowany, gdy „zwracany”, i prawdopodobnie eksploduje później (tj. Wskaźnik NULL). Ten sam problem nie wystąpi z wyjątkiem.
Błąd nie zostanie zignorowany. Czasami jednak chcesz, żeby nie wybuchło ... Więc musisz ostrożnie wybierać.
Czasami kody zwrotne muszą być przetłumaczone
Powiedzmy, że mamy następujące funkcje:
- doSomething, które może zwrócić int o nazwie NOT_FOUND_ERROR
- doSomethingElse, która może zwrócić wartość logiczną „false” (w przypadku niepowodzenia)
- doSomethingElseAgain, która może zwrócić obiekt Error (z zarówno __LINE__, __FILE__, jak i połową zmiennych stosu.
- doTryToDoSomethingWithAllThisMess, które, cóż ... Użyj powyższych funkcji i zwróć kod błędu typu ...
Jaki jest typ zwrotu funkcji doTryToDoSomethingWithAllThisMess, jeśli jedna z wywoływanych funkcji nie powiedzie się?
Kody zwrotne nie są uniwersalnym rozwiązaniem
Operatorzy nie mogą zwrócić kodu błędu. Konstruktorzy C ++ też nie mogą.
Kody zwrotne oznaczają, że nie możesz łączyć wyrażeń
Następstwem powyższego punktu. A jeśli chcę napisać:
CMyType o = add(a, multiply(b, c)) ;
Nie mogę, ponieważ wartość zwracana jest już używana (a czasami nie można jej zmienić). Zatem wartość zwracana staje się pierwszym parametrem, wysyłanym jako odniesienie ... Lub nie.
Wyjątki są wpisywane
Możesz wysłać różne klasy dla każdego rodzaju wyjątku. Wyjątki zasobów (tj. Brak pamięci) powinny być lekkie, ale wszystko inne może być tak ciężkie, jak to konieczne (podoba mi się wyjątek Java, który daje mi cały stos).
Każdy połów można następnie wyspecjalizować.
Nigdy nie używaj catch (...) bez ponownego rzucenia
Zwykle nie powinieneś ukrywać błędu. Jeśli nie wyrzucisz ponownie, przynajmniej zapisz błąd w pliku, otwórz skrzynkę wiadomości, cokolwiek ...
Wyjątkiem są ... NUKE
Jedynym problemem jest to, że ich nadużywanie spowoduje powstanie kodu pełnego prób / przechwyceń. Ale problem jest gdzie indziej: kto próbuje / przechwytuje jego kod za pomocą kontenera STL? Mimo to te kontenery mogą wysłać wyjątek.
Oczywiście w C ++ nigdy nie pozwól wyjątkowi wyjść z destruktora.
Wyjątki są ... synchroniczne
Pamiętaj, aby złapać je, zanim wyciągną twój wątek na kolanach lub rozprzestrzenią się w pętli wiadomości systemu Windows.
Rozwiązaniem może być ich zmieszanie?
Więc myślę, że rozwiązaniem jest rzucenie, gdy coś nie powinno się wydarzyć. A kiedy coś może się zdarzyć, użyj kodu powrotu lub parametru, aby umożliwić użytkownikowi reakcję na to.
Tak więc jedyne pytanie brzmi: „co jest czymś, co nie powinno się wydarzyć?”
To zależy od umowy pełnionej funkcji. Jeśli funkcja akceptuje wskaźnik, ale określa, że wskaźnik nie może mieć wartości NULL, wówczas można zgłosić wyjątek, gdy użytkownik wyśle wskaźnik NULL (pytanie brzmi, w C ++, gdy autor funkcji nie używa odwołań wskazówek, ale ...)
Innym rozwiązaniem byłoby pokazanie błędu
Czasami twój problem polega na tym, że nie chcesz błędów. Używanie wyjątków lub kodów powrotu błędów jest fajne, ale ... Chcesz o tym wiedzieć.
W mojej pracy używamy swego rodzaju „Assert”. W zależności od wartości pliku konfiguracyjnego, niezależnie od opcji debugowania / kompilacji wydania:
- zarejestruj błąd
- otwórz skrzynkę wiadomości z komunikatem „Hej, masz problem”
- otwórz skrzynkę wiadomości z komunikatem „Hej, masz problem, czy chcesz debugować”
Zarówno w przypadku programowania, jak i testowania umożliwia to użytkownikowi dokładne wskazanie problemu w momencie jego wykrycia, a nie po (gdy jakiś kod dba o zwracaną wartość lub wewnątrz zaczepu).
Dodawanie do starszego kodu jest łatwe. Na przykład:
void doSomething(CMyObject * p, int iRandomData)
{
// etc.
}
prowadzi kod podobny do:
void doSomething(CMyObject * p, int iRandomData)
{
if(iRandomData < 32)
{
MY_RAISE_ERROR("Hey, iRandomData " << iRandomData << " is lesser than 32. Aborting processing") ;
return ;
}
if(p == NULL)
{
MY_RAISE_ERROR("Hey, p is NULL !\niRandomData is equal to " << iRandomData << ". Will throw.") ;
throw std::some_exception() ;
}
if(! p.is Ok())
{
MY_RAISE_ERROR("Hey, p is NOT Ok!\np is equal to " << p->toString() << ". Will try to continue anyway") ;
}
// etc.
}
(Mam podobne makra, które są aktywne tylko podczas debugowania).
Zwróć uwagę, że na produkcji plik konfiguracyjny nie istnieje, więc klient nigdy nie widzi wyniku działania tego makra ... Ale w razie potrzeby łatwo go aktywować.
Wniosek
Kiedy kodujesz za pomocą kodów powrotnych, przygotowujesz się na niepowodzenie i masz nadzieję, że Twoja forteca testów jest wystarczająco bezpieczna.
Kiedy kodujesz za pomocą wyjątku, wiesz, że Twój kod może się nie powieść i zazwyczaj umieszczasz w swoim kodzie pułapkę przeciwpożarową w wybranym strategicznym miejscu. Ale zazwyczaj w twoim kodzie jest bardziej „co musi zrobić”, a potem „czego się boję, że się wydarzy”.
Ale kiedy w ogóle kodujesz, musisz użyć najlepszego dostępnego narzędzia, a czasami jest to „Nigdy nie ukrywaj błędu i pokaż go tak szybko, jak to możliwe”. Makro, o którym mówiłem powyżej, jest zgodne z tą filozofią.