Wyjątki

Obsługa błędów z użyciem mechanizmu wyjątków


dr inż. Aleksander Smywiński-Pohl

apohllo@o2.pl

http://apohllo.pl/dydaktyka/programowanie-obiektowe

Obsługa błędów w języku C

FILE * open_file(const char * file_name){
    FILE * file;
    if( access( file_name, F_OK ) != -1 ) {
        fopen(file_name, "r");
    } else {
        return (FILE *)-1;
    }
}
char * read_file(const char * file_name, count){
    FILE * file = open_file(file_name);
    char * result = (char*) malloc(sizeof(char) * count);
    if(file == -1){
        printf("Nie można otworzyć pliku %s\n", file_name);
        return (char *) -1;
    } else {
        fread(result, sizeof(char), count, file);
        fclose(file);
        retrun result;
    }
}
void read_files(){
    char * text = read_file("file1.txt", 10);
    if(text == -1){ 
        printf("%s", text);
    } else {
        return;
    }

    text = read_file("file2.txt", 20);
    if(text == -1){
        printf("%s", text);
    } else {
        return;
    }
}

Wyjątki w Javie

public FileReader openFile(String fileName) throws IOException {
    return new FileReader(fileName);
}
public char[] readFile(String fileName, int count) throws IOException {
    FileReader reader = openFile(fileName);
    char[] buffer = new char[count];
    reader.read(buffer, 0, count);
    return buffer;
}
public void readFiles(){
    char[] buffer;
    try {
        buffer = readFile("plik1.txt",10);
        System.out.println(buffer);
        buffer = readFile("plik2.txt",20);
        System.out.println(buffer);
    } catch (IOException e) {
        System.out.println("Problem z odczytaniem pliku " + e.getMessage());
    }
}

Instrukcja throw

if(Files.exists(Paths.get(fileName))){
    // do the work
} else {
    throw new FileNotFoundException(fileName + " nie istnieje");
}

Instrukcja throws

public FileReader openFile(String fileName) thorws FileNotFoundException {
    // ...
    throw new FileNotFoundException(fileName + " nie istnieje");
    // ...
}

Instrukcja try ... catch ... finally

try {
    // kod, który może rzucić wyjątek
} catch (FileNotFoundException ex) {
    // kod obsługi błędu
} finally {
    // kod wykonywany niezależnie od wystąpienia wyjątku
}
FileReader reader = null;
char[] buffer = new char[100];
try {
    reader = new FileReader(fileName);
    reader.read(buffer, 0, 100);
} catch (FileNotFoundException ex) {
    System.out.println(fileName + " nie istnieje. Sprawdź nazwę pliku.");
} finally {
    reader.close();
}

Resource acquisition is initialization (RAII)

FileReader reader = null;
char[] buffer = new char[100];
try {
    reader = new FileReader(fileName);
    reader.read(buffer, 0, 100);
} finally {
    if(reader != null){
        try {
            reader.close();
        } catch (IOException ex) {
            //obsługa błędu zamknięcia pliku
        }
    }
}

Instrukcja try z zasobami

char[] buffer = new char[100];
try(reader = new FileReader("file1.txt")) {
    reader.read(buffer, 0, 100);
}

Obiekty użyte w tej konstrukcji muszą implementować interfejs AutoCloseable

Rodzaje wyjątków

błąd w kodzie programu

np. NullPointerException

błąd związany ze złamaniem kontraktu

np. niepoprawny plik JSON, brakujący wymagane pole w bazie danych

błąd związany z dostępem do zasobów

np. brak połączenia z internetem, z bazą danych, problem z dostępem do pliku

Typy wyjątków

unchecked exceptions

  • klasy dziedziczące z RuntimeException
  • nie muszą być deklarowane, np. NullPointerException

checked exceptions

  • klasy dziedziczące z Exception i niedziedziczące z RuntimeException
  • muszą być deklarowane w sekcji throws lub obsłużone w bloku try...catch, np. FileNotFoundException

Łapanie wielu wyjątków

