Ruby Is In-logic assignment Bad?

hooopo for Shopper+ · 2012年08月30日 · 最后由 hooopo 回复于 2012年10月13日 · 3258 次阅读

这篇 [帖子][1] 里楼主提到如下写法:

 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

引来了关于 [In-logic assignment][2] 的讨论。反对观点如下:

  1. if 语句中 不应该出现 一个等号。因为, 一个等号 和 两个等号 看起了 极度相似,不仔细看,会造成混乱。
  2. 这样的代码, 绝对是未来bug的源泉, 你要是不改, 迟早还要再次中招. 建议有空看看Ruby重构吧.任何一个阅读过重构的人, 看到楼主这样写代码, 不发飙才怪

于是我找来 Ruby 社区最流行的一份 [Code style][3] 和 Rails 和 Rubinius 源码里面的片段来说明,即使看过重构,懂 [SRP][4] 的大牛们也是这样写代码的,Rails 团队里对代码有洁癖的人不少,他们不但没发飙而且自己还这样写。。。

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

所以我的结论是未来bug的源泉任何一个阅读过重构的人, 看到楼主这样写代码, 不发飙才怪都是假想出来的,带着这种情绪去讨论没有意义。

我的观点是:In-logic assignment is simple and abstracts repeated patterns (DRY). 经常写代码的同学应该都熟悉类似流程: 如果对象存在,然后对該对象进行处理;如果对象不存在返回 nil/抛出异常/新建对象。 In-logic assignment 就是对上面的模式的抽象。

类似的抽象还有Object.tapObject.try||= ,至于你对这些方法的使用场景没了解就使用,出现了错误这是你自己的问题。 就像我们经常写a ||= b,而不是写:

if a == nil || a == false
  a = b
end

经常写a.tap{|obj|do_some_thing_with(obj)},而不是去重复下面的代码:

a = xx
do_some_thing_with(a)
return a

经常写

if tag = Tag.find_or_create_by_name(name)
  tag.increment_counter(:visits_count)
end

而不是:

tag = Tag.find_by_name(name)
if tag
  tag.increment_counter(:visits_count)
else
  tag = Tag.create(:name => name)
  tag.increment_counter(:visits_count)
end

[1]: http://ruby-china.org/topics/5141 [2]: http://www.quirkey.com/blog/2009/02/13/ruby-idiom-in-logic-assignment/ [3]: https://github.com/bbatsov/ruby-style-guide [4]: http://en.wikipedia.org/wiki/Single_responsibility_principle

这些写法正是 Ruby 的黑道切口,不会用说明没有入行啊。:)

不过,那个 find_or_create_by_name,我更喜欢这样写:

Tag.where(:name => name).first_or_create.increment_counter(:visits_count)

肿么有人觉得 ln-logic assignment 这种写法不好的!

if a = Model.find
end

这个写法我也是从 Rails 的源代码里面学的,一直觉得挺好的

应该加到小贴士中。

好帖要顶啊!

ruby 的哲学的确是相当独特的, 我们总不至于希望自己一直在写着符合 ruby 语法的 java 吧

做企业级编程的人不认可 Ruby 的很大一部分原因就是,语法太灵活,写法不统一。

太灵活的东西就会让他们觉得控制不了,一控制不了就去用 Java 了。

尘归尘,土归土。爱写什么就写什么吧。. 选择自己喜欢的写。

如果整个世界只有楷书,还怎么彰显书法家的个性..

代码风格,仁者见仁。但是大家普遍追求的是写一种简略,易懂的代码。这也是为什么很多人喜欢 DSL。因为写起来很直观。尽管后面的实现比较复杂。

是不是这样写 assignment 是个人喜好,但这个代码确实是 bug 的源泉

粗略看下,这样的代码是说在 operation_name 为 nil 的时候就不去做 find_rule_operation_name; 但这个逻辑不应该在 find_rule_by_operation_name 的外面,否则在所有调用 find_by_operation_name 的时候都要这样防守一下。正确的应该在方法里面第一句就判断短路,这样原来代码里的条件就不需要了

def find_rule_by_operation_name(operation_name) return unless operation_name ....... end

在深想下,写这个代码的原因还是不好的 object abstraction.

源代码的逻辑是,通过 deal 找 operation, 如果有 operation 再通过 operation 找 rule

看上下文这个 method 应该是在一个类似 Rule 的 class 上面,也就是说有

Rule.tao_deal_operation_name(tao_deal)

这样写就很明显了,用 deal 来找 operation 跟 Rule 是不想关的,不应该在 Rule 上面。可以重构到 deal 上面

还有,直接在 Rule class 上的方法名不需要重复 rule.. 根据上下文跟方法们重新命名重构后是这样的:

class Rule
  def self.find_by_deal(tao_deal) 
    find_by_operation_name(tao_deal.operation)
  end

  def self.find_by_operation_name(operation_name)
    return unless operation_name
    ......
  end
end



@knwang 这个代码是有历史原因的,我的那篇帖子只是想抛出一个问题,引起大家的注意,以后就不会栽在这个上面了,但是似乎大家都不这么想,非要评价并且说服他人,说这样不好,那样不妙,这个我觉得似乎歪楼了

而且在帖子里我也说了 我觉得这样的用法没有什么问题,主要是自己的基础知识不过硬引起的,这次遇到了,我下次就会注意。 那大家都觉得有点问题,好吧,那我也只有说 好吧,这种代码是有点坏味道,但是大家还是应当注意一下,不要在小问题上犯错 大多程序员都有点小固执,讨论着就会有点小火气,大家消消火继续讨论就是了, 只是大家不要歪楼

你太敏感了 技术论坛说代码不说人

@knwang 呃,我想你误会了,我说的是那篇帖子http://ruby-china.org/topics/5141

13 楼 已删除

Linux C 也是这个风格 不过感觉 if str = get_some_string_or_nil; do_sth; end; 不如 str = get_some_string_or_nil; if str.blank? do_sth end 表达能力强

发现 coffescript 编译出来的 js 也是这样的风格:

fetchHistory = function(state) {
  var page;
  cacheCurrentPage();
  if (page = pageCache[state.position]) {
    changePage(page.title, page.body);
    recallScrollPosition(page);
    return triggerEvent('page:restore');
  } else {
    return fetchReplacement(document.location.href);
  }
};

但是由于 js 奇怪的作用域问题,在赋值之前要进行声明var page;写起来就比较罗嗦。 注:不能用if (var page = pageCache[state.position]),因为var xx = xxx永远返回undefined;

需要 登录 后方可回复, 如果你还没有账号请 注册新账号