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

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

无意中在 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 参数即可处理.

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

  • 避免不需要的元编程.

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

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

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

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

terrific

好贴,多谢!

支持业余时间做贡献的同学,我上次翻译到注解后就没时间继续弄了,有空的同学也帮忙翻译下。 @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, 你看看是怎么回事儿呢?

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