try {
    // ...
} catch (EOFException ex) {
    // obsługa błędu EOFException
} catch (IOException ex) {
    // obsługa błędu IOException
} catch (Exception ex) {
    // obsługa pozostałych wyjątków
}
try {
    // ...
} catch (Exception ex) {
    // obsługa wszystkich wyjątków
} catch (IOException ex) {
    // ten kod nigdy się nie wykona
} catch (EOFException ex) {
    // ten kod nigdy się nie wykona
}
try {
    // ...
} catch (FileNotFoundException | EOFException ex) {
    // wspólny kod obsługi błędów FileNotFoundException i EOFException
}

Praktyczne zalecenia

1. Obsługa wyjątków musi być faktycznie realizowana

  • W realnie działających systemach wyjątki faktycznie występują.
  • Ich obsługa nie może sprwadzać się do tego, że kod w ogóle się kompiluje.

2. Rzucaj wyjątki wcześnie, obsługuj późno

Im wcześniej zgłosisz sytuację nietypową, tym lepiej

  • Możesz podać więcej szczegółów na temat przyczyny błędu.

Im później obsłużysz sytuację nietypową, tym lepiej

  • Możesz obsłużyć wiele wyjątków za pomocą jednego mechanizmu.
  • Jesteś bliżej interfejsu użytkownika, co pozwala na wyprodukowanie stosownego komunikatu.

3. Dbaj o wszystkie otwierane zasoby

  • Zbyt duża liczba otwartych plików lub połączeń do bazy danych może spowodować zatrzymanie programu lub blokadę systemu operacyjnego.

4. Spróbuj znaleźć rozwiązanie problemu

  • FileNotFoundException - poproś użytkownika o poprawienie nazwy pliku
  • ConnectException - spróbuj ponownie wysłać zapytanie do zdalnego serwisu
  • InterruptedException - zakończ albo zrestartuj wątek przetwarzający zadanie

Ad. 1 - antywzorce

try { 
    // kod rzucający wyjątki
} catch(Exception ex) {
    // nic nie rób
}
try {
    // kod rzucający wyjątki
} catch (FileNotFoundException ex) {
    logger.error(ex.getMessage());
    throw ex;
}

Ad. 2 - antywzorce

try {
    // kod rzucający wyjątki
} catch (FileNotFoundException ex) {
    logger.error(ex.getMessage());
    return null;
}
public TransferDetails parse(String transferSpecification) 
        throws InvalidTransferSpecification {
    try {
        // kod rzucający wyjątek XMLParserError
    } catch (XMLParserError ex) {
        throw new InvalidTransferSpecification(transferSpecification);
    }
}

exception chaining - poprawne rozwiązanie

class Parser {
    public TransferDetails parse(String transferSpecification) 
            throws InvalidTransferSpecification {
        try {
            // kod rzucający wyjątek XMLParserError
        } catch (XMLParserError ex) {
            //                                                          vvvvvv
            throw new InvalidTransferSpecification(transferSpecification, ex);
        }
    }
}
InvalidTransferSpecification: ...
at Parser.parse()
at System.main()
Caused by: XMLParserError: ...
at XMLParser.readHeader()
at XMLParser.parse()

Ad. 2 - antywzroce

public void doSomeCalculation(){
    try {
        for(Element element : elements) {
            if(element == null){
                throw EmptyElementException();
            }
        }
    } catch (EmptyElementException ex) {
        // ...
    }
}

Ad. 3 obsługa zasobów

Najprościej zrobić to używając konstrukcji try z zasobami:

char[] buffer = new char[100];
try(reader = new FileReader("file1.txt")) {
    reader.read(buffer, 0, 100);
}

Uwaga na sekcję finally

try {
    // kod z wyjątkami
} finally {
    return 1;
}
try {
    // kod z wyjątkami
} catch(SomeException ex){
    ex.getSuppressedException();
}

Nie deklaruj (bardzo) wielu wyjątków

public launchICBM() thorws IOException, DatabaseException, NetworkException, 
    NullPointerException, ArithmeticException, WorldDoesNotExistException {
    // launch ICBM
}
public launchICBM() thorws LaunchException {
    try {
        // lanuch ICBM
    } catch(IOException | DatabaseException | ... ex) {
        throw new LaunchException("Something went wrong...", ex);
    }
}

Pytania?