异常处理¶
Crystal 处理错误的方式是通过引发和捕获异常。
引发异常¶
您可以通过调用顶级 raise
方法来引发异常。与其他关键字不同,raise
是一个具有两个重载的常规方法: 一个接受字符串,另一个 接受异常实例
raise "OH NO!"
raise Exception.new("Some error")
字符串版本只是使用该消息创建一个新的 Exception 实例。
只有 Exception
实例或子类可以被引发。
定义自定义异常¶
要定义自定义异常类型,只需从 Exception 派生。
class MyException < Exception
end
class MyOtherException < Exception
end
与往常一样,您可以为异常定义一个构造函数,也可以使用默认构造函数。
捕获异常¶
要捕获任何异常,请使用 begin ... rescue ... end
表达式
begin
raise "OH NO!"
rescue
puts "Rescued!"
end
# Output: Rescued!
要访问被捕获的异常,可以在 rescue
子句中指定一个变量
begin
raise "OH NO!"
rescue ex
puts ex.message
end
# Output: OH NO!
要捕获只是一种类型的异常(或其任何子类)
begin
raise MyException.new("OH NO!")
rescue MyException
puts "Rescued MyException"
end
# Output: Rescued MyException
有效的类型限制是 ::Exception
的子类,模块类型和它们的联合。
要访问它,请使用类似于类型限制的语法
begin
raise MyException.new("OH NO!")
rescue ex : MyException
puts "Rescued MyException: #{ex.message}"
end
# Output: Rescued MyException: OH NO!
可以指定多个 rescue
子句
begin
# ...
rescue ex1 : MyException
# only MyException...
rescue ex2 : MyOtherException
# only MyOtherException...
rescue
# any other kind of exception
end
您还可以通过指定联合类型来一次捕获多个异常类型
begin
# ...
rescue ex : MyException | MyOtherException
# only MyException or MyOtherException
rescue
# any other kind of exception
end
else¶
只有在没有捕获异常的情况下才执行 else
子句
begin
something_dangerous
rescue
# execute this if an exception is raised
else
# execute this if an exception isn't raised
end
只有在指定了至少一个 rescue
子句的情况下才能指定 else
子句。
ensure¶
ensure
子句在 begin ... end
或 begin ... rescue ... end
表达式的末尾执行,无论是否引发了异常
begin
something_dangerous
ensure
puts "Cleanup..."
end
# Will print "Cleanup..." after invoking something_dangerous,
# regardless of whether it raised or not
或者
begin
something_dangerous
rescue
# ...
else
# ...
ensure
# this will always be executed
end
ensure
子句通常用于清理,释放资源等。
简短语法形式¶
异常处理有简短的语法形式:假设方法或块定义是一个隐式 begin ... end
表达式,然后指定 rescue
、else
和 ensure
子句
def some_method
something_dangerous
rescue
# execute if an exception is raised
end
# The above is the same as:
def some_method
begin
something_dangerous
rescue
# execute if an exception is raised
end
end
使用 ensure
def some_method
something_dangerous
ensure
# always execute this
end
# The above is the same as:
def some_method
begin
something_dangerous
ensure
# always execute this
end
end
# Similarly, the shorthand also works with blocks:
(1..10).each do |n|
# potentially dangerous operation
rescue
# ..
else
# ..
ensure
# ..
end
类型推断¶
在异常处理程序的 begin
部分中声明的变量在 rescue
或 ensure
体中被视为 Nil
类型。例如
begin
a = something_dangerous_that_returns_Int32
ensure
puts a + 1 # error, undefined method '+' for Nil
end
即使 something_dangerous_that_returns_Int32
从未引发,或者 a
被赋值,然后执行了可能引发异常的方法,也会发生上述情况
begin
a = 1
something_dangerous
ensure
puts a + 1 # error, undefined method '+' for Nil
end
尽管很明显 a
始终会被赋值,但编译器仍然认为 a
可能没有机会被初始化。虽然这种逻辑将来可能会得到改进,但目前它迫使您将异常处理程序保持在必要的最小范围内,使代码的意图更加清晰
# Clearer than the above: `a` doesn't need
# to be in the exception handling code.
a = 1
begin
something_dangerous
ensure
puts a + 1 # works
end
处理错误的替代方法¶
虽然异常是处理错误的机制之一,但它们不是您的唯一选择。引发异常涉及分配内存,执行异常处理程序通常很慢。
标准库通常提供一些方法来完成某事:一个引发,一个返回 nil
。例如
array = [1, 2, 3]
array[4] # raises because of IndexError
array[4]? # returns nil because of index out of bounds
通常的做法是提供一个替代的“问题”方法来表示该方法的这种变体返回 nil
而不是引发异常。这允许用户选择是处理异常还是处理 nil
。但是请注意,这并不适用于所有方法,因为异常仍然是首选方式,因为它们不会用错误处理逻辑污染代码。