使用?::
?引用常量(包括類和模塊)和構(gòu)造器 (比如?Array()
?或者?Nokogiri::HTML()
)。永遠(yuǎn)不要使用?::
?來調(diào)用方法。
# 差
SomeClass::some_method
some_object::some_method
# 好
SomeClass.some_method
some_object.some_method
SomeModule::SomeClass::SOME_CONST
SomeModule::SomeClass()
使用?def
?時(shí),有參數(shù)時(shí)使用括號(hào)。方法不接受參數(shù)時(shí),省略括號(hào)。
# 差
def some_method()
# 此處省略方法體
# 好
def some_method
# 此處省略方法體
# 差
def some_method_with_parameters param1, param2
# 此處省略方法體
# 好
def some_method_with_parameters(param1, param2)
# 此處省略方法體
end
永遠(yuǎn)不要使用?for
?,除非你很清楚為什么。大部分情況應(yīng)該使用迭代器。for
?是由?each
?實(shí)現(xiàn)的。所以你繞彎了,而且?for
?沒有包含一個(gè)新的作用域 (each
?有 ),因此它區(qū)塊中定義的變量將會(huì)被外部所看到。
arr = [1, 2, 3]
# 差
for elem in arr do
puts elem
end
# 注意 elem 會(huì)被外部所看到
elem #=> 3
# 好
arr.each { |elem| puts elem }
# elem 不會(huì)被外部所看到
elem #=> NameError: undefined local variable or method `elem'
永遠(yuǎn)不要在多行的?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
?結(jié)構(gòu)更為常見也更精準(zhǔn)。
# 差
result = if some_condition then something else something_else end
# 好
result = some_condition ? something : something_else
三元操作符的每個(gè)分支只寫一個(gè)表達(dá)式。即不要嵌套三元操作符。嵌套情況使用?if/else
?結(jié)構(gòu)。
# 差
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
永遠(yuǎn)不要使用?if x: ...
——它已經(jīng)在 Ruby 1.9 被移除了。使用三元操作符。
# 差
result = if some_condition: something else something_else end
# 好
result = some_condition ? something : something_else
永遠(yuǎn)不要使用?if x; ...
?使用三元操作符。
利用 if 和 case 是表達(dá)式的特性。
# 差
if condition
result = x
else
result = y
end
# 好
result =
if condition
x
else
y
end
單行情況使用?when x then ...
。另一種語法?when x: ...
?已經(jīng)在 Ruby 1.9 被移除了。
永遠(yuǎn)不要使用?when x: ...
。參考前一個(gè)規(guī)則。
使用?!
?替代?not
。
# 差 - 因?yàn)椴僮鞣袃?yōu)先級(jí),需要用括號(hào)。
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
?這兩個(gè)關(guān)鍵字被禁止使用了。 總是使用?&&
?和?||
?來取代。
# 差
# 布爾表達(dá)式
if some_condition and some_other_condition
do_something
end
# 控制流程
document.saved? or document.save!
# 好
# 布爾表達(dá)式
if some_condition && some_other_condition
do_something
end
# 控制流程
document.saved? || document.save!
避免多行的?? :
(三元操作符);使用?if/unless
?來取代。
單行主體用?if/unless
?修飾符。另一個(gè)好的方法是使用?&&/||
?控制流程。
# 差
if some_condition
do_something
end
# 好
do_something if some_condition
# 另一個(gè)好方法
some_condition && do_something
避免在多行區(qū)塊后使用?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
否定判斷時(shí),unless
(或控制流程的?||
)優(yōu)于?if
(或使用?||
?控制流程)。
# 差
do_something if !some_condition
# 差
do_something if not some_condition
# 好
do_something unless some_condition
# 另一個(gè)好方法
some_condition || do_something
永遠(yuǎn)不要使用?unless
?和?else
?組合。改寫成肯定條件。
# 差
unless success?
puts 'failure'
else
puts 'success'
end
# 好
if success?
puts 'success'
else
puts 'failure'
end
不要使用括號(hào)圍繞?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
單行主體時(shí)盡量使用?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
無限循環(huán)用?Kernel#loop
,不用?while/until
?。
# 差
while true
do_something
end
until false
do_something
end
# 好
loop do
do_something
end
循環(huán)后條件判斷使用?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
忽略圍繞方法參數(shù)的括號(hào),如內(nèi)部 DSL (如:Rake, Rails, RSpec),Ruby 中帶有“關(guān)鍵字”狀態(tài)的方法(如:attr_reader
,puts
)以及屬性存取方法。所有其他的方法呼叫使用括號(hào)圍繞參數(shù)。
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
省略可選哈希參數(shù)的外部花括號(hào)。
# 差
user.set({ name: 'John', age: 45, permissions: { read: true } })
# 好
User.set(name: 'John', age: 45, permissions: { read: true })
如果方法是內(nèi)部 DSL 的一部分,那么省略外層的花括號(hào)和圓括號(hào)。
class Person < ActiveRecord::Base
# 差
validates(:name, { presence: true, length: { within: 1..10 } })
# 好
validates :name, presence: true, length: { within: 1..10 }
end
如果方法調(diào)用不需要參數(shù),那么省略圓括號(hào)。
# 差
Kernel.exit!()
2.even?()
fork()
'test'.upcase()
# 好
Kernel.exit!
2.even?
fork
'test'.upcase
當(dāng)被調(diào)用的方法是只有一個(gè)操作的區(qū)塊時(shí),使用Proc
。
# 差
names.map { |name| name.upcase }
# 好
names.map(&:upcase)
單行區(qū)塊傾向使用?{...}
?而不是?do...end
。多行區(qū)塊避免使用?{...}
(多行串連總是??丑陋)。在?do...end
?、 “控制流程”及“方法定義”,永遠(yuǎn)使用?do...end
?(如 Rakefile 及某些 DSL)。串連時(shí)避免使用?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)
某些人會(huì)爭論多行串連時(shí),使用?{...}
?看起來還可以,但他們應(yīng)該捫心自問——這樣代碼真的可讀嗎?難道不能把區(qū)塊內(nèi)容取出來放到小巧的方法里嗎?
顯性使用區(qū)塊參數(shù)而不是用創(chuàng)建區(qū)塊字面量的方式傳遞參數(shù)給區(qū)塊。此規(guī)則對(duì)性能有所影響,因?yàn)閰^(qū)塊先被轉(zhuǎn)化為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
避免在不需要控制流程的場合時(shí)使用?return
?。
# 差
def some_method(some_arr)
return some_arr.size
end
# 好
def some_method(some_arr)
some_arr.size
end
避免在不需要的情況使用?self
?。(只有在調(diào)用一個(gè) self write 訪問器時(shí)會(huì)需要用到。)
# 差
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
# 勉強(qiáng)可以
def initialize(options)
self.options = options
# 此處 options 和 self.options 都是等價(jià)的
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
不要在條件表達(dá)式里使用?=
?(賦值)的返回值,除非條件表達(dá)式在圓括號(hào)內(nèi)被賦值。這是一個(gè)相當(dāng)流行的 Ruby 方言,有時(shí)被稱為“safe assignment in condition”。
# 差 (還會(huì)有個(gè)警告)
if (v = array.grep(/foo/))
do_something(v)
...
end
# 差 (MRI 仍會(huì)抱怨, 但 RuboCop 不會(huì))
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 時(shí),把名字設(shè)為 Bozhidar。
name ||= 'Bozhidar'
不要使用?||=
?來初始化布爾變量。 (想看看如果現(xiàn)在的值剛好是?false
?時(shí)會(huì)發(fā)生什么。)
# 差——會(huì)把 `enabled` 設(shè)成真,即便它本來是假。
enabled ||= true
# 好
enabled = true if enabled.nil?
使用 &&= 可先檢查是否存在變量,如果存在則做相應(yīng)動(dòng)作。這樣就無需用?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
?臺(tái)面下所用的操作符,在case
?語句外的場合使用,會(huì)產(chǎn)生難以理解的代碼。
# 差
Array === something
(1..100) === 7
/something/ === some_string
# 好
something.is_a?(Array)
(1..100).include?(7)
some_string =~ /something/
避免使用 Perl 風(fēng)格的特殊變量(像是?$:
、$;
?等)。它們看起來非常神秘,除非用于單行腳本,否則不鼓勵(lì)使用。使用?English
?庫提供的友好別名。
# 差
$:.unshift File.dirname(__FILE__)
# 好
require 'English'
$LOAD_PATH.unshift File.dirname(__FILE__)
永遠(yuǎn)不要在方法名與左括號(hào)之間放一個(gè)空格。
# 差
f (3 + 2) + 1
# 好
f(3 + 2) + 1
如果方法的第一個(gè)參數(shù)由左括號(hào)開始的,則此方法調(diào)用應(yīng)該使用括號(hào)。舉個(gè)例子,如?f((3+2) + 1)
。
總是使用?-w
?來執(zhí)行 Ruby 解釋器,如果你忘了某個(gè)上述的規(guī)則,它就會(huì)警告你!
用新的 lambda 字面語法定義單行區(qū)塊,用?lambda
?方法定義多行區(qū)塊。
# 差
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
當(dāng)定義一個(gè)簡短且沒有參數(shù)的 lambda 時(shí),省略參數(shù)的括號(hào)。
# 差
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)
未使用的區(qū)塊參數(shù)和局部變量使用?_
?前綴或直接使用?_
(雖然表意性差些) 。Ruby解釋器和RuboCop都能辨認(rèn)此規(guī)則,并會(huì)抑制相關(guān)地有變量未使用的警告。
# 差
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 中是可以給常量重新賦值的(可能是重定向到某個(gè)流),但解釋器會(huì)警告。
使用?warn
?而不是?$stderr.puts
。除了更加清晰簡潔,如果你需要的話,?warn
?還允許你壓制(suppress)警告(通過?-W0
?將警告級(jí)別設(shè)為?0
)。
傾向使用?sprintf
?和它的別名?format
?而不是相當(dāng)隱晦的?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
?而不是相當(dāng)隱晦的使用字符串作參數(shù)的?Array#*
。
# 差
%w(one two three) * ', '
# => 'one, two, three'
# 好
%w(one two three).join(', ')
# => 'one, two, three'
當(dāng)處理你希望將變量作為數(shù)組使用,但不確定它是不是數(shù)組時(shí), 使用?[*var]
?或?Array()
?而不是顯式的?Array
?檢查。
# 差
paths = [paths] unless paths.is_a? Array
paths.each { |path| do_something(path) }
# 好
[*paths].each { |path| do_something(path) }
# 好(而且更具易讀性一點(diǎn))
Array(paths).each { |path| do_something(path) }
盡量使用范圍或?Comparable#between?
?來替換復(fù)雜的邏輯比較。
# 差
do_something if x >= 1000 && x < 2000
# 好
do_something if (1000...2000).include?(x)
# 好
do_something if x.between?(1000, 2000)
盡量用判斷方法而不是使用?==
?。比較數(shù)字除外。
# 差
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
?區(qū)塊。
使用?Kernel#at_exit
?。永遠(yuǎn)不要用?END
?區(qū)塊。
# 差
END { puts 'Goodbye!' }
# 好
at_exit { puts 'Goodbye!' }
避免使用 flip-flops 。
避免使用嵌套的條件來控制流程。 當(dāng)你可能斷言不合法的數(shù)據(jù),使用一個(gè)防御從句。一個(gè)防御從句是一個(gè)在函數(shù)頂部的條件聲明,這樣如果數(shù)據(jù)不合法就能盡快的跳出函數(shù)。
# 差
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
?而不是條件區(qū)塊。
# 差
[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
?。這不是一個(gè)硬性要求;如果使用別名增加了可讀性,使用它沒關(guān)系。這些有押韻的方法名是從 Smalltalk 繼承而來,在別的語言不通用。鼓勵(lì)使用?select
?而不是?find_all
?的理由是它跟?reject
?搭配起來是一目了然的。
不要用?count
?代替?size
。除了Array
其它Enumerable
對(duì)象都需要遍歷整個(gè)集合才能得到大小。
# 差
some_hash.count
# 好
some_hash.size
傾向使用?flat_map
?而不是?map
?+?flatten
?的組合。 這并不適用于深度大于 2 的數(shù)組,舉個(gè)例子,如果users.first.songs == ['a', ['b', 'c']]
?,則使用?map + flatten
?的組合,而不是使用?flat_map
。?flat_map
將數(shù)組變平坦一個(gè)層級(jí),而?flatten
?會(huì)將整個(gè)數(shù)組變平坦。
# 差
all_songs = users.map(&:songs).flatten.uniq
# 好
all_songs = users.flat_map(&:songs).uniq
使用?reverse_each
?,不用?reverse.each
?。?reverse_each
?不會(huì)重新分配新數(shù)組。
# 差
array.reverse.each { ... }
# 好
array.reverse_each { ... }
更多建議: