【问题标题】:Is this use of case statement a bad practice?这种使用 case 语句是一种不好的做法吗?
【发布时间】:2015-01-30 23:54:07
【问题描述】:

我有一些这样的代码:

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。为什么?这是意外行为吗?

【问题讨论】:

  • 什么是something
  • 有什么可以respond_to?(:class)
  • 如果唯一的限制是对象必须响应class,那么您的断言不正确。我可以轻松地创建一个定义了自己的class 方法的对象,并且从该方法中我可以返回任何我想要的东西。它不必是ClassModule,它可以是任意Ruby 对象。我认为如果人们问作用域是什么,那么您应该说您只是在谈论不覆盖 class 方法的对象,因此他们正在使用 Kernel#class

标签: ruby ruby-on-rails-4.1


【解决方案1】:

啊,这样一个看似简单的问题。但真的吗?

这里Product 是某个类,比如:

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 语句使用方法=== 来确定要返回哪个对象,这意味着您的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#=== 的工作原理:如果 ProductModuleModule 的后代之一的实例,则返回 true。嗯,是吗?

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(它既是一个模块又是一个类),而不是来自Object。正如我们在上面看到的,ProductClass 的一个实例,ClassModule 的一个子类。另请注意,这里===Class(和Module)的实例方法2

Class.instance_method(:===).owner
  #=> Module

所以Product 陷入了困境。它应该使用Module#===,它的父类(Class) 提供的实例方法,它从Module 继承,还是应该使用Object#===,它从它的超类Object 继承?答案是前者优先。

这是 Ruby 的“对象模型”的核心。我将不再多说,但我希望我已经为读者提供了一些工具,他们可以使用它们来弄清楚发生了什么(例如,Object#methodMethod#owner

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

Product.class #=> Class,

所以答案是“否”,意思是Product === Product 返回false

编辑:我发现我忘了实际回答标题中提出的问题。我认为这需要一个意见(所以不行),但我认为case 声明是自切片面包以来最伟大的事情。当需要使用===== 将各种值与参考值(例如,变量的内容或方法返回的值)进行比较时,它们特别有用。例如(参见Fixnum#===,其中=== 等价于==——请注意文档中的错字Regexp#===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 其实这个方法对所有对象都可用,但不是一般的 调用,因为该方法也是为后代定义的。

2 为了彻底混淆,Class 还有一个三等号类方法: Class.method(:===).owner #=> Module.

【讨论】:

  • @BorisStitnicky,我想起了this,大约 2:30。
【解决方案2】:

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

这被记录在这里,在它说“父母”的左栏中:

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

切线注释

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

假设您改为询问如何在 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

【讨论】:

  • 那么,为什么"".is_a?(Module) 会是假的?
  • "" 不是一个类,它是一个空字符串。 "".classClass,也是Module
  • 但如果我需要将我的条件类与更具体的东西进行比较。就像 AR Product.new.class (#=>Product) 只有当 == Product 时它才必须是真的,不是吗?
  • @okliv 我不明白你现在在问什么。我认为我已经充分回答了您的原始问题,因此如果您可以将其标记为已接受的答案,那就太好了。如果你想发布一个关于 ActiveRecord 的新的、单独的问题,那很好。请务必准确说明您的情况以及您要完成的工作。
  • 我在胡乱猜测,但也许您正在寻找的代码就是这样的:gist.github.com/DavidEGrayson/a86a83dcc72d57e8d57e
猜你喜欢
  • 2020-04-23
  • 1970-01-01
  • 2017-03-31
  • 1970-01-01
  • 2015-01-12
  • 1970-01-01
  • 1970-01-01
  • 2018-10-03
  • 1970-01-01
相关资源
最近更新 更多