红宝石“错误恢复下一个”功能
Posted
技术标签:
【中文标题】红宝石“错误恢复下一个”功能【英文标题】:ruby "on error resume next" function 【发布时间】:2012-06-29 05:21:28 【问题描述】:有没有办法在 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))
主要问题是上面捕获了所有异常,而不仅仅是我期望的异常,这让我有点紧张。
【问题讨论】:
【参考方案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
方法来实现,但那将是一个可怕的想法。
【讨论】:
:你能澄清一下吗?如果您打算编写“Publish(topic_name,value)”并使用“try_publish('topic1',value[0]*10+value[1])”调用,那么在调用该方法之前肯定会抛出数字异常吗? 对不起,我原来的答案一点用都没有。我现在通过使用一个块来传递可能失败的计算来修复它。由于块在内部开始/救援部分执行,异常将被捕获。 nanothief- 看起来好多了。作为一个刚接触 ruby 的 perl 人,我仍在思考代码块和 yield() 向基本上提出相同解决方案的其他人道歉:您的意见同样有价值,但我只能选择一个“接受的答案”。我已经学会了代码块的力量,谢谢大家!【参考方案2】:如果你仔细想想你在做什么,为什么你想要on error resume next
,我想你会发现你真的不需要压制所有 例外。正如其他发帖者所指出的那样,这将使查找和修复错误变得困难。
您的问题是您从互联网上抓取了一堆数字,并想对它们进行一些计算,但有些可能无效或丢失。对于无效/缺失的数字,您希望跳过任何会使用这些数字的计算。
一些可能的解决方案:
-
预过滤您的数据并删除任何无效数字。
将您想要执行的每个计算都放入一个自己的方法中。在方法定义上添加
rescue Exception
。
为不会引发除以零等异常的数字类定义“安全”包装器。使用这些包装器进行计算。
“包装器”可能看起来像这样(不要指望完整的、经过测试的代码;这只是为了给你一个想法):
# 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
【讨论】:
2.基本上是我现在在没有方法调用的情况下正在做的事情。您可以发布 3. 的示例代码吗? 作为一个 perl 的老家伙,但是一个 ruby n00b 我很难理解其中的大部分内容——尽管我确信它的代码很棒!运算符重载总是让我头疼。 “@__numeric__”是对父类/函数的某种引用吗? 不,它只是一个包含数字的变量,名称完全是任意的。请记住,在 Ruby 中,像 +、- 等“运算符”只是方法调用。我所做的只是在自定义类上定义这些运算符,该类包含一个数字并在尝试对数字执行任何操作之前进行一些检查。【参考方案3】:这显示了如何将一堆快速操作包装到一个循环中,每个操作都受到开始/救援的保护:
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
【讨论】:
如果您希望对每个值进行相同的计算,而不是不同的计算,那将可以正常工作def
是隐式开头,因此您可以从您的safe_publish
版本中删除begin
。
我说得对吗 -> 类似于 lambda 即匿名函数?仍在尝试掌握代码块、lambdas 和 Procs,如果有人有好的指导,我将不胜感激以上是关于红宝石“错误恢复下一个”功能的主要内容,如果未能解决你的问题,请参考以下文章