* 使用 `::` 引用常量(包括类和模块)和构造器 (比如 `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 { ... }
~~~