Ruby 一个很容易中招的地方

tumayun · 2012年08月24日 · 最后由 zw963 回复于 2012年09月09日 · 6061 次阅读

一个很容易中招的地方

def self.rule_exchange_anhao(tao_deal)
  find_rule_by_operation_name(operation_name) if operation_name = tao_deal_operation_name(tao_deal)
end

以上写法是错误的,同行 if 语句的内容部分引用到的变量的话,一定要在用之前就声明,不然会报错的

undefined local variable or method `operation_name' for #<Class:0x8c83d38>

应该改为多行 if 语句

def self.rule_exchange_anhao(tao_deal)
  if operation_name = tao_deal_operation_name(tao_deal)
    find_rule_by_operation_name(operation_name)
  end
end

刚才中招了

可能解析器会把后置的 if 表达式提到前面来再执行吧,不过我自己写 ruby 的代码从来就没用过后置的 if 表达式,觉得后置的更难读。

加个括号应该可以吧

@HungYuHei 哪加括号?

a = b if (b = 1.to_s)

这样?

undefined local variable or method `b' for #<Object:0x7fb6b7b242b8>

#2 楼 @HungYuHei 说的对,加个括号就 ok

find_rule_by_operation_name(operation_name) if operation_name = tao_deal_operation_name(tao_deal)​)

是不是优先级问题?

cc=ee 是赋值语句,返回永远是 true,除非 ee 的值是 nil 或 false 所以 if(cc=ee) 是错误的,ruby1.9 会有 warning: found = in conditional, should be ==

因该写成 if cc==ee .用两个等于号. 这是语言基础,也就那最基本的 10 页教程。

@zhaoguobin no! 加括号没有用的 @sevk cc=ee 是赋值语句,返回永远是 true 我觉得这句很有问题 ruby 里面返回的是赋值后的值 这里值为 nil 的话是不会执行下去的...

@sevk 我想你应该没有理解我的问题

#7 楼 @tumayun

怎么说呢?Ruby 虽然灵活,绝对不是让你这样用的。你这样的代码,绝对是未来 bug 的源泉,你要是不改,迟早还要再次中招。建议有空看看 Ruby 重构吧。

if 后面的条件最好不是赋值语句,会有

warning: found = in conditional, should be ==

@cantin 纯粹的赋值会有你说的问题,加上其他运算就不会有 warning @zw963 我觉得这样的用法没有什么问题,主要是自己的基础知识不过硬引起的,这次遇到了,我下次就会注意。谢谢你的建议,ruby 重构看了一部分。

谭老湿的风格毁人不倦啊...

if 后置一般都是做判断,很少在判断语句中写赋值。约定啊。

@yakjuly 确实,if 后置就应该只做判断

我觉得楼主想写出更酷的代码,但反而得不偿失

( operation_name = tao_deal_operation_name(tao_deal) ) && find_rule_by_operation_name(operation_name)

如果想少两句代码,这种方式是比较推荐的。符合一般理解

额,我不是在评价这种写法好不好,只是提醒一下大家。 或者我想多了。。。

@tumayun 恩,不错,学习了,大家只是好心的讨论这样的写法好坏,技术爱好者都是这样。。。

我想应该是范围域不一样

if b = 1
  a = b
end

a = b 中在 if 中,而

a = b if b = 1

在范围外,自然不能找到 b

前几天看到 Rubinius 的开发者抱怨 MRI2.0 会在后置条件里赋值的时候强制输出 warning: https://twitter.com/brixen/status/233688352552005632

至于还有这么奇怪的差异可能他还没发现,,,大概是 MRI 里对后置的 if/unless 处理有所不同。而 Rubinius 返回的结果是正常的(没有让我惊讶)。

#8 楼 @zw963 这和重构没关系啊

Ruby 這點的確不爽,不過全局的$n可以這麼用還是很給力的。

foo[$1] = $2 if 'name = value' =~ /(.*)\s*=\s*(.*)/

#20 楼 @hooopo

怎么没关系呀。任何一个阅读过重构的人,看到楼主这样写代码,不发飙才怪。

