哪些类型的错误应该利用 Ruby 的 Exception 类,其他类的处理策略是什么?

What types of error ought to utilise Ruby's Exception class, and what handling strategies for others?

提问人:jbk 提问时间:6/9/2023 最后编辑:jbk 更新时间:6/9/2023 访问量:40

问:

上下文是我正在构建的 CLI 国际象棋游戏项目,用于处理用户输入格式错误等条件,并根据各种游戏和棋子规则验证国际象棋动作。我正在寻求有关处理此类情况/条件/错误的最合适技术的指导——使用 Exception 类、编写我自己的救援方法和/或任何其他方法。

例如,在下面的方法中,我编写了一个自定义的“案例/条件处理程序”类型的方法,它只是向用户发送有关问题的消息,然后再次尝试代码,有效地循环,直到用户输入正确的格式,如下所示:puts

游戏#get_move & #rescue_invalid_format_error:

def get_move
  ...
  return rescue_invalid_format_error(move) unless chess_format?(move)
  ...
end

def rescue_invalid_format_error(move)
   puts "your move; '#{move}' was not formatted correctly, it should be formatted like; 'a1,a2', try again..."
   get_move
end

然后,在其他代码中,我最终构建了自定义 Exception 类,如下所示:

input_errors.rb:

class InputError < StandardError
  def initialize(message = 'Input error')
    super(message)
  end
end

并在“错误”情况下引发 Exception 对象,例如:

def add_move(indexed_move)
    if index_format?(indexed_move)
      true_move?(indexed_move) ? @moves << indexed_move : (raise InputError.new("Input; #{indexed_move} does not represent an actual move"))
    else
      raise InputError.new("Indexed_move; #{indexed_move} should be formatted like 'iiii'")
    end
  end

(尽管我没有将任何调用上述 #add_move 的代码与 begin rescue 块包装在一起,因为我认为 InputError 已经以某种方式处理了异常,在其祖先代码中的某个地方(尽管我不确定))。

所以,我问题的本质是;在 Ruby 中,用什么类型的错误处理方法/策略来处理什么类型的条件是正确的方法?

提前致谢。

Ruby 异常 错误处理

评论


答:

1赞 Max 6/9/2023 #1

在软件设计中使用异常没有“正确的方法”。例如,即使是流行的 Ruby 风格指南也基本上忽略了这个问题。它提出的最强烈的建议是不要对控制流使用异常,这在所有现代编程语言中都被广泛接受。

话虽如此,以下是我使用的指南(在 Ruby 和其他语言中)。

对库中的复杂错误使用异常

如果您正在开发一个希望在多个应用程序中重用的 Gem,则异常可能是从库中返回信息丰富的错误的好方法。例如,这种样式用于 Ruby 的几个标准部分,例如类。File

但要小心:你的库应该假设这些异常可能会被捕获,所以当这种情况发生时,它应该让自己处于某种合理的状态。您不会希望抛出一个非常自定义的异常,只是为了捕获它,现在您的库处于某种意想不到的状态,调用者在尝试执行任何操作时都会得到一个非漂亮的异常。RuntimeError

这种使用异常的一个例外:如果库的错误状态非常简单,那么引发自定义异常没有多大好处。它的主要好处是在自定义类型和消息中传达的信息。因此,如果您不需要它,请考虑简单地返回以指示错误。nil

对应用中真正异常的情况使用例外

如果你正在构建一个面向用户的应用程序(包括 CLI),请在代码中为那些确实不应该发生的事情的情况保留例外,但从技术上讲,它可以,例如,你有一个数组索引,它应该始终有效,但不知何故数组太小,一个变量应该已经初始化,但不知何故它仍然是,类似的东西。nil

另一种说法是:只在你不想抓住它的情况下使用异常,而异常更像是一种向你展示你的哪些假设是错误的方式,这样你就可以修复错误。这种用法的一个好处是,你通常在 Ruby 中免费获得它,而无需任何自定义代码,例如,你得到它,它会告诉你到底出了什么问题。通常,您唯一想要捕获此类异常的地方是整个应用程序的一些高级功能,以便用“对不起,出了点问题”消息来美化它。NoMethodErrorbegin rescue

In particular, getting input from the user and that input being invalid is entirely unexceptional. Users do that all the time. Usually in my CLI apps I'll add a helper function that takes the prompt for the user, an explanation of what the input should look like, and it internally loops forever until they input something correct. Such a method does not need any way to represent errors since it either returns valid input or it never returns at all.

In general, prefer existing exception types

Before you declare a custom exception class, double check that none of the existing exception types work for your situation.

评论

0赞 jbk 6/10/2023
An exceptionally helpful answer there @max, genuinely, despite the pun. Thanks very much for taking the time to write that up and for providing me with that guidance. I think that my error and exception related thinking was floored in that I was defining an exception far too broadly and was therefore considering the exceptions device for use in control flow.
0赞 jbk 6/10/2023
I'll read your answer a few times and do some further research and reading on the subject to really try to distill this. Answers to these things still somewhat evade me though; - difference, if any, between an error and an exception? - are exceptions there to assist the developer or the user? - why bother custom raising exceptions at all, as Ruby, I think, catches all of them anyway with a helpful error type and a message?
1赞 Max 6/12/2023
In Ruby there is not much difference between errors and exceptions. Just a name difference. Custom exceptions are most useful for library development, so that users of your library can catch your special exceptions and not exceptions from other libraries or normal Ruby code.