重构 Ruby Code Smells

victor · January 14, 2014 · Last by kgtong replied at February 13, 2014 · 12075 hits
Topic has been selected as the excellent topic by the admin.

写在前面,因为是摘抄和自己混写。所以出现简体繁体参杂的情况,请大家体谅,勿喷。

强烈推荐 https://github.com/tutsplus/ruby-refactoring

1. Duplicate code 重复的代码

如果你在一个以上的地点看到相同的程序结构,那么当可肯定:设法将它们合而为一,程序会变得更好。

  • 同一个 class 内的两个函数中含有重复的代码段
  • 两个兄弟 class 的成员函数中含有重复的代码段
  • 两个毫不相关的 class 内出现重复的代码段

注意:重复的代码是多数潜在 BUG 的温床!

2. Long method 过长的方法

拥有短函数的对象会活的比较好、比较长。

  • 程序愈长就愈难理解
  • 函数过长阅读起来也不方便
  • 小函数的价值:解释能力、共享能力、选择能力

原则:每当感觉需要以注释来说明点什么的时候,我们就把需要说明的东西写进一个独立的函数中。记着,起个好名字!

3. Feature envy 依恋情节

函数对某个类的兴趣高过对自己所处类的兴趣,就会产生对这个类的依恋情节,造成紧耦合。

原则:判断哪个类拥有最多被此函数使用的数据,然后将这个函数和那些数据摆在一起。 原则:将总是变化的东西放在一块。

# BAD
class A
  attr_reader :x, :y, :z

  def initialize x, y, z
    @x = x
    @y = y
    @z = z
  end

  def some_function
    y / z
  end
end

class B
  attr_reader :a

  def initialize a
    @a = a
  end

  def my_method
    a.x + a.y * a.z ** a.some_function
  end
end

# GOOD
class A
  attr_reader :x, :y, :z

  def initialize x, y, z
    @x = x
    @y = y
    @z = z
  end

  def some_function
    y / z
  end

  def special_method
    x + y * z ** some_function
  end
end

class B
  attr_reader :a

  def initialize a
    @a = a
  end

  def my_method
    a.special_method
  end
end

4. Data clumps 数据泥团

有些数据项,喜欢成群结队地待在一块。那就把它们绑起来放在一个新的类里面。这样就可以:

  • 缩短参数列表
  • 简化函数调用
class Point
  attr_reader :x, :y, :z

  def initialize x, y, z
    @x = x
    @y = y
    @z = z
  end

  #BAD
  def some_function _x, _y, _z
    # Do something with it
  end

  #GOOD
  def some_function point
    # Do something with it
  end  
end

5. Comments 过多的注释

过多注释的代码段,往往都是因为那段代码比较糟糕,散发着一股恶臭。

原则:当你感觉需要写注释时,请尝试重构,试着让所有注释都变得多余。

6. Divergent Change 发散式变化

我们希望软件能够更容易被修改。一旦需要修改,我们希望能够跳到系统的某一点,只在该处做修改。如果不能做到这点,你就踩入了 发散式变化霰弹式修改 的坑。

发散式变化:一个类受多种变化的影响

  • 数据库新加一个字段,同时修改三个函数:Load、Insert、Update
  • 新加一个角色二进制,同时修改四处
  • ...

原则:针对某一外界变化的所有相应修改,都只应该发生在单一类中

7. Primitive Obsession 基本类型偏执

代码中有很多基本数据类型的数据。

原则:如果看到一些基本类型数据,尝试定义一种新的数据类型,符合它当前所代表的对象类型。

class Product
  attr_reader :name, :color, :price
  def initialize name, color, price
    @name = name
    @color = color
    @price = price
  end
end

# BAD
[ "Shirt", "Pink", 10 ]

# GOOD
Product.new "Shirt", "Pink", 10

我所参考的视频,仅总结了如上 7 点,我自己又从其它文章中补了下面几个

8. Large Class 过大类

如果想利用单一类做太多事情,其内往往就会出现太多的成员变量。

  • 提取完成同一任务的相关变量到一个新的类
  • 干太多事情的类,可以考虑把责任委托给其他类

注意:一个类如果拥有太多的代码,也是代码重复、混乱、死亡的绝佳滋生地点。

9. Long Parameters List 过长的参数列表

