语法


  • 使用 :: 引用常量(包括类与模块)与构造器(比如 Array()Nokogiri::HTML())。不要使用 :: 调用常规方法。
    [link]

    1. # 差
    2. SomeClass::some_method
    3. some_object::some_method
    4. # 好
    5. SomeClass.some_method
    6. some_object.some_method
    7. SomeModule::SomeClass::SOME_CONST
    8. SomeModule::SomeClass()

  • 使用 def 定义方法时,如果有参数则使用括号,如果无参数则省略括号。
    [link]

    1. # 差
    2. def some_method()
    3. # 省略主体
    4. end
    5. # 好
    6. def some_method
    7. # 省略主体
    8. end
    9. # 差
    10. def some_method_with_parameters param1, param2
    11. # 省略主体
    12. end
    13. # 好
    14. def some_method_with_parameters(param1, param2)
    15. # 省略主体
    16. end

  • 方法调用应当使用括号包裹参数,尤其是第一个参数以 ( 开头时,比如 f((3 + 2) + 1)
    [link]

    1. x = Math.sin y # 差
    2. x = Math.sin(y) # 好
    3. array.delete e # 差
    4. array.delete(e) # 好
    5. temperance = Person.new 'Temperance', 30 # 差
    6. temperance = Person.new('Temperance', 30) # 好

    但在下述情况下可以省略括号:

    • 无参调用

      1. # 差
      2. Kernel.exit!()
      3. 2.even?()
      4. fork()
      5. 'test'.upcase()
      6. # 好
      7. Kernel.exit!
      8. 2.even?
      9. fork
      10. 'test'.upcase
    • 内部 DSL 的组成部分(比如 Rake、Rails、RSpec)

      1. validates(:name, presence: true) # 差
      2. validates :name, presence: true # 好
    • 具有“关键字”特性的方法

      1. class Person
      2. attr_reader(:name, :age) # 差
      3. attr_reader :name, :age # 好
      4. # 省略主体
      5. end
      6. puts(temperance.age) # 差
      7. puts temperance.age # 好

  • 定义可选参数时,将可选参数放置在参数列表尾部。如果可选参数出现在列表头部,则此方法在调用时可能会产生预期之外的结果。
    [link]

    1. # 差
    2. def some_method(a = 1, b = 2, c, d)
    3. puts "#{a}, #{b}, #{c}, #{d}"
    4. end
    5. some_method('w', 'x') # => '1, 2, w, x'
    6. some_method('w', 'x', 'y') # => 'w, 2, x, y'
    7. some_method('w', 'x', 'y', 'z') # => 'w, x, y, z'
    8. # 好
    9. def some_method(c, d, a = 1, b = 2)
    10. puts "#{a}, #{b}, #{c}, #{d}"
    11. end
    12. some_method('w', 'x') # => '1, 2, w, x'
    13. some_method('w', 'x', 'y') # => 'y, 2, w, x'
    14. some_method('w', 'x', 'y', 'z') # => 'y, z, w, x'

  • 定义变量时,避免并行赋值。但当右值为方法调用返回值,或是与 * 操作符配合使用,或是交换两个变量的值,并行赋值也是可以接受的。并行赋值的可读性通常不如分开赋值。
    [link]

    1. # 差
    2. a, b, c, d = 'foo', 'bar', 'baz', 'foobar'
    3. # 好
    4. a = 'foo'
    5. b = 'bar'
    6. c = 'baz'
    7. d = 'foobar'
    8. # 好 - 交换两个变量的值
    9. a = 'foo'
    10. b = 'bar'
    11. a, b = b, a
    12. puts a # => 'bar'
    13. puts b # => 'foo'
    14. # 好 - 右值为方法调用返回值
    15. def multi_return
    16. [1, 2]
    17. end
    18. first, second = multi_return
    19. # 好 - 与 * 操作符配合使用
    20. first, *list = [1, 2, 3, 4] # first => 1, list => [2, 3, 4]
    21. hello_array = *'Hello' # => ["Hello"]
    22. a = *(1..3) # => [1, 2, 3]

  • 除非必要,否则避免在并行赋值时使用单字符的 _ 变量。优先考虑前缀形式的下划线变量,而不是直接使用 _,因为前者可以提供一定的语义信息。但当赋值语句左侧出现带 * 操作符的变量时,使用 _ 也是可以接受的。
    [link]

    1. foo = 'one,two,three,four,five'
    2. # 差 - 可有可无,且无任何有用信息
    3. first, second, _ = foo.split(',')
    4. first, _, _ = foo.split(',')
    5. first, *_ = foo.split(',')
    6. # 好
    7. a, = foo.split(',')
    8. a, b, = foo.split(',')
    9. # 好 - 可有可无,但提供了额外信息
    10. first, _second = foo.split(',')
    11. first, _second, = foo.split(',')
    12. first, *_ending = foo.split(',')
    13. # 好 - 占位符,_ 担当最后一个元素
    14. *beginning, _ = foo.split(',')
    15. *beginning, something, _ = foo.split(',')

  • 永远不要使用 for, 除非你很清楚为什么。大部分情况下,你应该使用迭代器。for 是由 each 实现的,所以你绕弯了。另外,for 没有引入一个新的作用域 (each 有),因此在它内部定义的变量在外部仍是可见的。
    [link]

    1. arr = [1, 2, 3]
    2. # 差
    3. for elem in arr do
    4. puts elem
    5. end
    6. # 注意,elem 可在 for 循环外部被访问
    7. elem # => 3
    8. # 好
    9. arr.each { |elem| puts elem }
    10. # 注意,elem 不可在 each 块外部被访问
    11. elem # => NameError: undefined local variable or method `elem'

  • 永远不要在多行 if/unless 中使用 then
    [link]

    1. # 差
    2. if some_condition then
    3. # 省略主体
    4. end
    5. # 好
    6. if some_condition
    7. # 省略主体
    8. end

  • 在多行 if/unless 中,总是把条件表达式与 if/unless 放置在同一行。
    [link]

    1. # 差
    2. if
    3. some_condition
    4. do_something
    5. do_something_else
    6. end
    7. # 好
    8. if some_condition
    9. do_something
    10. do_something_else
    11. end

  • 倾向使用三元操作符(?:)而不是 if/then/else/end 结构。前者更为常见且简练。
    [link]

    1. # 差
    2. result = if some_condition then something else something_else end
    3. # 好
    4. result = some_condition ? something : something_else

  • 三元操作符的每个分支只写一个表达式。即不要嵌套三元操作符。嵌套情况请使用 if/else 结构。
    [link]

    1. # 差
    2. some_condition ? (nested_condition ? nested_something : nested_something_else) : something_else
    3. # 好
    4. if some_condition
    5. nested_condition ? nested_something : nested_something_else
    6. else
    7. something_else
    8. end

  • 永远不要使用 if x; ...。使用三元操作符来替代。
    [link]

    1. # 差
    2. result = if some_condition; something else something_else end
    3. # 好
    4. result = some_condition ? something : something_else

  • 利用“ifcase 是表达式”的这个特性。
    [link]

    1. # 差
    2. if condition
    3. result = x
    4. else
    5. result = y
    6. end
    7. # 好
    8. result =
    9. if condition
    10. x
    11. else
    12. y
    13. end

  • case 表达式中,单行情况使用 when x then ... 语法。另一种语法 when x: ... 在 Ruby 1.9 之后被移除了。
    [link]


  • 不要使用 when x; ... 语法。参考前一条规则。
    [link]


  • 使用 ! 而不是 not
    [link]

    1. # 差 - 因为操作符的优先级,这里必须使用括号
    2. x = (not something)
    3. # 好
    4. x = !something

  • 避免使用 !!
    [link]

    !! 会将表达式结果转换为布尔值,但对于流程控制的表达式通常并不需要如此显式的转换过程。如果需要做 nil 检查,那么调用对象的 nil? 方法。

    1. # 差
    2. x = 'test'
    3. # 令人费解的 nil 检查
    4. if !!x
    5. # 省略主体
    6. end
    7. # 好
    8. x = 'test'
    9. if x
    10. # 省略主体
    11. end

  • 永远不要使用 andor 关键字。使用 &&|| 来替代。
    [link]

    1. # 差
    2. # 布尔表达式
    3. ok = got_needed_arguments and arguments_are_valid
    4. # 流程控制
    5. document.save or fail(RuntimeError, "Failed to save document!")
    6. # 好
    7. # 布尔表达式
    8. ok = got_needed_arguments && arguments_are_valid
    9. # 流程控制
    10. fail(RuntimeError, "Failed to save document!") unless document.save
    11. # 流程控制
    12. document.save || fail(RuntimeError, "Failed to save document!")

  • 避免使用多行三元操作符(?:)。使用 if/unless 来替代。
    [link]


  • 对于单行主体,倾向使用 if/unless 修饰语法。另一种方法是使用流程控制 &&/||
    [link]

    1. # 差
    2. if some_condition
    3. do_something
    4. end
    5. # 好
    6. do_something if some_condition
    7. # 好 - 使用流程控制
    8. some_condition && do_something

  • 避免在多行区块后使用 if/unless 修饰语法。
    [link]

    1. # 差
    2. 10.times do
    3. # 省略多行主体
    4. end if some_condition
    5. # 好
    6. if some_condition
    7. 10.times do
    8. # 省略多行主体
    9. end
    10. end

  • 避免使用嵌套 if/unless/while/until 修饰语法。适当情况下,使用 &&/|| 来替代。
    [link]

    1. # 差
    2. do_something if other_condition if some_condition
    3. # 好
    4. do_something if some_condition && other_condition

  • 对于否定条件,倾向使用 unless 而不是 if(或是使用流程控制 ||)。
    [link]

    1. # 差
    2. do_something if !some_condition
    3. # 差
    4. do_something if not some_condition
    5. # 好
    6. do_something unless some_condition
    7. # 好
    8. some_condition || do_something

  • 不要使用 unlesselse 的组合。将它们改写成肯定条件。
    [link]

    1. # 差
    2. unless success?
    3. puts 'failure'
    4. else
    5. puts 'success'
    6. end
    7. # 好
    8. if success?
    9. puts 'success'
    10. else
    11. puts 'failure'
    12. end

  • 不要使用括号包裹流程控制中的条件表达式。
    [link]

  1. # 差
  2. if (x > 10)
  3. # 省略主体
  4. end
  5. # 好
  6. if x > 10
  7. # 省略主体
  8. end

这个规则的一个例外是条件表达式中的安全赋值


  • 在多行 while/until 中,不要使用 while/until condition do
    [link]

    1. # 差
    2. while x > 5 do
    3. # 省略主体
    4. end
    5. until x > 5 do
    6. # 省略主体
    7. end
    8. # 好
    9. while x > 5
    10. # 省略主体
    11. end
    12. until x > 5
    13. # 省略主体
    14. end

  • 对于单行主体,倾向使用 while/until 修饰语法。
    [link]

    1. # 差
    2. while some_condition
    3. do_something
    4. end
    5. # 好
    6. do_something while some_condition

  • 对于否定条件,倾向使用 until 而不是 while
    [link]

    1. # 差
    2. do_something while !some_condition
    3. # 好
    4. do_something until some_condition

  • 对于无限循环,使用 Kernel#loop 而不是 while/until
    [link]

    1. # 差
    2. while true
    3. do_something
    4. end
    5. until false
    6. do_something
    7. end
    8. # 好
    9. loop do
    10. do_something
    11. end

  • 对于后置条件循环语句,倾向使用 Kernel#loopbreak 的组合,而不是 begin/end/untilbegin/end/while
    [link]

    1. # 差
    2. begin
    3. puts val
    4. val += 1
    5. end while val < 0
    6. # 好
    7. loop do
    8. puts val
    9. val += 1
    10. break unless val < 0
    11. end

  • 对于可选参数的哈希,省略其外围的花括号。
    [link]

    1. # 差
    2. user.set({ name: 'John', age: 45, permissions: { read: true } })
    3. # 好
    4. user.set(name: 'John', age: 45, permissions: { read: true })

  • 对于 DSL 的内部方法调用,同时省略其外围的圆括号与花括号。
    [link]

    1. class Person < ActiveRecord::Base
    2. # 差
    3. validates(:name, { presence: true, length: { within: 1..10 } })
    4. # 好
    5. validates :name, presence: true, length: { within: 1..10 }
    6. end

  • 当被调用方法是当前区块中唯一操作时,倾向使用简短的传参语法。
    [link]

    1. # 差
    2. names.map { |name| name.upcase }
    3. # 好
    4. names.map(&:upcase)

  • 对于单行主体,倾向使用 {...} 而不是 do...end。对于多行主体,避免使用 {...}。对于“流程控制”或“方法定义”(比如 Rakefile、其他 DSL 构成片段),总是使用 do...end。避免在链式方法调用中使用 do...end
    [link]

    1. names = %w[Bozhidar Steve Sarah]
    2. # 差
    3. names.each do |name|
    4. puts name
    5. end
    6. # 好
    7. names.each { |name| puts name }
    8. # 差
    9. names.select do |name|
    10. name.start_with?('S')
    11. end.map { |name| name.upcase }
    12. # 好
    13. names.select { |name| name.start_with?('S') }.map(&:upcase)

    某些人可能会争论在多行链式方法调用时使用 {...} 看起来还可以。但他们应该扪心自问——这样的代码真的可读吗?难道不能把区块内容提取出来放到小巧的方法里吗?


  • 优先考虑使用显式区块参数,以避免某些情况下通过创建区块的手法来传递参数给其他区块。此规则对性能有所影响,因为区块会被转换为 Proc 对象。
    [link]

    1. require 'tempfile'
    2. # 差
    3. def with_tmp_dir
    4. Dir.mktmpdir do |tmp_dir|
    5. Dir.chdir(tmp_dir) { |dir| yield dir } # 通过创建区块的手法来传递参数
    6. end
    7. end
    8. # 好
    9. def with_tmp_dir(&block)
    10. Dir.mktmpdir do |tmp_dir|
    11. Dir.chdir(tmp_dir, &block)
    12. end
    13. end
    14. with_tmp_dir do |dir|
    15. puts "dir is accessible as a parameter and pwd is set: #{dir}"
    16. end

  • 避免在不需要流程控制的情况下使用 return
    [link]

    1. # 差
    2. def some_method(some_arr)
    3. return some_arr.size
    4. end
    5. # 好
    6. def some_method(some_arr)
    7. some_arr.size
    8. end

  • 避免在不需要的情况下使用 self。(只有在调用 self 的修改器、以保留字命名的方法、重载的运算符时才需要)
    [link]

    1. # 差
    2. def ready?
    3. if self.last_reviewed_at > self.last_updated_at
    4. self.worker.update(self.content, self.options)
    5. self.status = :in_progress
    6. end
    7. self.status == :verified
    8. end
    9. # 好
    10. def ready?
    11. if last_reviewed_at > last_updated_at
    12. worker.update(content, options)
    13. self.status = :in_progress
    14. end
    15. status == :verified
    16. end

  • 避免局部变量遮蔽方法调用,除非它们具有相同效果。
    [link]

    1. class Foo
    2. attr_accessor :options
    3. # 勉强可以
    4. def initialize(options)
    5. self.options = options
    6. # 此处 self.options 与 options 具有相同效果
    7. end
    8. # 差
    9. def do_something(options = {})
    10. unless options[:when] == :later
    11. output(self.options[:message])
    12. end
    13. end
    14. # 好
    15. def do_something(params = {})
    16. unless params[:when] == :later
    17. output(options[:message])
    18. end
    19. end
    20. end

  • 不要在条件表达式中使用 =(赋值语句)的返回值,除非赋值语句包裹在括号之中。这种惯用法被称作条件表达式中的安全赋值
    [link]

    1. # 差 - 会出现警告
    2. if v = array.grep(/foo/)
    3. do_something(v)
    4. ...
    5. end
    6. # 好 - 尽管 Ruby 解释器仍会出现警告,但 RuboCop 不会
    7. if (v = array.grep(/foo/))
    8. do_something(v)
    9. ...
    10. end
    11. # 好
    12. v = array.grep(/foo/)
    13. if v
    14. do_something(v)
    15. ...
    16. end

  • 优先考虑简短的自我赋值语法。
    [link]

    1. # 差
    2. x = x + y
    3. x = x * y
    4. x = x**y
    5. x = x / y
    6. x = x || y
    7. x = x && y
    8. # 好
    9. x += y
    10. x *= y
    11. x **= y
    12. x /= y
    13. x ||= y
    14. x &&= y

  • 当变量尚未初始化时,使用 ||= 对其进行初始化。
    [link]

    1. # 差
    2. name = name ? name : 'Bozhidar'
    3. # 差
    4. name = 'Bozhidar' unless name
    5. # 好 - 当且仅当 name 为 nil 或 false 时,设置 name 的值为 'Bozhidar'
    6. name ||= 'Bozhidar'

  • 不要使用 ||= 对布尔变量进行初始化。
    [link]

    1. # 差 - 设置 enabled 的值为 true,即使其原本的值是 false
    2. enabled ||= true
    3. # 好
    4. enabled = true if enabled.nil?

  • 使用 &&= 预先检查变量是否存在,如果存在,则做相应动作。使用 &&= 语法可以省去 if 检查。
    [link]

    1. # 差
    2. if something
    3. something = something.downcase
    4. end
    5. # 差
    6. something = something ? something.downcase : nil
    7. # 勉强可以
    8. something = something.downcase if something
    9. # 好
    10. something = something && something.downcase
    11. # 更好
    12. something &&= something.downcase

  • 避免使用 case 语句等价操作符 ===。从名称可知,这是 case 语句隐式使用的操作符,在 case 语句外的场合中使用,会产生难以理解的代码。
    [link]

    1. # 差
    2. Array === something
    3. (1..100) === 7
    4. /something/ === some_string
    5. # 好
    6. something.is_a?(Array)
    7. (1..100).include?(7)
    8. some_string =~ /something/

  • 能使用 == 就不要使用 eql?。提供更加严格比较的 eql? 在实践中极少使用。
    [link]

    1. # 差 - 对于字符串,eql? 与 == 具有相同效果
    2. 'ruby'.eql? some_str
    3. # 好
    4. 'ruby' == some_str
    5. 1.0.eql? x # 当需要区别 Fixnum 1 与 Float 1.0 时,eql? 是具有意义的

  • 避免使用 Perl 风格的特殊变量(比如 $:$; 等)。它们看起来非常神秘,但除了单行脚本,其他情况并不鼓励使用。建议使用 English 程序库提供的友好别名。
    [link]

    1. # 差
    2. $:.unshift File.dirname(__FILE__)
    3. # 好
    4. require 'English'
    5. $LOAD_PATH.unshift File.dirname(__FILE__)

  • 永远不要在方法名与左括号之间添加空格。
    [link]

    1. # 差
    2. f (3 + 2) + 1
    3. # 好
    4. f(3 + 2) + 1

  • 运行 Ruby 解释器时,总是开启 -w 选项来。如果你忘了某个上述某个规则,它就会警告你!
    [link]


  • 不要在方法中嵌套定义方法,使用 lambda 方法来替代。 嵌套定义产生的方法,事实上和外围方法处于同一作用域(比如类作用域)。此外,“嵌套方法”会在定义它的外围方法每次调用时被重新定义。
    [link]

    1. # 差
    2. def foo(x)
    3. def bar(y)
    4. # 省略主体
    5. end
    6. bar(x)
    7. end
    8. # 好 - 作用同前,但 bar 不会在 foo 每次调用时被重新定义
    9. def bar(y)
    10. # 省略主体
    11. end
    12. def foo(x)
    13. bar(x)
    14. end
    15. # 好
    16. def foo(x)
    17. bar = ->(y) { ... }
    18. bar.call(x)
    19. end

  • 对于单行区块,使用新的 lambda 字面量定义语法。对于多行区块,使用 lambda 定义语法。
    [link]

    1. # 差
    2. l = lambda { |a, b| a + b }
    3. l.call(1, 2)
    4. # 好 - 但看起来怪怪的
    5. l = ->(a, b) do
    6. tmp = a * 7
    7. tmp * b / 50
    8. end
    9. # 好
    10. l = ->(a, b) { a + b }
    11. l.call(1, 2)
    12. l = lambda do |a, b|
    13. tmp = a * 7
    14. tmp * b / 50
    15. end

  • 定义 lambda 方法时,如果有参数则使用括号。
    [link]

    1. # 差
    2. l = ->x, y { something(x, y) }
    3. # 好
    4. l = ->(x, y) { something(x, y) }

  • 定义 lambda 方法时,如果无参数则省略括号。
    [link]

    1. # 差
    2. l = ->() { something }
    3. # 好
    4. l = -> { something }

  • 倾向使用 proc 而不是 Proc.new
    [link]

    1. # 差
    2. p = Proc.new { |n| puts n }
    3. # 好
    4. p = proc { |n| puts n }

  • 对于 lambda 方法或代码块,倾向使用 proc.call() 而不是 proc[]proc.()
    [link]

    1. # 差 - 看上去像是枚举器的存取操作
    2. l = ->(v) { puts v }
    3. l[1]
    4. # 差 - 极少见的调用语法
    5. l = ->(v) { puts v }
    6. l.(1)
    7. # 好
    8. l = ->(v) { puts v }
    9. l.call(1)

  • 未被使用的区块参数或局部变量,添加 _ 前缀或直接使用 _(尽管表意性略差)。这种做法可以抑制 Ruby 解释器或 RuboCop 等工具发出“变量尚未使用”的警告。
    [link]

    1. # 差
    2. result = hash.map { |k, v| v + 1 }
    3. def something(x)
    4. unused_var, used_var = something_else(x)
    5. # ...
    6. end
    7. # 好
    8. result = hash.map { |_k, v| v + 1 }
    9. def something(x)
    10. _unused_var, used_var = something_else(x)
    11. # ...
    12. end
    13. # 好
    14. result = hash.map { |_, v| v + 1 }
    15. def something(x)
    16. _, used_var = something_else(x)
    17. # ...
    18. end

  • 使用 $stdout/$stderr/$stdin 而不是 STDOUT/STDERR/STDINSTDOUT/STDERR/STDIN 是常量,尽管在 Ruby 中允许给常量重新赋值(可能是重定向某些流),但解释器会发出警告。
    [link]


  • 使用 warn 而不是 $stderr.puts。除了更加简练清晰外,warn 允许你在需要时通过设置解释器选项(使用 -W0 将警告级别设置为 0)来抑制警告。
    [link]


  • 倾向使用 sprintf 或其别名 format 而不是相当晦涩的 String#% 方法。
    [link]

    1. # 差
    2. '%d %d' % [20, 10]
    3. # => '20 10'
    4. # 好
    5. sprintf('%d %d', 20, 10)
    6. # => '20 10'
    7. # 好
    8. sprintf('%{first} %{second}', first: 20, second: 10)
    9. # => '20 10'
    10. format('%d %d', 20, 10)
    11. # => '20 10'
    12. # 好
    13. format('%{first} %{second}', first: 20, second: 10)
    14. # => '20 10'

  • 倾向使用 Array#join 而不是相当晦涩的带字符参数的 Array#* 方法。
    [link]

    1. # 差
    2. %w[one two three] * ', '
    3. # => 'one, two, three'
    4. # 好
    5. %w[one two three].join(', ')
    6. # => 'one, two, three'

  • 当你希望处理的变量类型是数组,但不太确定其是否真的是数组时,通过使用 Array() 来替代显式的数组类型检查与转换。
    [link]

  1. # 差
  2. paths = [paths] unless paths.is_a? Array
  3. paths.each { |path| do_something(path) }
  4. # 差 - 总是构建新的数组对象
  5. [*paths].each { |path| do_something(path) }
  6. # 好
  7. Array(paths).each { |path| do_something(path) }

  • 通过使用范围或 Comparable#between? 来替代复杂的比较逻辑。
    [link]

    1. # 差
    2. do_something if x >= 1000 && x <= 2000
    3. # 好
    4. do_something if (1000..2000).include?(x)
    5. # 好
    6. do_something if x.between?(1000, 2000)

  • 倾向使用谓词方法而不是 == 操作符。但数值比较除外。
    [link]

    1. # 差
    2. if x % 2 == 0
    3. end
    4. if x % 2 == 1
    5. end
    6. if x == nil
    7. end
    8. # 好
    9. if x.even?
    10. end
    11. if x.odd?
    12. end
    13. if x.nil?
    14. end
    15. if x.zero?
    16. end
    17. if x == 0
    18. end

  • 不做显式的 non-nil 检查,除非检查对象是布尔变量。
    [link]

    1. # 差
    2. do_something if !something.nil?
    3. do_something if something != nil
    4. # 好
    5. do_something if something
    6. # 好 - 检查对象是布尔变量
    7. def value_set?
    8. !@some_boolean.nil?
    9. end

  • 避免使用 BEGIN 区块。
    [link]


  • 永远不要使用 END 区块。使用 Kernel#at_exit 来替代。
    [link]

    1. # 差
    2. END { puts 'Goodbye!' }
    3. # 好
    4. at_exit { puts 'Goodbye!' }

  • 避免使用 flip-flops 操作符。
    [link]


  • 流程控制中,避免使用嵌套条件。
    [link]

    倾向使用防御从句进行非法数据断言。防御从句是指处于方法顶部的条件语句,其能尽早地退出方法。

    1. # 差
    2. def compute_thing(thing)
    3. if thing[:foo]
    4. update_with_bar(thing[:foo])
    5. if thing[:foo][:bar]
    6. partial_compute(thing)
    7. else
    8. re_compute(thing)
    9. end
    10. end
    11. end
    12. # 好
    13. def compute_thing(thing)
    14. return unless thing[:foo]
    15. update_with_bar(thing[:foo])
    16. return re_compute(thing) unless thing[:foo][:bar]
    17. partial_compute(thing)
    18. end

    循环中,倾向使用 next 而不是条件区块。

    1. # 差
    2. [0, 1, 2, 3].each do |item|
    3. if item > 1
    4. puts item
    5. end
    6. end
    7. # 好
    8. [0, 1, 2, 3].each do |item|
    9. next unless item > 1
    10. puts item
    11. end

  • 倾向使用 map 而不是 collectfind 而不是 detectselect 而不是 find_allreduce 而不是 inject 以及 size 而不是 length。这不是一个硬性要求,如果使用别名可以增强可读性,使用它也没关系。这些别名方法继承自 Smalltalk 语言,但在别的语言并不通用。鼓励使用 select 而不是 find_all 的理由是前者与 reject 搭配起来一目了然。
    [link]


  • 不要使用 count 作为 size 的替代方案。除了 Array 外,其他 Enumerable 对象都需要通过枚举整个集合才可以确定数目。
    [link]

    1. # 差
    2. some_hash.count
    3. # 好
    4. some_hash.size

  • 倾向使用 flat_map 而不是 map + flatten 的组合。此规则并不适用于深度超过 2 层的数组。举例来说,如果 users.first.songs == ['a', ['b','c']] 成立,则使用 map + flatten 的组合而不是 flat_mapflat_map 只能平坦化一个层级,而 flatten 能够平坦化任意多个层级。
    [link]

    1. # 差
    2. all_songs = users.map(&:songs).flatten.uniq
    3. # 好
    4. all_songs = users.flat_map(&:songs).uniq

  • 倾向使用 reverse_each 而不是 reverse.each,因为某些混入 Enumerable 模块的类可能会提供 reverse_each 的高效版本。即使这些类没有提供专门特化的版本,继承自 Enumerable 的通用版本至少能保证性能与 reverse.each 相当。
    [link]

    1. # 差
    2. array.reverse.each { ... }
    3. # 好
    4. array.reverse_each { ... }