重构的基本要素就是要让代码清晰易读,楼主的代码,在条件语境中赋值,是重构强烈不推荐的。怎么能没有关系呢?

#21 楼 @shouya

这个用法很好啊。很明显,这个是条件语义,而非赋值语义。

#18 楼 @azhao

其实没必要在这个上面纠结,如果一定要表达楼主的意思,可以这样写更清晰:

b = 1 and a = b
if operation_name = tao_deal_operation_name(tao_deal)
  #do some thing
end

# 重构如下
operation_name = tao_deal_operation_name(tao_deal)
if operation_name
  #do some thing
end

if 语句中 不应该出现 一个等号。 因为, 一个等号两个等号 看起了 极度相似, 不仔细看,会造成混乱。 这是一个坏习惯。

好吧,这种代码是有点坏味道,但是大家还是应当注意一下,不要在小问题上犯错

@zw963 @ery

使用=(赋值符号)的返回作为表达式的值是可以的,但是记得在记得在两边加上括号。

# 好的 - 表明了赋值的意图
if (v = array.grep(/foo/)) ...

# 不好的
if v = array.grep(/foo/) ...

# 好的 - 不仅表明了赋值,还表明了正确的优先级
if (v = self.next_value) == "hello" ...

from:http://ruby-china.org/wiki/coding-style

#25 楼 @tumayun 不论怎样,还是要谢谢你的分享和提醒,谢谢!:)

#26 楼 @hooopo

不是不能用,只是不推荐而已。我看着还是别扭,这就好像重构里面讲,你通过方法参数传递进来一个参数,然后在方法体内,又对这个参数重新赋值一样,这样方法里的参数还是原来的那个参数吗?你可以这样用,不过你到底是要传递形参进来,还是赋值?而且对于 Ruby 这种传值而不是传引用的动态语言,会引起歧义。

有时候多一行代码更清晰一些。看似小问题,你可能在几个月后,浏览代码,在这个地方栽跟头。

该什么语义就什么语义,这绝对不会阻碍你写出优质的代码。Wiki 上只是说如果非要那么写,一定加括号,这是好的风格 (虽然不加括号一样执行正确), 但是并不代表支持那么写。

#28 楼 @zw963 In-logic assignment 是 Ruby Idiom。大家都这么用,甚至流行的 code stye 也推荐。

多一行代码更清晰一些。。。真不能理解。。。

刚才随便在 Rails 和 Rubinius 里找了几行,讨论问题还是要回到现实世界:

https://github.com/rails/rails/blob/master/activerecord/lib/active_record/sanitization.rb#L59 https://github.com/rails/rails/blob/master/activerecord/lib/active_record/reflection.rb#L298 https://github.com/rails/rails/blob/master/activerecord/lib/active_record/nested_attributes.rb#L281 https://github.com/rubinius/rubinius/blob/master/lib/un.rb#L227

#28 楼 @zw963 我认同你的观点。 这个地方,我栽过几次,不过是其他语言。 程序猿都很固执。 多一行代码更清晰。 非常有道理。 一流的代码是简单明了, 而不是玩弄语法。

一流的代码是简单明了,

实在理解不了你的简单明了....

而不是玩弄语法。

这不叫玩语法,这叫入乡随俗。就像函数式语言里用列表解析是习俗,而嵌套循环就是奇葩。

好的代码应该是 simple and abstracts repeated patterns (DRY). 我认为 In-logic assignment 做到了。同样的还有 Object.tapObject.tryObject.blank?||=(这个可以叫做 memoizable assignment partten?) 还有 Rails 里的 find_or_create方法。

这些都是为了解决实际问题而生的 Ruby Idoim。我用着很 happy,这是一个好特性。

#24 楼 @ery 我觉得这种写法比较好,更好懂。if 应该尽量用来做条件判断。

#32 楼 @hooopo

呵呵,你举的例子很好啊。不过和你之前的说法自相矛盾... 那你说他们毫无例外的都没有加括号,这是谁错了呢?

