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