Bắt đầu, giải cứu và đảm bảo trong Ruby?


547

Gần đây tôi đã bắt đầu lập trình trong Ruby và tôi đang xem xét xử lý ngoại lệ.

Tôi đã tự hỏi nếu ensureRuby tương đương với finallyC #? Tôi có nên:

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

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

hay tôi nên làm điều này?

#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

ensuređược gọi không có vấn đề gì, ngay cả khi một ngoại lệ không được nêu ra?


1
Không phải là tốt. Theo quy định, khi giao dịch với các tài nguyên bên ngoài, bạn luôn muốn tài nguyên mở 'nằm trong beginkhối.
Nowaker

Câu trả lời:


1181

Có, ensuređảm bảo rằng mã luôn được đánh giá. Đó là lý do tại sao nó được gọi là ensure. Vì vậy, nó tương đương với Java và C # finally.

Luồng chung của begin/ rescue/ else/ ensure/ endtrông như thế này:

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

Bạn có thể rời khỏi rescue, ensurehoặc else. Bạn cũng có thể bỏ qua các biến trong trường hợp bạn sẽ không thể kiểm tra ngoại lệ trong mã xử lý ngoại lệ của mình. (Vâng, bạn luôn có thể sử dụng các biến ngoại lệ toàn cầu để truy cập ngoại lệ cuối cùng đã được đưa ra, nhưng đó là một chút hacky chút.) Và bạn có thể bỏ qua các lớp ngoại lệ, trong trường hợp tất cả các trường hợp ngoại lệ mà kế thừa từ StandardErrorsẽ bị bắt. (Xin lưu ý rằng điều này không có nghĩa là tất cả các trường hợp ngoại lệ được bắt, bởi vì có những trường hợp ngoại lệ là trường hợp Exceptionnhưng không phải StandardError. Chủ yếu là trường hợp ngoại lệ rất nghiêm trọng thỏa hiệp sự toàn vẹn của chương trình như SystemStackError, NoMemoryError, SecurityError, NotImplementedError, LoadError, SyntaxError, ScriptError, Interrupt,SignalExceptionhoặc SystemExit.)

Một số khối hình thành các khối ngoại lệ ngầm. Ví dụ, định nghĩa phương thức cũng là các khối ngoại lệ, vì vậy thay vì viết

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

bạn chỉ viết

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

hoặc là

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

Điều tương tự áp dụng cho các classđịnh nghĩa và moduleđịnh nghĩa.

Tuy nhiên, trong trường hợp cụ thể mà bạn đang hỏi về, thực sự có một thành ngữ tốt hơn nhiều. Nói chung, khi bạn làm việc với một số tài nguyên mà bạn cần dọn sạch vào cuối, bạn sẽ làm điều đó bằng cách chuyển một khối cho một phương thức thực hiện tất cả việc dọn dẹp cho bạn. Nó tương tự như một usingkhối trong C #, ngoại trừ việc Ruby thực sự đủ mạnh để bạn không phải chờ đợi các linh mục cao cấp của Microsoft từ trên núi xuống và thay đổi trình biên dịch của họ cho bạn. Trong Ruby, bạn có thể tự thực hiện nó:

# 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

Và bạn biết gì: cái này đã có sẵn trong thư viện lõi như File.open. Nhưng đó là một mô hình chung mà bạn có thể sử dụng trong mã của riêng mình, để thực hiện bất kỳ loại dọn dẹp tài nguyên nào (à la usingtrong C #) hoặc các giao dịch hoặc bất cứ điều gì khác mà bạn có thể nghĩ đến.

Trường hợp duy nhất không hoạt động, nếu có được và giải phóng tài nguyên được phân phối trên các phần khác nhau của chương trình. Nhưng nếu nó được bản địa hóa, như trong ví dụ của bạn, thì bạn có thể dễ dàng sử dụng các khối tài nguyên này.


BTW: trong C # hiện đại, usingthực sự không cần thiết, bởi vì bạn có thể tự thực hiện các khối tài nguyên theo kiểu Ruby:

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);
});

