PO Wyjątki

Z Studia Informatyczne
Wersja z dnia 01:19, 23 lip 2006 autorstwa Jsroka (dyskusja | edycje)
(różn.) ← poprzednia wersja | przejdź do aktualnej wersji (różn.) | następna wersja → (różn.)
Przejdź do nawigacjiPrzejdź do wyszukiwania

Obsługa sytuacji wyjątkowych

Nawet w proprawnie napisanych programach należy się liczyć z możliwością wystąpienia sytuacji wyjątkowych. Najczęstrzym rodzajem błedów których nie można uniknąć jest niedostępność różnego rodzaju zasobów, np. niemożliwość zapisu do pliku dyskowego czy problemy z komunikacją przez sięć. Wiele problemów może się pojawić gdy nieumiejętnie korzystamy z kodu napisanego przez kogoś innego lub gdy inni nieumiejętnie korzystają z kodu napisanego przez nas. Wiąże się to zazwyczaj z niezaglądaniem do dokumentacji technicznej lub z jej słabą jakością. Typowym przykładem takiej sytuacji jest przekazanie metodzie niepoprawnych danych, np. wartości null gdy spodziewała się referencji do obiektu lub krótszej niż oczekiwała tablicy.

Dobrze napisany kod powinien zakładać możliwość wystąpienia takich wyjątkowych sytuacji. Zazwyczaj jednak nie da się podjąć żadnych działań naprawczych w miejscu wykrycia błędu, trzeba przerwać normalny tok działania programu i przekazać informacje o błędzie "na zewnętrz" do szerszego kontekstu (do metody, która wywołała metodę w której występiła sytuacja wyjątkowa lub jeszcze dalej), gdzie mogą być pojęte działania napracze. Starsze języki programowania jak C nie zawierały żadnych specjalnych mechanizmów ułatwiających radzenie sobie w takich sytuacjach. Istniał natomiast kilka ogólnie przyjętych schematów postępowania. Można zwracać specjalną wartość oznaczającą błąd lub ustawiać glabalnie dostępną flagę, którą następnie trzeba skontrolować. Z czasem okazało się jednak, że programiści nie byli konsekwentni w przestrzeganiu tych konwencji. Zakładali, że błędy zdarzają się jedynie w kodzie innych, a w ich nie. Często byli też po prostu leniwi, gdyż obsługa wszystkich nietypowych sytuacji, np. sprawdzanie czy błąd nie wystąpił po wywołaniu każdej kolejnej metody, powodowałaby wielokrotne napęcznienie kodu oraz drastycznie utrudniła jego stworzenie i zrozumienie. W efekcie sytuacje wyjątkowe nie były prawidłowo rozpoznawane i program wykonywał się dalej powodując wystąpienie jeszcze większych problemów. Brak specjanlnych mechanizmów wymuszających obsługę sytuacji wyjątkowych był ważnym ograniczeniem przy tworzeniu dużych, solidnych i łatwych w pielęgnowaniu programów.

Mechanizm obsługi wyjątków

Nowoczesne obiektowe języki programowania mają wbudowany specjalny mechanizm ułatwiający i upraszczający radzenie sobie z obsługą systuacji wyjątkowych. W chwili wykrycia takiej sytuacji można stworzyć specjalny obiekt nazywany wyjątkiem (ang. exception), zawrzeć w nim wszystkie informacje na temat tego co się stało i przy pomocy specjalnej instrukcji throw (w niktórych językach raise) zgłosić ten wyjątek do obsłużenia. Zgłoszenie wyjątku wymusza przerwanie normalnego trybu wykonywania programu i rozwinięcie stosu wywołań aż do napotkania kontekstu zawierającego kod obsługi dla wyjątków tego rodzaju. Jeżeli cały stos zostanie rozwinięty, a wyjątku nie obsłużono, program zostaje przerywany.

Nieobsłużone wyjątki

Metoda głębiej() w pokazanej poniżej klasie UnhandledException zgłasza wyjątek NullPointerException (jeden ze standardowych typów wyjątków Javy), gdy przekazany jej parametr ma wartość null.

public class UnhandledException {
  void głęboko(String s) throws NullPointerException {
    System.out.println("poczętek głęboko");
    głębiej(s);
    System.out.println("koniec głęboko");
  }
  
  void głębiej(String s) throws NullPointerException {
    System.out.println("poczętek głębiej");
    if (s == null) throw new NullPointerException();
    System.out.println("koniec głębiej");
  }
  public static void main(String[] args) throws NullPointerException {
    UnhandledException ue = new UnhandledException();
    System.out.println("przed głęboko");
    ue.głęboko(null);
    System.out.println("po głęboko");
  }
}

Z powodu zgłoszenia wyjątku stos wywołań jest rozwijany, a ponieważ żadna z kolejno znajdujących się tam metod głębiej(), głęboko() i main() nie zawiera kodu obsługi wyjątku, program zostaje przerwany. W takiej sytuacji Java wypisuje na standardowe wyjście błędu informacje o wyjątku, który spowodował przerwanie programu. Ich poziom szczegółowości zależy od ustawień kompilatora.

przed głęboko
poczętek głęboko
poczętek głębiej
Exception in thread "main" java.lang.NullPointerException
  at test.UnhandledException.głębiej(UnhandledException.java:12)
  at test.UnhandledException.głęboko(UnhandledException.java:6)
  at test.UnhandledException.main(UnhandledException.java:19)

Nadzorowanie bsługi wyjątków przez kompilator

Warto zwrócić uwagę, że w deklaracjach wszystkich meotod klasy UnhandledException występuje klazula throws, która informuje jakich wyjątków można się podziewać w wyniku wywołania danej metody (). Występowanie tej klauzuli jest wymuszane przez kompilator jeżeli z metody mogą wydostać sie jakieś nieobsłużone wyjątki. Dzięki temu programista nie ma możliwości przeoczyć żadnego wyjątku i jeżeli nie chce wszystkich obsłużyć musi świadomie wymienic je (bądź ich nadklasy) w deklaracji metody.

Obsługa wyjątku

try {
  //kod który może zgłosić wyjątek
} catch (Typ1 w) {
  //obsługa wyjątków Typ1
}
} catch (Typ2 w) {
  //obsługa wyjątków Typ2
}
} catch (Typ3 w) {
  //obsługa wyjątków Typ3
}
public class HandledException {
  void głęboko(String s) throws NullPointerException {
    System.out.println("poczętek głęboko");
    głębiej(s);
    System.out.println("koniec głęboko");
  }
  void głębiej(String s) throws NullPointerException {
    System.out.println("poczętek głębiej");
    if (s == null) throw new NullPointerException();
    System.out.println("koniec głębiej");
  }
  public static void main(String[] args) {
    try {
      HandledException ep = new HandledException();
      System.out.println("przed głęboko");
      ep.głęboko(null);
      System.out.println("po głęboko");
    } catch (NullPointerException e) {
      System.out.println("obsługa wyjątku");
      e.printStackTrace(System.out);
    }
    System.out.println("po obsłużeniu wyjątku");
  }
}
przed głęboko
poczętek głęboko
poczętek głębiej
obsługa wyjątku
java.lang.NullPointerException
  at test.HandledException.głębiej(HandledException.java:12)
  at test.HandledException.głęboko(HandledException.java:6)
  at test.HandledException.main(HandledException.java:20)
po obsłużeniu wyjątku