在多个地方重新引发相同一组异常的DRY方式

3

简述:

Ruby中是否有一种方法可以DRY化以下代码:

def entry_point_one
  begin
    do_something
  rescue MySyntaxErrorOne, MySyntaxErrorTwo, MySyntaxErrorEtc => syn_err
    raise syn_err.exception(syn_err.message)
  end
end

def entry_point_two
  begin
    do_something_else
  rescue MySyntaxErrorOne, MySyntaxErrorTwo, MySyntaxErrorEtc => syn_err
    raise syn_err.exception(syn_err.message)
  end
end

更长:

我正在构建一个解释器。这个解释器可以使用不同的入口点进行调用。如果我向这个解释器提供了一个“脏”字符串,我期望它会引发一个错误。然而,如果我不想被直接或间接由 do_something 调用的每个方法的整个回溯信息所干扰,那就最好不要这样做,尤其是因为解释器使用了递归。

正如您在上面的片段中所看到的,我已经知道一种重新引发错误并从而删除回溯的方法。我想要做的是删除上述示例中的重复内容。到目前为止,我所能想到的最接近的方法是这样的:

def entry_point_one
  re_raise_known_exceptions {do_something}
end

def entry_point_two
  re_raise_known_exceptions {do_something_else}
end

def re_raise_known_exceptions
  yield
rescue MySyntaxErrorOne, MySyntaxErrorTwo, MySyntaxErrorEtc => syn_err
    raise syn_err.exception(syn_err.message)
end

但这会导致方法“重新抛出已知异常”在回溯中显示。

编辑:我想要的东西可能类似于C预处理宏。

5个回答

3
您可以直接在数组上使用splat。
从IRB中直接获取:
COMMON_ERRORS = [ArgumentError, RuntimeError] # add your own 

def f
  yield
rescue *COMMON_ERRORS => err
  puts "Got an error of type #{err.class}"
end


f{ raise ArgumentError.new }
Got an error of type ArgumentError

f{ raise 'abc' }
Got an error of type RuntimeError

虽然这不是我想要的,但还是谢谢你展示了那个平台。 - Patrick Huizinga
哦,我现在明白你的意思了,抱歉。 - Orion Edwards

2

在进一步思考后,我想到了以下内容:

interpreter_block {do_something}

def interpreter_block
  yield
rescue ExceptionOne, ExceptionTwo, ExceptionEtc => exc
  raise exc.exception(exc.message)
end

虽然还不完全符合我的要求,但至少现在回溯信息中的额外条目看起来更好了一些。


1

这有点粗糙,但是就清理回溯信息而言,像这样做非常好:

class Interpreter

  def method1
    error_catcher{ puts 1 / 0 }
  end

  def error_catcher
    yield
  rescue => err
    err.set_backtrace(err.backtrace - err.backtrace[1..2])
    raise err
  end

end

主要的技巧在于这行代码:err.set_backtrace(err.backtrace - err.backtrace[1..2])。如果没有它,我们会得到以下结果(从IRB中):
ZeroDivisionError: divided by 0
  from (irb):43:in `/'
  from (irb):43:in `block in method1'
  from (irb):47:in `error_catcher'
  from (irb):43:in `method1'
  from (irb):54
  from /Users/peterwagenet/.ruby_versions/ruby-1.9.1-p129/bin/irb:12:in `<main>'

我们不希望第二和第三行出现在这里。因此,我们将其删除,最终得到:

ZeroDivisionError: divided by 0
  from (irb):73:in `/'
  from (irb):73:in `method1'
  from (irb):84
  from /Users/peterwagenet/.ruby_versions/ruby-1.9.1-p129/bin/irb:12:in `<main>'

1

这可能有点邪恶,但我认为你可以简单地从回溯中删除该行;-)

COMMON_ERRORS = [ArgumentError, RuntimeError]

def interpreter_block
  yield
rescue *COMMON_ERRORS => err
  err.backtrace.delete_if{ |line| line=~/interpreter_block/ }
  raise err
end

虽然我不确定这是否是一个好主意。但之后你会有很多乐趣来调试你的解释器。

另外提醒一下:Treetop 可能会对你有所帮助。


无法正常工作,因为您在尝试删除回溯的那部分之后才在interpreter_block内部引发了异常。 - Patrick Huizinga
顺便说一下,我不关心任何邪恶的东西,因为异常应该通知有关待解释字符串中的错误,而不是解释器本身。 - Patrick Huizinga

0

如果你在异常中已经拥有了所有需要的信息,而且根本不需要回溯,那么你可以定义自己的错误并抛出它,而不是重新抛出现有的异常。这将为其提供一个新鲜的回溯。(当然,假设你的示例代码不完整,并且救援块中还有其他处理发生--否则最好的选择就是让错误自然地冒泡上升。)

class MyError < StandardError; end

def interpreter_block
  yield
rescue ExceptionOne, ExceptionTwo, ExceptionEtc => exc
  raise MyError
end

我仍然想要回溯,但我不想看到解释器中的31.4个嵌套方法。我想要捕获的异常是由于提供的字符串中出现错误而引发的异常。因此,解释器内部的回溯并不像异常本身那样重要。 - Patrick Huizinga

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接