Wyjątki

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


apohllo@agh.edu.pl

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

konsultacje: wtorek 15:30 - 18:00, pokój 4.61

Wyjątki

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

In [ ]:
FILE * open_file(const char * file_name){
    FILE * file;
    if( access( file_name, F_OK ) != -1 ) {
        return fopen(file_name, "r");
    } else {
        return (FILE *)-1;
    }
}
In [ ]:
char * read_file(const char * file_name, int 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;
    }
}
In [ ]:
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

In [55]:
import java.io.FileReader; 
import java.io.IOException;

class FileOpener {
    public static FileReader openFile(String fileName) throws IOException {
        return new FileReader(fileName);
    }
}
In [56]:
import java.io.FileReader; 
import java.io.IOException;

class SingleFileReader {
    public static char[] readFile(String fileName, int count) throws IOException {
        FileReader reader = FileOpener.openFile(fileName);
        char[] buffer = new char[count];
        reader.read(buffer, 0, count);
        return buffer;
    }
}
In [65]:
import java.io.FileReader; 

class MultipleFileReader {
    public static void readFiles(){
        char[] buffer;
        try {
            buffer = SingleFileReader.readFile(".",10);
            System.out.println(buffer);
            buffer = SingleFileReader.readFile("plik2.txt",20);
            System.out.println(buffer);
        } catch (IOException ex) {
            System.out.println("Problem z odczytaniem pliku " + ex.getMessage());
            ex.printStackTrace();
        }
    }
}
In [66]:
MultipleFileReader.readFiles();
Problem z odczytaniem pliku . (Is a directory)
java.io.FileNotFoundException: . (Is a directory)
	at java.base/java.io.FileInputStream.open0(Native Method)
	at java.base/java.io.FileInputStream.open(FileInputStream.java:220)
	at java.base/java.io.FileInputStream.<init>(FileInputStream.java:158)
	at java.base/java.io.FileInputStream.<init>(FileInputStream.java:113)
	at java.base/java.io.FileReader.<init>(FileReader.java:58)
	at REPL.$JShell$16M$FileOpener.openFile($JShell$16M.java:31)
	at REPL.$JShell$17T$SingleFileReader.readFile($JShell$17T.java:31)
	at REPL.$JShell$18X$MultipleFileReader.readFiles($JShell$18X.java:33)
	at REPL.$JShell$53.do_it$($JShell$53.java:28)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:564)
	at io.github.spencerpark.ijava.execution.IJavaExecutionControl.lambda$execute$1(IJavaExecutionControl.java:85)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1135)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:844)

Instrukcja throw

In [67]:
import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.FileNotFoundException;


String fileName = "some_file.txt";
if(Files.exists(Paths.get(fileName))){
    // do the work
} else {
    throw new FileNotFoundException(fileName + " nie istnieje");
}
---------------------------------------------------------------------------
java.io.FileNotFoundException: some_file.txt nie istnieje
	at .(#54:1)

Instrukcja throws

In [ ]:
public FileReader openFile(String fileName) throws FileNotFoundException {
    // ...
    throw new FileNotFoundException(fileName + " nie istnieje");
    // ...
}

Instrukcja try ... catch ... finally

In [ ]:
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
}
In [68]:
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();
}
some_file.txt nie istnieje. Sprawdź nazwę pliku.
---------------------------------------------------------------------------
java.lang.NullPointerException: null
	at .(#55:1)

Resource acquisition is initialization (RAII)

In [ ]:
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

In [ ]:
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

Przyczyny 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

In [69]:
try {
    throw new EOFException();
} catch (EOFException ex) {
    System.out.println("Obsługa w sekcji 1");
} catch (IOException ex) {
    System.out.println("Obsługa w sekcji 2");
} catch (Exception ex) {
    System.out.println("Obsługa w sekcji 3");
}
Obsługa w sekcji 1
In [70]:
try {
    throw new IOException();
} catch (EOFException ex) {
    System.out.println("Obsługa w sekcji 1");
} catch (IOException ex) {
    System.out.println("Obsługa w sekcji 2");
} catch (Exception ex) {
    System.out.println("Obsługa w sekcji 3");
}
Obsługa w sekcji 2
In [71]:
try {
    throw new EOFException();
} catch (Exception ex) {
    System.out.println("Obsługa w sekcji 1");
} catch (IOException ex) {
    System.out.println("Obsługa w sekcji 2");
} catch (EOFException ex) {
    System.out.println("Obsługa w sekcji 3");
}
|   } catch (IOException ex) {
|       System.out.println("Obsługa w sekcji 2");
|   } catch (EOFException ex) {
exception java.io.IOException has already been caught

|   } catch (EOFException ex) {
|       System.out.println("Obsługa w sekcji 3");
|   }
exception java.io.EOFException has already been caught
In [ ]:
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, łap 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, pokaż zawartość katalogu lub poinformuj, że ścieżka jest niepoprawna
  • ConnectException - spróbuj ponownie wysłać zapytanie do zdalnego serwisu, poproś użytkownika o sprawdzenie połączenia internetowego
  • InterruptedException - zakończ albo zrestartuj wątek przetwarzający zadanie

Antywzorce

Ad. 1 - antywzorce

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

Ad. 2 - antywzorce

In [ ]:
try {
    // kod rzucający wyjątki
} catch (FileNotFoundException ex) {
    logger.error(ex.getMessage());
    return null;
}
In [ ]:
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

In [ ]:
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);
        }
    }
}
In [ ]:
InvalidTransferSpecification: ...
at Parser.parse()
at System.main()
Caused by: XMLParserError: ...
at XMLParser.readHeader()
at XMLParser.parse()

Ad. 2 - antywzroce

In [ ]:
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:

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

Uwaga na sekcję finally

In [ ]:
try {
    // kod z wyjątkami
} finally {
    return 1;
}
In [ ]:
char[] buffer = new char[100];
try(reader = new FileReader("file1.txt")) {
    reader.read(buffer, 0, 100);
} catch(SomeException ex){
    ex.getSuppressed();
}

Nie deklaruj (bardzo) wielu wyjątków

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

Pytania?