Computer >> Máy Tính >  >> Lập trình >> Ruby

Hướng dẫn cho người mới bắt đầu về các ngoại lệ trong Ruby

Hôm trước, tôi đang tìm kiếm một giới thiệu về ngoại lệ Ruby được viết cho người mới bắt đầu - những người biết cú pháp cơ bản của Ruby nhưng không thực sự chắc chắn ngoại lệ là gì hoặc tại sao nó lại hữu ích. Tôi không thể tìm thấy một cái nào đó, vì vậy tôi quyết định tự mình đi thử. Tôi hy vọng bạn thấy nó hữu dụng. Nếu có bất kỳ điểm nào gây nhầm lẫn, vui lòng tweet cho tôi tại @StarrHorne. :)

Ngoại lệ là gì?

Ngoại lệ là cách của Ruby để đối phó với các sự kiện bất ngờ.

Nếu bạn đã từng mắc lỗi đánh máy trong mã của mình, khiến chương trình của bạn gặp sự cố với một thông báo như SyntaxError hoặc NoMethodError , thì bạn đã thấy các trường hợp ngoại lệ đang hoạt động.

Khi bạn nêu ra một ngoại lệ trong Ruby, thế giới sẽ dừng lại và chương trình của bạn bắt đầu ngừng hoạt động. Nếu không có gì ngăn quá trình này, chương trình của bạn cuối cùng sẽ thoát ra với một thông báo lỗi.

Đây là một ví dụ. Trong đoạn mã dưới đây, chúng tôi cố gắng chia cho số không. Điều này là không thể, vì vậy Ruby tạo ra một ngoại lệ có tên là ZeroDivisionError . Chương trình thoát và in một thông báo lỗi.

1 / 0
# Program crashes and outputs: "ZeroDivisionError: divided by 0"

Các chương trình bị lỗi có xu hướng khiến người dùng của chúng tôi tức giận. Vì vậy, chúng tôi thường muốn dừng quá trình tắt này và phản ứng với lỗi một cách thông minh.

Đây được gọi là "giải cứu", "xử lý" hoặc "bắt" một ngoại lệ. Tất cả chúng đều có nghĩa giống nhau. Đây là cách bạn làm điều đó trong Ruby:

begin
  # Any exceptions in here... 
  1/0
rescue
  # ...will cause this code to run
  puts "Got an exception, but I'm responding intelligently!"
  do_something_intelligent()
end

# This program does not crash.
# Outputs: "Got an exception, but I'm responding intelligently!"

Ngoại lệ vẫn xảy ra, nhưng nó không khiến chương trình bị sập vì nó đã được "giải cứu". Thay vì thoát ra, Ruby chạy mã trong khối cứu hộ, khối này sẽ in ra một thông báo.

Điều này là tốt, nhưng nó có một hạn chế lớn. Nó cho chúng tôi biết "đã xảy ra sự cố" mà không cho chúng tôi biết điều gì đã sai.

Tất cả thông tin về những gì đã xảy ra sẽ được chứa trong một đối tượng ngoại lệ.

Đối tượng ngoại lệ

Các đối tượng ngoại lệ là các đối tượng Ruby bình thường. Họ giữ tất cả dữ liệu về "điều gì đã xảy ra" đối với trường hợp ngoại lệ bạn vừa giải cứu.

Để lấy đối tượng ngoại lệ, bạn sẽ sử dụng một cú pháp cứu hộ hơi khác.

# Rescues all errors, an puts the exception object in `e`
rescue => e

# Rescues only ZeroDivisionError and puts the exception object in `e`
rescue ZeroDivisionError => e

Trong ví dụ thứ hai ở trên, ZeroDivisionError là lớp của đối tượng trong e . Tất cả các "loại" ngoại lệ mà chúng ta đã nói đến thực sự chỉ là tên lớp.

Các đối tượng ngoại lệ cũng giữ dữ liệu gỡ lỗi hữu ích. Hãy xem xét đối tượng ngoại lệ cho ZeroDivisionError của chúng tôi .

begin
  # Any exceptions in here... 
  1/0
