Ruby/Przetwarzanie wyjątków: rescue
[edytuj] Przetwarzanie wyjątków: rescue
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