* 使用 `::` 引用常量(包括类和模块)和构造器 (比如 `Array()` 或者 `Nokogiri::HTML()`)。永远不要使用 `::` 来调用方法。 ~~~ # 差 SomeClass::some_method some_object::some_method # 好 SomeClass.some_method some_object.some_method SomeModule::SomeClass::SOME_CONST SomeModule::SomeClass() ~~~ * 使用 `def` 时,有参数时使用括号。方法不接受参数时,省略括号。 ~~~ # 差 def some_method() # 此处省略方法体 # 好 def some_method # 此处省略方法体 # 差 def some_method_with_parameters param1, param2 # 此处省略方法体 # 好 def some_method_with_parameters(param1, param2) # 此处省略方法体 end ~~~ * 永远不要使用 `for` ,除非你很清楚为什么。大部分情况应该使用迭代器。`for` 是由 `each` 实现的。所以你绕弯了,而且 `for` 没有包含一个新的作用域 (`each` 有 ),因此它区块中定义的变量将会被外部所看到。 ~~~ arr = [1, 2, 3] # 差 for elem in arr do puts elem end # 注意 elem 会被外部所看到 elem #=> 3 # 好 arr.each { |elem| puts elem } # elem 不会被外部所看到 elem #=> NameError: undefined local variable or method `elem' ~~~ * 永远不要在多行的 `if/unless` 中使用 `then`。 ~~~ # 差 if some_condition then # 此处省略语句体 end # 好 if some_condition # 此处省略语句体 end ~~~ * 总是在多行的 `if/unless` 中把条件语句放在同一行。 ~~~ # 差 if some_condition do_something do_something_else end # 好 if some_condition do_something do_something_else end ~~~ * 三元操作符 `? :` 比 `if/then/else/end` 结构更为常见也更精准。 ~~~ # 差 result = if some_condition then something else something_else end # 好 result = some_condition ? something : something_else ~~~ * 三元操作符的每个分支只写一个表达式。即不要嵌套三元操作符。嵌套情况使用 `if/else` 结构。 ~~~ # 差 some_condition ? (nested_condition ? nested_something : nested_something_else) : something_else # 好 if some_condition nested_condition ? nested_something : nested_something_else else something_else end ~~~ * 永远不要使用 `if x: ...`——它已经在 Ruby 1.9 被移除了。使用三元操作符。 ~~~ # 差 result = if some_condition: something else something_else end # 好 result = some_condition ? something : something_else ~~~ * 永远不要使用 `if x; ...` 使用三元操作符。 * 利用 if 和 case 是表达式的特性。 ~~~ # 差 if condition result = x else result = y end # 好 result = if condition x else y end ~~~ * 单行情况使用 `when x then ...`。另一种语法 `when x: ...` 已经在 Ruby 1.9 被移除了。 * 永远不要使用 `when x: ...`。参考前一个规则。 * 使用 `!` 替代 `not`。 ~~~ # 差 - 因为操作符有优先级,需要用括号。 x = (not something) # 好 x = !something ~~~ * 避免使用 `!!`。 ~~~ # 差 x = 'test' # obscure nil check if !!x # body omitted end x = false # double negation is useless on booleans !!x # => false # 好 x = 'test' unless x.nil? # body omitted end ~~~ * `and` 和 `or` 这两个关键字被禁止使用了。 总是使用 `&&` 和 `||` 来取代。 ~~~ # 差 # 布尔表达式 if some_condition and some_other_condition do_something end # 控制流程 document.saved? or document.save! # 好 # 布尔表达式 if some_condition && some_other_condition do_something end # 控制流程 document.saved? || document.save! ~~~ * 避免多行的 `? :`(三元操作符);使用 `if/unless` 来取代。 * 单行主体用 `if/unless` 修饰符。另一个好的方法是使用 `&&/||` 控制流程。 ~~~ # 差 if some_condition do_something end # 好 do_something if some_condition # 另一个好方法 some_condition && do_something ~~~ * 避免在多行区块后使用 `if` 或 `unless`。 ~~~ # 差 10.times do # multi-line body omitted end if some_condition # 好 if some_condition 10.times do # multi-line body omitted end end ~~~ * 否定判断时,`unless`(或控制流程的 `||`)优于 `if`(或使用 `||` 控制流程)。 ~~~ # 差 do_something if !some_condition # 差 do_something if not some_condition # 好 do_something unless some_condition # 另一个好方法 some_condition || do_something ~~~ * 永远不要使用 `unless` 和 `else` 组合。改写成肯定条件。 ~~~ # 差 unless success? puts 'failure' else puts 'success' end # 好 if success? puts 'success' else puts 'failure' end ~~~ * 不要使用括号围绕 `if/unless/while` 的条件式。 ~~~ # 差 if (x > 10) # 此处省略语句体 end # 好 if x > 10 # 此处省略语句体 end ~~~ * 在多行 `while/until` 中不要使用 `while/until condition do` 。 ~~~ # 差 while x > 5 do # 此处省略语句体 end until x > 5 do # 此处省略语句体 end # 好 while x > 5 # 此处省略语句体 end until x > 5 # 此处省略语句体 end ~~~ * 单行主体时尽量使用 `while/until` 修饰符。 ~~~ # 差 while some_condition do_something end # 好 do_something while some_condition ~~~ * 否定条件判断尽量使用 `until` 而不是 `while` 。 ~~~ # 差 do_something while !some_condition # 好 do_something until some_condition ~~~ * 无限循环用 `Kernel#loop`,不用 `while/until` 。 ~~~ # 差 while true do_something end until false do_something end # 好 loop do do_something end ~~~ * 循环后条件判断使用 `Kernel#loop` 和 `break`,而不是 `begin/end/until` 或者 `begin/end/while`。 ~~~ # 差 begin puts val val += 1 end while val < 0 # 好 loop do puts val val += 1 break unless val < 0 end ~~~ * 忽略围绕方法参数的括号,如内部 DSL (如:Rake, Rails, RSpec),Ruby 中带有“关键字”状态的方法(如:`attr_reader`,`puts`)以及属性存取方法。所有其他的方法呼叫使用括号围绕参数。 ~~~ class Person attr_reader :name, :age # 忽略 end temperance = Person.new('Temperance', 30) temperance.name puts temperance.age x = Math.sin(y) array.delete(e) bowling.score.should == 0 ~~~ * 省略可选哈希参数的外部花括号。 ~~~ # 差 user.set({ name: 'John', age: 45, permissions: { read: true } }) # 好 User.set(name: 'John', age: 45, permissions: { read: true }) ~~~ * 如果方法是内部 DSL 的一部分,那么省略外层的花括号和圆括号。 ~~~ class Person < ActiveRecord::Base # 差 validates(:name, { presence: true, length: { within: 1..10 } }) # 好 validates :name, presence: true, length: { within: 1..10 } end ~~~ * 如果方法调用不需要参数,那么省略圆括号。 ~~~ # 差 Kernel.exit!() 2.even?() fork() 'test'.upcase() # 好 Kernel.exit! 2.even? fork 'test'.upcase ~~~ * 当被调用的方法是只有一个操作的区块时,使用`Proc`。 ~~~ # 差 names.map { |name| name.upcase } # 好 names.map(&:upcase) ~~~ * 单行区块倾向使用 `{...}` 而不是 `do...end`。多行区块避免使用 `{...}`(多行串连总是​​丑陋)。在 `do...end` 、 “控制流程”及“方法定义”,永远使用 `do...end` (如 Rakefile 及某些 DSL)。串连时避免使用 `do...end`。 ~~~ names = %w(Bozhidar Steve Sarah) # 差 names.each do |name| puts name end # 好 names.each { |name| puts name } # 差 names.select do |name| name.start_with?('S') end.map { |name| name.upcase } # 好 names.select { |name| name.start_with?('S') }.map(&:upcase) ~~~ 某些人会争论多行串连时,使用 `{...}` 看起来还可以,但他们应该扪心自问——这样代码真的可读吗?难道不能把区块内容取出来放到小巧的方法里吗? * 显性使用区块参数而不是用创建区块字面量的方式传递参数给区块。此规则对性能有所影响,因为区块先被转化为`Proc`。 ~~~ require 'tempfile' # 差 def with_tmp_dir Dir.mktmpdir do |tmp_dir| Dir.chdir(tmp_dir) { |dir| yield dir } # block just passes arguments end end # 好 def with_tmp_dir(&block) Dir.mktmpdir do |tmp_dir| Dir.chdir(tmp_dir, &block) end end with_tmp_dir do |dir| # 使用上面的方法 puts "dir is accessible as a parameter and pwd is set: #{dir}" end ~~~ * 避免在不需要控制流程的场合时使用 `return` 。 ~~~ # 差 def some_method(some_arr) return some_arr.size end # 好 def some_method(some_arr) some_arr.size end ~~~ * 避免在不需要的情况使用 `self` 。(只有在调用一个 self write 访问器时会需要用到。) ~~~ # 差 def ready? if self.last_reviewed_at > self.last_updated_at self.worker.update(self.content, self.options) self.status = :in_progress end self.status == :verified end # 好 def ready? if last_reviewed_at > last_updated_at worker.update(content, options) self.status = :in_progress end status == :verified end ~~~ * 避免局部变量 shadowing 外部方法,除非它们彼此相等。 ~~~ class Foo attr_accessor :options # 勉强可以 def initialize(options) self.options = options # 此处 options 和 self.options 都是等价的 end # 差 def do_something(options = {}) unless options[:when] == :later output(self.options[:message]) end end # 好 def do_something(params = {}) unless params[:when] == :later output(options[:message]) end end end ~~~ * 不要在条件表达式里使用 `=` (赋值)的返回值,除非条件表达式在圆括号内被赋值。这是一个相当流行的 Ruby 方言,有时被称为“safe assignment in condition”。 ~~~ # 差 (还会有个警告) if (v = array.grep(/foo/)) do_something(v) ... end # 差 (MRI 仍会抱怨, 但 RuboCop 不会) if v = array.grep(/foo/) do_something(v) ... end # 好 v = array.grep(/foo/) if v do_something(v) ... end ~~~ * 变量自赋值用简写方式。 ~~~ # 差 x = x + y x = x * y x = x**y x = x / y x = x || y x = x && y # 好 x += y x *= y x **= y x /= y x ||= y x &&= y ~~~ * 如果变量未被初始化过,用 `||=` 来初始化变量并赋值。 ~~~ # 差 name = name ? name : 'Bozhidar' # 差 name = 'Bozhidar' unless name # 好 仅在 name 为 nil 或 false 时,把名字设为 Bozhidar。 name ||= 'Bozhidar' ~~~ * 不要使用 `||=` 来初始化布尔变量。 (想看看如果现在的值刚好是 `false` 时会发生什么。) ~~~ # 差——会把 `enabled` 设成真,即便它本来是假。 enabled ||= true # 好 enabled = true if enabled.nil? ~~~ * 使用 &&= 可先检查是否存在变量,如果存在则做相应动作。这样就无需用 `if` 检查变量是否存在了。 ~~~ # 差 if something something = something.downcase end # 差 something = something ? something.downcase : nil # 可以 something = something.downcase if something # 好 something = something && something.downcase # 更好 something &&= something.downcase ~~~ * 避免使用 `case` 语句的 `===` 操作符(case equality operator)。从名称可知,这是 `case` 台面下所用的操作符,在`case` 语句外的场合使用,会产生难以理解的代码。 ~~~ # 差 Array === something (1..100) === 7 /something/ === some_string # 好 something.is_a?(Array) (1..100).include?(7) some_string =~ /something/ ~~~ * 避免使用 Perl 风格的特殊变量(像是 `$:`、`$;` 等)。它们看起来非常神秘,除非用于单行脚本,否则不鼓励使用。使用 `English` 库提供的友好别名。 ~~~ # 差 $:.unshift File.dirname(__FILE__) # 好 require 'English' $LOAD_PATH.unshift File.dirname(__FILE__) ~~~ * 永远不要在方法名与左括号之间放一个空格。 ~~~ # 差 f (3 + 2) + 1 # 好 f(3 + 2) + 1 ~~~ * 如果方法的第一个参数由左括号开始的,则此方法调用应该使用括号。举个例子,如 `f((3+2) + 1)`。 * 总是使用 `-w` 来执行 Ruby 解释器,如果你忘了某个上述的规则,它就会警告你! * 用新的 lambda 字面语法定义单行区块,用 `lambda` 方法定义多行区块。 ~~~ # 差 lambda = lambda { |a, b| a + b } lambda.call(1, 2) # 正确,但看着怪怪的 l = ->(a, b) do tmp = a * 7 tmp * b / 50 end # 好 l = ->(a, b) { a + b } l.call(1, 2) l = lambda do |a, b| tmp = a * 7 tmp * b / 50 end ~~~ * 当定义一个简短且没有参数的 lambda 时,省略参数的括号。 ~~~ # 差 l = ->() { something } # 好 l = -> { something } ~~~ * 用 `proc` 而不是 `Proc.new`。 ~~~ # 差 p = Proc.new { |n| puts n } # 好 p = proc { |n| puts n } ~~~ * 用 `proc.call()` 而不是 `proc[]` 或 `proc.()`。 ~~~ # 差 - 看上去像枚举访问 l = ->(v) { puts v } l[1] # 也不好 - 不常用的语法 l = ->(v) { puts v } l.(1) # 好 l = ->(v) { puts v } l.call(1) ~~~ * 未使用的区块参数和局部变量使用 `_` 前缀或直接使用 `_`(虽然表意性差些) 。Ruby解释器和RuboCop都能辨认此规则,并会抑制相关地有变量未使用的警告。 ~~~ # 差 result = hash.map { |k, v| v + 1 } def something(x) unused_var, used_var = something_else(x) # ... end # 好 result = hash.map { |_k, v| v + 1 } def something(x) _unused_var, used_var = something_else(x) # ... end # 好 result = hash.map { |_, v| v + 1 } def something(x) _, used_var = something_else(x) # ... end ~~~ * 使用 `$stdout/$stderr/$stdin` 而不是 `STDOUT/STDERR/STDIN`。`STDOUT/STDERR/STDIN` 是常量,虽然在 Ruby 中是可以给常量重新赋值的(可能是重定向到某个流),但解释器会警告。 * 使用 `warn` 而不是 `$stderr.puts`。除了更加清晰简洁,如果你需要的话, `warn` 还允许你压制(suppress)警告(通过 `-W0` 将警告级别设为 `0`)。 * 倾向使用 `sprintf` 和它的别名 `format` 而不是相当隐晦的 `String#%` 方法. ~~~ # 差 '%d %d' % [20, 10] # => '20 10' # 好 sprintf('%d %d', 20, 10) # => '20 10' # 好 sprintf('%{first} %{second}', first: 20, second: 10) # => '20 10' format('%d %d', 20, 10) # => '20 10' # 好 format('%{first} %{second}', first: 20, second: 10) # => '20 10' ~~~ * 倾向使用 `Array#join` 而不是相当隐晦的使用字符串作参数的 `Array#*`。 ~~~ # 差 %w(one two three) * ', ' # => 'one, two, three' # 好 %w(one two three).join(', ') # => 'one, two, three' ~~~ * 当处理你希望将变量作为数组使用,但不确定它是不是数组时, 使用 `[*var]` 或 `Array()` 而不是显式的 `Array` 检查。 ~~~ # 差 paths = [paths] unless paths.is_a? Array paths.each { |path| do_something(path) } # 好 [*paths].each { |path| do_something(path) } # 好(而且更具易读性一点) Array(paths).each { |path| do_something(path) } ~~~ * 尽量使用范围或 `Comparable#between?` 来替换复杂的逻辑比较。 ~~~ # 差 do_something if x >= 1000 && x < 2000 # 好 do_something if (1000...2000).include?(x) # 好 do_something if x.between?(1000, 2000) ~~~ * 尽量用判断方法而不是使用 `==` 。比较数字除外。 ~~~ # 差 if x % 2 == 0 end if x % 2 == 1 end if x == nil end # 好 if x.even? end if x.odd? end if x.nil? end if x.zero? end if x == 0 end ~~~ * 除非是布尔值,不用显示检查它是否不是 `nil` 。 ~~~ # 差 do_something if !something.nil? do_something if something != nil # 好 do_something if something # 好——检查的是布尔值 def value_set? !@some_boolean.nil? end ~~~ * 避免使用 `BEGIN` 区块。 * 使用 `Kernel#at_exit` 。永远不要用 `END` 区块。 ~~~ # 差 END { puts 'Goodbye!' } # 好 at_exit { puts 'Goodbye!' } ~~~ * 避免使用 flip-flops 。 * 避免使用嵌套的条件来控制流程。 当你可能断言不合法的数据,使用一个防御从句。一个防御从句是一个在函数顶部的条件声明,这样如果数据不合法就能尽快的跳出函数。 ~~~ # 差 def compute_thing(thing) if thing[:foo] update_with_bar(thing) if thing[:foo][:bar] partial_compute(thing) else re_compute(thing) end end end # 好 def compute_thing(thing) return unless thing[:foo] update_with_bar(thing[:foo]) return re_compute(thing) unless thing[:foo][:bar] partial_compute(thing) end ~~~ 使用 `next` 而不是条件区块。 ~~~ # 差 [0, 1, 2, 3].each do |item| if item > 1 puts item end end # 好 [0, 1, 2, 3].each do |item| next unless item > 1 puts item end ~~~ * 倾向使用 `map` 而不是 `collect` , `find` 而不是 `detect` , `select` 而不是 `find_all` , `reduce` 而不是 `inject`以及 `size` 而不是 `length` 。这不是一个硬性要求;如果使用别名增加了可读性,使用它没关系。这些有押韵的方法名是从 Smalltalk 继承而来,在别的语言不通用。鼓励使用 `select` 而不是 `find_all` 的理由是它跟 `reject` 搭配起来是一目了然的。 * 不要用 `count` 代替 `size`。除了`Array`其它`Enumerable`对象都需要遍历整个集合才能得到大小。 ~~~ # 差 some_hash.count # 好 some_hash.size ~~~ * 倾向使用 `flat_map` 而不是 `map` + `flatten` 的组合。 这并不适用于深度大于 2 的数组,举个例子,如果`users.first.songs == ['a', ['b', 'c']]` ,则使用 `map + flatten` 的组合,而不是使用 `flat_map`。 `flat_map`将数组变平坦一个层级,而 `flatten` 会将整个数组变平坦。 ~~~ # 差 all_songs = users.map(&:songs).flatten.uniq # 好 all_songs = users.flat_map(&:songs).uniq ~~~ * 使用 `reverse_each` ,不用 `reverse.each` 。 `reverse_each` 不会重新分配新数组。 ~~~ # 差 array.reverse.each { ... } # 好 array.reverse_each { ... } ~~~