# The Ruby Style Guide
> Hey jude, don't make it bad.
> Take a sad song and make it better.
> Remember to let her into your heart,
> Then you can start to make it better.
* [github Ruby style guide](https://github.com/styleguide/ruby)
* [community-driven Ruby style guide](https://github.com/bbatsov/ruby-style-guide)
* [Ruby on Rails 3 Style Guide](https://github.com/bbatsov/rails-style-guide)
* [代碼大全](http://book.douban.com/subject/1477390/)
* [代碼整潔之道](http://book.douban.com/subject/4199741/)
* [重構(gòu)](http://book.douban.com/subject/1229923/)
## 排版
* 使用`UTF-8`編碼
```VimL
set encoding=utf-8
set fileencodings=utf-8,gb2312,gb18030,gbk,ucs-bom,cp936,latin1 " 如果你要打開的文件編碼不在此列吏砂,那就添加進(jìn)去
set termencoding=utf-8
```
* 使用等寬字體
```VimL
set guifont=Bitstream\ Vera\ Sans\ Mono\ 12
```
* 使用兩個空格作為縮進(jìn)
```Ruby
# good
def some_method
do_something
end
# bad - four spaces
def some_method
do_something
end
```
* 不要在縮進(jìn)中留下制表符(Tab)芯砸。設(shè)置編輯器芬位,自動將制表符展開為兩個空格
```VimL
" 示例1:全局設(shè)置
set autoindent
set expandtab
set tabstop=2 shiftwidth=2 softtabstop=2
```
```VimL
" 示例2:每語言單獨設(shè)置
autocmd FileType ruby,eruby,yaml set ai ts=2 sw=2 sts=2 et
```
* 正確拼寫英文單詞
```Ruby
# bad
def has_joind?(talbe_name)
return false unless scoped.respond_to?(:joins_values)
scoped.joins_values.any?{|joins| joins =~ /inner\s+join\s+#{talbe_name}/i }
end
# good
def has_joined?(table_name)
return false unless scoped.respond_to?(:joins_values)
scoped.joins_values.any? { |joins| joins =~ /inner\s+join\s+#{table_name}/i }
end
```
* 盡量將行的長度控制在80個字符以下
```Ruby
# bad
def some_method
a_long_long_long_long_long_long_long_method_call if condition1 && condition2 && condition3
a_long_long_long_long_long_long_long_operation1 && a_long_long_long_long_long_long_long_operation2 && a_long_long_long_long_long_long_long_operation3
end
# good, 但更好的做法是考慮重命名方法或?qū)⑾鄳?yīng)邏輯抽取成子方法
def some_method
if condition1 && condition2 && condition3
a_long_long_long_long_long_long_long_method_call
end
a_long_long_long_long_long_long_long_operation1 &&
a_long_long_long_long_long_long_long_operation2 &&
a_long_long_long_long_long_long_long_operation3
end
```
* 不要在行尾留下空白字符
```VimL
set list
set listchars=tab:,.,trail:.,extends:#,nbsp:. " Highlight problematic whitespace
```
* 在以下位置加上空格:操作符前后, 逗號摸袁、冒號撼短、分號后祈秕,'{'前后牺堰, '}'前
```Ruby
sum = 1 + 2
result = 10 / (1 + 2)
a, b = 1, 2
1 > 2 ? some_operation : other_operation
[1, 2, 3].each { |e| puts e }
hash = { key1: 'luo', key2: 'xin' }
def find(name, options = {})
result
end
```
唯一的例外是指數(shù)操作符
```Ruby
# bad
e = M * c ** 2
# good
e = M * c**2
```
* 以下位置沒有空格:'(', '['后和']', ')'前
```Ruby
some(arg).other
[1, 2, 3].length
```
* 保證`when`和`case`的縮進(jìn)層次相同
```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
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
```
* 使用空行分隔不同方法及同一方法的不同邏輯段
```Ruby
def some_method
data = initialize(options)
first_method_call_with(data)
second_method_call_with(data)
third_method_call_with(data)
data.result
end
def other_method
result
end
```
* 如果使用表達(dá)式賦值梁呈,表達(dá)式的縮進(jìn)要保持一致
```Ruby
# bad
result = if index > 10
do_something
elsif index > 5
do_other_thing
else
puts 'I can not bear it.'
end
# good
result = if index > 10
do_something
elsif index > 5
do_other_thing
else
puts 'I can not bear it.'
end
```
* 多行賦值時缨伊,為保持美觀摘刑,盡量將等號對齊
```Ruby
# ok
var1 = 1
var12 = 12
var12345 = 12345
var123 = 123
# good
var1 = 1
var12 = 12
var12345 = 12345
var123 = 123
```
* 使用哈希賦值時,如果哈希長度過長刻坊,應(yīng)該用多行表示枷恕,且盡量保持對齊
```Ruby
# very bad
result = {:key1=>:value1,:key2=>:value2}
# bad
result = { :this_is_the_first_key => :this_is_the_first_value, :this_is_another_key => :this_is_another_value, :this_is_the_last_key => :this_is_the_last_value }
# good for ruby 1.8
result = { :key1 => :value1, :key2 => :value2 }
result = { :this_is_the_first_key => :this_is_the_first_value,
:this_is_another_key => :this_is_another_value,
:this_is_the_last_key => :this_is_the_last_value }
# good for ruby 1.9
result = { key1: :value1, key2: :value2 }
result = { this_is_the_first_key: :this_is_the_first_value,
this_is_another_key: :this_is_another_value,
this_is_the_last_key: :this_is_the_last_value }
```
## 語法
* 定義方法時,如果帶參要加上括號谭胚,否則不要加括號
```Ruby
def some_method
# body omitted
end
def some_method_with_arguments(arg1, arg2)
# body omitted
end
```
* 使用迭代器徐块,而不要使用for語句
```Ruby
# bad
for elem in arr do
puts elem
end
# good
arr.each { |elem| puts elem }
```
* 不要在多行的if/unless語句中使用then
```Ruby
# bad
if some_condition then
# body omitted
end
# good
if some_condition
# body omitted
end
```
* 不要使用冗長的三元表達(dá)式(?:)。但在邏輯較簡單灾而,能用一行表達(dá)的情況下胡控,使用三元表達(dá)式而不是if/then/else/end語句
```Ruby
# bad
result = if some_condition then something else something_else end
# good
result = some_condition ? something : something_else
```
* 三元表達(dá)式中不要出現(xiàn)邏輯分支。在這種情況下應(yīng)該使用if/else語句
```Ruby
# bad
some_condition ? (nested_condition ? nested_something : nested_something_else) : something_else
# good
if some_condition
nested_condition ? nested_something : nested_something_else
else
something_else
end
```
* 不要使用and和or. 總是使用&&和||
```Ruby
# bad. 注意操作符的優(yōu)先級
# 等價于(result = find_by_name(name)) or find_by_code(code) or fail 'wow!'
result = find_by_name(name) or find_by_code(code) or fail 'wow!'
# good
result = find_by_name(name) || find_by_code(code) || fail('wow!')
```
* 不要跨行使用三元表達(dá)式, 使用if/unless語句
* 在語句非常簡短的情況下使用單行后置if/unless語句
```Ruby
# bad
if some_condition
do_something
end
# good
do_something if some_condition
```
* 不要使用unless/else語句旁趟,將它改寫為if/else語句
```Ruby
# bad
unless success?
puts 'failure'
else
puts 'success'
end
# good
if success?
puts 'success'
else
puts 'failure'
end
```
* 不要對if/unless/while語句的中的單個條件使用括號
```Ruby
# bad
if (x > 10)
# body omitted
end
# good
if x > 10
# body omitted
end
# good, 不用括號語義會改變昼激。即使用括號也不要用or
if (x = something) && y
# body omitted
end
```
* 單行block使用花括號{...}. 多行block使用do...end. 不要使用do...end進(jìn)行鏈?zhǔn)秸{(diào)用
```Ruby
names = %w(Bozhidar Steve Sarah)
# good
names.each { |name| puts name }
# bad
names.each do |name|
puts name
end
# good
names.select { |name| name.start_with?('S') }.map { |name| name.upcase }
# bad
names.select do |name|
name.start_with?("S")
end.map { |name| name.upcase }
# ok, 但更佳的方式是把復(fù)雜邏輯包裝成方法調(diào)用
names.select { |name| name.start_with?('S') && name.end_with?('e') }.
map { |name| name.upcase }
```
* 不要在不必要的情況下使用return
```Ruby
# bad
def some_method(some_arr)
return some_arr.size
end
# good
def some_method(some_arr)
some_arr.size
end
```
* 方法定義時,在定義有默認(rèn)值的參數(shù)時,等號的前后要加上空格
```Ruby
# bad
def some_method(arg1=:default, arg2=nil, arg3=[])
# do something...
end
# good
def some_method(arg1 = :default, arg2 = nil, arg3 = [])
# do something...
end
```
* 可以使用賦值語句(=)的返回值橙困,但要注意優(yōu)先級瞧掺,需要時以括號環(huán)繞
```Ruby
# ok
if (v = array.grep(/foo/))
# do something...
end
# good
if v = array.grep(/foo/)
# do something...
end
# also good - 用括號保證操作符優(yōu)先級正確
if (v = self.next_value) == "hello"
# do something...
end
```
* 可以使用||=給變量賦初值,但不要使用||=給布爾類型變量賦初值(因為原始值可能為false)
```Ruby
# set name to Bozhidar, only if it's nil or false
name ||= 'Bozhidar'
# bad - would set enabled to true even if it was false
enabled ||= true
# good
enabled = true if enabled.nil?
```
* 在用實例變量實現(xiàn)memoize模式的方法中凡傅,使用defined?而不是||=
```Ruby
# bad, 當(dāng)do_something的返回值為nil或false時辟狈,沒有起到memoize的效果
def some_method
@result ||= do_something
end
# good
def some_method
defined?(@result) ? @result : @result = do_something
end
```
* 不要在方法名和括號中間加空格
```Ruby
# bad
f (3 + 2) + 1
# bad
def fun (a)
a * 2
end
# good
f(3 + 2) + 1
# good
def fun(a)
a * 2
end
```
* 用下劃線取代未使用的block參數(shù)
```Ruby
# bad
result = hash.map { |k, v| v + 1 }
# good
result = hash.map { |_, v| v + 1 }
```
## 命名規(guī)則
* 方法和變量使用snake_case
```Ruby
# bad
def someMethod
myName = 'luoxin'
end
# good
def some_method
my_name = 'luoxin'
end
```
* 類和模塊的定義使用CamelCase. 注意保持首字母縮寫詞的原有形式,如HTTP, RFC, XML等
```Ruby
# bad
module HttpServer
# ...
end
# good
module HTTPServer
# ...
end
```
* 常量的定義使用SCREAMING_SNAKE_CASE
```Ruby
# bad
class Week
Days_In_A_Week = 7
end
# good
class Week
DAYS_IN_A_WEEK = 7
end
```
* 配置相關(guān)信息不要使用常量
```Ruby
# bad
class JavaService
JAVA_SERVICE_URL = '192.168.8.81'
end
# good
class JavaService
cattr_accessor :url, instance_write: false
self.url = '192.168.8.81'
end
```
* 邏輯判斷的方法(返回值為布爾型)應(yīng)該使用形容詞或動詞完成時態(tài)像捶,并以問號結(jié)尾上陕。不要使用is_前綴
```Ruby
# bad
def empty
something.count > 0
end
# bad
def is_empty?
something.count > 0
end
# good
def empty?
something.count > 0
end
```
* 使用具有描述性的方法名和變量名
```Ruby
# bad
def gen_ymdhms
# ...
end
# good
def generate_timestamp
# ...
end
# bad
all = deals.map { |x| x.id }
# good
deal_ids = deals.map { |deal| deal.id }
```
## 集合
* 當(dāng)聲明全由字符串組成的數(shù)組時桩砰,使用%w()
```Ruby
# bad
STATES = ['draft', 'open', 'closed']
# good
STATES = %w(draft open closed)
```
* 如果集合中的元素皆為唯一拓春,使用Set而不是Array
```Ruby
s = Set.new
s << 1 << 2 << 3 << 2 << 1 #
```
* 盡量使用symbol而不是string作為哈希的鍵
```Ruby
# bad
hash = { 'one' => 1, 'two' => 2, 'three' => 3 }
# good for ruby 1.8
hash = { :one => 1, :two => 2, :three => 3 }
# good for ruby 1.9
hash = { one: 1, two: 2, three: 3 }
```
## 字符串
* 使用字符串插入(interpolation),不要使用字符串加法
```Ruby
# bad
email_with_name = user.name + ' <' + user.email + '>'
# good
email_with_name = "#{user.name} <#{user.email}>"
```
* 當(dāng)不需要字符串插入或者處理\t, \n等特殊字符時亚隅,使用單引號
```Ruby
# bad
require "active_support"
name = "Bozhidar"
# good
require 'active_support'
name = 'Bozhidar'
```
* 構(gòu)造字符串時不要使用字符串加法, 要使用字符串連接操作符
```Ruby
# good and also fast
html = ''
html << 'Page title'
paragraphs.each { |paragraph| html << "#{paragraph}" }
```
## 特殊構(gòu)造符
* 使用%w構(gòu)造字符串?dāng)?shù)組
```Ruby
STATES = %w(draft open closed)
```
* 使用%()構(gòu)造單行的硼莽、包含雙引號和插入的字符串
```Ruby
# bad (no interpolation needed)
%(Some text)
# should be 'Some text'
# bad (no double-quotes)
%(This is #{quality} style)
# should be "This is #{quality} style"
# bad (multiple lines)
%(\n#{exclamation}\n)
# should be a heredoc
# bad (no double-quotes)
%(#{name})
# should be "#{name}"
# good (requires interpolation, has double quotes, single line)
%(#{name})
```
* 對多行的字符串使用heredoc
```Ruby
# bad (multiple lines)
%(\n#{exclamation}\n)
# should be a heredoc
# good
<<-HTML
#{exclamation}
HTML
```
* 在正則表達(dá)式中包含多個'/'時使用%r
```Ruby
# bad
%r(\s+)
# still bad
%r(^/(.*)$)
# should be /^\/(.*)$/
# good
%r(^/blog/2011/(.*)$)
```
* 不要使用%q, %Q, %x, %s, and %W. 使用%構(gòu)造符時,首選()作為分割符
```Ruby
# bad
name = %q(I'm luoxin)
# should be "I'm luoxin" or 'I\'m luoxin'
# bad
div = %Q()
# should be %()
# bad
div = %Q[div id="#@div_id" name="#@div_name">]
# should be %()
# good
js = %[alert("Hi, #@user_name");]
```
## 其他
* 代碼中嵌入SQL語句時煮纵,SQL關(guān)鍵字大寫懂鸵,其他字符小寫
```Ruby
# bad
User.find_by_sql('select id, name from users where id < 5')
# good
User.find_by_sql('SELECT id, name FROM users WHERE id < 5')
```
* 如果代碼中嵌入的SQL語句過長,使用換行來劃分邏輯
```Ruby
# bad, too long
ActiveRecord::Base.connection.execute('INSERT INTO hui800.deals VALUES (id, code, title, start_time, end_time) SELECT id, code, name, start_date, end_date FROM coupons.deal')
# good
ActiveRecord::Base.connection.execute <<-SQL
INSERT INTO hui800.deals
VALUES (id, code, title, start_time, end_time)
SELECT id, code, name, start_date, end_date
FROM coupons.deal
SQL
```
* 不要出現(xiàn)N+1查詢
```Ruby
# bad, N+1
users = User.where('id < 100')
users.each { |u| puts u.school }
# good
users = User.where('id < 100').includes(:school)
users.each { |u| puts u.school }
```
* 方法參數(shù)的個數(shù)盡量不超過兩個行疏,最多不超過三個(*args除外)匆光。如果有多選項,一般通過最末的哈希參數(shù)來傳遞
```Ruby
# bad
def my_cache(key, city_id = nil, page = nil, per_page = nil, order_by = nil)
end
# good
def my_cache(key, options = {})
end
```
* 在Ruby 1.9中使用新的哈希語法
```Ruby
# bad for Ruby 1.9
hash = { :a => 1, :b => 2, :c => 3 }
# good for Ruby 1.9
hash = { a: 1, b: 2, c: 3 }
```
* 在Ruby 1.9中使用新的lambda語法
```Ruby
# bad for Ruby 1.9
my_lambda = lambda { |x| puts x }
my_lambda.call(3)
# good for Ruby 1.9
my_lambda = ->(x) { puts x }
my_lambda.(3)
```
* 在不期望修改的對象上調(diào)用freeze方法
```Ruby
# bad
configuration = { database: 'tuan800', adapter: 'mysql2' }
# good
configuration = { database: 'tuan800', adapter: 'mysql2' }.freeze
```
* 熟練使用一種編輯器酿联。推薦使用Vim或Emacs
## 原則
1. Consistency(一致性)
2. KISS(簡潔性)
3. Common sense
4. Follow your heart
> 思想终息、語言、文字贞让,是一體的周崭,假如念起來亂糟糟,意思也不
> 會好——這是最簡單的真理喳张,但假如沒有前輩來告訴我续镇,我怎么
> 會知道啊。有時我也寫點不負(fù)責(zé)任的粗糙文字销部,以后重讀時摸航,
> 慚愧得無地自容,真想自己脫了褲子請道乾先生打我兩棍舅桩。孟
> 子曾說酱虎,無恥之恥,無恥矣〗龋現(xiàn)在我在文學(xué)上是個有廉恥的人逢净,
> 都是多虧了這些先生的教誨。對我來說,他們的作品是比鞭子
> 還有力量的鞭策爹土。
> 我一直想承認(rèn)我的文學(xué)師承是這樣一條鮮為人知的線索甥雕。這是
> 給我臉上貼金。我最終寫出了這些胀茵,不是因為我的書已經(jīng)寫得
> 好了社露,而是因為,不把這個秘密說出來琼娘,對現(xiàn)在的年輕人是不
> 公道的峭弟。沒有人告訴他們這些,只按名聲來理解文學(xué)脱拼,就會不
> 知道什么是壞瞒瘸,什么是好。
> --王小波《我的師承》