Ruby/Przetwarzanie wyjątków: rescue
Przetwarzanie wyjątków: rescue
[edytuj]Wykonujący się program może napotkać na niespodziewane problemy. Plik, które chce odczytać może nie istnieć, dysk może być pełny, gdy trzeba zapisać dane, a użytkownik wprowadza niepoprawny rodzaj danych wejściowych.
irb(main):001:0> plik = open('jakis_plik') Errno::ENOENT: No such file or directory - jakis_plik from (irb):1:in `initialize' from (irb):1:in `open' from (irb):1
Solidny program powinien radzić sobie z takimi sytuacjami sensownie i wdzięcznie. Sprostanie temu wymaganiu może być irytującym zadaniem. Od programistów języka C oczekuje się sprawdzania wyniku każdego wywołania systemowego które potencjalnie mogło się nie powieść oraz natychmiastowego zdecydowania, co należy zrobić:
FILE *plik = fopen("jakis_plik", "r");
if (plik == NULL) {
fprintf( stderr, "Plik nie istnieje.\n" );
exit(1);
}
bajty_przeczytane = fread( buf, 1, bajty_zadane, file );
if (bajty_przeczytane != bajty_zadane) {
/* tutaj wiecej kodu obslugi bledow... */
}
...
Jest to bardzo męcząca praktyka, którą programiści mają w zwyczaju traktować niedbale i pomijać, czego rezultatem jest to, że program źle sobie radzi z wyjątkami. Z drugiej strony, porządne wykonanie tej pracy czyni programy trudnymi do czytania, ponieważ duża ilość kodu obsługi wyjątków przesłania właściwą logikę programu.
W Rubim, tak jak w wielu współczesnych językach programowania, możemy radzić sobie z wyjątkami poszczególnych bloków kodu oddzielnie, co jednak skutecznie acz nie nadmiernie obciąża programistę lub każdego, kto będzie potem czytał kod. Blok kodu oznaczony słowem begin wykonuje się dopóki nie napotka na wyjątek, który powoduje przekierowanie kontroli do bloku zarządzania błędami, rozpoczynającego się od rescue. Jeżeli nie wystąpi żaden wyjątek, kod z bloku rescue nie jest używany. Następująca metoda zwraca pierwszą linię z pliku tekstowego lub nil jeżeli napotka wyjątek:
def pierwsza_linia(nazwa_pliku)
begin
plik = open(nazwa_pliku)
info = plik.gets
plik.close
info # Ostatnia obliczona rzecz jest zwracana
rescue
nil # Nie mozesz przeczytac pliku? więc nie zwracaj łancucha
end
end
Będą występować sytuacje, gdy będziemy chcieli kreatywnie pracować nad problemem. Tutaj, jeśli plik, który żądamy jest niedostępny, możemy spróbować użyć standardowego wejścia:
begin
plik = open("jakis_plik")
rescue
plik = STDIN
end
begin
# ... przetwarzaj wejscie ...
rescue
# ... tutaj obsluguj wyjatki.
end
Słowo kluczowe retry może być używane w bloku rescue, by wystartować blok begin od początku. Pozwala to nam przepisać poprzedni przykład nieco zwięźlej:
nazwap = "jakis_plik"
begin
plik = open(nazwap)
# ... przetwarzaj wejscie ...
rescue
nazwap = "STDIN"
retry
end
Jednakże, mamy tutaj pewną wadę. Nieistniejący plik sprawi, że pętla ta będzie powtarzana w nieskończoność. Musisz zwracać uwagę na tego rodzaju pułapki podczas przetwarzania wyjątków.
Każda biblioteka Rubiego podnosi wyjątek jeśli wystąpi jakiś błąd. Ty również możesz podnosić wyjątki jawnie w kodzie. By podnieść wyjątek użyj słowa kluczowego raise. Przyjmuje ono jeden argument, którym powinien być łańcuch znakowy opisujący wyjątek. Argument jest wprawdzie opcjonalny, jednak nie powinien być pomijany. Będzie on mógł być później dostępny za pomocą specjalnej zmiennej globalnej $!.
begin
raise "test2"
rescue
puts "Wystapil blad: #{$!}"
end
#=> Wystapil blad: test2
Zmienna $! zwraca konkretny obiekt który jest podklasą klasy Exception. Klasa Exception jest nadklasą (niekoniecznie wprost) wszystkich wyjątków. Cechę tę można efektywnie wykorzystać podczas definiowania różnych bloków obsługujących poszczególne typy wyjątków.
begin
plik = open("jakis_plik")
rescue SystemCallError
puts "Blad WE/WY: #{$!}"
rescue Exception
puts "Blad: #{$!}"
end
#=> Blad WE/WY: No such file or directory - jakis_plik
Operacja otwarcia pliku generuje wyjątek będący podklasą SystemCallError więc zostanie wykonany blok obsługujący ten wyjątek. Interpreter po kolei sprawdza wszystkie bloki rescue i wykonuje pierwszy pasujący. Z tego też powodu nie należy umieszczać rescue Exception jako pierwszego, gdyż Exception jako nadklasa wszystkich wyjątków będzie tu zawsze pasować i blok obsługujący Exception będzie zawsze wykonywany.
Jeżeli podmienimy plik = open("jakis_plik") na np. raise "jakis blad" wykonany zostanie blok rescue obsługujący Exception:
begin
raise "jakis blad"
rescue SystemCallError
puts "Blad WE/WY: #{$!}"
rescue Exception
puts "Blad: #{$!}"
end
#=> Blad: jakis blad
Zamiast zmiennej $! można używać zmiennych nazwanych stosując operator => i składnię przypominającą definiowanie wpisu tablicy asocjacyjnej.
begin
# ... jakis kod ...
rescue SystemCallError => e
puts "Blad we/wy: #{e}"
rescue Exception => e
puts "Blad: #{e}"
end