Ruby - wyjątki

Wykonanie i wyjątek zawsze idą w parze. Jeśli otwierasz plik, który nie istnieje, to jeśli nie poradziłeś sobie z tą sytuacją poprawnie, Twój program zostanie uznany za złej jakości.

Program zatrzymuje się, jeśli wystąpi wyjątek. Dlatego wyjątki są używane do obsługi różnego rodzaju błędów, które mogą wystąpić podczas wykonywania programu i podjęcia odpowiednich działań zamiast całkowitego zatrzymania programu.

Ruby zapewnia niezły mechanizm obsługi wyjątków. Umieszczamy kod, który może zgłosić wyjątek w bloku początku / końca i używamy klauzul rescue , aby poinformować Ruby o typach wyjątków, które chcemy obsłużyć.

Składnia

begin  
# -  
rescue OneTypeOfException  
# -  
rescue AnotherTypeOfException  
# -  
else  
# Other exceptions
ensure
# Always will be executed
end

Wszystko od początku do ratowania jest chronione. Jeśli podczas wykonywania tego bloku kodu wystąpi wyjątek, kontrola jest przekazywana do bloku między ratowaniem a zakończeniem .

Dla każdej klauzuli rescue w bloku begin Ruby porównuje podniesiony wyjątek z każdym z parametrów po kolei. Dopasowanie powiedzie się, jeśli wyjątek nazwany w klauzuli rescue jest taki sam jak typ aktualnie zgłaszanego wyjątku lub jest nadklasą tego wyjątku.

W przypadku, gdy wyjątek nie pasuje do żadnego z określonych typów błędów, możemy użyć klauzuli else po wszystkich klauzulach rescue .

Przykład

#!/usr/bin/ruby

begin
   file = open("/unexistant_file")
   if file
      puts "File opened successfully"
   end
rescue
      file = STDIN
end
print file, "==", STDIN, "\n"

Spowoduje to następujący wynik. Możesz zobaczyć, że STDIN jest podstawiany do pliku, ponieważ otwarcie nie powiodło się.

#<IO:0xb7d16f84>==#<IO:0xb7d16f84>

Korzystanie z instrukcji retry

Możesz przechwycić wyjątek za pomocą bloku ratunkowego , a następnie użyć instrukcji retry , aby wykonać blok begin od początku.

Składnia

begin
   # Exceptions raised by this code will 
   # be caught by the following rescue clause
rescue
   # This block will capture all types of exceptions
   retry  # This will move control to the beginning of begin
end

Przykład

#!/usr/bin/ruby

begin
   file = open("/unexistant_file")
   if file
      puts "File opened successfully"
   end
rescue
   fname = "existant_file"
   retry
end

Poniżej przedstawiono przebieg procesu -

  • Wystąpił wyjątek podczas otwarcia.
  • Poszedł na ratunek. nazwa fname została przeniesiona.
  • Ponowna próba przeszła na początek początku.
  • Tym razem plik otwiera się pomyślnie.
  • Kontynuacja zasadniczego procesu.

NOTE- Zauważ, że jeśli plik o zmienionej nazwie nie istnieje, ten przykładowy kod ponawia nieskończoną liczbę powtórzeń. Zachowaj ostrożność, jeśli używasz ponownej próby dla procesu wyjątku.

Korzystanie z instrukcji raise

Możesz użyć instrukcji raise , aby zgłosić wyjątek. Poniższa metoda wywołuje wyjątek za każdym razem, gdy jest wywoływana. Druga wiadomość zostanie wydrukowana.

Składnia

raise 

OR

raise "Error Message" 

OR

raise ExceptionType, "Error Message"

OR

raise ExceptionType, "Error Message" condition

Pierwsza forma po prostu ponownie zgłasza bieżący wyjątek (lub błąd RuntimeError, jeśli nie ma bieżącego wyjątku). Jest to używane w programach obsługi wyjątków, które muszą przechwycić wyjątek przed przekazaniem go dalej.

Druga forma tworzy nowy wyjątek RuntimeError , ustawiając jego komunikat na podany ciąg. Ten wyjątek jest następnie podnoszony na stosie wywołań.

Trzecia forma używa pierwszego argumentu do utworzenia wyjątku, a następnie ustawia skojarzony komunikat na drugi argument.

Czwarta forma jest podobna do trzeciej, ale możesz dodać dowolną instrukcję warunkową, na przykład chyba, że chcesz zgłosić wyjątek.

Przykład

#!/usr/bin/ruby

begin  
   puts 'I am before the raise.'  
   raise 'An error has occurred.'  
   puts 'I am after the raise.'  
rescue  
   puts 'I am rescued.'  
end  
puts 'I am after the begin block.'

To da następujący wynik -

I am before the raise.  
I am rescued.  
I am after the begin block.

Jeszcze jeden przykład pokazujący użycie podbicia -

#!/usr/bin/ruby

begin  
   raise 'A test exception.'  
rescue Exception => e  
   puts e.message  
   puts e.backtrace.inspect  
end

To da następujący wynik -

A test exception.
["main.rb:4"]

Korzystanie z instrukcji zapewnienia