81
Lưu ý rằng, mặc dù các ensurecâu lệnh được thực thi sau cùng, chúng không phải là giá trị trả về.
Chris

30
Tôi thích nhìn thấy những đóng góp phong phú như thế này trên SO. Nó vượt lên trên và vượt xa những gì OP yêu cầu để áp dụng cho nhiều nhà phát triển hơn, nhưng vẫn còn trong chủ đề. Tôi đã học được một vài điều từ câu trả lời này + chỉnh sửa. Cảm ơn bạn đã không chỉ viết "Có, ensuređược gọi bất kể điều gì."
Dennis

3
Lưu ý, đảm bảo rằng KHÔNG được đảm bảo để hoàn thành. Hãy xem trường hợp bạn có bắt đầu / đảm bảo / kết thúc bên trong một chuỗi và sau đó bạn gọi Thread.kill khi dòng đầu tiên của khối đảm bảo được gọi. Điều này sẽ khiến phần còn lại của đảm bảo không thực thi.
Teddy

5
@Teddy: đảm bảo được đảm bảo để bắt đầu thực thi, không được đảm bảo để hoàn thành. Ví dụ của bạn là quá mức cần thiết - một ngoại lệ đơn giản bên trong khối đảm bảo cũng sẽ khiến nó thoát ra.
Martin Konecny

3
cũng lưu ý rằng không có đảm bảo đảm bảo được gọi. Tôi nghiêm túc đấy Sự cố mất điện / lỗi phần cứng / sự cố os có thể xảy ra và nếu phần mềm của bạn rất nghiêm trọng, điều đó cũng cần được xem xét.
EdvardM

37

FYI, ngay cả khi một ngoại lệ được nêu lại trong rescuephần, ensurekhối sẽ được thực thi trước khi thực thi mã tiếp tục đến trình xử lý ngoại lệ tiếp theo. Ví dụ:

begin
  raise "Error!!"
rescue
  puts "test1"
  raise # Reraise exception
ensure
  puts "Ensure block"
end

14

Nếu bạn muốn đảm bảo một tệp được đóng, bạn nên sử dụng dạng khối File.open:

File.open("myFile.txt", "w") do |file|
  begin
    file << "#{content} \n"
  rescue
  #handle the error here
  end
end

3
Tôi đoán nếu bạn không muốn xử lý lỗi nhưng chỉ cần nâng nó lên và đóng xử lý tệp, bạn không cần bắt đầu giải cứu ở đây?
rogerdpack


5

Có, ensureĐẢM BẢO nó được chạy mọi lúc, vì vậy bạn không cần file.closetrong beginkhối.

Nhân tiện, một cách tốt để kiểm tra là làm:

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

Bạn có thể kiểm tra xem liệu "========= bên trong khối đảm bảo" sẽ được in ra khi có ngoại lệ. Sau đó, bạn có thể nhận xét câu lệnh làm tăng lỗi và xem ensurecâu lệnh có được thực thi hay không bằng cách xem có gì được in ra không.


4

Đây là lý do tại sao chúng ta cần ensure:

def hoge
  begin
    raise
  rescue  
    raise # raise again
  ensure  
    puts 'ensure' # will be executed
  end  
  puts 'end of func' # never be executed
end  

4

Có, ensuregiống như finally đảm bảo rằng khối sẽ được thực thi . Điều này rất hữu ích để đảm bảo rằng các tài nguyên quan trọng được bảo vệ, ví dụ như đóng một tệp xử lý lỗi hoặc giải phóng một mutex.


Ngoại trừ trong trường hợp của anh ấy / cô ấy, không có gì đảm bảo cho tập tin được đóng lại, bởi vì File.openmột phần KHÔNG nằm trong khối đảm bảo bắt đầu. Chỉ file.closelà nhưng nó không đủ.
Nowaker
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.