语法
-
使用
::
引用常量(包括类和模块)和构造器 (比如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 { ... }
更多建议: