【问题标题】:Is there a comparison Operator that returns true if both values are set and equal: false if both are nil如果两个值都设置且相等,是否有一个比较运算符返回 true:如果两者都为 nil,则返回 true
【发布时间】:2020-12-16 13:22:56
【问题描述】:

是否有一种理想的或更短的方法来“比较两个值但不允许它们为 nil”。

一种方法是:

(!left.nil? || !right.nil?) && (left == right)

这里可以省略其中一个 nil 检查,但我将其留在示例中,以更好地显示意图。

在 Ruby 核心、Kernel 或 BasicObject 上,equal?eql?== 旁边是否有与此匹配的方法?

我的目标类似于例如minitest assert_equal 确实(为简洁而编辑,第 5 行无检查):

def assert_equal exp, act, msg = nil
  msg = message(msg, E) { diff exp, act }
  result = assert exp == act, msg

  if nil == exp then
    refute_nil exp, "Use assert_nil if expecting nil."
  end

  result
end

在我的情况下,这是一个自动化系统,我必须反复防范双方都是nil。例如

class ContactPolicy < ApplicationPolicy
  def add?
    !actor.id.nil? && subject.id == actor.id
  end
end

在以各种形式重复该模式之后,我想知道是否没有更好的 Ruby 主义。我现在倾向于使用null-objects 进行重构,def ==(other) 总是返回 false。不过,这个“相等但不为零”的问题是否仍然很有趣。

【问题讨论】:

  • 如果你检查left == right,你只需要检查一个是nil,而不是两个
  • 谢谢,萨拉。我试图解释:!right.nil?这里可以省略。我会尝试在编辑中进一步澄清这一点。
  • 你的代码的intent是模糊的。您似乎正在尝试检查某些值是否既是defined? 又不是零,以避免(nil == nil) == true。无论您是否可以创建更短的表达式,我都会专注于更好地传达意图。我在下面提供了一个很好的中间立场恕我直言的答案,但我真的建议要么重构你的代码以避免首先需要这样做,要么更明确而不是争取单线。跨度>

标签: ruby comparison


【解决方案1】:

可能的方法

最佳答案将取决于为什么您认为这两个值都可能为零,以及原始问题中未显示的代码的其他方面。如果没有该上下文,这似乎主要是为了防止 nil 值引发 NameError 异常,同时还阻止您的表达式解析为 (nil == nil) == true。如果是这种情况,您可以采取以下方法之一:

  1. 拯救所有可能的异常。
  2. 明确救援 NameError。
  3. 避免异常处理,并使用条件表达式链来测试您的变量是否既已定义又非空。

处理来自未定义变量的 NameError 异常

您可能会挽救所有异常,但在一般情况下,这通常被认为是一种糟糕的方法。

# Swallow anything descended from Exception. This is 
# common in the wild, so it's idiomatic by definition,
# but it can cast too wide of a net sometimes, and may
# lead to unexpected problems.
left == right rescue nil

拯救 NameError 会更好,但由你决定程序应该如何处理异常。也许你会决定设置变量,然后retry 救出块,但当然还有其他方法。

# Rescue just NameError; other exceptions would still
# get raised when necessary.
begin
  left == right
rescue NameError => e
  # handle the exception
end

避免未定义的变量

在许多情况下,Ruby 会在给定范围内自动激活变量,但您的示例不是其中之一。在这种情况下,您可以依靠运算符优先级来确保您的值是使用 defined? 关键字定义的。

例如,假设leftright要么是undefined?,要么可以是respond_to?:nil?,那么你可以使用下面的表达式来:

  1. 确保 leftright 均已定义。如果它们都是数组字面量中定义的变量,则返回 "expression"
  2. 使用Enumerable#none?(混入Array)和Array继承的Object#nil?方法确保[left, right]中的值都不是nil
  3. 检查两个已定义的非零变量是否相等。
defined? [left, right] and
  [left, right].none? &:nil? and
  left == right
#=> nil

根据您的问题,预期当您的变量解析为nil == nil 时,整个表达式仍应返回false。 相同的条件表达式还可以防止将两个变量都设置为 nil 而不是简单地未定义。例如:

# Test with nil values.
left, right = nil, nil

defined? [left, right] and
  [left, right].none? &:nil? and
  left == right
#=> false

【讨论】:

  • 它不是针对 NameError 的保护。这是对误报的保护。例如。 Minitest 在assert_equal(nil, nil) 引发异常时为您处理此问题(将用户指向assert_nil)。就我而言,这是一个授权系统,例如,我必须反复检查!actor.id.nil? &amp;&amp; actor.id == subject.id。我只是想知道该模式是否存在 Ruby 主义。
  • @berkes 最好的办法是编写一个类似def authorized?; # insert clear, multi-line validations; end 的帮助程序来干燥您的代码并简化调用,同时使实际验证尽可能明确且易于维护。您还可以看看像 Devise 这样流行的 gem 是如何实现这个特定用例的。
  • 我确实用一些意图揭示的方法解决了这个问题,导致!anon? &amp;&amp; own? alike 检查。灵感来自权威 BTW。 (我不是 Devise 的粉丝……)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-12-25
  • 2015-07-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-31
  • 2022-12-04
相关资源
最近更新 更多