分享 Ruby 编程风格介绍 (补全篇)

zw963 · 2012年03月15日 · 最后由 zw963 回复于 2012年03月21日 · 7876 次阅读

无意中在 ruby-china 的 wiki 看到了这篇有关 Ruby 编码风格的文章. http://ruby-china.org/wiki/coding-style 由于本人平常就很在意这些,大部分想法,都和我的 Ruby 笔记中的个人惯例部分 惊人的相似。看来我的代码风格,编码习惯应该不错。哈哈。

我见后半部分还没有翻译,我就补充上了。欢迎指正。

我就从Annotations这个部分开始往下写了。前面的内容看原帖。


代码注解

  • 代码的注解应该总是写在被注释代码的上面,并且紧贴被注释代码。

  • 注解的标题应该紧跟一个冒号以及一个空格,用来突出显示该注释描述的内容.

  • 如果需要多行注释,第二行注释应该在#之后缩进两个空格。(译者注:以上两条规则在 Ruby 源码中都不多看到,前者在 Lisp 源码较多见,而后者从没见过)

def bar
  # FIXME: This has crashed occasionally since v3.2.1. It may
  #   be related to the BarBazUtil upgrade.
  baz(:quux)
end





  • 如果代码很直白,添加注解就显得多余,也可以在代码所在行的尾部提供简短的注解说明。不过这应该在很少的情况下使用,并且不被提倡。
def bar
  sleep 100 # OPTIMIZE
end





  • 使用TODO标题描述 漏掉的功能或打算加入的新特性

  • 使用FIXME标题描述 需要被修复的有问题代码

  • 使用OPTIMIZE标题描述 可能有性能瓶颈,需要优化的代码。

  • 使用HACK标题描述 感觉上需要重构的代码

  • 使用REVIEW标题描述 关键性代码,需要稍后不断的检查该代码是否工作正确。

  • 只要对阅读代码有帮助,也可以使用其他直白的注解标题,但记得在 README 中注明。