rescue ZeroDivisionError => e
  puts "Exception Class: #{ e.class.name }"
  puts "Exception Message: #{ e.message }"
  puts "Exception Backtrace: #{ e.backtrace }"
end

# Outputs:
# Exception Class: ZeroDivisionError
# Exception Message: divided by 0
# Exception Backtrace: ...backtrace as an array...

Giống như hầu hết các trường hợp ngoại lệ của Ruby, nó chứa một thông báo và một backtrace cùng với tên lớp của nó.

Nâng cao các trường hợp ngoại lệ của riêng bạn

Cho đến nay chúng ta chỉ nói về việc giải cứu các trường hợp ngoại lệ. Bạn cũng có thể kích hoạt các trường hợp ngoại lệ của riêng mình. Quá trình này được gọi là "nâng cao". Bạn làm điều đó bằng cách gọi raise phương pháp.

Khi bạn nêu ra các ngoại lệ của riêng mình, bạn sẽ phải chọn loại ngoại lệ nào để sử dụng. Bạn cũng có thể đặt thông báo lỗi.

Đây là một ví dụ:

begin
  # raises an ArgumentError with the message "you messed up!"
  raise ArgumentError.new("You messed up!")
rescue ArgumentError => e  
  puts e.message
end

# Outputs: You messed up! 

Như bạn có thể thấy, chúng tôi đang tạo một đối tượng lỗi mới (ArgumentError ) với một thông báo tùy chỉnh ("Bạn đã nhầm lẫn!") và chuyển nó đến raise phương pháp.

Đây là Ruby, raise có thể được gọi theo một số cách:

# This is my favorite because it's so explicit
raise RuntimeError.new("You messed up!")

# ...produces the same result
raise RuntimeError, "You messed up!"

# ...produces the same result. But you can only raise 
# RuntimeErrors this way
raise "You messed up!"

Đặt ngoại lệ tùy chỉnh

Các ngoại lệ tích hợp sẵn của Ruby là rất tốt, nhưng chúng không bao gồm mọi trường hợp sử dụng có thể có.

Điều gì sẽ xảy ra nếu bạn đang xây dựng một hệ thống người dùng và muốn đưa ra một ngoại lệ khi người dùng cố gắng truy cập vào một phần ngoài giới hạn của trang web? Không có ngoại lệ tiêu chuẩn nào của Ruby phù hợp, vì vậy cách tốt nhất của bạn là tạo một loại ngoại lệ mới.

Để tạo một ngoại lệ tùy chỉnh, chỉ cần tạo một lớp mới kế thừa từ StandardError .

class PermissionDeniedError < StandardError

end

raise PermissionDeniedError.new()

Đây chỉ là một lớp Ruby bình thường. Điều đó có nghĩa là bạn có thể thêm các phương thức và dữ liệu vào nó giống như bất kỳ lớp nào khác. Hãy thêm một thuộc tính có tên là "action":

class PermissionDeniedError < StandardError

  attr_reader :action

  def initialize(message, action)
    # Call the parent's constructor to set the message
    super(message)

    # Store the action in an instance variable
    @action = action
  end

end

# Then, when the user tries to delete something they don't
# have permission to delete, you might do something like this:
raise PermissionDeniedError.new("Permission Denied", :delete)

Phân cấp lớp

Chúng tôi vừa tạo một ngoại lệ tùy chỉnh bằng cách phân lớp StandardError , chính nó là lớp con Exception .

Trên thực tế, nếu bạn nhìn vào hệ thống phân cấp lớp của bất kỳ ngoại lệ nào trong Ruby, bạn sẽ thấy nó cuối cùng dẫn trở lại Exception . Đây, tôi sẽ chứng minh điều đó cho bạn. Đây là hầu hết các ngoại lệ được tích hợp sẵn của Ruby, được hiển thị theo thứ bậc:

Exception
 NoMemoryError
 ScriptError
   LoadError
   NotImplementedError
   SyntaxError
 SignalException
   Interrupt
 StandardError
   ArgumentError
   IOError
     EOFError
   IndexError
   LocalJumpError
   NameError
     NoMethodError
   RangeError
     FloatDomainError
   RegexpError
   RuntimeError
   SecurityError
   SystemCallError
   SystemStackError
   ThreadError
   TypeError
   ZeroDivisionError
 SystemExit

Tất nhiên, bạn không cần phải ghi nhớ tất cả những điều này. Tôi đang chỉ cho bạn vì ý tưởng về hệ thống phân cấp này rất quan trọng vì một lý do cụ thể.

Khắc phục lỗi của một lớp cụ thể cũng giải cứu lỗi của các lớp con của nó.

Hãy thử lại ...

Khi bạn rescue StandardError , bạn không chỉ giải cứu các ngoại lệ với lớp StandardError nhưng cả những đứa con của nó. Nếu bạn nhìn vào biểu đồ, bạn sẽ thấy đó là rất nhiều:ArgumentError , IOError , v.v.

Nếu bạn phải rescue Exception , bạn sẽ giải cứu mọi trường hợp ngoại lệ, đó sẽ là một ý kiến ​​rất tồi

Giải cứu Tất cả các Trường hợp Ngoại lệ (cách không tốt)

Nếu bạn muốn bị la mắng, hãy chuyển đến phần tràn ngăn xếp và đăng một số mã giống như sau:

// Don't do this 
begin
  do_something()
rescue Exception => e
  ...
end

Đoạn mã trên sẽ giải cứu mọi ngoại lệ. Đừng làm điều đó! Nó sẽ phá vỡ chương trình của bạn theo những cách kỳ lạ.

Đó là bởi vì Ruby sử dụng ngoại lệ cho những thứ khác ngoài lỗi. Nó cũng sử dụng chúng để xử lý các thông báo từ hệ điều hành được gọi là "Tín hiệu". Nếu bạn đã từng nhấn "ctrl-c" để thoát khỏi một chương trình, bạn đã sử dụng một tín hiệu. Bằng cách loại bỏ tất cả các trường hợp ngoại lệ, bạn cũng loại bỏ các tín hiệu đó.

Ngoài ra còn có một số loại ngoại lệ - như lỗi cú pháp - thực sự có thể khiến chương trình của bạn gặp sự cố. Nếu bạn kiềm chế chúng, bạn sẽ không bao giờ biết khi nào mình mắc lỗi chính tả hoặc các lỗi khác.

Khắc phục mọi lỗi (Cách đúng đắn)

Quay lại và xem biểu đồ phân cấp lớp và bạn sẽ thấy rằng tất cả các lỗi bạn muốn giải cứu đều là con của StandardError

Điều đó có nghĩa là nếu bạn muốn giải cứu "tất cả các lỗi", bạn nên giải cứu StandardError .

begin
  do_something()
rescue StandardError => e
  # Only your app's exceptions are swallowed. Things like SyntaxErrror are left alone. 
end

Trên thực tế, nếu bạn không chỉ định một lớp ngoại lệ, Ruby sẽ giả định rằng bạn có nghĩa là StandardError

begin
  do_something()
rescue => e
  # This is the same as rescuing StandardError
end

Khắc phục các lỗi cụ thể (Cách tiếp cận tốt nhất)

Bây giờ bạn đã biết cách giải quyết tất cả các lỗi, bạn nên biết rằng đó thường là một ý tưởng tồi, một mùi mã, được coi là có hại, v.v.

Chúng thường là dấu hiệu cho thấy bạn quá lười biếng để tìm ra những trường hợp ngoại lệ cụ thể nào cần được giải cứu. Và chúng hầu như sẽ luôn quay lại ám ảnh bạn.

Vì vậy, hãy dành thời gian và làm đúng. Giải cứu các trường hợp ngoại lệ cụ thể.

begin
  do_something()
rescue Errno::ETIMEDOUT => e
  // This will only rescue Errno::ETIMEDOUT exceptions
end

Bạn thậm chí có thể giải cứu nhiều loại ngoại lệ trong cùng một khối cứu hộ, vì vậy không có lý do gì. :)

begin
  do_something()
rescue Errno::ETIMEDOUT, Errno::ECONNREFUSED => e
end