4 sie 2009

Deklarować rzucane wyjątki? Tak, ale tylko w komentarzu!

Załóżmy, że piszemy metodę, która obsługuje błędy przez rzucenie wyjątku:
int calculate( int something ) throw( MyException )
{
    int calculated = 0;

    if( something < 0 )
    {
        throw MyException( "Parametr something musi byc dodatni" );
    }

    initCalculator();

    return calculated;
}

Pytanie: czy powinniśmy zadeklarować, że metoda rzuca wyjątek?

Przykładowo:
int calculate( int something ) throw( MyException );
Bardzo mi się podoba podejście Javy w kwestii deklarowania rzucanych wyjątków:
jeśli metoda rzuca jakiś wyjątek to musi on być zadeklarowany w nagłówku metody.
Pilnuje tego kompilator: jeśli programista rzuci wyjątek bez odpowiedniej deklaracji throw w nagłówku metody, to otrzyma błąd kompilacji.

Dzięki takiemu podejściu osoba wykorzystująca naszą metodę wie jakie wyjątki mogą być przez nią rzucone, przez co unikamy błędów wykonania typu "unhandled exception".
Co więcej jeśli ktoś wywoła naszą metodę bez odpowiedniej obsługi wyjątku, to otrzyma błąd kompilacji!

A co się dzieje w c++? Niestety tutaj podejście jest mniej racjonalne :-(

Przede wszystkim: kompilator do niczego nas nie zmusza. Nie musimy deklarować rzucanego wyjątku. Ale załóżmy, że jesteśmy porządni (programowaliśmy w Javie :-) i deklarujemy wyjątek. Co się dzieje?

Spójrzmy na ten fragment kodu:
struct MyException{ MyException( const string &what ){} };
struct OtherException{ OtherException( const string &what ){} };

void initCalculator() throw( OtherException )
{
    throw OtherException( "Kalkulator nie jest zainstalowany" );
}

int calculate( int something ) throw( MyException )
{
    int calculated = 0;

    if( something < 0 )
    {
        throw MyException( "Parametr something musi być dodatni" );
    }

    initCalculator();

    return calculated;
}

int main()
{
    try
    {
        calculate( 2 );
        cout << "ok" << endl;
    }
    catch( const MyException &exc )
    {
        cout << "MyException" << endl;
    }
    catch( const OtherException &exc )
    {
        cout << "OtherException" << endl;
    }
    catch( ... )
    {
        cout << "całkiem inny wyjątek" << endl;
    }
    return 0;
}

Wynik działania programu jest taki:
This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.

Jak widzimy main() wywołuje calculate(), która wywołuje initCalculator();
Niestety initCalculator() rzuca nam wyjątek OtherException, który został zadeklarowany w initCalculator() ale nie został zadeklarowany w calculate().

I teraz najciekawsze:
Wyjątek OtherException nie zostanie złapany w main, pomimo że mamy tam odpowiednią klauzulę catch( OtherException )!!!

Co się dzieje?
Kompilator widzi deklarację:
int calculate( int something ) throw( MyException )
i zakłada, że tylko wyjątki typu MyException mogą być przez nią rzucone.
Jeśli calculate() rzuci inny wyjątek, to natychmiast spowoduje to wywołanie globalnej funkcji kończącej program (ustawianej przez ::set_terminate()).

A jeśli nie zadeklarujemy, że metoda calculate() rzuca jakikolwiek wyjątek, to wyjątkek rzucony przez initCalculator() zostanie złapany w main(). Dokładnie tak, jak się tego spodziewamy!

Jak zatem postępować?

Deklarować wyjątki w sposób nieformalny, umieszczając słowo kluczowe throw w komentarzu:
int calculate( int something ) //throw( MyException )
Dzięki temu:
- użytkownik naszej metody będzie wiedział jakich wyjątków może się spodziewać
- jeśli nasza metoda calculate przypadkiem rzuci innym wyjątkiem, to catch(...) go złapie!

Brak komentarzy:

Prześlij komentarz