赢得一个定制的New Relic弹球机!只要让其他数据呆子注册FutureStack就可以了。 现在注册

这是Weird Ruby系列文章的第2部分。不要错过怪异的Ruby第1部分:结束的开始,Weird Ruby Part 3: Fun with the Flip-Flop Phenom, 和怪异的Ruby Part 4: Code Pods (Blocks、Procs和Lambdas)

欢迎来到关于Ruby语言的怪异之处以及它们存在的原因的小系列文章的第二篇。上个月我们讨论了Ruby中的begin-end构造,以及它如何导致一些相当意想不到的行为(参见怪异的Ruby第1部分:结束的开始)。在这篇文章中,我们将介绍在begin-end块中使用的其他一些子句,如rescue、else和ensure,其中包括一些即使是有经验的开发人员也可能会出错的陷阱。

救助方

ruby爱好者使用救援经常处理异常,你可能已经习惯了。如果您不熟悉Ruby中的异常,那么您可能需要阅读这个总结

假设有一些特别重要的任务完成:fire_ze_missiles.

键盘的红宝石

我们需要发射导弹(请不要用Ruby发射导弹;事实上,也许根本就不发射导弹,好吗?)我们想确保我们为用户记录了一个无用的错误代码,以及错误时的消息。

begin fire_ze_missiles rescue standarderror => e log“错误4279er:#{e.message}”结束

要拯救的参数的第一部分是您想要处理的错误类。的StandardError上面的例子告诉Ruby我们只需要处理standderror的实例或者从standderror派生出来的其他错误。本例中我们可以不使用它,因为它实际上是Ruby的默认值(如果您键入救援= > e相反,Ruby会假定您只需要标准错误)。只要记住,Ruby中并非所有异常都源自StandardError,您必须对此进行补救例外把它们都弄到手。

我觉得从Exception中拯救出来和发射导弹一样好,因为像这样的救援可能会造成各种破坏:

begin fire_ze_missiles rescue Exception => ep e retry while true结束

重试在这种情况下将无限期地重新运行代码,不断尝试发射导弹,无论我们提出什么样的异常。的p eLine只是简写把e.inspect。让我们试着用Ctrl-C来停止导弹:

^ C中断

试图用Ctrl-C跳出我们的程序将引发中断,我们优雅地抢救并输出,供您观赏。然后我们马上回去砸那个导弹按钮。您可能会在使用一个阻止您使用Ctrl-C退出的开发工具时感到非常沮丧,所以请帮助这个世界,让这种同理心引导您远离这种行为。

我们试试别的吧。您可以通过从命令行发送一个信号来礼貌地要求进程退出。如果我们的流程有一个ID为1337807,我们可以发送杀了1337807我们可以从程序中得到这个:

kill 1337807 

如果你已经用Ctrl-C挽救了Interrupt以防止用户逃脱,那么终止信号很可能是他们的下一步,而从信号异常中挽救也会真正激起他们的愤怒。这正是那种会激励未来的你去建造一个时间机器,然后回来复仇的事情,所以千万不要这样做。

在我们的示例中,Interrupt和SignalException错误都得到了挽救,因为它们派生于Exception,让这些错误继续其业务是非常重要的。以下是其他一些错误,你从Exception抢救无效:NoMemoryError,SyntaxError,LoadError,SystemStackError。可怕的,对吧?这就是为什么您几乎总是应该提供一个特定的错误类型来挽救或保留standderror的默认值。

Ruby还为我们提供了一种使用其他的子句中显式不引发异常时:

开始fire_ze_missilesrescue重试#为了好运再试一次,否则记录“我们设置了炸弹。”结束

在这种情况下,我们将只记录如果我们设法发射导弹没有错误。即使我们必须重新启动它们,如果我们顺利地完成了begin-rescue块,我们将运行else子句。

确保破坏

如果我们想运行一些东西,不管是否有异常?Ruby为我们提供了确保:

开始fire_ze_missiles rescue重试#只是为了好运,否则记录“We set them the bomb.”确保wtf_mate结束

我们的wtf_mate方法将运行导弹发射是否按计划进行。如果我们在没有错误的情况下启动导弹,我们才会报告我们才会设置炸弹,但我们每次都会达到炸弹。

如果我们在发射导弹之前有一些工作要完成,我们可以将其放在begin块中,并将导弹发射本身留在ensure中,这样我们就可以确保它每次都运行:

开始,确保导弹发射结束

现在,如果我们被那些粗鲁的标准错误惊醒,我们仍然可以成功地用核武器摧毁这个星球。让我们添加一个增加到我们的ensure block来查看发生了什么(增加只需将标准交易带有给定消息即可。

begin have_a_nap确保提升'wtf mate!'launch_ze_missiles结束

在这种情况下,我们不会继续发射导弹。这里的解决方法是省略我们的增加行,或任何其他有引发错误风险的代码。即使你的确保块中没有可以引发错误的代码,你仍然可以在你的进程中引发一个错误:

thread = thread.new do begin has_a_nap确保do_something_super_safe launch_ze_missiles结束base.belongs_to(:我们)thread.raise

有很多关于线程#提高他有充分的理由:这通常是一个非常糟糕的想法。在本例中,根据我们午休的时间和使用该基数的困难程度,raise可以在begin-ensure-end块中的线程上的任何位置被调用。

如果我们在敲击时调用线程上的提升,我们仍然会击中我们的确保块并启动导弹。线程#riote只是提高了一个RuntimeError在它被召唤的地方。正如我们之前讨论的那样,如果我们设法在我们的确保块内提出错误,我们将在那里停止一切并立即扣除。

同样,您可能认为有一个简单的解决方案:不要使用#raise,对吗?我敢打赌,在了解了您的代码库之后,您一定会感到欣慰,因为您知道自己并没有在线程中进行提升。很不幸,我有个坏消息。我在你的代码库中找到了这段代码:

x =线程。当前y坐标=线程。Start {begin sleep SEC rescue => e x.raise e else x.raise exception, message end}

很明显,你在计时器线程上调用了raise,你可能会看到一些不可预测的结果。好吧,我承认,我没有实际上通读代码。这个片段实际上来自timeout.rb如果您没有在代码中显式地使用它,那么您很可能使用了依赖于它的库。

如果你正在编写一个gem,那么很有可能有人会在你的确保块周围超时,而这是你没有预料到的,所以当你的确保失败,你的导弹无法发射时,你要做好准备。

这可能偶尔会令人沮丧,但不要太生气——这可能正是您希望从Thread#raise中获得的行为。如果您正在调用Thread#raise,则希望该线程完成现在。如果您将超时设置为10秒,那么您不希望在10和一半Ruby完成它正在做的事情后的几秒钟。你想让暂停时间在10秒后启动把那条线烧成灰烬。

这可能需要一点额外的思考,以这样的方式编写您的代码,您不需要确保任何东西,但这将是值得的。至少,要意识到你的“保证”政策是不可预测的,并据此制定计划。

命运还在继续

我将在第三部分中回来展示我的怪异Ruby行为集合中的一些怪异行为。如果你有任何想在本系列中看到的建议,请发送电子邮件给我:jonan@newrelic.com

开头写着“感谢阅读!”' ensure puts '<3 Jonan' end

红宝石键盘映像礼貌的Shutterstock.com