Rozpocząć, uratować i zapewnić w Ruby?

Niedawno zacząłem programować w Rubim i patrzę na obsługę wyjątków.

Zastanawiałem się, czy ensure jest odpowiednikiem Rubiego finally W C#? Powinienem:

file = File.open("myFile.txt", "w")

begin
  file << "#{content} \n"
rescue
  #handle the error here
ensure
  file.close unless file.nil?
end
Czy powinienem to zrobić?
#store the file
file = File.open("myFile.txt", "w")

begin
  file << "#{content} \n"
  file.close
rescue
  #handle the error here
ensure
  file.close unless file.nil?
end

Czy ensure jest wywoływany bez względu na wszystko, nawet jeśli wyjątek nie jest podniesiony?

Author: the Tin Man, 2010-02-03

7 answers

Tak, ensure zapewnia, że kod jest zawsze oceniany. Dlatego nazywa się ensure. Jest więc odpowiednikiem Javy i C#finally.

Ogólny przepływ begin/rescue/else/ensure/end wygląda tak:

begin
  # something which might raise an exception
rescue SomeExceptionClass => some_variable
  # code that deals with some exception
rescue SomeOtherException => some_other_variable
  # code that deals with some other exception
else
  # code that runs only if *no* exception was raised
ensure
  # ensure that this code always runs, no matter what
  # does not change the final value of the block
end

Możesz pominąć rescue, ensure lub else. Możesz również pominąć zmienne, w takim przypadku nie będziesz mógł sprawdzić wyjątku w kodzie obsługi wyjątków. (Cóż, zawsze możesz użyć zmiennej Global exception, aby uzyskać dostęp do ostatniego wyjątku to było podniesione, ale to trochę za trudne.) I możesz pominąć klasę exception, w którym to przypadku wszystkie wyjątki dziedziczone z StandardError zostaną przechwycone. (Proszę zauważyć, że nie oznacza to, że wszystkie wyjątki są przechwytywane, ponieważ istnieją wyjątki, które są instancjami Exception, ale nie StandardError. Głównie bardzo poważne wyjątki, które zagrażają integralności programu, takie jak SystemStackError, NoMemoryError, SecurityError, NotImplementedError, LoadError, SyntaxError, ScriptError, Interrupt, SignalException lub SystemExit.)

Niektóre bloki tworzą ukryte bloki WYJĄTKÓW. Na przykład definicje metod są domyślnie również blokami WYJĄTKÓW, więc zamiast pisać

def foo
  begin
    # ...
  rescue
    # ...
  end
end

Piszesz tylko

def foo
  # ...
rescue
  # ...
end

Lub

def foo
  # ...
ensure
  # ...
end

To samo dotyczy definicji class i definicji module.

Jednak w konkretnym przypadku, o który pytasz, istnieje znacznie lepszy idiom. Ogólnie rzecz biorąc, gdy pracujesz z jakimś zasobem, który musisz wyczyścić na końcu, robisz to przekazując blok do metody, która robi wszystko za Ciebie. Jest podobny do bloku using W C#, z tym że Ruby jest na tyle potężny, że nie musisz czekać, aż arcykapłani Microsoftu zejdą z góry i łaskawie zmienią za Ciebie kompilator. W Ruby możesz po prostu zaimplementować go samodzielnie:

# This is what you want to do:
File.open('myFile.txt', 'w') do |file|
  file.puts content
end

# And this is how you might implement it:
def File.open(filename, mode='r', perm=nil, opt=nil)
  yield filehandle = new(filename, mode, perm, opt)
ensure
  filehandle&.close
end

I co wiecie: to jest już dostępne w Bibliotece Głównej jako File.open. Ale jest to ogólny wzór, który można wykorzystać w swoim własny kod, jak również, do implementacji wszelkiego rodzaju czyszczenia zasobów (à la using W C#) lub transakcji lub cokolwiek innego można myśleć.

Jedyny przypadek, w którym to nie działa, jeśli pozyskiwanie i zwalnianie zasobów jest dystrybuowane w różnych częściach programu. Ale jeśli jest zlokalizowany, jak w twoim przykładzie, możesz łatwo użyć tych bloków zasobów.


BTW: w nowoczesnym C#, using jest rzeczywiście zbędny, ponieważ można zaimplementować bloki zasobów w stylu Ruby siebie:

class File
{
    static T open<T>(string filename, string mode, Func<File, T> block)
    {
        var handle = new File(filename, mode);
        try
        {
            return block(handle);
        }
        finally
        {
            handle.Dispose();
        }
    }
}

// Usage:

File.open("myFile.txt", "w", (file) =>
{
    file.WriteLine(contents);
});
 1214
Author: Jörg W Mittag,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-03 10:57:40

Dla twojej wiadomości, nawet jeśli wyjątek zostanie ponownie podniesiony w sekcji rescue, Blok ensure zostanie wykonany przed kontynuacją wykonywania kodu do następnego programu obsługi wyjątków. Na przykład:

begin
  raise "Error!!"
rescue
  puts "test1"
  raise # Reraise exception
ensure
  puts "Ensure block"
end
 40
Author: alup,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-25 23:24:44

Jeśli chcesz mieć pewność, że plik jest zamknięty, powinieneś użyć formy blokowej File.open:

File.open("myFile.txt", "w") do |file|
  begin
    file << "#{content} \n"
  rescue
  #handle the error here
  end
end
 14
Author: Farrel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-25 23:25:13

TAK, ensure jest wywoływany w każdych okolicznościach. Aby uzyskać więcej informacji zobacz "Exceptions, Catch, and Throw " z podręcznika programowania Ruby i wyszukaj "ensure".

 7
Author: Milan Novota,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-25 23:27:11

Tak, ensure zapewnia, że jest uruchamiany za każdym razem, więc nie potrzebujesz file.close w bloku begin.

Przy okazji, dobrym sposobem na test jest zrobienie:

begin
  # Raise an error here
  raise "Error!!"
rescue
  #handle the error here
ensure
  p "=========inside ensure block"
end

Możesz sprawdzić, czy "=========inside ensure block " zostanie wydrukowany, gdy wystąpi wyjątek. Następnie możesz skomentować instrukcję, która powoduje błąd i sprawdzić, czy Instrukcja ensure jest wykonywana, sprawdzając, czy coś zostanie wydrukowane.

 5
Author: Aaron Qian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-25 23:29:16

Dlatego potrzebujemy ensure:

def hoge
  begin
    raise
  rescue  
    raise # raise again
  ensure  
    puts 'ensure' # will be executed
  end  
  puts 'end of func' # never be executed
end  
 5
Author: kuboon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-25 23:29:32

Tak, ensure jak finally gwarantuje wykonanie bloku. Jest to bardzo przydatne, aby upewnić się, że krytyczne zasoby są chronione, np. zamknięcie uchwytu pliku w przypadku błędu lub zwolnienie mutex.

 5
Author: Chris McCauley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-02 14:30:51