1

有没有办法在 ruby​​ 中执行旧的“on error resume next”例程?

我已经从其他地方动态填充了一系列值(准确地说是从 MQTT 主题中读取),然后我想对它们进行一堆数值计算并发布结果。这些值应该是数字,但可能缺少或非数字。

目前我的代码看起来像

values=[]


//values get loaded here 

begin
  Publish('topic1',value[0]*10+value[1])
rescue TypeError,NoMethodError,ZeroDivisionError
end

begin
  Publish('topic2',value[3]/value[4])
rescue TypeError,NoMethodError,ZeroDivisionError
end

//etc etc

如果由于任何原因计算失败,程序应该跳过该步骤并继续。

它有效,但肯定有比所有相同的 begin..rescue 块更好的方法吗?毕竟,Ruby 是关于“DRY”的。

有没有办法重写上面的内容,以便使用单个 begin..rescue 构造,同时仍然允许尝试所有计算?

更新

做类似的事情有多安全

def safe_Publish(topic,value)
  return if value.nil?
  Publish(topic,value)
end

并使用 safe_Publish('topic2',(value[3]/value[4] rescue nil)) 调用

主要问题是,上面的内容不仅捕获了我所期待的所有异常,这让我有点紧张。

4

3 回答 3

1

on error resume next编码风格真的很危险——因为它让你很难找到你不小心引入程序的新错误。相反,我会编写一个不抛出这些异常的不同版本的发布:

def try_publish(topic_name)
  begin
    Publish('topic1',yield)
  rescue TypeError,NoMethodError,ZeroDivisionError
    # are you sure you don't want to do anything here? Even logging the errors
    # somewhere could be useful.
  end
end

然后,您可以使用以下命令调用它:

try_publish('topic1') { value[0]*10+value[1] }

如果表达式抛出 TypeError、NoMethodError 或 ZeroDivisionError,它们将被捕获并忽略。

现在您的原始方法不需要任何救援。


如果你真的想要一个on error resume next,你可以通过猴子修补raise内核中的方法来做到这一点,但那将是一个可怕的想法。

于 2012-06-29T05:50:35.487 回答
0

这显示了如何将一堆快速操作包装到一个循环中,每个操作都受到开始/救援的保护:

values = [1,2,3,0,4]
ops = [ ->{values[0]/values[1]}, ->{values[2]/values[3]} ]

ops.each do |op|
  begin
    puts "answer is #{op.call}"
  rescue ZeroDivisionError
    puts "cannot divide by zero"
  end
end

但是,我更喜欢该safe_publish方法,因为您可以对其进行单元测试,并且它将进行安全调用和处理错误的逻辑封装在一个地方:

def safe_publish(topic, &block)
  begin
    value = block.call
    publish(topic, value)
  rescue
    # handle the error
  end
end

然后您可以使用以下代码调用它:

safe_publish 'topic0' do
  value[0]*10+value[1]
end
于 2012-06-29T05:33:31.633 回答
0

如果您更仔细地考虑一下自己在做什么以及为什么要这样做on error resume next,我想您会发现您实际上并不需要抑制所有异常。正如其他发帖者所指出的那样,这将使查找和修复错误变得困难。

您的问题是您从互联网上抓取了一堆数字,并想对它们进行一些计算,但有些可能无效或丢失。对于无效/缺失的数字,您希望跳过任何使用这些数字的计算。

一些可能的解决方案:

  1. 预先过滤您的数据并删除任何无效数字。
  2. 将您想要执行的每个计算都放入一个自己的方法中。rescue Exception在方法定义上加上一个。
  3. 为不会引发除以零等异常的数字类定义“安全”包装器。使用这些包装器进行计算。

“包装器”可能看起来像这样(不要指望完整的、经过测试的代码;这只是为了给你一个想法):

# This is not designed for "mixed" arithmetic between SafeNumerics and ordinary Numerics,
# but if you want to do mixed arithmetic, that can also be achieved
# more checks will be needed, and it will also need a "coerce" method
class SafeNumeric
  attr_reader :__numeric__
  def initialize(numeric)
    @__numeric__ = numeric.is_a?(String) ? numeric.to_f : numeric
  end

  def zero?
    @__numeric__.zero?
  end
  def /(other)
    if other.zero? || @__numeric__.nil? || other.__numeric__.nil?
      SafeNumeric.new(nil) # could use a constant for this to reduce allocations
    else
      SafeNumeric.new(@__numeric__ / other.__numeric__)
    end
  end

  def to_s; @__numeric__.to_s; end
  def inspect; @__numeric__.inspect; end

  # methods are also needed for +, -, *
end

然后像这样使用它:

numbers = scraped_from_net.map { |n| SafeNumeric.new(n) }
# now you can do arithmetic on "numbers" at will
于 2012-06-29T06:47:26.917 回答