Czasami trzeba zagwarantować, że część przetwarzania zostanie wykonana na końcu bloku kodu, niezależnie od tego, czy został zgłoszony wyjątek. Na przykład, możesz mieć otwarty plik przy wejściu do bloku i musisz się upewnić, że zostanie zamknięty, gdy blok zostanie zamknięty.

Zapewnić klauzula robi tylko to. zapewniają, że następuje po ostatniej klauzuli ratunkowej i zawiera fragment kodu, który zawsze będzie wykonywany po zakończeniu bloku. Nie ma znaczenia, czy blok wychodzi normalnie, czy zgłasza i ratuje wyjątek, czy też jest zakończony przez nieprzechwycony wyjątek, blok zapewniający zostanie uruchomiony.

Składnia

begin 
   #.. process 
   #..raise exception
rescue 
   #.. handle error 
ensure 
   #.. finally ensure execution
   #.. This will always execute.
end

Przykład

begin
   raise 'A test exception.'
rescue Exception => e
   puts e.message
   puts e.backtrace.inspect
ensure
   puts "Ensuring execution"
end

To da następujący wynik -

A test exception.
["main.rb:4"]
Ensuring execution

Korzystanie z instrukcji else

Jeśli klauzula else jest obecna, następuje po klauzulach ratunkowych i przed jakimkolwiek zapewnieniem .

Treść klauzuli else jest wykonywana tylko wtedy, gdy żaden wyjątek nie został zgłoszony przez główną część kodu.

Składnia

begin 
   #.. process 
   #..raise exception
rescue 
   # .. handle error
else
   #.. executes if there is no exception
ensure 
   #.. finally ensure execution
   #.. This will always execute.
end

Przykład

begin
   # raise 'A test exception.'
   puts "I'm not raising exception"
rescue Exception => e
   puts e.message
   puts e.backtrace.inspect
else
   puts "Congratulations-- no errors!"
ensure
   puts "Ensuring execution"
end

To da następujący wynik -

I'm not raising exception
Congratulations-- no errors!
Ensuring execution

Podniesiony komunikat o błędzie można przechwycić za pomocą $! zmienna.

Złap i rzuć

Chociaż wyjątkowy mechanizm podnoszenia i ratowania świetnie nadaje się do przerywania wykonywania, gdy coś pójdzie nie tak, czasami miło jest móc wyskoczyć z jakiejś głęboko zagnieżdżonej konstrukcji podczas normalnego przetwarzania. Tutaj przydaje się łapanie i rzucanie.

Haczyk definiuje blok, który jest oznaczony daną nazwą (który może być symbolem lub String). Blok jest wykonywany normalnie, dopóki nie zostanie napotkany rzut.

Składnia

throw :lablename
#.. this will not be executed
catch :lablename do
#.. matching catch will be executed after a throw is encountered.
end

OR

throw :lablename condition
#.. this will not be executed
catch :lablename do
#.. matching catch will be executed after a throw is encountered.
end

Przykład

W poniższym przykładzie zastosowano rzut do zakończenia interakcji z użytkownikiem, jeśli „!” jest wpisywany w odpowiedzi na dowolny monit.

def promptAndGet(prompt)
   print prompt
   res = readline.chomp
   throw :quitRequested if res == "!"
   return res
end

catch :quitRequested do
   name = promptAndGet("Name: ")
   age = promptAndGet("Age: ")
   sex = promptAndGet("Sex: ")
   # ..
   # process information
end
promptAndGet("Name:")

Powinieneś wypróbować powyższy program na swoim komputerze, ponieważ wymaga on ręcznej interakcji. To da następujący wynik -

Name: Ruby on Rails
Age: 3
Sex: !
Name:Just Ruby

Wyjątek klasy

Standardowe klasy i moduły Rubiego wywołują wyjątki. Wszystkie klasy wyjątków tworzą hierarchię, z klasą Exception na górze. Następny poziom zawiera siedem różnych typów -

  • Interrupt
  • NoMemoryError
  • SignalException
  • ScriptError
  • StandardError
  • SystemExit

Na tym poziomie jest jeszcze jeden wyjątek, Fatal, ale interpreter języka Ruby używa tego tylko wewnętrznie.

Zarówno ScriptError, jak i StandardError mają wiele podklas, ale nie musimy tutaj wdawać się w szczegóły. Ważne jest to, że jeśli tworzymy własne klasy wyjątków, muszą one być podklasami klasy Exception lub jednej z jej potomków.

Spójrzmy na przykład -

class FileSaveError < StandardError
   attr_reader :reason
   def initialize(reason)
      @reason = reason
   end
end

Spójrzmy teraz na poniższy przykład, który użyje tego wyjątku -

File.open(path, "w") do |file|
begin
   # Write out the data ...
rescue
   # Something went wrong!
   raise FileSaveError.new($!)
end
end

Ważną tutaj linią jest podniesienie FileSaveError.new ($!) . Wywołujemy raise, aby zasygnalizować, że wystąpił wyjątek, przekazując mu nowe wystąpienie FileSaveError, z tego powodu, że określony wyjątek spowodował niepowodzenie zapisu danych.