Ruby 风格指南
序幕
榜样很重要
——墨菲警官《机器战警》
Ruby风格指南,灵感来自PEP-8,另有Rails风格指南。
Ruby 风格指南中推荐实际使用中的最佳实践,即如何写出易维护的Ruby代码。指南的作用指导实践,指南源自作者的经验,《Programming Ruby 1.9》,《The Ruby Programming Language》。
Transmuter 可以生成本指南的 PDF 或 HTML 版本。rubocop 项目会自动检查你的 Ruby 代码是否符合这份 Ruby 风格指南。
目录
源代码排版
所有风格都又丑又难读,自己的除外。几乎人人都这样想。把“自己的除外”拿掉,他们或许是对的…
——Jerry Coffin(论缩排)
- 使用
UTF-8
作为源文件的编码。 -
每个缩排层级使用两个空格。不要使用硬 tab。
# 差 - 四个空格 def some_method do_something end # 好 def some_method do_something end
- 使用 Unix 风格的换行符。(BSD/Solaris/Linux/OSX 的用户不用担心,Windows 用户要格外小心。)
- 如果你使用 Git ,可用下面这个配置,来保护你的项目不被 Windows 的换行符干扰:
$ git config --global core.autocrlf true
-
不使用
;
隔开语句和表达式。推论——一行一条语句
。# 差 puts 'foobar'; # 不必要的分号 puts 'foo'; puts 'bar' # 一行里有两个表达式 # 好 puts 'foobar' puts 'foo' puts 'bar' puts 'foo', 'bar' # 仅对 puts 适用
-
对于没有成员的类,尽可能使用单行类定义。
# 差 , 解析器会将这种写法转换成FooError = Class.new(StandardError) class FooError < StandardError end # 勉强可以 class FooError < StandardError; end # 好 FooError = Class.new(StandardError)
-
定义方法时避免单行写法。尽管还是有些人喜欢这么用的。但是单行定义很容易出错,因为它在语法上有些古怪。无论如何——一个单行方法里的表达式不应该多于 1 个。
# 差 def too_much; something; something_else; end # 勉强可以——注意第一个 ; 是必需的 def no_braces_method; body end # 勉强可以——注意第二个 ; 是可选的 def no_braces_method; body; end # 勉强可以——语法上正确,但是没有 ; 让它有些难读 def some_method() body end # 好 def some_method body end
这个规则的一个例外是空方法。
# 好 def no_op; end
-
操作符前后的空格。在逗号
,
、冒号:
及分号;
之后,在{
前后,在}
之前。 Ruby 解释器(大部分情况下)忽略空格。但要写出可读性高的代码,正确使用空格是关键。sum = 1 + 2 a, b = 1, 2 1 > 2 ? true : false; puts 'Hi' [1, 2, 3].each { |e| puts e }
(针对操作符)唯一的例外是当使用指数操作符时:
# 差 e = M * c ** 2 # 好 e = M * c**2
{
和}
需要额外说明,因为他们是用在块(block)、 哈希字面量(hash literals),以及嵌入字符串的表达式中。 对于哈希字面量来说,两种风格都是可接受的。# 好——`{` 之后和 `}` 之前有空格 { one: 1, two: 2 } # 好——`{` 之后和 `}` 之前没有空格 {one: 1, two: 2}
第一个种风格稍微更具可读性(而且有争议的是,一般在 Ruby 社区里更受欢迎)。 第二种风格具有可为块和哈希字面量添加可视化的差别的优点。 无论你选哪一种都行——但是最好保持一致。
至于嵌入表达式(embedded expressions),这儿也有两个可接受的选择:
# 好——没有空格 "string#{expr}" # 可以——更具可读性(有些争议) "string#{ expr }"
第一种风格极为流行,一般其他人都会建议你坚持这种风格。 第二种风格有些更具可读性(虽然颇具争议)。 正如哈希那样——选一种风格并且保持一致。
-
(
、[
之后,]
、)
之前,不要有空格。some(arg).other [1, 2, 3].length
-
!
后不要有空格。# bad ! something # good !something
-
把
when
跟case
缩排在同一层。我知道很多人不同意这一点,但这是《The Ruby Programming Language》及《Programming Ruby》所使用的风格。# 差,更倾向这种表述方式 case when song.name == 'Misty' puts 'Not again!' when song.duration > 120 puts 'Too long!' when Time.now.hour > 21 puts "It's too late" else song.play end # 好 case when song.name == 'Misty' puts 'Not again!' when song.duration > 120 puts 'Too long!' when Time.now.hour > 21 puts "It's too late" else song.play end
-
当赋值一个条件表达式的结果给一个变量时,保持分支的缩排在同一层。
# 差 - 非常复杂 kind = case year when 1850..1889 then 'Blues' when 1890..1909 then 'Ragtime' when 1910..1929 then 'New Orleans Jazz' when 1930..1939 then 'Swing' when 1940..1950 then 'Bebop' else 'Jazz' end # 这里合并为一句, some_cond ? calc_something : calc_something_else result = if some_cond calc_something else calc_something_else end # 好 - 结构很清晰 kind = case year when 1850..1889 then 'Blues' when 1890..1909 then 'Ragtime' when 1910..1929 then 'New Orleans Jazz' when 1930..1939 then 'Swing' when 1940..1950 then 'Bebop' else 'Jazz' end result = if some_cond calc_something else calc_something_else end # 好 ( 避免代码让行宽过长 ) kind = case year when 1850..1889 then 'Blues' when 1890..1909 then 'Ragtime' when 1910..1929 then 'New Orleans Jazz' when 1930..1939 then 'Swing' when 1940..1950 then 'Bebop' else 'Jazz' end result = if some_cond calc_something else calc_something_else end
-
在
def
之间使用空行,并且用空行把方法分成合乎逻辑的段落。def some_method data = initialize(options) data.manipulate! data.result end def some_method result end
-
函数最后一个参数后面不要加逗号,特别是每个参数单独一样的时候
# 差 - 虽然移动和增删参数的时候会很简单,但仍不推荐 some_method( size, count, color, ) # 差 some_method(size, count, color, ) # 好 some_method(size, count, color)
-
当给方法的参数赋默认值时,在
=
两边使用空格:# 差 def some_method(arg1=:default, arg2=nil, arg3=[]) # 做一些任务... end # 好 def some_method(arg1 = :default, arg2 = nil, arg3 = []) # 做一些任务... end
虽然几本 Ruby 书建议用第一个风格,不过第二个风格在实践中更为常见(并可争议地可读性更高一点)。
-
避免在不需要的时候使用行继续符 \ 。实际编码时,除非用于连接字符串, 否则避免在任何情况下使用行继续符。
# 差 result = 1 - \ 2 # 好 (但是仍然丑到爆) result = 1 \ - 2 long_string = 'First part of the long string' \ ' and second part of the long string'
-
使用链式方法时风格统一。社区认为前引点号和末端点号都是好的风格。
- (可选 A)和当一个链式方法调用需要在另一行继续时,将
.
放在第二行。
# 差 - 为了理解第二行需要去查阅第一行 one.two.three. four # 好 - 第二行在做什么立刻变得很清晰 one.two.three .four
- (可选 B)末尾用点号表示表达式没有结束
# bad - need to read ahead to the second line to know that the chain continues one.two.three .four # good - it's immediately clear that the expression continues beyond the first line one.two.three. four
- (可选 A)和当一个链式方法调用需要在另一行继续时,将
两种方法各自优点参阅这里。
-
方法参数过长时,将它对齐排列在多行。当对齐的参数由于线宽不适合对齐时, 简单的在第一行之后缩进也是可以接受的。
# 初始(行太长了) def send_mail(source) Mailer.deliver(to: 'bob@example.com', from: 'us@example.com', subject: 'Important message', body: source.text) end # 差(两倍缩排) def send_mail(source) Mailer.deliver( to: 'bob@example.com', from: 'us@example.com', subject: 'Important message', body: source.text) end # 好 def send_mail(source) Mailer.deliver(to: 'bob@example.com', from: 'us@example.com', subject: 'Important message', body: source.text) end # 好(普通缩排) def send_mail(source) Mailer.deliver( to: 'bob@example.com', from: 'us@example.com', subject: 'Important message', body: source.text) end
-
用字面量构建数组时,如果跨行,应对齐。
# 差 - 未对齐 menu_item = ['Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Baked beans', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam'] # 好 menu_item = [ 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Baked beans', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam' ] # good menu_item = ['Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Baked beans', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam']
-
大数字添加下划线来改善可读性。
# 差 - 有几个零? num = 1000000 # 好 - 更容易被人脑解析。 num = 1_000_000
- 使用 RDoc 以及它的惯例来撰写 API 文档。注解区块及
def
不要用空行隔开。 - 每一行限制在 80 个字符内,我要省空间,狂超80字。
- 避免行尾空格。
- 文件以空白行结尾。
-
不要使用区块注释。它们不能由空白引导(
=begin
必须顶头开始),并且不如普通注释容易辨认。# 差 = begin 一行注释 另一行注释 = end # 好 # 一行注释 # 另一行注释
语法
-
使用
::
引用常量(包括类和模块)和构造器 (比如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_arguments arg1, arg2 # 此处省略方法体 # 好 def some_method_with_arguments(arg1, arg2) # 此处省略方法体 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 是表达式的特性
```Ruby # 差 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
-
循环后条件判断使用
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 # 这样看起来更像申明式的dsl # 忽略 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
-
如果方法调用不需要参数,那么省略圆括号。
# bad Kernel.exit!() 2.even?() fork() 'test'.upcase() # good Kernel.exit! 2.even? fork 'test'.upcase
-
单行区块倾向使用
{...}
而不是do...end
。多行区块避免使用{...}
(多行串连总是丑陋)。在do...end
、 “控制流程”及“方法定义”,永远使用do...end
(如 Rakefile 及某些 DSL)。串连时避免使用do...end
。names = ['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 { |name| name.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) # Dir的chdir方法本身接受块 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 访问器时会需要用到。读得时候不需要,写的时候需要,这是为了避免歧义。status = :in_progress
本身可以看作局部变量赋值)# 差 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 为 nil 或 false 时,把名字设为 Bozhidar。 name ||= 'Bozhidar'
-
不要使用
||=
来初始化布尔变量。 (想看看如果现在的值刚好是false
时会发生什么。)# 差——会把 `enabled` 设成真,即便它本来是假。 enabled ||= true # 好 enabled = true if enabled.nil?
-
使用
&&=
可先检查是否存在变量,如果存在则做相应动作。这样就无需用if
检查变量是否存在了。# 差 if something something = something.downcase end # 差 something = something ? nil : something.downcase # 可以 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
库提供的友好别名。# bad $:.unshift File.dirname(__FILE__) # __FILE__类型的变量给人以迷惑 # good 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
-
用
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 # good result = hash.map { |_k, v| v + 1 } # 块存在的参数需求和相应变量 def something(x) _unused_var, used_var = something_else(x) # ... end # good 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方法是何含义 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
而不是条件区块
```Ruby
# bad
[0, 1, 2, 3].each do |item|
if item > 1
puts item
end
end
# good
[0, 1, 2, 3].each do |item|
next unless item > 1
puts item
end
```
命名
程式设计的真正难题是替事物命名及使缓存失效。
——Phil Karlton
-
标识符用英语命名。
# 差 - 变量名用带有拉丁文的保加利亚语写成。 zaplata = 1_000 # 好 salary = 1_000
-
符号、方法与变量使用蛇底式小写(snake_case)。
# 差 :'some symbol' :SomeSymbol :someSymbol someVar = 5 def someMethod ... end def SomeMethod ... end # 好 :some_symbol def some_method ... end
-
类别与模组使用驼峰式大小写(CamelCase)。(保留类似 HTTP、RFC、XML 这种缩写为大写。)
# 差 class Someclass ... end class Some_Class ... end class SomeXml ... end # 好 class SomeClass ... end class SomeXML ... end
-
文件名用蛇底式小写,如
hello_world.rb
。 -
目录名用蛇底式小写,如
lib/hello_world/hello_world.rb
。 -
每个类/模块都在单独的文件,文件名用蛇底式小写而不是驼峰式大小写。
-
其他常数使用尖叫蛇底式大写(SCREAMING_SNAKE_CASE)。
# 差 SomeConst = 5 # 好 SOME_CONST = 5
-
判断式方法的名字(返回布尔值的方法)应以问号结尾。 (例如:
Array#empty?
)。不返回布尔值的方法不应用问号结尾。 -
有潜在危险性的方法,若此危险方法有安全版本存在时,应以安全版本名加上惊叹号结尾(例如:改动
self
或参数、exit!
(不会向exit
那样运行 finalizers), 等等方法)。 -
如果存在潜在的危险方法(即修改
self
或者参数的方法,不像exit
那样运行 finalizers 的exit!
,等等)的安全版本,那么 * 危险 * 方法的名字应该以惊叹号结尾。# 不好 - 没有对应的安全方法 class Person def update! end end # 好 class Person def update end end # 好 class Person def update! end def update end end
-
如果可能的话,根据危险方法(bang)来定义对应的安全方法(non-bang)。
class Array def flatten_once! res = [] each do |e| [*e].each { |f| res << f } end replace(res) end def flatten_once dup.flatten_once! # dup方法是何含义?? end end
-
在简短区块中使用
reduce
时,把参数命名为|a, e|
(累加器(accumulator
),元素(element
)) -
在定义二元操作符时,把参数命名为
other
(<<
与[]
是这条规则的例外,因为它们的语义不同)。def +(other) # body omitted end
-
倾向使用
map
而不是collect
,find
而不是detect
,select
而不是find_all
,reduce
而不是inject
以及size
而不是length
。这不是一个硬性要求;如果使用别名增加了可读性,使用它没关系。这些有押韵的方法名是从 Smalltalk 继承而来,在别的语言不通用。鼓励使用select
而不是find_all
的理由是它跟reject
搭配起来是一目了然的。 -
不用
count
代替size
。除了Array
,其它Enumerable
对象都需要遍历整个集合才能得到大小。# bad some_hash.count # good 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 { ... }
注释
良好的代码是最佳的文档。当你要加一个注释时,扪心自问,“如何改善代码让它不需要注释?” 改善代码,再写相应文档使之更清楚。
——Steve McConnell
- 编写让人一目了然的代码然后忽略这一节的其它部分。我是认真的!
- 用英语写注释。
#
与注释文字之间使用一个空格。- 注释超过一个单词了,应句首大写并使用标点符号。句号后使用一个空格。
-
避免肤浅的注释。
# 差 counter += 1 # 计数器加一
- 及时更新注释。过时的注解比没有注解还差。
好代码就像是好的笑话 - 它不需要解释
——Russ Olsen
- 避免替烂代码写注释。重构代码让它们看起来一目了然。(要嘛就做,要嘛不做——不要只是试试看。——Yoda)
注解
- 注解应该直接写在相关代码那行之前。
- 注解关键字后面,跟着一个冒号及空格,接着是描述问题的文字。
-
如果需要用多行来描述问题,后续行要放在
#
号后面并缩排两个空格。def bar # FIXME: 这在 v3.2.1 版本之后会异常崩溃,或许与 # BarBazUtil 的版本更新有关 baz(:quux) end
-
在问题是显而易见的情况下,任何的文档会是多余的,注解应放在有问题的那行的最后,并且不需更多说明。这个用法应该是例外而不是规则。
def bar sleep 100 # OPTIMIZE end
- 使用
TODO
标记以后应加入的特征与功能。 - 使用
FIXME
标记需要修复的代码。 - 使用
OPTIMIZE
标记可能影响性能的缓慢或效率低下的代码。 - 使用
HACK
标记代码异味,即那些应该被重构的可疑编码习惯。 - 使用
REVIEW
标记需要确认其编码意图是否正确的代码。举例来说:REVIEW:
我们确定用户现在是这么做的吗? - 如果你觉得恰当的话,可以使用其他定制的注解关键字,但别忘记录在项目的
README
或类似文档中。
类与模块
-
在类别定义里使用一致的结构, 这个结构很不错。
class Person # 首先是 extend 与 include extend SomeModule include AnotherModule # 接着是常量 SOME_CONSTANT = 20 # 接下来是属性宏 attr_reader :name # 跟着是其它的宏(如果有的话) validates :name # 公开的类别方法接在下一行 def self.some_method end # 跟着是公开的实例方法 def some_method end # 受保护及私有的方法,一起放在接近结尾的地方 protected def some_protected_method end private def some_private_method end end
-
如果某个类需要多行代码,则不要嵌套在其它类中。应将其独立写在文件中,存放以包含它的类的的名字命名的文件夹中。– java中的类强制写在一行代码中
# 差 # foo.rb class Foo class Bar # 30个方法 end class Car # 20个方法 end # 30个方法 end # 好 # foo.rb class Foo # 30个方法 end # foo/bar.rb class Foo class Bar # 30个方法 end end # foo/car.rb class Foo class Car # 20个方法 end end
-
倾向使用模块,而不是只有类别方法的类。类别应该只在产生实例是合理的时候使用。
# 差 class SomeClass def self.some_method # 省略函数体 end def self.some_other_method end end # 好 module SomeClass module_function def some_method # 省略函数体 end def some_other_method end end
-
当你想将模块的实例方法变成类别方法时,偏爱使用
module_function
胜过extend self
。 第一次看到这种表述。# 差 module Utilities extend self def parse_something(string) # 做一些事 end def other_utility_method(number, string) # 做另一些事 end end # 好 module Utilities module_function def parse_something(string) # 做一些事 end def other_utility_method(number, string) # 做另一些事 end end
- 当设计类型层级时,确认它们符合 Liskov 替换原则。
- 尽可能让你的类型越 SOLID 越好。
-
永远替类型提供一个适当的
to_s
方法给来表示领域模型。class Person attr_reader :first_name, :last_name def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end # 必须实现的方法 def to_s "#{@first_name #@last_name}" end end
-
使用
attr
系列函数来定义琐碎的访问器或 mutators。# 差 class Person def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end def first_name @first_name end def last_name @last_name end end # 好,ruby语言本身带来的惯例约束 class Person attr_reader :first_name, :last_name def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end end
-
不要使用
attr
。使用attr_reader
,attr_accessor
以及attr_writter
。# 差 - ruby 1.9 中就不推荐了 attr :something, true attr :one, :two, :three # behaves as attr_reader # 好 attr_accessor :something attr_reader :one, :two, :three
-
考虑使用
Struct.new
,它替你定义了那些琐碎的访问器(accessors),构造器(constructor)以及比较操作符(comparison operators)。# 好 class Person attr_reader :first_name, :last_name def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end end # 更好, `Struct.new`方法是啥,难道是结构体 Person = Struct.new(:first_name, :last_name) do end
- 不要扩展
Struct.new
。它已经是个类了。对它扩展不但引入了无意义的类的层次也会在该文件多次被require时出现奇怪的错误。 -
考虑加入
工厂方法
以提供附加的有意义的方式来生成一个特定的类实例。设计模式之工厂方法class Person def self.create(options_hash) # body omitted end end
-
倾向使用鸭子类型 而不是继承。
## 差 class Animal # 抽象方法 def speak end end # 继承超类 class Duck < Animal def speak puts 'Quack! Quack' end end # 继承超类 class Dog < Animal def speak puts 'Bau! Bau!' end end ## 好 class Duck def speak puts 'Quack! Quack' end end class Dog def speak puts 'Bau! Bau!' end end
-
由于类变量在继承中产生的“讨厌的”行为,避免使用类变量(
@@
)。class Parent @@class_var = 'parent' def self.print_class_var puts @@class_var end end class Child < Parent @@class_var = 'child' end Parent.print_class_var # => will print "child"
如同你所看到的,在类型层级中的所有类其实都共享单独一个类变量。通常情况下应该倾向使用实例变量而不是类变量。注:不太明白,类变量有何用处。
- 依据方法的目的用途指定适当的可见层级(
private
,protected
)。别把所有方法都设为public
(方法的缺省值)。我们现在是在写“Ruby”,不是“Python”。 -
将
public
,protected
,private
和被应用的方法定义保持一致的缩排。在上下各留一行来强调这个可见性应用于之后的所有方法。class SomeClass def public_method # ... end private def private_method # ... end def another_private_method # ... end end
-
使用
def self.method
来定义 Singleton 方法。在代码重构时如果修改类名也无需重复多次修改了。class TestClass # 差 def TestClass.some_method # 省略方法体 end # 好 def self.some_other_method # 省略方法体 end # 另一种便捷的方式,单件方法有何用处 class << self def first_method # 省略方法体 end def second_method_etc # 省略方法体 end end end
异常
-
使用
fail
方法来抛出异常。仅在捕捉到异常时使用raise
来重新抛出异常(因为没有失败,所以只是显式地有目的性地抛出一个异常)begin fail 'Oops'; rescue => error raise if error.message != 'Oops' end
-
如果
fail/raise
只有两个参数,无需显性指定RuntimeError
。# 差 fail RuntimeError, 'message' # 好——默认就是 RuntimeError fail 'message'
-
将异常类和消息作为参数给
fail/raise
,而不是异常类的的实例。# 差 fail SomeException.new('message') # 无法使用 `fail SomeException.new('message'), backtrace`. # 好 fail SomeException, 'message' # 可以使用 `fail SomeException, 'message', backtrace`.
-
永远不要从
ensure
区块返回。如果你显式地从ensure
区块中的一个方法返回,那么这方法会如同没有异常般的返回。实际上,异常会被默默丢掉。def foo begin fail ensure return 'very bad idea' end end
-
尽可能使用隐式的
begin
区块。# 差 def foo begin # 此处放主要逻辑 rescue # 错误处理放在此处 end end # 好 def foo # 此处放主要逻辑 rescue # 错误处理放在此处 end
-
通过 contingency 方法 (一个由 Avdi Grimm 创造的词) 来减少
begin
区块的使用。# 差 begin something_that_might_fail rescue IOError # 处理 IOError end begin something_else_that_might_fail rescue IOError # 处理 IOError end # 好 def with_io_error_handling yield rescue IOError # 处理 IOError, 这里可能表示,特定异常的处理逻辑类似 end with_io_error_handling { something_that_might_fail } with_io_error_handling { something_else_that_might_fail }
-
不要抑制异常。
begin # 这里发生了一个异常 rescue SomeError # 拯救子句完全没有做事 end # 差 do_something rescue nil
-
避免使用
rescue
的修饰符形式。# 差 - 这捕捉了所有的 StandardError 异常。 do_something rescue nil
-
不要为了控制流程而使用异常。
# 差 begin n / d rescue ZeroDivisionError puts 'Cannot divide by 0!' end # 好 if d.zero? puts 'Cannot divide by 0!' else n / d end
-
避免救援
Exception
类别。这会把信号困住,并呼叫exit
,导致你需要kill -9
进程。# 差 begin # 呼叫 exit 及杀掉信号会被捕捉(除了 kill -9) exit rescue Exception puts "you didn't really want to exit, right?" # 异常处理 end # 好 begin # 一个不明确的 rescue 子句捕捉的是 StandardError, # 而不是许多编程者所设想的 Exception。 rescue => e # 异常处理 end # 也好 begin # 这里发生一个异常 rescue StandardError => e # 异常处理 end
-
把较具体的异常放在救援串连的较上层,不然它们永远不会被拯救。这是代码规则还是语法描述。
# 差 begin # 一些代码 rescue Exception => e # 一些处理 rescue StandardError => e # 一些处理 end # 好 begin # 一些代码 rescue StandardError => e # 一些处理 rescue Exception => e # 一些处理 end
-
在
ensure
区块中释放你的程式的外部资源。f = File.open('testfile') begin # .. 处理 rescue # .. 错误处理 ensure f.close unless f.nil? end
-
用
ensure
区块释放程序使用的外部资源。f = File.open('testfile') begin # .. process rescue # .. handle error ensure f.close unless f.nil? end
-
倾向使用标准库的异常类而不是导入新的异常类,这需要更多对标准库的理解。
集合
-
倾向数组及哈希的字面表示法(除非你需要给构造器传入参数)。
# 差 arr = Array.new hash = Hash.new # 好 arr = [] hash = {}
-
创建元素为单词(没有空格和特殊符号)的数组时,用
%w
而不是 [] 方法。仅当数组有两个及以上元素时才应用这个规则。# 差 STATES = ['draft', 'open', 'closed'] # 好 STATES = %w(draft open closed)
-
当你需要一个符号的数组(并且不需要保持 Ruby 1.9 兼容性)时,使用
%i
。仅当数组只有两个及以上元素时才应用这个规则。# 差 STATES = [:draft, :open, :closed] # 好 STATES = %i(draft open closed)
-
避免在
Array
和Hash
字面量中的最后一个元素后面使用逗号。特别是元素同一行的情况下# 差 - 方面移动、增加和修改参数,但仍不建议使用 VALUES = [ 1001, 2020, 3333, ] # 差 VALUES = [1001, 2020, 3333, ] # 好 VALUES = [1001, 2020, 3333]
-
避免在数组中创造巨大的间隔。
arr = [] arr[100] = 1 # 现在你有一个很多 nil 的数组
-
当处理的元素没有重复时,使用
Set
来替代Array
。Set
实现了无序、无重复值的集合。Set
的方法同数组类一样直观,还可像哈希中那样快速查找元素。 -
尽量用符号来取代字符串作为哈希的键。
# 差 hash = { 'one' => 1, 'two' => 2, 'three' => 3 } # 好 hash = { one: 1, two: 2, three: 3 }
-
避免使用可变的对象作为键值,键值应当是不可变的。
-
当哈希的键为符号时,使用 Ruby 1.9 的哈希的字面语法。
# 差 hash = { :one => 1, :two => 2, :three => 3 } # 好 hash = { one: 1, two: 2, three: 3 }
-
但哈希的键有符号也有字符串时,不使用Ruby 1.9的字面量语法。
# 差 { a: 1, 'b' => 2 } # 好 { :a => 1, 'b' => 2 }
-
用
Hash#key?
。不用Hash#has_key?
。用Hash#value?
。不用Hash#has_value?
。松本提到过已经不推荐使用较长的形式了。# 差 hash.has_key?(:test) hash.has_value?(value) # 好 hash.key?(:test) hash.value?(value)
-
在处理应该存在的哈希键时,使用
fetch
。heroes = { batman: 'Bruce Wayne', superman: 'Clark Kent' } # 差 - 如果我们打错字的话,我们就无法找到对的英雄了 heroes[:batman] # => "Bruce Wayne" heroes[:supermen] # => nil # 好 - fetch 会抛出一个 KeyError 来使这个问题明显 heroes.fetch(:supermen)
-
在使用
fetch
时,使用第二个参数设置默认值。batman = { name: 'Bruce Wayne', is_evil: false } # 差 - 如果我们仅仅使用 || 操作符,那么当值为假时,我们不会得到预期的结果 batman[:is_evil] || true # => true # 好 - fetch 在遇到假值时依然正确 batman.fetch(:is_evil, true) # => false
-
尽量用
fetch
加区块而不是直接设定默认值。batman = { name: 'Bruce Wayne' } # 差 - 默认值是立即求值 batman.fetch(:powers, get_batman_powers) # get_batman_powers 需要复杂的计算 # 好 - 区块是惰性求职,只有当 KeyError 异常时才执行 batman.fetch(:powers) { get_batman_powers }
-
Ruby 1.9 的哈希是有序的,利用这个特性。
-
在遍历一个集合时,不要改动它。
字符串
-
尽量使用字符串插值(interpolation),而不是字符串连接(concatenation)。
# 差 email_with_name = user.name + ' <' + user.email + '>' # 好 email_with_name = "#{user.name} <#{user.email}>"
-
考虑替字符串插值留白。這使插值在字符串里看起來更清楚。
"#{ user.last_name }, #{ user.first_name }"
-
选定一个字符串字面量创建的风格。Ruby 社区认可两种分割,默认用单引号(风格 A)和默认用双引号(风格 B)
(风格 A)当你不需要插入特殊符号如
\t
,\n
,'
, 等等时,尽量使用单引号的字符串。# 差 name = "Bozhidar" # 好 name = 'Bozhidar'
(风格 B) 用双引号。除非字符串中含有双引号,或者含有你希望抑制的转义字符。
# 差 name = 'Bozhidar' # 好 name = "Bozhidar"
-
不要用
?x
。从 Ruby 1.9 开始,?x
和'x'
是等价的(只包括一个字符的字符串)。# bad char = ?c # good char = 'c'
-
别忘了使用
{}
来围绕被插入字符串的实例与全局变量。class Person attr_reader :first_name, :last_name def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end # 差 - 有效,但难看 def to_s "#@first_name #@last_name" end # 好 def to_s "#{@first_name} #{@last_name}" end end $global = 0 # 差 puts "$global = #$global" # 好 puts "$global = #{$global}"
-
字符串插值不要用
Object#to_s
。Ruby 默认会调用该方法。# bad message = "This is the #{result.to_s}." # good message = "This is the #{result}."
-
当你需要建构庞大的数据块(chunk)时,避免使用
String#+
。 使用String#<<
来替代。<<
就地改变字符串实例,因此比String#+
来得快。String#+
创造了一堆新的字符串对象。# 好也比较快,构建生成HTML数据的时候使用`<<`追加新的字符串。 html = '' html << '<h1>Page title</h1>' paragraphs.each do |paragraph| html << "<p>#{paragraph}</p>" end
-
heredocs 中的多行文字会保留前缀空白。因此做好如何缩进的规划。
code = <<-END.gsub(/^\s+\|/, '') |def test | some_method | other_method |end END #=> "def\n some_method\n \nother_method\nend"
正则表达式
有些人在面对问题时,不经大脑便认为,「我知道,这里该用正则表达式」。现在他要面对两个问题了。
——Jamie Zawinski
-
如果只需要在字符串中简单的搜索文字,不要使用正则表达式:
string['text']
。 -
针对简单的字符串查询,可以直接在字符串索引中直接使用正则表达式。
match = string[/regexp/] # 获得匹配正则表达式的内容 first_group = string[/text(grp)/, 1] # 或得分组的内容 string[/text (grp)/, 1] = 'replace' # string => 'text replace'
-
当你不需要替结果分组时,使用非分组的表达式
/(first|second)/ # 差 /(?:first|second)/ # 好
-
不要使用 Perl 遗风的变量来表示捕获的正则分组(如
$1
、$2
等),它们看起来神神秘秘的。使用Regexp.last_match[n]
。/(regexp)/ =~ string ... # 差 process $1 # 好 process Regexp.last_match[1]
-
避免使用数字来获取分组。因为很难明白他们代表的意思。应该使用命名群组来替代。
# 差 /(regexp)/ =~ string ... process Regexp.last_match[1] # 好 /(?<meaningful_var>regexp)/ =~ string ... process meaningful_var
-
字符类别只有几个你需要关心的特殊字符:
^
、-
、\、]
,所以你不用转义[]
中的.
或中括号。 -
小心使用
^
与$
,它们匹配的是一行的开始与结束,不是字符串的开始与结束。如果你想要匹配整个字符串,使用\A
与\z
。(译注:\Z
实为/\n?\z/
,使用\z
才能匹配到有含新行的字符串的结束)string = "some injection\nusername" string[/^username$/] # 匹配 string[/\Ausername\z/] # 不匹配
-
针对复杂的正则表达式,使用
x
修饰符。可提高可读性并可以加入有用的注释。只是要注意空白字符会被忽略。regexp = %r{ start # 一些文字 \s # 空白字元 (group) # 第一组 (?:alt1|alt2) # 一些替代方案 end }x
-
针对复杂的替换,
sub
或gsub
可以与区块或哈希结合使用。
百分比字面
-
需要插值与嵌入双引号的单行字符串使用
%()
(是%Q
的简写)。多行字符串,最好用 heredocs 。# 差(不需要插值) %(<div class="text">Some text</div>) # 应该使用 '<div class="text">Some text</div>' # 差(没有双引号) %(This is #{quality} style) # 应该使用 "This is #{quality} style" # 差(多行) %(<div>\n<span class="big">#{exclamation}</span>\n</div>) # 应该是一个 heredoc # 好(需要插值、有双引号以及单行) %(<tr><td class="name">#{name}</td>)
-
没有
'
和"
的字符串不要使用%q
。除非需要插值,否则普通字符串可读性更好。# 差 name = %q(Bruce Wayne) time = %q(8 o'clock) question = %q("What did you say?") # 好 name = 'Bruce Wayne' time = "8 o'clock" question = '"What did you say?"'
-
只有正则表达式要匹配多于一个的
/
字元时,使用%r
。# 差 %r(\s+) # 仍然差 %r(^/(.*)$) # 应当是 /^\/(.*)$/ # 好 %r(^/blog/2011/(.*)$)
-
除非调用的命令中用到了反引号(这种情况不常见),否则不要用
%x
。# 差 date = %x(date) # 好 date = `date` echo = %x(echo `date`)
-
不要用
%s
。社区倾向使用:"some string"
来创建含有空白的符号。 -
用
%
表示字面量时使用()
,%r
除外。因为(
在正则中比较常用。# 差 %w[one two three] %q{"Test's king!", John said.} # 好 %w(one tho three) %q("Test's king!", John said.)
元编程
-
避免无谓的元编程。
-
写一个函数库时不要使核心类混乱(不要使用 monkey patch)。
- 倾向使用区块形式的
class_eval
而不是字符串插值(string-interpolated)的形式。-
当你使用字符串插值形式时,总是提供
__FILE__
及__LINE__
,使你的 backtrace 看起来有意义:class_eval "def use_relative_model_naming?; true; end", __FILE__, __LINE__
-
倾向使用
define_method
而不是class_eval{ def ... }
-
-
当使用
class_eval
(或其它的eval
)搭配字符串插值时,添加一个注解区块,来演示如果做了插值的样子(我从 Rails 代码学来的一个实践):# activesupport/lib/active_support/core_ext/string/output_safety.rb UNSAFE_STRING_METHODS.each do |unsafe_method| if 'String'.respond_to?(unsafe_method) class_eval <<-EOT, __FILE__, __LINE__ + 1 def #{unsafe_method}(*args, &block) # def capitalize(*args, &block) to_str.#{unsafe_method}(*args, &block) # to_str.capitalize(*args, &block) end # end def #{unsafe_method}!(*args) # def capitalize!(*args) @dirty = true # @dirty = true super # super end # end EOT end end
- 元编程避免使用
method_missing
。会让 Backtraces 变得很凌乱;行为没有列在#methods
里;拼错的方法调用可能默默的工作(nukes.launch_state = false
)。考虑使用 delegation, proxy, 或是define_method
来取代。如果你必须使用method_missing
,- 确保 也定义了
respond_to_missing?
- 仅捕捉字首定义良好的方法,像是
find_by_*
——让你的代码愈肯定(assertive) 愈好。 - 在语句的最后调用
super
-
delegate 到确定的、非魔法方法中:
# 差 def method_missing?(meth, *args, &block) if /^find_by_(?<prop>.*)/ =~ meth # ... lots of code to do a find_by else super end end # 好 def method_missing?(meth, *args, &block) if /^find_by_(?<prop>.*)/ =~ meth find_by(prop, *args, &block) else super end end # 最好的方式,可能是每个可找到的属性被声明后,使用 define_method。
- 确保 也定义了
其它
ruby -w
写安全的代码。- 避免使用哈希作为可选参数。这个方法是不是做太多事了?(对象初始器是本规则的例外)。
- 避免方法长于 10 行代码(LOC)。理想上,大部分的方法会小于 5 行。空行不算进 LOC 里。
- 避免参数列表长于三或四个参数。
- 如果你真的需要“全局”方法,把它们加到 Kernel 并设为私有的。
-
使用模块变量代替全局变量。
# 差 $foo_bar = 1 # 好 module Foo class << self attr_accessor :bar end end Foo.bar = 1
- 当
alias_method
可以做到时,避免使用alias
。 - 使用
OptionParser
来解析复杂的命令行选项及ruby -s
来处理琐碎的命令行选项。 - 使用
Time.now
而不是Time.new
来获取系统时间。 - 用函数式的方法编程,在有意义的情况下避免赋值 (mutation)。
- 不要改变参数,除非那是方法的目的。
- 避免超过三层的区块嵌套。
- 保持一致性。在理想的世界里,遵循这些准则。
- 使用常识。
工具
以下是一些工具,让你自动检查 Ruby 代码是否符合本指南。
RuboCop
RuboCop 是一个基于本指南的 Ruby 代码风格检查工具。 RuboCop 涵盖了本指南相当大的部分,支持 MRI 1.9 和 MRI 2.0,而且与 Emacs 整合良好。
RubyMine
授权
This work is licensed under a Creative Commons Attribution 3.0 Unported License
傲娇的使用Disqus