如何避免短路评估

Posted

技术标签:

【中文标题】如何避免短路评估【英文标题】:How to avoid short-circuit evaluation on 【发布时间】:2010-10-03 23:27:30 【问题描述】:

我正在使用 Ruby on Rails 并希望验证两种不同的模型:

if (model1.valid? && model2.valid?)
...
end

但是,“&&”运算符使用短路评估(即仅当“model1.valid?”为真时才评估“model2.valid?”),这会阻止在model1 无效时执行model2.valids。

是否存在不使用短路评估的“&&”等价物?我需要评估这两个表达式。

【问题讨论】:

出于好奇,为什么需要对两者都进行评估?通常,如果它们没有副作用,短路是可取的。为什么会#valid?有副作用吗? 这两个模型与同一个表单相关:最后,它们都必须经过验证才能执行操作。没有副作用,只是因为它是一个表单上的两个模型。 嗯...短路是正是你想要的。如果模型1无效,那为什么还要检查模型2? 公平地说,一次验证两个模型允许在两个模型上分别向用户报告错误。仅在您修复模型 1 错误后才通知模型 2 中的验证错误,这很烦人。 作为参考,#valid?不通知用户错误。它只用错误填充模型错误数组。然后控制器再次显示新的/编辑视图,然后显示错误。 【参考方案1】:

试试这个:

([model1, model2].map(&:valid?)).all?

如果两者都有效,它将返回 true,并在两个实例上创建错误。

【讨论】:

谢谢!很棒的代码!顺便说一句,我发现了“全部?”方法,我不知道“地图”方法可以与这样的语法一起使用(“&:有效?”)。我会使用这个解决方案。 @BrenoSalgado,为什么不呢?我认为恰恰相反:这是一种非常巧妙的规避短路评估的方法。 (我这样做了,因为:使用 map、&:method_name 等等?) 使用map是多余的,Enumerable#all?占用一个块。 请注意map(&:valid?)这一步很重要:[model1, model2].all?(&:valid?)是一个短路操作(如果model1有错误,它不会收集model2上的错误。)跨度> 对我来说,这比仅仅设置一个有效的结果更加冗长和混乱?首先调用一个变量。如果您不想设置变量并且不想在其他人可能无法获得它的基础上使用 & 运算符,我会说多花两美分并调用有效?像 malclocke 一样在数组中两次。【参考方案2】:

& 工作得很好。

irb(main):007:0> def a
irb(main):008:1> puts "a"
irb(main):009:1> false
irb(main):010:1> end
=> nil

irb(main):011:0> def b
irb(main):012:1> puts "b"
irb(main):013:1> true
irb(main):014:1> end
=> nil

irb(main):015:0> a && b
a
=> false

irb(main):016:0> a & b
a
b
=> false

irb(main):017:0> a and b
a
=> false

【讨论】:

valid 是一种在 ActiveRecord 实例上运行验证的方法,填充相关的错误,以便可以将它们报告给用户。 请注意,您必须小心 & | 的左侧或 ^ 运算符,因为它们是仅在 TrueClass 和 FalseClass 上定义的方法,AFAICT... Matt 先生好点 - 同时报告两个模型的错误是一个有价值的目标。删除了“不良代码气味”评论 :) @Mike:有趣——我得做更多的 irb 调查。 PS 我喜欢 irb 调查这样的事情! 有趣!我从来没有仔细研究过 & 和 |在逻辑上下文中,因为我会自动将它们读取为按位运算。【参考方案3】:

怎么样:

if [model1.valid?,model2.valid?].all?
  ...
end

为我工作。

【讨论】:

【参考方案4】:

分别评估它们并将结果存储在变量中。然后在这些布尔值之间使用一个简单的 && :)

【讨论】:

【参考方案5】:

使代码 sn-p 对于未来的开发人员更易于维护的关键概念之一是它的expressiveness。

让我们考虑以下示例:

([model1, model2].map(&:valid?)).all?

[model1.valid?,model2.valid?].all?

他们俩都做得很好,但是当未来的开发人员遇到其中任何一个而没有看到任何解释性的 cmets、您的意图文档或没有直接联系到您时,这个开发人员会在不知道目的是什么的情况下修改它们以避免短路评估。

如果没有测试,情况会变得更糟。

这就是为什么我建议引入一个小的包装器方法,它可以立即让所有事情变得清晰。

def without_short_circuit_evaluation(*conditions)
  conditions.all?
end

然后在你的代码库中的某个地方。

if without_short_circuit_evaluation(model1.valid?, model2.valid?)
  # do something
end

【讨论】:

【参考方案6】:

您可以将一个块传递给all?,而不是创建一个额外的数组。

[model_instance_1, model_instance_2].all? |i| i.valid? 

【讨论】:

哇,虽然没有马特的回答那么短,但这个答案是有效的,而且比 & 好得多。 为什么这个答案有反对票?这个答案有什么问题不是很明显吗? @Paul 性能和默默无闻。 Symbol#to_proc 比在旧版本的 ruby​​ 上传递块要慢得多。 (虽然 2 个元素并不是真正的问题。)它是核心库的一个相对较新的补充,利用了旧的但不常用的类型强制语法。

以上是关于如何避免短路评估的主要内容,如果未能解决你的问题,请参考以下文章

是否存在程序员可能希望避免对布尔表达式进行短路评估的合理场景? [关闭]

如何避免“更改此条件,使其不总是评估为“假””

360度评估互评如何避免恶意打分

动态代码评估:不安全的反序列化(Spring Boot 2) - 如何避免与执行器相关的强化问题,还是误报?

AngularJS自定义验证指令 - 如何避免使用隔离范围

JAX:避免对沿一个轴使用不同数量的元素评估的函数进行即时重新编译