Không phải tất cả các lỗi đều nguy hiểm. Một số chỉ cho biết rằng bạn cần thử lại. May mắn thay, Ruby cung cấp một vài cơ chế thú vị giúp bạn dễ dàng "thử lại" - mặc dù không phải tất cả chúng đều rõ ràng hoặc nổi tiếng. Trong bài đăng này, chúng ta sẽ xem xét các cơ chế này và cách chúng hoạt động trong thế giới thực.
Giới thiệu retry
Ok - điều này là hiển nhiên, nhưng chỉ khi bạn biết nó tồn tại. Cá nhân tôi đã hoàn thành tốt sự nghiệp Ruby của mình trước khi tôi biết đến từ khóa thú vị "thử lại".
Thử lại được tích hợp sẵn trong hệ thống giải cứu ngoại lệ của Ruby. Nó khá đơn giản. Nếu bạn sử dụng "thử lại" trong khối giải cứu của mình, nó sẽ làm cho phần mã đã được giải cứu chạy lại. Hãy xem một ví dụ.
begin
retries ||= 0
puts "try ##{ retries }"
raise "the roof"
rescue
retry if (retries += 1) < 3
end
# ... outputs the following:
# try #0
# try #1
# try #2
Có một số điều cần lưu ý ở đây:
-
Khi thử lại được gọi, tất cả của mã ở giữa bắt đầu và giải cứu được chạy lại. Chắc chắn nhất không không "bắt đầu từ nơi nó đã dừng lại" hoặc bất cứ điều gì tương tự.
-
Nếu bạn không cung cấp một số cơ chế để giới hạn số lần thử lại, bạn sẽ gặp phải một vòng lặp vô hạn.
-
Mã trong cả khối bắt đầu và khối giải cứu đều có thể truy cập vào cùng một biến số thử lại trong phạm vi chính.
Sự cố với retry
Mặc dù thử lại là rất tốt nhưng nó có một số hạn chế. Điều chính là toàn bộ khối bắt đầu được chạy lại. Nhưng đôi khi điều đó không lý tưởng.
Ví dụ:hãy tưởng tượng rằng bạn đang sử dụng một viên ngọc quý cho phép bạn đăng cập nhật trạng thái lên Twitter, Facebook và nhiều trang web khác bằng cách sử dụng một lệnh gọi phương thức. Nó có thể trông giống như thế này.
SocialMedia.post_to_all("Zomg! I just ate the biggest hamburger")
# ...posts to Twitter API
# ...posts to Facebook API
# ...etc
Nếu một trong các API không phản hồi, viên ngọc sẽ xuất hiện SocialMedia ::TimeoutError và hủy bỏ. Nếu chúng tôi bắt gặp ngoại lệ này và thử lại, chúng tôi sẽ gặp phải các bài đăng trùng lặp vì quá trình thử lại sẽ bắt đầu lại từ đầu.
begin
SocialMedia.post_to_all("Zomg! I just ate the biggest hamburger")
rescue SocialMedia::TimeoutError
retry
end
# ...posts to Twitter API
# facebook error
# ...posts to Twitter API
# facebook error
# ...posts to Twitter API
# and so on
Sẽ thật tuyệt nếu chúng ta có thể nói với gem "Chỉ cần bỏ qua facebook và tiếp tục truy cập danh sách API để đăng lên."
May mắn thay cho chúng tôi, Ruby cho phép chúng tôi làm chính xác điều đó.
LƯU Ý:Tất nhiên giải pháp thực sự cho vấn đề này là tái kiến trúc thư viện mạng xã hội. Nhưng đây không phải là trường hợp sử dụng duy nhất cho các kỹ thuật mà tôi sẽ chỉ cho bạn.
Tiếp tục Giải cứu
Những câu nói tục có xu hướng khiến mọi người sợ hãi. Nhưng đó chỉ là vì chúng không được sử dụng thường xuyên và trông hơi kỳ quặc. Nhưng khi bạn hiểu những điều cơ bản thì chúng thực sự khá đơn giản.
Sự tiếp tục giống như một "điểm lưu" trong mã của bạn, giống như trong một trò chơi điện tử. Bạn có thể đi ra ngoài và làm những việc khác, sau đó quay trở lại điểm lưu và mọi thứ sẽ như bạn đã để nguyên.
... ok, vì vậy nó không phải là một phép loại suy hoàn hảo, nhưng nó khá hiệu quả. Hãy xem một số mã:
require "continuation"
counter = 0
continuation = callcc { |c| c } # define our savepoint
puts(counter += 1)
continuation.call(continuation) if counter < 5 # jump back to our savepoint
Bạn có thể nhận thấy một vài điều kỳ lạ. Hãy xem qua chúng:
-
Chúng tôi sử dụng phương thức callcc để tạo một đối tượng Tiếp tục. Không có cú pháp OO rõ ràng nào cho việc này.
-
Lần đầu tiên
continuation
biến được gán, nó được đặt thành giá trị trả về củacallcc
của khối. Đó là lý do tại sao khối phải ở đó. -
Mỗi lần chúng ta quay lại điểm lưu,
continuation
biến được gán cho bất kỳ đối số nào mà chúng ta chuyểncall
phương pháp. Đó là lý do tại sao chúng tôi sử dụngcontinuation.call(continuation)
trông có vẻ kỳ lạ cú pháp.
Thêm Tiếp tục vào Ngoại lệ
Chúng tôi sẽ sử dụng các phép liên tục để thêm một skip
cho tất cả các trường hợp ngoại lệ. Ví dụ dưới đây cho thấy nó sẽ hoạt động như thế nào. Bất cứ khi nào tôi giải cứu một ngoại lệ, tôi có thể gọi skip
, điều này sẽ khiến mã tạo ra ngoại lệ hoạt động như thể nó chưa từng xảy ra.
begin
raise "the roof"
puts "The exception was ignored"
rescue => e
e.skip
end
# ...outputs "The exception was ignored"
Để làm được điều này, tôi sẽ phải phạm một vài tội lỗi. Exception
chỉ là một lớp học. Điều đó có nghĩa là tôi có thể monekypatch nó để thêm một skip
phương pháp.
class Exception
attr_accessor :continuation
def skip
continuation.call
end
end
Bây giờ chúng ta cần đặt continuation
thuộc tính cho mọi ngoại lệ. Hóa ra là raise
chỉ là một phương thức mà chúng tôi có thể ghi đè.
BTW, đoạn mã dưới đây được lấy gần như nguyên văn từ trình chiếu tuyệt vời của Advi Những điều bạn chưa biết về Ngoại lệ. Tôi không thể nghĩ ra cách nào tốt hơn để triển khai nó hơn thế này:
require 'continuation'
module StoreContinuationOnRaise
def raise(*args)
callcc do |continuation|
begin
super
rescue Exception => e
e.continuation = continuation
super(e)
end
end
end
end
class Object
include StoreContinuationOnRaise
end
Bây giờ tôi có thể gọi skip
cho bất kỳ ngoại lệ nào và nó sẽ giống như ngoại lệ chưa bao giờ xảy ra.