# ruby-style-guide
**Repository Path**: frech/ruby-style-guide
## Basic Information
- **Project Name**: ruby-style-guide
- **Description**: The Ruby Style Guide of Airbnb
- **Primary Language**: Unknown
- **License**: MIT
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 2
- **Forks**: 0
- **Created**: 2016-12-29
- **Last Updated**: 2021-03-24
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
原链接: https://github.com/JuanitoFatas/ruby-style-guide
Airbnb的ruby style。
# 序幕
> 榜样很重要。
> ——墨菲警官《机器战警》
身为 Ruby 开发者,有件总是令我烦心的事——Python 开发者有一份好的编程风格参考指南([PEP-8][])而我们永远没有一份官方指南,一份记录 Ruby 编程风格及最佳实践的指南。我确信风格很重要。我也相信像 Ruby 这样的黑客社区应该可以自己编写这梦寐以求的文档。
这份指南开始是作为我们公司内部的 Ruby 编程指南(由我所写的)。后来,我决定把成果贡献给广大的 Ruby 社区,况且这个世界再多一份公司内部文档又有何意义。然而由社区制定及策动的一系列 Ruby 编程实践、惯例及风格确能让世界受益。
从编写这份指南开始,我收到了来自世界各地优秀 Ruby 社区的很多用户反馈。衷心感谢所有建议及帮助!同心协力,我们能创造出让每一个 Ruby 开发者受益的资源。
顺道一提,如果你对 Rails 感兴趣,你可以看看这份 [Ruby on Rails 风格指南][rails-style-guide]。
# Ruby 风格指南
这份 Ruby 风格指南推荐的是 Ruby 的最佳实践,现实世界中的 Ruby 程序员据此可以写出可维护的高质量代码。我们只说实际使用中的用法。指南再好,但里面说的过于理想化结果大家拒绝使用或者可能根本没人用,又有何意义。
本指南分为几个小节,每一小节由几条相关的规则构成。我尽力在每条规则后面说明理由(如果省略了说明,那是因为其理由显而易见)。
这些规则不是我凭空想象出来的——它们中的绝大部分来自我多年以来作为职业软件工程师的经验,来自 Ruby 社区成员的反馈和建议,以及几个评价甚高的 Ruby 编程资源,像[《Programming Ruby》][pickaxe]以及[《The Ruby Programming Language》][trpl]。
Ruby 社区尚未就某些规则达成明显的共识,比如字符串字面量的引号、哈希字面量两端是否应该添加空格、多行链式方法调用中 `.` 操作符的位置。对于这种情况,本指南列出了所有可选的流行风格,你可以任选其一并坚持使用。
本指南会一直更新,随着 Ruby 本身的发展,新的规则会添加进来,过时的规则会被剔除。
许多项目有其自己的编程风格指南(往往是源于本指南而创建)。当项目的风格指南与本指南发生冲突时,应以项目级的指南为准。
你可以使用 [Pandoc][] 生成本指南的 PDF 或 HTML 版本。
[RuboCop][] 工具会自动检查你的 Ruby 代码是否符合这份 Ruby 风格指南。
本指南有以下翻译版本:
* [简体中文](https://github.com/JuanitoFatas/ruby-style-guide/blob/master/README-zhCN.md)
* [繁体中文](https://github.com/JuanitoFatas/ruby-style-guide/blob/master/README-zhTW.md)
* [法文](https://github.com/gauthier-delacroix/ruby-style-guide/blob/master/README-frFR.md)
* [德文](https://github.com/arbox/de-ruby-style-guide/blob/master/README-deDE.md)
* [日文](https://github.com/fortissimo1997/ruby-style-guide/blob/japanese/README.ja.md)
* [韩文](https://github.com/dalzony/ruby-style-guide/blob/master/README-koKR.md)
* [葡萄牙文](https://github.com/rubensmabueno/ruby-style-guide/blob/master/README-PT-BR.md)
* [俄文](https://github.com/arbox/ruby-style-guide/blob/master/README-ruRU.md)
* [西班牙文](https://github.com/alemohamad/ruby-style-guide/blob/master/README-esLA.md)
* [越南文](https://github.com/scrum2b/ruby-style-guide/blob/master/README-viVN.md)
## 目录
* [源代码排版](#源代码排版)
* [语法](#语法)
* [命名](#命名)
* [注释](#注释)
* [注解](#注解)
* [类与模块](#类与模块)
* [异常](#异常)
* [集合](#集合)
* [数值](#数值)
* [日期与时间](#日期与时间)
* [字符串](#字符串)
* [正则表达式](#正则表达式)
* [百分号字面量](#百分号字面量)
* [元编程](#元编程)
* [其他](#其他)
* [工具](#工具)
## 源代码排版
> 所有风格都又丑又难读,自己的除外。几乎人人都这样想。把“自己的除外”拿掉,他们或许是对的...
> ——Jerry Coffin(论缩排)
*
使用 `UTF-8` 作为源文件的编码。
[[link](#utf-8)]
*
每个缩排层级使用两个**空格**。不要使用制表符。
[[link](#spaces-indentation)]
```Ruby
# 差 - 四个空格
def some_method
do_something
end
# 好
def some_method
do_something
end
```
*
使用 Unix 风格的换行符。(\*BSD/Solaris/Linux/OS X 系统的用户不需担心,Windows 用户则要格外小心。)
[[link](#crlf)]
* 如果你使用 Git,可用下面这个配置来保护你的项目不被 Windows 的换行符干扰:
```bash
$ git config --global core.autocrlf true
```
*
不要使用 `;` 隔开语句与表达式。推论:一行一条语句。
[[link](#no-semicolon)]
```Ruby
# 差
puts 'foobar'; # 不必要的分号
puts 'foo'; puts 'bar' # 一行里有两个表达式
# 好
puts 'foobar'
puts 'foo'
puts 'bar'
puts 'foo', 'bar' # 仅对 puts 适用
```
*
对于没有主体的类,倾向使用单行定义。
[[link](#single-line-classes)]
```Ruby
# 差
class FooError < StandardError
end
# 勉强可以
class FooError < StandardError; end
# 好
FooError = Class.new(StandardError)
```
*
定义方法时,避免单行写法。尽管这种写法有时颇为普遍,但其略显古怪的定义语法容易使人犯错。无论如何,至少保证单行写法的方法不应该拥有一个以上的表达式。
[[link](#no-single-line-methods)]
```Ruby
# 差
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
```
这个规则的一个例外是空方法。
```Ruby
# 好
def no_op; end
```
*
操作符前后适当地添加空格,在逗号 `,`、冒号 `:` 及分号 `;` 之后。尽管 Ruby 解释器(大部分情况下)会忽略空格,但适量的空格可以增强代码的可读性。
[[link](#spaces-operators)]
```Ruby
sum = 1 + 2
a, b = 1, 2
class FooError < StandardError; end
```
(对于操作符)唯一的例外是当使用指数操作符时:
```Ruby
# 差
e = M * c ** 2
# 好
e = M * c**2
```
*
`(`、`[` 之后,`]`、`)` 之前,不要添加任何空格。在 `{` 前后,在 `}` 之前添加空格。
[[link](#spaces-braces)]
```Ruby
# 差
some( arg ).other
[ 1, 2, 3 ].each{|e| puts e}
# 好
some(arg).other
[1, 2, 3].each { |e| puts e }
```
`{` 与 `}` 需要额外说明,因为它们可以同时用在区块、哈希字面量及字符串插值中。
对于哈希字面量,有两种可被接受的风格。第一种风格更具可读性(在 Ruby 社区里似乎更为流行)。第二种风格的优点是,在视觉上使得区块与哈希字面量有所区分。无论你选择何种风格,务必在使用时保持连贯性。
```Ruby
# 好 - { 之后 与 } 之前有空格
{ one: 1, two: 2 }
# 好 - { 之后 与 } 之前无空格
{one: 1, two: 2}
```
对于插值表达式,括号内两端不要添加空格。
```Ruby
# 差
"From: #{ user.first_name }, #{ user.last_name }"
# 好
"From: #{user.first_name}, #{user.last_name}"
```
*
`!` 之后,不要添加任何空格。
[[link](#no-space-bang)]
```Ruby
# 差
! something
# 好
!something
```
*
范围的字面量语法中,不要添加任何空格。
[[link](#no-space-inside-range-literals)]
```Ruby
# 差
1 .. 3
'a' ... 'z'
# 好
1..3
'a'...'z'
```
*
把 `when` 与 `case` 缩排在同一层级。这是《Programming Ruby》与《The Ruby Programming Language》中早已确立的风格。
[[link](#indent-when-to-case)]
```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
```
*
当将一个条件表达式的结果赋值给一个变量时,保持分支缩排在同一层级。
[[link](#indent-conditional-assignment)]
```Ruby
# 差 - 非常费解
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
# 好 - 并且更好地利用行宽
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
```
*
在各个方法定义之间添加空行,并且将方法分成若干合乎逻辑的段落。
[[link](#empty-lines-between-methods)]
```Ruby
def some_method
data = initialize(options)
data.manipulate!
data.result
end
def some_method
result
end
```
*
避免在方法调用的最后一个参数之后添加逗号,尤其当参数没有分布在同一行时。
[[link](#no-trailing-params-comma)]
```Ruby
# 差 - 尽管移动、新增、删除参数颇为方便,但仍不推荐这种写法
some_method(
size,
count,
color,
)
# 差
some_method(size, count, color, )
# 好
some_method(size, count, color)
```
*
当给方法的参数赋予默认值时,在 `=` 前后添加空格。
[[link](#spaces-around-equals)]
```Ruby
# 差
def some_method(arg1=:default, arg2=nil, arg3=[])
# 做一些事情
end
# 好
def some_method(arg1 = :default, arg2 = nil, arg3 = [])
# 做一些事情
end
```
尽管有几本 Ruby 书籍推荐使用第一种风格,但第二种在实践中更为常见(而且似乎更具可读性)。
*
避免在非必要的情形下使用续行符 `\`。在实践中,除了字符串拼接,避免在其他任何地方使用续行。
[[link](#no-trailing-backslash)]
```Ruby
# 差
result = 1 - \
2
# 好 - 但仍然丑到爆
result = 1 \
- 2
long_string = 'First part of the long string' \
' and second part of the long string'
```
*
使用统一的风格进行多行链式方法调用。在 Ruby 社区中存在两种流行的风格:前置 `.`(风格 A)与后置 `.`(风格 B)。
[[link](#consistent-multi-line-chains)]
* **(风格 A)** 当多行链式方法调用需要另起一行继续时,将 `.` 放在第二行开头。
```Ruby
# 差 - 需要查看第一行才能理解第二行在做什么
one.two.three.
four
# 好 - 立刻能够明白第二行在做什么
one.two.three
.four
```
* **(风格 B)** 将 `.` 放在第一行末尾,以表示当前表达式尚未结束。
```Ruby
# 差 - 需要查看第二行才能知道链式方法调用是否结束
one.two.three
.four
# 好 - 立刻能够明白第二行还有其他方法调用
one.two.three.
four
```
两种风格各自优点查阅[这里](https://github.com/bbatsov/ruby-style-guide/pull/176)。
*
当方法调用参数过长时,将它们排列在多行并对齐。若对齐后长度超过行宽限制,将首个参数位置挪到下一行进行缩排也是可以接受的。
[[link](#no-double-indent)]
```Ruby
# 初始(行太长了)
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
```
*
当构建数组时,若元素跨行,应当保持对齐。
[[link](#align-multiline-arrays)]
```Ruby
# 差 - 没有对齐
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'
]
# 好
menu_item =
['Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam',
'Baked beans', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam']
```
*
使用 `_` 语法改善大数的数值字面量的可读性。
[[link](#underscores-in-numerics)]
```Ruby
# 差 - 有几个零?
num = 1000000
# 好 - 方便人脑理解
num = 1_000_000
```
*
当数值需要前缀标识进制时,倾向使用小写字母。使用 `0o` 标识八进制,使用 `0x` 标识十六进制,使用 `0b` 标识二进制。十进制数值无需前缀(`0d`)标识。
[[link](#numeric-literal-prefixes)]
```Ruby
# 差
num = 01234
num = 0O1234
num = 0X12AB
num = 0B10101
num = 0D1234
num = 0d1234
# 好 - 方便区分数值前缀与具体数字
num = 0o1234
num = 0x12AB
num = 0b10101
num = 1234
```
*
使用 [RDoc][rdoc] 及其惯例来编写 API 文档。注意,不要在注释与 `def` 之间添加空行。
[[link](#rdoc-conventions)]
*
将单行长度控制在 80 个字符内。
[[link](#80-character-limits)]
*
避免行尾空格。
[[link](#no-trailing-whitespace)]
*
文件以空白行结束。
[[link](#newline-eof)]
*
不要使用区块注释。它们不能被空白字符引导,且不如常规注释容易辨认。
[[link](#no-block-comments)]
```Ruby
# 差
=begin
comment line
another comment line
=end
# 好
# comment line
# another comment line
```
## 语法
*
使用 `::` 引用常量(包括类与模块)与构造器(比如 `Array()`、`Nokogiri::HTML()`)。不要使用 `::` 调用常规方法。
[[link](#double-colons)]
```Ruby
# 差
SomeClass::some_method
some_object::some_method
# 好
SomeClass.some_method
some_object.some_method
SomeModule::SomeClass::SOME_CONST
SomeModule::SomeClass()
```
*
使用 `def` 定义方法时,如果有参数则使用括号,如果无参数则省略括号。
[[link](#method-parens)]
```Ruby
# 差
def some_method()
# 省略主体
end
# 好
def some_method
# 省略主体
end
# 差
def some_method_with_parameters param1, param2
# 省略主体
end
# 好
def some_method_with_parameters(param1, param2)
# 省略主体
end
```
*
方法调用应当使用括号包裹参数,尤其是第一个参数以 `(` 开头时,比如 `f((3 + 2) + 1)`;
[[link](#method-invocation-parens)]
```Ruby
x = Math.sin y # 差
x = Math.sin(y) # 好
array.delete e # 差
array.delete(e) # 好
temperance = Person.new 'Temperance', 30 # 差
temperance = Person.new('Temperance', 30) # 好
```
但在下述情况下可以省略括号:
* 无参调用
```Ruby
# 差
Kernel.exit!()
2.even?()
fork()
'test'.upcase()
# 好
Kernel.exit!
2.even?
fork
'test'.upcase
```
* 内部 DSL 的组成部分(比如 Rake、Rails、RSpec)
```Ruby
expect(bowling.score).to eq 0 # 差
expect(bowling.score).to eq(0) # 好
```
* 具有“关键字”特性的方法
```Ruby
class Person
attr_reader(:name, :age) # 差
attr_reader :name, :age # 好
# 省略主体
end
puts(temperance.age) # 差
puts temperance.age # 好
```
*
定义可选参数时,将可选参数放置在参数列表尾部。如果可选参数出现在列表头部,则此方法在调用时可能会产生预期之外的结果。
[[link](#optional-arguments)]
```Ruby
# 差
def some_method(a = 1, b = 2, c, d)
puts "#{a}, #{b}, #{c}, #{d}"
end
some_method('w', 'x') # => '1, 2, w, x'
some_method('w', 'x', 'y') # => 'w, 2, x, y'
some_method('w', 'x', 'y', 'z') # => 'w, x, y, z'
# 好
def some_method(c, d, a = 1, b = 2)
puts "#{a}, #{b}, #{c}, #{d}"
end
some_method('w', 'x') # => '1, 2, w, x'
some_method('w', 'x', 'y') # => 'y, 2, w, x'
some_method('w', 'x', 'y', 'z') # => 'y, z, w, x'
```
*
定义变量时,避免并行赋值。但当右值为方法调用返回值,或是与 `*` 操作符配合使用,或是交换两个变量的值,并行赋值也是可以接受的。并行赋值的可读性通常不如分开赋值。
[[link](#parallel-assignment)]
```Ruby
# 差
a, b, c, d = 'foo', 'bar', 'baz', 'foobar'
# 好
a = 'foo'
b = 'bar'
c = 'baz'
d = 'foobar'
# 好 - 交换两个变量的值
a = 'foo'
b = 'bar'
a, b = b, a
puts a # => 'bar'
puts b # => 'foo'
# 好 - 右值为方法调用返回值
def multi_return
[1, 2]
end
first, second = multi_return
# 好 - 与 * 操作符配合使用
first, *list = [1, 2, 3, 4] # first => 1, list => [2, 3, 4]
hello_array = *'Hello' # => ["Hello"]
a = *(1..3) # => [1, 2, 3]
```
*
除非必要,否则避免在并行赋值时使用单字符的 `_` 变量。优先考虑前缀形式的下划线变量,而不是直接使用 `_`,因为前者可以提供一定的语义信息。但当赋值语句左侧出现带 `*` 操作符的变量时,使用 `_` 也是可以接受的。
[[link]](#trailing-underscore-variables)
```Ruby
foo = 'one,two,three,four,five'
# 差 - 可有可无,且无任何有用信息
first, second, _ = foo.split(',')
first, _, _ = foo.split(',')
first, *_ = foo.split(',')
# 好
a, = foo.split(',')
a, b, = foo.split(',')
# 好 - 可有可无,但提供了额外信息
first, _second = foo.split(',')
first, _second, = foo.split(',')
first, *_ending = foo.split(',')
# 好 - 占位符,_ 担当最后一个元素
*beginning, _ = foo.split(',')
*beginning, something, _ = foo.split(',')
```
*
永远不要使用 `for`, 除非你很清楚为什么。大部分情况下,你应该使用迭代器。`for` 是由 `each` 实现的,所以你绕弯了。另外,`for` 没有引入一个新的作用域 (`each` 有),因此在它内部定义的变量在外部仍是可见的。
[[link](#no-for-loops)]
```Ruby
arr = [1, 2, 3]
# 差
for elem in arr do
puts elem
end
# 注意,elem 可在 for 循环外部被访问
elem # => 3
# 好
arr.each { |elem| puts elem }
# 注意,elem 不可在 each 块外部被访问
elem # => NameError: undefined local variable or method `elem'
```
*
永远不要在多行 `if`/`unless` 中使用 `then`。
[[link](#no-then)]
```Ruby
# 差
if some_condition then
# 省略主体
end
# 好
if some_condition
# 省略主体
end
```
*
在多行 `if/unless` 中,总是把条件表达式与 `if/unless` 放置在同一行。
[[link](#same-line-condition)]
```Ruby
# 差
if
some_condition
do_something
do_something_else
end
# 好
if some_condition
do_something
do_something_else
end
```
*
倾向使用三元操作符(`?:`)而不是 `if/then/else/end` 结构。前者更为常见且简练。
[[link](#ternary-operator)]
```Ruby
# 差
result = if some_condition then something else something_else end
# 好
result = some_condition ? something : something_else
```
*
三元操作符的每个分支只写一个表达式。即不要嵌套三元操作符。嵌套情况请使用 `if/else` 结构。
[[link](#no-nested-ternary)]
```Ruby
# 差
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; ...`。使用三元操作符来替代。
[[link](#no-semicolon-ifs)]
```Ruby
# 差
result = if some_condition; something else something_else end
# 好
result = some_condition ? something : something_else
```
*
利用“`if` 与 `case` 是表达式”的这个特性。
[[link](#use-if-case-returns)]
```Ruby
# 差
if condition
result = x
else
result = y
end
# 好
result =
if condition
x
else
y
end
```
*
在 `case` 表达式中,单行情况使用 `when x then ...` 语法。另一种语法 `when x: ...` 在 Ruby 1.9 之后被移除了。
[[link](#one-line-cases)]
*
不要使用 `when x; ...` 语法。参考前一条规则。
[[link](#no-when-semicolons)]
*
使用 `!` 而不是 `not`。
[[link](#bang-not-not)]
```Ruby
# 差 - 因为操作符的优先级,这里必须使用括号
x = (not something)
# 好
x = !something
```
*
避免使用 `!!`。
[[link](#no-bang-bang)]
`!!` 会将表达式结果转换为布尔值,但对于流程控制的表达式通常并不需要如此显式的转换过程。如果需要做 `nil` 检查,那么调用对象的 `nil?` 方法。
```Ruby
# 差
x = 'test'
# 令人费解的 nil 检查
if !!x
# 省略主体
end
# 好
x = 'test'
unless x.nil?
# 省略主体
end
```
*
永远不要使用 `and` 与 `or` 关键字。使用 `&&` 与 `||` 来替代。
[[link](#no-and-or-or)]
```Ruby
# 差
# 布尔表达式
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` 来替代。
[[link](#no-multiline-ternary)]
*
对于单行主体,倾向使用 `if`/`unless` 修饰语法。另一种方法是使用流程控制 `&&`/`||`。
[[link](#if-as-a-modifier)]
```Ruby
# 差
if some_condition
do_something
end
# 好
do_something if some_condition
# 好 - 使用流程控制
some_condition && do_something
```
*
避免在多行区块后使用 `if`/`unless` 修饰语法。
[[link](#no-multiline-if-modifiers)]
```Ruby
# 差
10.times do
# 省略多行主体
end if some_condition
# 好
if some_condition
10.times do
# 省略多行主体
end
end
```
*
避免使用嵌套 `if`/`unless`/`while`/`until` 修饰语法。适当情况下,使用 `&&`/`||` 来替代。
[[link](#no-nested-modifiers)]
```Ruby
# 差
do_something if other_condition if some_condition
# 好
do_something if some_condition && other_condition
```
*
对于否定条件,倾向使用 `unless` 而不是 `if`(或是使用流程控制 `||`)。
[[link](#unless-for-negatives)]
```Ruby
# 差
do_something if !some_condition
# 差
do_something if not some_condition
# 好
do_something unless some_condition
# 好
some_condition || do_something
```
*
不要使用 `unless` 与 `else` 的组合。将它们改写成肯定条件。
[[link](#no-else-with-unless)]
```Ruby
# 差
unless success?
puts 'failure'
else
puts 'success'
end
# 好
if success?
puts 'success'
else
puts 'failure'
end
```
*
不要使用括号包裹流程控制中的条件表达式。
[[link](#no-parens-around-condition)]
```Ruby
# 差
if (x > 10)
# 省略主体
end
# 好
if x > 10
# 省略主体
end
```
这个规则的一个例外是[条件表达式中的安全赋值](#safe-assignment-in-condition)。
*
在多行 `while/until` 中,不要使用 `while/until condition do`。
[[link](#no-multiline-while-do)]
```Ruby
# 差
while x > 5 do
# 省略主体
end
until x > 5 do
# 省略主体
end
# 好
while x > 5
# 省略主体
end
until x > 5
# 省略主体
end
```
*
对于单行主体,倾向使用 `while/until` 修饰语法。
[[link](#while-as-a-modifier)]
```Ruby
# 差
while some_condition
do_something
end
# 好
do_something while some_condition
```
*
对于否定条件,倾向使用 `until` 而不是 `while`。
[[link](#until-for-negatives)]
```Ruby
# 差
do_something while !some_condition
# 好
do_something until some_condition
```
*
对于无限循环,使用 `Kernel#loop` 而不是 `while/until`。
[[link](#infinite-loop)]
```ruby
# 差
while true
do_something
end
until false
do_something
end
# 好
loop do
do_something
end
```
*
对于后置条件循环语句,倾向使用 `Kernel#loop` 与 `break` 的组合,而不是 `begin/end/until` 或 `begin/end/while`。
[[link](#loop-with-break)]
```Ruby
# 差
begin
puts val
val += 1
end while val < 0
# 好
loop do
puts val
val += 1
break unless val < 0
end
```
*
对于可选参数的哈希,省略其外围的花括号。
[[link](#no-braces-opts-hash)]
```Ruby
# 差
user.set({ name: 'John', age: 45, permissions: { read: true } })
# 好
user.set(name: 'John', age: 45, permissions: { read: true })
```
*
对于 DSL 的内部方法调用,同时省略其外围的圆括号与花括号。
[[link](#no-dsl-decorating)]
```Ruby
class Person < ActiveRecord::Base
# 差
validates(:name, { presence: true, length: { within: 1..10 } })
# 好
validates :name, presence: true, length: { within: 1..10 }
end
```
*
当被调用方法是当前区块中唯一操作时,倾向使用简短的传参语法。
[[link](#single-action-blocks)]
```Ruby
# 差
names.map { |name| name.upcase }
# 好
names.map(&:upcase)
```
*
对于单行主体,倾向使用 `{...}` 而不是 `do...end`。对于多行主体,避免使用 `{...}`。对于“流程控制”或“方法定义”(比如 Rakefile、其他 DSL 构成片段),总是使用 `do...end`。避免在链式方法调用中使用 `do...end`。
[[link](#single-line-blocks)]
```Ruby
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` 对象。
[[link](#block-argument)]
```Ruby
require 'tempfile'
# 差
def with_tmp_dir
Dir.mktmpdir do |tmp_dir|
Dir.chdir(tmp_dir) { |dir| yield dir } # 通过创建区块的手法来传递参数
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`。
[[link](#no-explicit-return)]
```Ruby
# 差
def some_method(some_arr)
return some_arr.size
end
# 好
def some_method(some_arr)
some_arr.size
end
```
*
避免在不需要的情况下使用 `self`。(只有在调用 `self` 的修改器时才需要)
[[link](#no-self-unless-required)]
```Ruby
# 差
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
```
*
避免局部变量遮蔽方法调用,除非它们具有相同效果。
[[link](#no-shadowing)]
```Ruby
class Foo
attr_accessor :options
# 勉强可以
def initialize(options)
self.options = options
# 此处 self.options 与 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
```
*
不要在条件表达式中使用 `=`(赋值语句)的返回值,除非赋值语句包裹在括号之中。这种惯用法被称作**条件表达式中的安全赋值**。
[[link](#safe-assignment-in-condition)]
```Ruby
# 差 - 会出现警告
if v = array.grep(/foo/)
do_something(v)
...
end
# 好 - 尽管 Ruby 解释器仍会出现警告,但 RuboCop 不会
if (v = array.grep(/foo/))
do_something(v)
...
end
# 好
v = array.grep(/foo/)
if v
do_something(v)
...
end
```
*
优先考虑简短的自我赋值语法。
[[link](#self-assignment)]
```Ruby
# 差
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
```
*
当变量尚未初始化时,使用 `||=` 对其进行初始化。
[[link](#double-pipe-for-uninit)]
```Ruby
# 差
name = name ? name : 'Bozhidar'
# 差
name = 'Bozhidar' unless name
# 好 - 当且仅当 name 为 nil 或 false 时,设置 name 的值为 'Bozhidar'
name ||= 'Bozhidar'
```
*
不要使用 `||=` 对布尔变量进行初始化。
[[link](#no-double-pipes-for-bools)]
```Ruby
# 差 - 设置 enabled 的值为 true,即使其原本的值是 false
enabled ||= true
# 好
enabled = true if enabled.nil?
```
*
使用 `&&=` 预先检查变量是否存在,如果存在,则做相应动作。使用 `&&=` 语法可以省去 `if` 检查。
[[link](#double-amper-preprocess)]
```Ruby
# 差
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` 语句隐式使用的操作符,在 `case` 语句外的场合中使用,会产生难以理解的代码。
[[link](#no-case-equality)]
```Ruby
# 差
Array === something
(1..100) === 7
/something/ === some_string
# 好
something.is_a?(Array)
(1..100).include?(7)
some_string =~ /something/
```
*
能使用 `==` 就不要使用 `eql?`。提供更加严格比较的 `eql?` 在实践中极少使用。
[[link](#eql)]
```Ruby
# 差 - 对于字符串,eql? 与 == 具有相同效果
'ruby'.eql? some_str
# 好
'ruby' == some_str
1.0.eql? x # 当需要区别 Fixnum 1 与 Float 1.0 时,eql? 是具有意义的
```
*
避免使用 Perl 风格的特殊变量(比如 `$:`、`$;` 等)。它们看起来非常神秘,但除了单行脚本,其他情况并不鼓励使用。建议使用 `English` 程序库提供的友好别名。
[[link](#no-cryptic-perlisms)]
```Ruby
# 差
$:.unshift File.dirname(__FILE__)
# 好
require 'English'
$LOAD_PATH.unshift File.dirname(__FILE__)
```
*
永远不要在方法名与左括号之间添加空格。
[[link](#parens-no-spaces)]
```Ruby
# 差
f (3 + 2) + 1
# 好
f(3 + 2) + 1
```
*
运行 Ruby 解释器时,总是开启 `-w` 选项来。如果你忘了某个上述某个规则,它就会警告你!
[[link](#always-warn-at-runtime)]
*
不要在方法中嵌套定义方法,使用 lambda 方法来替代。 嵌套定义产生的方法,事实上和外围方法处于同一作用域(比如类作用域)。此外,“嵌套方法”会在定义它的外围方法每次调用时被重新定义。
[[link](#no-nested-methods)]
```Ruby
# 差
def foo(x)
def bar(y)
# 省略主体
end
bar(x)
end
# 好 - 作用同前,但 bar 不会在 foo 每次调用时被重新定义
def bar(y)
# 省略主体
end
def foo(x)
bar(x)
end
# 好
def foo(x)
bar = ->(y) { ... }
bar.call(x)
end
```
*
对于单行区块,使用新的 lambda 字面量定义语法。对于多行区块,使用 `lambda` 定义语法。
[[link](#lambda-multi-line)]
```Ruby
# 差
l = lambda { |a, b| a + b }
l.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 方法时,如果有参数则使用括号。
[[link](#stabby-lambda-with-args)]
```Ruby
# 差
l = ->x, y { something(x, y) }
# 好
l = ->(x, y) { something(x, y) }
```
*
定义 lambda 方法时,如果无参数则省略括号。
[[link](#stabby-lambda-no-args)]
```Ruby
# 差
l = ->() { something }
# 好
l = -> { something }
```
*
倾向使用 `proc` 而不是 `Proc.new`。
[[link](#proc)]
```Ruby
# 差
p = Proc.new { |n| puts n }
# 好
p = proc { |n| puts n }
```
*
对于 lambda 方法或代码块,倾向使用 `proc.call()` 而不是 `proc[]` 或 `proc.()`。
[[link](#proc-call)]
```Ruby
# 差 - 看上去像是枚举器的存取操作
l = ->(v) { puts v }
l[1]
# 差 - 极少见的调用语法
l = ->(v) { puts v }
l.(1)
# 好
l = ->(v) { puts v }
l.call(1)
```
*
未被使用的区块参数或局部变量,添加 `_` 前缀或直接使用 `_`(尽管表意性略差)。这种做法可以抑制 Ruby 解释器或 RuboCop 等工具发出“变量尚未使用”的警告。
[[link](#underscore-unused-vars)]
```Ruby
# 差
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 中允许给常量重新赋值(可能是重定向某些流),但解释器会发出警告。
[[link](#global-stdout)]
*
使用 `warn` 而不是 `$stderr.puts`。除了更加简练清晰外,`warn` 允许你在需要时通过设置解释器选项(使用 `-W0` 将警告级别设置为 0)来抑制警告。
[[link](#warn)]
*
倾向使用 `sprintf` 或其别名 `format` 而不是相当晦涩的 `String#%` 方法。
[[link](#sprintf)]
```Ruby
# 差
'%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#*` 方法。
[[link](#array-join)]
```Ruby
# 差
%w(one two three) * ', '
# => 'one, two, three'
# 好
%w(one two three).join(', ')
# => 'one, two, three'
```
*
当你希望处理的变量类型是数组,但不太确定其是否真的是数组时,通过使用 `Array()` 来替代显式的数组类型检查与转换。
[[link](#array-coercion)]
```Ruby
# 差
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?` 来替代复杂的比较逻辑。
[[link](#ranges-or-between)]
```Ruby
# 差
do_something if x >= 1000 && x <= 2000
# 好
do_something if (1000..2000).include?(x)
# 好
do_something if x.between?(1000, 2000)
```
*
倾向使用谓词方法而不是 `==` 操作符。但数值比较除外。
[[link](#predicate-methods)]
```Ruby
# 差
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
```
*
不做显式的 `non-nil` 检查,除非检查对象是布尔变量。
[[link](#no-non-nil-checks)]
```ruby
# 差
do_something if !something.nil?
do_something if something != nil
# 好
do_something if something
# 好 - 检查对象是布尔变量
def value_set?
!@some_boolean.nil?
end
```
*
避免使用 `BEGIN` 区块。
[[link](#no-BEGIN-blocks)]
*
永远不要使用 `END` 区块。使用 `Kernel#at_exit` 来替代。
[[link](#no-END-blocks)]
```ruby
# 差
END { puts 'Goodbye!' }
# 好
at_exit { puts 'Goodbye!' }
```
*
避免使用 flip-flops 操作符。
[[link](#no-flip-flops)]
*
流程控制中,避免使用嵌套条件。
[[link](#no-nested-conditionals)]
倾向使用防御从句进行非法数据断言。防御从句是指处于方法顶部的条件语句,其能尽早地退出方法。
```Ruby
# 差
def compute_thing(thing)
if thing[:foo]
update_with_bar(thing[:foo])
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
# 差
[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` 搭配起来一目了然。
[[link](#map-find-select-reduce-size)]
*
不要使用 `count` 作为 `size` 的替代方案。除了 `Array` 外,其他 `Enumerable` 对象都需要通过枚举整个集合才可以确定数目。
[[link](#count-vs-size)]
```Ruby
# 差
some_hash.count
# 好
some_hash.size
```
*
倾向使用 `flat_map` 而不是 `map + flatten` 的组合。此规则并不适用于深度超过 2 层的数组。举例来说,如果 `users.first.songs == ['a', ['b','c']]` 成立,则使用 `map + flatten` 的组合而不是 `flat_map`。`flat_map` 只能平坦化一个层级,而 `flatten` 能够平坦化任意多个层级。
[[link](#flat-map)]
```Ruby
# 差
all_songs = users.map(&:songs).flatten.uniq
# 好
all_songs = users.flat_map(&:songs).uniq
```
*
倾向使用 `reverse_each` 而不是 `reverse.each`,因为某些混入 `Enumerable` 模块的类可能会提供 `reverse_each` 的高效版本。即使这些类没有提供专门特化的版本,继承自 `Enumerable` 的通用版本至少能保证性能与 `reverse.each` 相当。
[[link](#reverse-each)]
```Ruby
# 差
array.reverse.each { ... }
# 好
array.reverse_each { ... }
```
## 命名
> 程序设计的真正难题是替事物命名及使缓存失效。
> ——Phil Karlton
*
标识符使用英文命名。
[[link](#english-identifiers)]
```Ruby
# 差 - 标识符使用非 ASCII 字符
заплата = 1_000
# 差 - 标识符使用拉丁文写法的保加利亚单词
zaplata = 1_000
# 好
salary = 1_000
```
*
符号、方法、变量使用蛇底式小写(`snake_case`)。
[[link](#snake-case-symbols-methods-vars)]
```Ruby
# 差
:'some symbol'
:SomeSymbol
:someSymbol
someVar = 5
def someMethod
...
end
def SomeMethod
...
end
# 好
:some_symbol
def some_method
...
end
```
*
类与模块使用驼峰式大小写(`CamelCase`)。(HTTP、RFC、XML 等首字母缩写应该仍旧保持大写形式)
[[link](#camelcase-classes)]
```Ruby
# 差
class Someclass
...
end
class Some_Class
...
end
class SomeXml
...
end
class XmlSomething
...
end
# 好
class SomeClass
...
end
class SomeXML
...
end
class XMLSomething
...
end
```
*
文件名使用蛇底式小写,如 `hello_world.rb`。
[[link](#snake-case-files)]
*
目录名使用蛇底式小写,如 `lib/hello_world/hello_world.rb`。
[[link](#snake-case-dirs)]
*
尽量使一个源文件中只有一个类或模块。文件名就是类名或模块名,但使用蛇底式小写而不是驼峰式大小写。
[[link](#one-class-per-file)]
*
其他常量使用尖叫蛇底式大写(SCREAMING_SNAKE_CASE)。
[[link](#screaming-snake-case)]
```Ruby
# 差
SomeConst = 5
# 好
SOME_CONST = 5
```
*
谓词方法(返回布尔值的方法)的名字应当以问号结尾。(比如 `Array#empty?`)。不返回布尔值的方法不应以问号结尾。
[[link](#bool-methods-qmark)]
*
谓词方法的名字应当避免使用 `is`、`does`、`can` 等助动词作为前缀。这些助动词在实际场景中显得冗余,且与标准库的命名习惯(比如 `empty?`、`include?`)很不一致。
[[link](#bool-methods-prefix)]
```Ruby
# 差
class Person
def is_tall?
true
end
def can_play_basketball?
false
end
def does_like_candy?
true
end
end
# 好
class Person
def tall?
true
end
def basketball_player?
false
end
def likes_candy?
true
end
end
```
*
具有潜在**危险性**的方法,当其存在对应安全版本的方法时,其名字应当以惊叹号结尾。(比如修改 `self` 或参数值的方法、相对 `exit` 方法不会在退出时运行 finalizers 执行清理工作的 `exit!` 方法等)
[[link](#dangerous-method-bang)]
```Ruby
# 差 - 没有对应安全版本的方法
class Person
def update!
end
end
# 好
class Person
def update
end
end
# 好
class Person
def update!
end
def update
end
end
```
*
尽量根据危险方法来定义对应安全版本的方法。
[[link](#safe-because-unsafe)]
```Ruby
class Array
def flatten_once!
res = []
each do |e|
[*e].each { |f| res << f }
end
replace(res)
end
def flatten_once
dup.flatten_once!
end
end
```
*
当配合单行区块使用 `reduce` 时,将参数命名为 `|a, e|`(accumulator/累加器,element/元素)。
[[link](#reduce-blocks)]
*
当定义二元操作符时,将参数命名为 `other`(`<<` 与 `[]` 例外,因为其语义与此不同)。
[[link](#other-arg)]
```Ruby
def +(other)
# 省略主体
end
```
## 注释
> 良好的代码自身就是最佳的文档。当你要添加一个注释时,扪心自问,“如何改善代码让它不需要注释?” 改善代码,再写相应文档使之更清楚。
> ——Steve McConnell
*
编写让人一目了然的代码然后忽略这一节的其它部分。我是认真的!
[[link](#no-comments)]
*
使用英文编写注释。
[[link](#english-comments)]
*
前导 `#` 与注释文本之间应当添加一个空格。
[[link](#hash-space)]
*
注释超过一个单词时,句首字母应当大写,并在语句停顿或结尾处使用标点符号。句号后添加[一个空格](http://en.wikipedia.org/wiki/Sentence_spacing)。
[[link](#english-syntax)]
*
避免无谓的注释。
[[link](#no-superfluous-comments)]
```Ruby
# 差
counter += 1 # Increments counter by one.
```
*
及时更新注释。过时的注释比没有注释还要糟糕。
[[link](#comment-upkeep)]
> 好的代码就像是好的笑话 —— 它不需要解释。
> ——Russ Olsen
*
避免替烂代码编写注释。重构它们使其变得一目了然。(要么做,要么不做,不要只是试试看。——Yoda)
[[link](#refactor-dont-comment)]
### 注解
*
注解应该直接写在相关代码之前那行。
[[link](#annotate-above)]
*
注解关键字后面,跟着一个冒号及空格,接着是描述问题的文本。
[[link](#annotate-keywords)]
*
如果需要用多行来描述问题,后续行要放在 `#` 后面并缩排两个空格。
[[link](#indent-annotations)]
```Ruby
def bar
# FIXME: This has crashed occasionally since v3.2.1. It may
# be related to the BarBazUtil upgrade.
baz(:quux)
end
```
*
当问题是显而易见时,任何文档都是多余的,注解应当放在有问题的那行末尾且不带任何多余说明。这个用法应该算是例外而不是规则。
[[link](#rare-eol-annotations)]
```Ruby
def bar
sleep 100 # OPTIMIZE
end
```
*
使用 `TODO` 标记应当加入的特征与功能。
[[link](#todo)]
*
使用 `FIXME` 标记需要修复的代码。
[[link](#fixme)]
*
使用 `OPTIMIZE` 标记可能引发性能问题的低效代码。
[[link](#optimize)]
*
使用 `HACK` 标记代码异味,即那些应当被重构的可疑编码习惯。
[[link](#hack)]
*
使用 `REVIEW` 标记需要确认与编码意图是否一致的可疑代码。比如,`REVIEW: Are we sure this is how the client does X currently?`。
[[link](#review)]
*
适当情况下,可以自行定制其他注解关键字,但别忘记在项目的 `README` 或类似文档中予以说明。
[[link](#document-annotations)]
## 类与模块
*
在类定义中,使用一致的结构。
[[link](#consistent-classes)]
```Ruby
class Person
# 首先是 extend 与 include
extend SomeModule
include AnotherModule
# 内部类
CustomErrorKlass = Class.new(StandardError)
# 接着是常量
SOME_CONSTANT = 20
# 接下来是属性宏
attr_reader :name
# 跟着是其他宏(如果有的话)
validates :name
# 公开的类方法接在下一行
def self.some_method
end
# 初始化方法在类方法和实例方法之间
def initialize
end
# 跟着是公开的实例方法
def some_method
end
# 受保护及私有的方法等放在接近结尾的地方
protected
def some_protected_method
end
private
def some_private_method
end
end
```
*
如果嵌套类数目较多,进而导致外围类定义较长,则将它们从外围类中提取出来,分别放置在单独的以嵌套类命名的文件中,并将文件归类至以外围类命名的文件夹下。
[[link](#file-classes)]
```Ruby
# 差
# 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
```
*
定义只有类方法的数据类型时,倾向使用模块而不是类。只有当需要实例化时才使用类。
[[link](#modules-vs-classes)]
```Ruby
# 差
class SomeClass
def self.some_method
# 省略主体
end
def self.some_other_method
# 省略主体
end
end
# 好
module SomeModule
module_function
def some_method
# 省略主体
end
def some_other_method
# 省略主体
end
end
```
*
当你想将模块的实例方法变成类方法时,倾向使用 `module_function` 而不是 `extend self`。
[[link](#module-function)]
```Ruby
# 差
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
```
*
当设计类的层次结构时,确保它们符合[里式替换原则](http://en.wikipedia.org/wiki/Liskov_substitution_principle)。
[[link](#liskov)]
*
让你的类尽量满足 [SOLID 原则](http://en.wikipedia.org/wiki/SOLID_(object-oriented_design)) 。
[[link](#solid-design)]
*
总是替那些用以表示领域模型的类提供一个适当的 `to_s` 方法。
[[link](#define-to-s)]
```Ruby
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` 系列方法来定义琐碎的存取器或修改器。
[[link](#attr_family)]
```Ruby
# 差
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
# 好
class Person
attr_reader :first_name, :last_name
def initialize(first_name, last_name)
@first_name = first_name
@last_name = last_name
end
end
```
*
对于访问器方法,避免使用 `get_` 作为名字前缀;对于更改器方法,避免使用 `set_` 作为名字前缀。Ruby 语言中,通常使用 `attr_name` 作为访问器的方法名,使用 `attr_name=` 作为更改器的方法名。
[[link](#accessor_mutator_method_names)]
```Ruby
# 差
class Person
def get_name
"#{@first_name} #{@last_name}"
end
def set_name(name)
@first_name, @last_name = name.split(' ')
end
end
# 好
class Person
def name
"#{@first_name} #{@last_name}"
end
def name=(name)
@first_name, @last_name = name.split(' ')
end
end
```
*
避免使用 `attr`。使用 `attr_reader` 与 `attr_accessor` 来替代。
[[link](#attr)]
```Ruby
# 差 - 创建单个存取方法(此方法在 Ruby 1.9 之后被移除了)
attr :something, true
attr :one, :two, :three # 类似于 attr_reader
# 好
attr_accessor :something
attr_reader :one, :two, :three
```
*
优先考虑使用 `Struct.new`。它替你定义了那些琐碎的访问器、构造器及比较操作符。
[[link](#struct-new)]
```Ruby
# 好
class Person
attr_accessor :first_name, :last_name
def initialize(first_name, last_name)
@first_name = first_name
@last_name = last_name
end
end
# 更好
Person = Struct.new(:first_name, :last_name) do
end
```
*
不要扩展 `Struct.new` 实例化后的对象。对它进行扩展不但引入了毫无意义的类层次,而且在此文件被多次引入时可能会产生奇怪的错误。
[[link](#no-extend-struct-new)]
```Ruby
# 差
class Person < Struct.new(:first_name, :last_name)
end
# 好
Person = Struct.new(:first_name, :last_name)
```
*
优先考虑通过工厂方法的方式创建某些具有特定意义的实例对象。
[[link](#factory-methods)]
```Ruby
class Person
def self.create(options_hash)
# 省略主体
end
end
```
*
倾向使用[鸭子类型](https://en.wikipedia.org/wiki/Duck_typing)而不是继承。
[[link](#duck-typing)]
```Ruby
# 差
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
```
*
避免使用类变量(`@@`)。类变量在继承方面存在令人生厌的行为。
[[link](#no-class-vars)]
```Ruby
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 # => 此处打印的结果为 'child'
```
如你所见,在类的层次结构中所有类都会共享同一类变量。通常情况下,倾向使用类实例变量而不是类变量。
*
根据方法的目的与用途设置适当的可见级别(`private`、`protected`)。不要什么都不做就把所有方法设置为 `public`(默认值)。毕竟我们写的是 **Ruby** 而不是 **Python**。
[[link](#visibility)]
*
把 `public`、`protected`、`private` 与其作用的方法缩排在同一层级。且在其上下各留一行以强调此可见级别作用于之后的所有方法。
[[link](#indent-public-private-protected)]
```Ruby
class SomeClass
def public_method
# ...
end
private
def private_method
# ...
end
def another_private_method
# ...
end
end
```
*
使用 `def self.method` 定义类方法。这种做法使得在代码重构时,即使修改了类名也无需做多次修改。
[[link](#def-self-class-methods)]
```Ruby
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
```
*
在类的词法作用域中定义方法别名时,倾向使用 `alias`。因为定义期间 `alias` 与 `self` 指向的都是词法作用域,除非明确说明,否则该别名所引用的方法不会在运行期间被改变,或是在任何子类中被修改。
[[link](#alias-method-lexically)]
```Ruby
class Westerner
def first_name
@names.first
end
alias given_name first_name
end
```
因为 `alias` 与 `def` 一样都是关键字,倾向使用裸字而不是符号或字符串。也就是说,使用 `alias foo bar` 而不是 `alias :foo :bar`。
另外需要了解 Ruby 是如何处理别名和继承的:别名所引用的原始方法是在定义期间被指定的,而不是运行期间。
```Ruby
class Fugitive < Westerner
def first_name
'Nobody'
end
end
```
在这个例子中,`Fugitive#given_name` 仍然调用原先的 `Westerner#first_name` 方法,而不是 `Fugitive#first_name`。如果想要覆写 `Fugitive#given_name`,必须在子类中重新定义。
```Ruby
class Fugitive < Westerner
def first_name
'Nobody'
end
alias given_name first_name
end
```
*
在运行期间定义模块方法、类方法、单件方法的别名时,总是使用 `alias_method`。在上述情况下,使用 `alias` 可能会导致预期之外的结果。
[[link](#alias-method)]
```Ruby
module Mononymous
def self.included(other)
other.class_eval { alias_method :full_name, :given_name }
end
end
class Sting < Westerner
include Mononymous
end
```
## 异常
*
对于异常处理,倾向使用 `raise` 而不是 `fail`。
[[link](#prefer-raise-over-fail)]
```Ruby
# 差
fail SomeException, 'message'
# 好
raise SomeException, 'message'
```
*
不要在带双参数形式的 `raise` 方法中显式指定 `RuntimeError`。
[[link](#no-explicit-runtimeerror)]
```Ruby
# 差
raise RuntimeError, 'message'
# 好 - 默认就是 RuntimeError
raise 'message'
```
*
倾向使用带异常类、消息的双参数形式调用 `raise` 方法,而不是使用异常的实例。
[[link](#exception-class-messages)]
```Ruby
# 差 - 并无 raise SomeException.new('message') [, backtraces] 这种调用形式
raise SomeException.new('message')
# 好 - 与调用形式 raise SomeException [, 'message' [, backtraces]] 保持一致
raise SomeException, 'message'
```
*
永远不要从 `ensure` 区块返回。如果你显式地从 `ensure` 区块返回,那么其所在的方法会如同永远不会发生异常般的返回。事实上,异常被默默地丢弃了。
[[link](#no-return-ensure)]
```Ruby
def foo
raise
ensure
return 'very bad idea'
end
```
*
尽可能隐式地使用 `begin/rescue/ensure/end` 区块。
[[link](#begin-implicit)]
```Ruby
# 差
def foo
begin
# 主逻辑
rescue
# 异常处理逻辑
end
end
# 好
def foo
# 主逻辑
rescue
# 异常处理逻辑
end
```
*
通过使用 **contingency 方法**(一个由 Avdi Grimm 创造的词)来减少 `begin/rescue/ensure/end` 区块的使用。
[[link](#contingency-methods)]
```Ruby
# 差
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 }
```
*
不要抑制异常。
[[link](#dont-hide-exceptions)]
```Ruby
# 差
begin
# 抛出异常
rescue SomeError
# 不做任何相关处理
end
# 差
do_something rescue nil
```
*
避免使用 `rescue` 修饰语法。
[[link](#no-rescue-modifiers)]
```Ruby
# 差 - 这里将会捕捉 StandardError 及其所有子孙类的异常
read_file rescue handle_error($!)
# 好 - 这里只会捕获 Errno::ENOENT 及其所有子孙类的异常
def foo
read_file
rescue Errno::ENOENT => ex
handle_error(ex)
end
```
*
不要将异常处理作为流程控制使用。
[[link](#no-exceptional-flows)]
```Ruby
# 差
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` 来终止进程。
[[link](#no-blind-rescues)]
```Ruby
# 差 - 信号与 exit 方法产生的异常会被捕获(除了 kill -9)
begin
exit
rescue Exception
puts "you didn't really want to exit, right?"
# 处理异常
end
# 好 - 没有指定具体异常的 rescue 子句默认捕获 StandardError
begin
# 抛出异常
rescue => e
# 处理异常
end
# 好 - 指定具体异常 StandardError
begin
# 抛出异常
rescue StandardError => e
# 处理异常
end
```
*
把较具体的异常放在处理链的较上层,不然它们永远不会被执行。
[[link](#exception-ordering)]
```Ruby
# 差
begin
# 抛出异常
rescue StandardError => e
# 处理异常
rescue IOError => e
# 处理异常,但事实上永远不会被执行
end
# 好
begin
# 抛出异常
rescue IOError => e
# 处理异常
rescue StandardError => e
# 处理异常
end
```
*
在 `ensure` 区块释放程序的外部资源。
[[link](#release-resources)]
```Ruby
f = File.open('testfile')
begin
# .. 文件操作
rescue
# .. 处理异常
ensure
f.close if f
end
```
*
在调用资源获取方法时,尽量使用具备自动清理功能的版本。
[[link](#auto-release-resources)]
```Ruby
# 差 - 需要显式关闭文件描述符
f = File.open('testfile')
# ...
f.close
# 好 - 文件描述符会被自动关闭
File.open('testfile') do |f|
# ...
end
```
*
倾向使用标准库中的异常类而不是引入新的类型。
[[link](#standard-exceptions)]
## 集合
*
对于数组与哈希,倾向使用字面量语法来构建实例(除非你需要给构造器传递参数)。
[[link](#literal-array-hash)]
```Ruby
# 差
arr = Array.new
hash = Hash.new
# 好
arr = []
hash = {}
```
*
当创建一组元素为单词(没有空格或特殊字符)的数组时,倾向使用 `%w` 而不是 `[]`。此规则只适用于数组元素有两个或以上的时候。
[[link](#percent-w)]
```Ruby
# 差
STATES = ['draft', 'open', 'closed']
# 好
STATES = %w(draft open closed)
```
*
当创建一组符号类型的数组(且不需要保持 Ruby 1.9 兼容性)时,倾向使用 `%i`。此规则只适用于数组元素有两个或以上的时候。
[[link](#percent-i)]
```Ruby
# 差
STATES = [:draft, :open, :closed]
# 好
STATES = %i(draft open closed)
```
*
避免在数组与哈希的字面量语法的最后一个元素之后添加逗号,尤其当元素没有分布在同一行时。
[[link](#no-trailing-array-commas)]
```Ruby
# 差 - 尽管移动、新增、删除元素颇为方便,但仍不推荐这种写法
VALUES = [
1001,
2020,
3333,
]
# 差
VALUES = [1001, 2020, 3333, ]
# 好
VALUES = [1001, 2020, 3333]
```
*
避免在数组中创造巨大的间隔。
[[link](#no-gappy-arrays)]
```Ruby
arr = []
arr[100] = 1 # 现在你有一个很多 nil 的数组
```
*
当访问数组的首元素或尾元素时,倾向使用 `first` 或 `last` 而不是 `[0]` 或 `[-1]`。
[[link](#first-and-last)]
*
当处理的对象不存在重复元素时,使用 `Set` 来替代 `Array`。`Set` 是实现了无序且无重复元素的集合类型。它兼具 `Array` 的直观操作与 `Hash` 的快速查找。
[[link](#set-vs-array)]
*
倾向使用符号而不是字符串作为哈希键。
[[link](#symbols-as-keys)]
```Ruby
# 差
hash = { 'one' => 1, 'two' => 2, 'three' => 3 }
# 好
hash = { one: 1, two: 2, three: 3 }
```
*
避免使用可变对象作为哈希键。
[[link](#no-mutable-keys)]
*
当哈希键为符号时,使用 Ruby 1.9 的字面量语法。
[[link](#hash-literals)]
```Ruby
# 差
hash = { :one => 1, :two => 2, :three => 3 }
# 好
hash = { one: 1, two: 2, three: 3 }
```
*
当哈希键既有符号又有字符串时,不要使用 Ruby 1.9 的字面量语法。
[[link](#no-mixed-hash-syntaces)]
```Ruby
# 差
{ a: 1, 'b' => 2 }
# 好
{ :a => 1, 'b' => 2 }
```
*
倾向使用 `Hash#key?` 而不是 `Hash#has_key?`,使用 `Hash#value?` 而不是 `Hash#has_value?`。Matz [在此](http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/43765)提及,较长形式的方法将被废弃。
[[link](#hash-key)]
```Ruby
# 差
hash.has_key?(:test)
hash.has_value?(value)
# 好
hash.key?(:test)
hash.value?(value)
```
*
倾向使用 `Hash#each_key` 而不是 `Hash#keys.each`,使用 `Hash#each_value` 而不是 `Hash#values.each`。
[[link](#hash-each)]
```Ruby
# 差
hash.keys.each { |k| p k }
hash.values.each { |v| p v }
hash.each { |k, _v| p k }
hash.each { |_k, v| p v }
# 好
hash.each_key { |k| p k }
hash.each_value { |v| p v }
```
*
当处理应该存在的哈希键时,使用 `Hash#fetch`。
[[link](#hash-fetch)]
```Ruby
heroes = { batman: 'Bruce Wayne', superman: 'Clark Kent' }
# 差 - 如果我们打错了哈希键,则难以发现这个错误
heroes[:batman] # => 'Bruce Wayne'
heroes[:supermann] # => nil
# 好 - fetch 会抛出 KeyError 使这个错误显而易见
heroes.fetch(:supermann)
```
*
当为哈希键的值提供默认值时,倾向使用 `Hash#fetch` 而不是自定义逻辑。
[[link](#hash-fetch-defaults)]
```Ruby
batman = { name: 'Bruce Wayne', is_evil: false }
# 差 - 如果仅仅使用 || 操作符,那么当值为假时,我们不会得到预期结果
batman[:is_evil] || true # => true
# 好 - fetch 在遇到假值时依然可以正确工作
batman.fetch(:is_evil, true) # => false
```
*
当提供默认值的求值代码具有副作用或开销较大时,倾向使用 `Hash#fetch` 的区块形式。
[[link](#use-hash-blocks)]
```Ruby
batman = { name: 'Bruce Wayne' }
# 差 - 此形式会立即求值,如果调用多次,可能会影响程序的性能
batman.fetch(:powers, obtain_batman_powers) # obtain_batman_powers 开销较大
# 好 - 此形式会惰性求值,只有抛出 KeyError 时,才会产生开销
batman.fetch(:powers) { obtain_batman_powers }
```
*
当需要一次性从哈希中获取多个键的值时,使用 `Hash#values_at`。
[[link](#hash-values-at)]
```Ruby
# 差
email = data['email']
username = data['nickname']
# 好
email, username = data.values_at('email', 'nickname')
```
*
利用“Ruby 1.9 之后的哈希是有序的”的这个特性。
[[link](#ordered-hashes)]
*
当遍历集合时,不要改动它。
[[link](#no-modifying-collections)]
*
当访问集合中的元素时,倾向使用对象所提供的方法进行访问,而不是直接调用对象属性上的 `[n]` 方法。这种做法可以防止你在 `nil` 对象上调用 `[]`。
[[link](#accessing-elements-directly)]
```Ruby
# 差
Regexp.last_match[1]
# 好
Regexp.last_match(1)
```
*
当为集合提供存取器时,尽量支持索引值为 `nil` 的访问形式。
[[link](#provide-alternate-accessor-to-collections)]
```Ruby
# 差
def awesome_things
@awesome_things
end
# 好
def awesome_things(index = nil)
if index && @awesome_things
@awesome_things[index]
else
@awesome_things
end
end
```
## 数值
*
通过 `Integer` 检查对象是否是数值类型,而不是 `Fixnum` 或 `Bignum`。因为 `Fixnum` 或 `Bignum` 表达的数值大小存在范围限定。
[[link](#integer-type-checking)]
```Ruby
timestamp = Time.now.to_i
# 差
timestamp.is_a? Fixnum
timestamp.is_a? Bignum
# 好
timestamp.is_a? Integer
```
*
对于随机数的生成,倾向使用 Range 来表示,而不是 Integer + 偏移量,这样可以更加清晰地表达你的意图,类比于投掷骰子。
[[link](#random-numbers)]
```Ruby
# 差
rand(6) + 1
# 好
rand(1..6)
```
## 字符串
*
倾向使用字符串插值或字符串格式化,而不是字符串拼接。
[[link](#string-interpolation)]
```Ruby
# 差
email_with_name = user.name + ' <' + user.email + '>'
# 好
email_with_name = "#{user.name} <#{user.email}>"
# 好
email_with_name = format('%s <%s>', user.name, user.email)
```
*
使用统一的风格创建字符串字面量。在 Ruby 社区中存在两种流行的风格:默认单引号(风格 A)与默认双引号(风格 B)。
[[link](#consistent-string-literals)]
* **(风格 A)** 当你不需要字符串插值或特殊字符(比如 `\t`、`\n`、`'`)时,倾向使用单引号。
```Ruby
# 差
name = "Bozhidar"
# 好
name = 'Bozhidar'
```
* **(风格 B)** 除非字符串中包含双引号,或是你希望抑制转义字符,否则倾向使用双引号。
```Ruby
# 差
name = 'Bozhidar'
# 好
name = "Bozhidar"
```
本指南使用第一种风格。
*
不要使用 `?x` 字面量语法。在 Ruby 1.9 之后,`?x` 与 `'x'`(只包含单个字符的字符串)是等价的。
[[link](#no-character-literals)]
```Ruby
# 差
char = ?c
# 好
char = 'c'
```
*
不要忘记使用 `{}` 包裹字符串插值中的实例变量或全局变量。
[[link](#curlies-interpolate)]
```Ruby
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 会自动调用它。
[[link](#no-to-s)]
```Ruby
# 差
message = "This is the #{result.to_s}."
# 好
message = "This is the #{result}."
```
*
当你需要构造巨大的数据块时,避免使用 `String#+`,使用 `String#<<` 来替代。`String#<<` 通过修改原始对象进行拼接工作,其比 `String#+` 效率更高,因为后者需要产生一堆新的字符串对象。
[[link](#concat-strings)]
```Ruby
# 差
html = ''
html += '
#{paragraph}
" end # 好 - 并且效率更高 html = '' html << '#{paragraph}
" end ``` * 当存在更快速、更专业的替代方案时,不要使用 `String#gsub`。 [[link](#dont-abuse-gsub)] ```Ruby url = 'http://example.com' str = 'lisp-case-rules' # 差 url.gsub('http://', 'https://') str.gsub('-', '_') # 好 url.sub('http://', 'https://') str.tr('-', '_') ``` * heredocs 中的多行文本会保留各行的前导空白。因此做好如何缩排的规划。 [[link](#heredocs)] ```Ruby code = <<-END.gsub(/^\s+\|/, '') |def test | some_method | other_method |end END # => "def test\n some_method\n other_method\nend\n" ``` * 使用 Ruby 2.3 新增的 `<<~` 操作符来缩排 heredocs 中的多行文本。 [[link](#squiggly-heredocs)] ```Ruby # 差 - 使用 Powerpack 程序库的 String#strip_margin code = <<-END.strip_margin('|') |def test | some_method | other_method |end END # 差 code = <<-END def test some_method other_method end END # 好 code = <<~END def test some_method other_method end END ``` ## 日期与时间 * 避免使用 `DateTime`,除非你确实需要处理历法改革(儒略/格里历的改革),此时通过设置 `start` 参数来明确你的意图。 [[link](#no-datetime)] ```Ruby # 差 - 使用 DateTime 表示当前时间 DateTime.now # 好 - 使用 Time 表示当前时间 Time.now # 差 - 使用 DateTime 表示近现代日期 DateTime.iso8601('2016-06-29') # 好 - 使用 Date 表示近现代日期 Date.iso8601('2016-06-29') # 好 - 使用 DateTime 表示日期,通过设置 start 参数为 Date::ENGLANG 明确表示使用 England 历法改革版本 DateTime.iso8601('1751-04-23', Date::ENGLAND) ``` ## 正则表达式 > 有些人在面对问题时,不经大脑便认为,“我知道,这里该用正则表达式”。现在他要面对两个问题了。"What did you say?"
) ``` * 只有当正则表达式中存在一个或以上的 `/` 字符时,才使用 `%r`。 [[link](#percent-r)] ```Ruby # 差 %r{\s+} # 好 %r{^/(.*)$} %r{^/blog/2011/(.*)$} ``` * 除非调用的命令使用了反引号(这种情况并不多见),否则不要使用 `%x`。 [[link](#percent-x)] ```Ruby # 差 date = %x(date) # 好 date = `date` echo = %x(echo `date`) ``` * 避免使用 `%s`。倾向使用 `:"some string"` 来创建含有空白字符的符号。 [[link](#percent-s)] * 使用 `%` 字面量语法时,倾向使用 `()`(`%r` 除外,因为圆括号在正则表达式中比较常用,此时可以使用 `{}` 等其他形式来替代)。 [[link](#percent-literal-braces)] ```Ruby # 差 %w[one two three] %q{"Test's king!", John said.} # 好 %w(one two three) %q("Test's king!", John said.) ``` ## 元编程 * 避免无谓的元编程。 [[link](#no-needless-metaprogramming)] * 当编写程序库时,不要使核心类混乱(不要使用 monkey patch)。 [[link](#no-monkey-patching)] * 对于 `class_eval` 方法,倾向使用区块形式,而不是字符串插值形式。 [[link](#block-class-eval)] - 当使用字符串插值形式时,总是提供 `__FILE__` 及 `__LINE__`,以使你的调用栈看起来具有意义: ```Ruby class_eval 'def use_relative_model_naming?; true; end', __FILE__, __LINE__ ``` - 倾向使用 `define_method` 而不是 `class_eval { def ... }` * 当使用 `class_eval`(或其他的 `eval`)的字符串插值形式时,添加一个注释区块来说明它是如何工作的(来自 Rails 代码中的技巧)。 [[link](#eval-comment-docs)] ```Ruby # 摘录自 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}(*params, &block) # def capitalize(*params, &block) to_str.#{unsafe_method}(*params, &block) # to_str.capitalize(*params, &block) end # end def #{unsafe_method}!(*params) # def capitalize!(*params) @dirty = true # @dirty = true super # super end # end EOT end end ``` * 避免使用 `method_missing`。它会使你的调用栈变得凌乱;其方法不被罗列在 `#methods` 中;拼错的方法可能会默默地工作(`nukes.launch_state = false`)。优先考虑使用委托、代理、或是 `define_method` 来替代。如果你必须使用 `method_missing` 的话,务必做到以下几点: [[link](#no-method-missing)] - 确保[同时定义了 `respond_to_missing?`](http://blog.marc-andre.ca/2010/11/methodmissing-politely.html)。 - 仅仅捕获那些具有良好语义前缀的方法,像是 `find_by_*`——让你的代码愈确定愈好。 - 在语句的最后调用 `super`。 - 委托到确定的、非魔术的方法,比如: ```Ruby # 差 def method_missing?(meth, *params, &block) if /^find_by_(?