太长的参数列表难以理解,太多参数会造成前后不一致、不易使用,而且你需要更多数据时,就不得不修改它。

原则:参数不超过 3 个!

10. Shotgun Surgery 霰弹式修改

如果每遇到某种变化,你都必须在许多不同的类内做出许多小修改以响应之。如果需要修改的代码散布四处,你不但难以找到它们,也很容易忘记某个重要的修改。

霰弹式修改:一种变化引起多个类相应的修改

11. Switch Statement Switch 语句

面向对象程序的一个最明显特征就是:少用 switch 语句。从本质上说 switch 语句的问题在于重复。

原则:看到 switch 你就应该考虑使用多态来替换它。

12. Temporary Field 暂时成员变量

有时你会看到这样的对象:其内某个成员变量仅为某种特定的情形而设。这样的代码容易让人不解,因为你通常认为对象在所有时候都需要它的所有变量。

在变量未被使用的情况下猜测当初设置目的,会让你发疯。

一个对象的属性可能只在某些情况下才有意义。这样的代码将难以理解。专门建立一个对象来持有这样的孤儿属性,把只和他相关的行为移到该类。最常见的是一个特定的算法需要某些只有该算法才有用的变量。

最后

尚有如下情况未列入,欢迎大家补充。

  • Parallel Inheritance Hierarchies
  • Speculative Generality
  • Message Chain
  • Middle Man
  • Inappropriate Intimacy
  • Alternative Classes with Different Interfaces
  • Incomplete Library Class
  • Data Class

相关阅读和参考

可以看 Refactoring 这本书(Ruby 版)的第三章

#2 楼 @donghua 这本书一直摆在桌面,今天是看某视频,顺手按照视频总结了一下。

Inappropriate Intimacy 一个 class 利用了另一个 class 过多的实现细节 (One class exploits too much knowledge about the implementation of another)(你知道的太多了。。。)。 重构:Move Method,Move Field 如果真的需要把它们放到某处,Extract Class,如果真的有重叠的话。或者引入一个 Delegate 来隐藏 (hide) 实现如何实现。 翻译的,嘿嘿。

顺便补充下想关的命令和 gem,rake stats,SimpleCov,flog, saikuro. 后面两个没用过。觉得工具也非常重要,有个量化标准,而不是依据个人的喜好。

觉得 comments 有的时候该加还是要加比如 这种 ' function getPayloadBytes() { // 1 MB return Math.pow(10, 6); }'

参考资料<>

Programming well, programming in style,,, 大赞,,,

大赞啊。这种分享精神,值得称赞。

學到了。。。

学到了,大赞

一万个赞~

赞!!!!

不错,学习了。

写的真不错啊

[quote]

  1. Comments 过多的注释

过多注释的代码段,往往都是因为那段代码比较糟糕,散发着一股恶臭。

原则:当你感觉需要写注释时,请尝试重构,试着让所有注释都变得多余。

[/quote]

虽然我的开发经验尚欠,但我个人感觉这并不一定是由于代码比较糟糕,有时也可能是由于需求本身就有些模糊甚至奇怪。在这种情况下,前一个程序员可能会希望在自己离职以后,也能让接手重构或修改此段代码的人了解最关键的需求,而尽可能不需要查阅其它的文档。因此注释里写到的东西可能更多地在讲需求(或者业务逻辑)方面(即使它看起来像是在解释代码)的问题。

在这个意义上,我想这一段可以补充为:

在注释过多的情况下,如果注释更多地在阐释代码的运行时,尝试重构程序;而如果注释更多地在阐释需求相关 or 业务逻辑时(尽管有些情况下它们看起来像是在解释程序),那么尝试重构需求,尽量让需求能够更加简单无误地被表述出来。

#2 楼 @donghua 原来这本书还有 ruby 版本的!多谢 : )

很好的分享!

对于"将总是变化的东西放在一块"这一点,让我想到了一个最近常见的现象:好的代码项目中每个 git commit 里平均文件改动数量会相对比较低。因为通常一个 commit 一般只改一个功能。要是每次想改一个功能就需要改动好多个不同的文件,也许就是重构的时候了。

17 Floor has deleted
18 Floor has deleted
19 Floor has deleted
You need to Sign in before reply, if you don't have an account, please Sign up first.