如果一定让我两者选其一,我宁可选择你介绍的代码示例,反正不推荐的用法都用了,还加那两个括号干嘛?只要了解 Ruby 运算符的结合性,就会知道,几乎所有情况下,不加括号的表达式很少出错,因为一开始 Ruby 就是为不加括号而设计的。

但是如果让我自己写,我一定会写两行,因为写在一起除了可能会让你出错,没有任何好处可言。这是个条件表达式,又不是方法,或者 block, 这样的代码,在 if 语句内赋值,甚至会给我一个错觉,是不是声明了一个条件表达式内的本地变量?? , 如果不这样写法,我根本不可能有这样的错觉。

#32 楼 @hooopo

我记得之前有篇回帖,你有介绍如何在 github 上引用一段代码,不过我没注意看。

再给我介绍下你是怎么做到的?难道手动输入或者有什么一键点击?

#22 楼 @zw963 不,其實這個涉及到賦值了,在=~內部,的確有把匹配內容賦給$1的行為。

譬如說,這麼做就不行:

foo[name] = value if 'name = value' =~ /(?<name>.*)\s*=\s*(?<value>.*)/

這個是局部變量作用域的問題因為下面的代碼正常工作:

if 'name = value' =~ /(?<name>.*)\s*=\s*(?<value>.*)/
  foo[name] = value
end

#38 楼 @shouya

我看了半天才明白你要表达的意思,这样的代码说实话,更乱了。 第一行之所以不工作,是因为先执行的 name = value 的缘故吧。如果是从右往左执行,就可以了。

#37 楼 @hooopo

stop 是啥意思?是回答我上面的那个问题吗?

#40 楼 @zw963 直接点击左边行号然后复制地址栏的地址,比如在 pentadactyl 里直接点行号然后 yy 一下就 OK 了

#41 楼 @aptx4869

知道了。谢了。

#39 楼 @zw963 呃,這個… 其實我還是沒有明白你的意思,'name = value'不是字串嗎怎麼會被執行?

#43 楼 @shouya

嗨~ 我又看了下咱俩的回帖,其实是我没明白你的意思。惭愧呀~

其实你一直在说一个方面,而我以为你一直在说另一个方面。哈哈。

我刚刚想了想,确实蛮有趣的。

foo[name] = value if 'name = value' =~ /(?<name>.*)\s*=\s*(?<value>.*)/

这个之所以无法实现,其实因为:在使用一个本地变量之前,这个本地变量必须首先被定义。而本地变量的定义是在源码解析的过程中完成的。这就和下面的代码效果类似:

if false
  x = 0
end
p x    # => x = nil

在语法解析阶段,在使用本地变量 x 之前,解释器看到了 x = 0 这个赋值语句,首先初始化 x这个变量为 nil ,虽然稍后没有具体执行 x = 0 对 x 进行赋值,但是这个 x 已经被初始化了。

上面那个正则匹配的示例,有点类似于下面这个示例:

p x if x = 0   # = > undefined local variable or method `x' for main:Object

之所以出错,是因为在源码解析阶段,运行 p x 的时候,这个 x 是不存在的。只有在运行 x = 0 之后,x 才是存在的。所以这个语句在执行阶段,看起来应该是这样的:p x if 0

而后面的有关$1,$2的那个示例,则不一样:

foo[$1] = $2 if 'name = value' =~ /(.*)\s*=\s*(.*)/

诸如 $1, $2 这些变量,在解释器一开始的时候,就已经被定义并初始化为 ni l . 然后在源码解析时,括号内的结果直接被赋值给之前定义的 $1, $2, 所以在执行时,$1, $2 就是可用的。

以上结论都是猜的,因为不懂 C, 也没办法验证,不妨请出 @skandhas , 能否单独开贴,或者在这里,给大概解释下,Ruby 解释器在解释一个 .rb 文件时,大概过程分为几个大的步骤。

BTW: @skandhas, 我发现可能我邮箱有些问题,给好些个人发邮件都没回复,我前阵子给你推荐了个 Emacs 插件,发到你 163 邮箱了,你是不是没收到?

hooopo Is In-logic assignment Bad? 提及了此话题。 07月12日 13:44
需要 登录 后方可回复, 如果你还没有账号请 注册新账号