类相关

  • 当设计一个类时,务必记住LSP原则。(译者注:LSP 原则大概含义为:如果一个函数中引用了`父类的实例', 则一定可以使用其子类的实例替代,并且函数的基本功能不变。(虽然功能允许被扩展)

  • 尽量使你的类更加健壮,稳固。

  • 为你自己的类定义 to_s 方法,用来表现这个类实例对象的字符化表现形式。

class Person
  attr_reader :first_name, :last_name

  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end

  def to_s
    "#@first_name #@last_name"
  end
end





  • 尽量使用 attr 来定义属性访问器或修改器方法。
# bad
class Person
  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end

  def first_name
    @first_name
  end

  def last_name
    @last_name
  end
end

# good
class Person
  attr_reader :first_name, :last_name

  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end
end





  • 考虑添加工厂方法,用以灵活的创建一个特定类的实例。
class Person
  def self.create(options_hash)
    # body omitted
  end
end






  • Ruby 的基本价值观之一:duck-typing 优先于继承。
# bad
class Animal
  # abstract method
  def speak
  end
end

# extend superclass
class Duck < Animal
  def speak
    puts 'Quack! Quack'
  end
end

# extend superclass
class Dog < Animal
  def speak
    puts 'Bau! Bau!'
  end
end

# good
class Duck
  def speak
    puts 'Quack! Quack'
  end
end

class Dog
  def speak
    puts 'Bau! Bau!'
  end
end






  • 应该总是避免使用类变量。
class Parent
  @@class_var = 'parent'

  def self.print_class_var
    puts @@class_var
  end
end

class Child < Parent
  @@class_var = 'child'
end

Parent.print_class_var # => will print "child"






正如上例看到的,所有的类实例共享类变量,并且可以直接修改类变量,此时使用类实例变量是更好的主意。

  • 总是为类的实例方法定义适当的可见性.(private, protected, private) 不应该总是使用 public (默认可见性为 public), 这不是 Python!

  • 可见性关键字应该和方法定义有相同的缩进,并且不同的关键字之间要空行分隔。

class SomeClass
  def public_method
    # ...
  end

  private
  def private_method
    # ...
  end
end






  • 总是使用 self 来定义单例方法。当代码重构时,这将使得方法定义代码更加具有灵活性。
class TestClass
  # bad
  def TestClass.some_method
    # body omitted
  end

  # good
  def self.some_other_method
    # body omitted
  end

  # Also possible and convenient when you
  # have to define many singleton methods.
  class << self
    def first_method
      # body omitted
    end

    def second_method_etc
      # body omitted
    end
  end
end



异常处理

  • 尽量不要抑制异常被正常抛出。
begin
  # an exception occurs here
rescue SomeError
  # the rescue clause does absolutely nothing
end

  • 不要使用异常来代替流程控制语句。
# bad
begin
  n / d
rescue ZeroDivisionError
  puts "Cannot divide by 0!"
end

# good
if n.zero?
  puts "Cannot divide by 0!"
else
  n / d


  • 应该总是避免拦截最顶级的 Exception 异常类。
# bad 
begin
  # an exception occurs here
rescue
  # exception handling
end

# still bad
begin
  # an exception occurs here
rescue Exception
  # exception handling
end






  • 将更具体 (或特殊的) 的异常处理代码放在通用的异常处理代码之前. 否则,这些异常处理代码永远不会被处理。
# bad
begin
  # some code
rescue Exception => e
  # some handling
rescue StandardError => e
  # some handling
end

# good
begin
  # some code
rescue StandardError => e
  # some handling
rescue Exception => e
  # some handling
end






  • 使用 ensure 语句,来确保总是执行一些特地的操作。
f = File.open("testfile")
begin
  # .. process
rescue
  # .. handle error
ensure
  f.close unless f.nil?
end






  • 除非必要,尽可能使用 Ruby 现有的异常类。(而不是总派生自己的异常类)

集合

  • 总是使用%w的方式来定义字符串数组。(译者注:w 表示英文单词 word, 而且定义之间千万不能有逗号)
# bad
STATES = ['draft', 'open', 'closed']

# good
STATES = %w(draft open closed)






  • 避免直接引用靠后的数组元素,这样隐式的之前的元素都被赋值为 nil.
arr = []
arr[100] = 1 # now you have an array with lots of nils






  • 如果要确保元素唯一,则使用 Set 代替 Array.Set 更适合于无顺序的,并且元素唯一的集合. 集合具有类似于数组一致性操作以及哈希的快速查找。

  • 尽可能使用 hash 代替字符串作为哈希键。

# bad
hash = { 'one' => 1, 'two' => 2, 'three' => 3 }

# good
hash = { one: 1, two: 2, three: 3 }






  • 避免使用易变对象作为哈希键。

  • 应该尽可能的使用 Ruby1.9 的新哈希语法。

# bad
hash = { :one => 1, :two => 2, :three => 3 }

# good
hash = { one: 1, two: 2, three: 3 }






  • 记住,在 Ruby1.9 中,哈希的表现不再是无序的。(译者注:Ruby1.9 将会记住元素插入的序列)

  • 当遍历一个集合的同时,不要修改这个集合。


字符串

  • 优先使用字符串插值来代替字符串串联.
# bad
email_with_name = user.name + ' <' + user.email + '>'

# good
email_with_name = "#{user.name} <#{user.email}>"






  • 当不需要使用字符串插值或某些特殊字符时,应该优先使用单引号。
# bad
name = "Bozhidar"

# good
name = 'Bozhidar'






  • 当使用字符串插值替换实例变量时,应该省略{}.
class Person
  attr_reader :first_name, :last_name

  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end

  # bad
  def to_s
    "#{@first_name} #{@last_name}"
  end

  # good
  def to_s
    "#@first_name #@last_name"
  end
end






  • 操作较大的字符串时,避免使用+, 如果需要修改被操作字符串,应该总是使用<<作为代替。
# good and also fast
html = ''
html << '<h1>Page title</h1>'

paragraphs.each do |paragraph|
  html << "<p>#{paragraph}</p>"
end







正则表达式

  • 如果只是需要查找普通字符串,不要使用 RE. 例如:string['text'] (译者注:示例什么意思?)

  • 针对简单的结构,你可以直接使用 string[/RE/] 的方式来查询。(译者注:string[] 难道是新添加的语法?)

match = string[/regexp/]             # get content of matched regexp
first_group = string[/text(grp)/, 1] # get content of captured group
string[/text (grp)/, 1] = 'replace'  # string => 'text replace'






  • 当无需引用分组内容时,应该使用 (?:RE) 代替 (RE). (会提高性能)
/(first|second)/   # bad
/(?:first|second)/ # good






  • 避免使用$1-$9风格的分组引用,而应该使用 1.9 新增的命名分组来代替。
# bad
/(regexp)/ =~ string
...
process $1

# good
/(?<meaningful_var>regexp)/ =~ string
...
process meaningful_var






  • 有关 RE 集合 [...], 他们只有以下几个特殊关键字值得注意:^, -, \, ] 所以,不要在集合中,转义.或者[, 他们是正常字符。

  • 注意,^$, 他们匹配行首和行尾,而不是一个字符串的结尾. 如果你想匹配整个字符串,用\A和\E. (译者注,A 和 Z 分别为英文的第一个和最后一个字符)

string = "some injection\nusername"
string[/^username$/]   # matches
string[/\Ausername\Z/] # don't match






  • 使用 x 修饰符来匹配复杂的表达式,这将使得 RE 更具可读性,你可以添加一些有用的注释. 注意,所有空格将被忽略。
regexp = %r{
  start         # some text
  \s            # white space char
  (group)       # first group
  (?:alt1|alt2) # some alternation
  end
}x






  • gusb 和 sub 也支持哈希以及代码块形式语法,可用于复杂情形下的替换操作. * * * * *

百分号

  • 应该大量的使用%w.

  • 应该使用%() 的方式,来定义需要字符串插值以及包含"符号的单行字符串. 多行字符串,尽量使用 here doc 格式。(译者注:我好喜欢%() 的方式,可能是%() 比%{}写起来方便的缘故)

# bad (no interpolation needed)
%(<div class="text">Some text</div>)
# should be '<div class="text">Some text</div>'

# bad (no double-quotes)
%(This is #{quality} style)
# should be "This is #{quality} style"

# bad (multiple lines)
%(<div>\n<span class="big">#{exclamation}</span>\n</div>)
# should be a heredoc.

# good (requires interpolation, has quotes, single line)
%(<tr><td class="name">#{name}</td>)






  • 使用%r的方式定义包含多个/符号的正则表达式。
# bad
%r(\s+)

# still bad
%r(^/(.*)$)
# should be /^\/(.*)$/

# good
%r(^/blog/2011/(.*)$)






  • 尽量避免%q, %Q, %x, %s, 和%W.

  • 优先使用 () 作为%类语法格式的分隔符. (译者注,本人很喜欢%(...), 不过 Programming Ruby 中,显然更喜欢使用%{}的方式)


元编程

  • 在写自己的库时,不要进行不必要的元编程 (例如修改核心库,不需要给他们猴子补丁). * * * * *

杂项

  • 总是打开 Ruby -w 开关。应该写没有警告提示的代码。

  • 通常情况下,尽量避免使用哈希作为方法参数。(此时应该考虑这个方法是不是功能太多?)

  • 避免一个方法内容超过 10 行代码,理想情况下,大多数方法内容应该少于 5 行.(不算空行)

  • 尽量避免方法的参数超过三个。

  • 有时候,必须用到全局方法,应该增加这些方法到 Kernel 模块。

  • 尽可能使用类实例变量代替全局变量。(译者注:是类实例变量, 而不是类的实例变量. 汗~~)

#bad
$foo_bar = 1

#good
class Foo
  class << self
    attr_accessor :bar
  end
end

Foo.bar = 1






  • 尽可能的使用 alias_method 代替 alias.

  • 使用OptionParser来解析复杂的命令行选项,较简单的命令行,-s 参数即可处理。

  • 按照功能来编写方法,当方法名有意义时,应该避免方法功能被唐突的改变。

  • 避免不需要的元编程。

  • 除非必要,避免更改已经定义的方法的参数。

  • 避免超过三级的代码块嵌套。

  • 应该持续性的遵守以上指导方针。

  • 尽量使用 (生活中的) 常识。(译者注:这应该是编程的最高境界?)

好贴,多谢!

支持业余时间做贡献的同学,我上次翻译到注解后就没时间继续弄了,有空的同学也帮忙翻译下。 @wx1452 把 Wiki 也修改下吧,这里的排版效果不是很好。

直接更新到 wiki 吧

@willmouse 呵,搞错了,不是我

#3 楼 @willmouse

markdown 的排版,我一直一头雾水,不得要领, 下午又研究了半天,貌似现在是不是好点了?

为啥不推荐用 %q 呢?不明白,多行的时候 %q 多好,哈哈

/?xx/ 看到这种写法想到 python,呵呵

#7 楼 @vkill

原文是指:尽量用通用的的形式,例如:"", '', 即使用,也直接用%替换%q.

#6 楼 @zw963 个别

  • 元素好像还是有些问题,我看
  • 里面还多了一个

    元素,导致小点和文字不在一行了,不知道是不是前台 markdown 的问题,不过 Wiki 里面的是没问题的。 可以直接将你翻译的部分的 markdown 语法复制到 Wiki 中的相应部分。

  • markdown 不如富文本直接

    有关这篇翻译,已经更新到 Wiki.

    (我之前竟然不知道注册用户直接就有编辑和创建 Wiki 权限,汗~~)

    不过目录超级链接搞不定。而且之前非我翻译的部分的页内跳转在 Chrome 下面一律无效. @huacnlee, 你看看是怎么回事儿呢?

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