这种使用案例陈述的做法是不好的吗?

Is this use of case statement a bad practice?

我有一些这样的代码:

case Product.new.class # ActiveRecord instance class => Product
when Module
  'this condition will always be true'
when Product
  'i need this to be true, but first condition is always true, so it never happens'
end

在这里,when Module 始终是 true。为什么?这是意外行为吗?

这不是错误。在Ruby中,名字为Class的class是名字为Module的class的子class。因此 Class 的每个实例也是 Module.

这在此处记录,在左栏中显示 "Parent":

http://www.ruby-doc.org/core-2.2.0/Class.html

你问这是否是一种不好的做法。是的。这是一个不好的做法,因为你的 case 语句中的第一个 case 保证 运行,所以这意味着 case 语句的所有其他 case 都是无法访问的代码,永远不会 运行。无法访问的代码是一种不好的做法。最好这样编写代码:

case something
when Product
  # we know that it is a Product
when Customer
  # we know that it is a Customer
else
  # handle other cases
end

切线评论

您向我们展示了一些不起作用的代码,然后询问它是否是 "bad practice",这很奇怪。显然,如果代码在任何情况下都不起作用,那么它就是一种糟糕的做法!你需要先让你的代码工作。在它工作之后,您可以尝试考虑不同版本的代码并评估每个版本以查看它是 "bad practice" 还是 "good practice",但前提是这些不同版本确实有效。

假设您问的是如何在 Ruby 中编写一个将数字乘以 4 的方法。

这是一些不起作用的代码,所以我们甚至不会讨论它是否是错误的做法:

def foo(x)
  x * 3
end

下面是一些有效的代码,但在各种方面都是不好的做法:

def foo(x)
return x + x+ x + x - x + x*1
end

下面是一些有效的代码,是很好的做法:

def foo(x)
  x * 4
end

啊,这么简单的问题。但是是吗?

这里Product是一些class,说:

Product = Class.new

Product.new.class
  #=> Product

您的案例陈述可以简化为

case Product
when Module
  'this condition will always be true'
when Product
  'i need this to be true, so it never happens'
end

回想一下 case 语句使用方法 === 来确定哪个 object 到 return,这意味着您的 case 语句等同于

if Module === Product
  'this condition will always be true'
elsif Product === Product
  'i need this to be true, so it never happens'
end

让我们看看这两个逻辑表达式是如何求值的:

Module  === Product  #=> true 
Product === Product  #=> false 

注意这是

的语法糖
Module.===(Product)  #=> true
Product.===(Product) #=> false

检查方法 Module#=== 的文档以了解其工作原理:它 returns true 如果 ProductModule 的实例或Module 的后代之一。嗯,是吗?

Product.class   #=> Class 
Class.ancestors #=> [Class, Module, Object, Kernel, BasicObject] 

是!现在呢:

Product === Product

Product有方法===吗?:

Product.methods.include?(:===)
  #=> true

它是从哪里来的(毕竟我们没有定义它)?先来看看:

Product.ancestors
  #=> [Product, Object, Kernel, BasicObject]

Object有方法===吗?检查文档我们发现它确实如此:Object#===.1

因此 Object#=== 被调用。正确的?让我们确认一下:

Product.method(:===).owner
  #=> Module

糟糕!它来自 Module(既是模块又是 class),而不是来自 Object。正如我们在上面看到的,ProductClass 的实例,而 ClassModule 的子 class。另请注意,此处 ===Class(和 Module2:

的实例方法
Class.instance_method(:===).owner
  #=> Module

于是Product进退两难。它应该使用 Module#===,一个由 parent (Class) 提供的实例方法,它继承自 Module,还是应该使用 Object#===,即它继承自它的superclass, Object?答案是前者优先

这是 Ruby 的 "object model" 的核心。我不会再多说了,但我希望我已经为读者提供了一些工具,他们可以使用这些工具来弄清楚发生了什么(例如,Object#method and Method#owner.

由于 Product === ProductModule == Product 一样使用 Module#===,我们通过回答问题来确定前者 return 是否为 true," 是 Product ProductProduct 的后代之一的实例?”。产品没有后代并且

Product.class #=> Class,

所以答案是"no",意思是Product === Product returns false.

编辑: 我发现我忘了实际回答标题中提出的问题。我想这需要一个意见(SO no-no),但我认为 case 陈述是自切片面包以来最伟大的事情。当需要使用 ===== 将各种值与参考值(例如,变量的内容或通过方法 return 编辑的值)进行比较时,它们特别有用。例如(参见 Fixnum#===, where === is equivalent to ==--note the typo in the docs, Regexp#=== and Range#===):

str =
case x
when 1                   then 'cat'
when 2,3                 then 'dog'
when (5..Float#INFINITY) then 'cow'
else                          'pig'
end

result =
case obj
when String
  ...
when Array
  ...
end

case str
when /\d/
  ...
when /[a-z]/
  ...
end

然而,除此之外,我经常使用 case statement 代替 if..elsif..else..end,因为我认为它更整洁、更美观:

case
when time == 5pm
  feed the dog
when day == Saturday
  mow the lawn
...
end

1 其实这个方法是所有object都可以用的,只是不是一般 被调用是因为该方法也是为后代定义的。

2 为了彻底混淆,Class 也有一个 triple-equals class 方法: Class.method(:===).owner #=> Module.