今天review了一次ruby編程規(guī)范,把一些之前我不熟識的在這里記錄下來购公。
- 對于沒有主體的類萌京,傾向使用單行定義。 [link]
# 差
class FooError < StandardError
end
# 勉強(qiáng)可以
class FooError < StandardError; end
# 好
FooError = Class.new(StandardError)
- 把 when與 case縮排在同一層級宏浩。這是《Programming Ruby》與《The Ruby Programming Language》中早已確立的風(fēng)格知残。 [link]
# 差
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
- 當(dāng)將一個條件表達(dá)式的結(jié)果賦值給一個變量時,保持分支縮排在同一層級比庄。 [link]
# 差 - 非常費(fèi)解
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
# 好 - 結(jié)構(gòu)清晰
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
- 使用 _ 語法改善大數(shù)的數(shù)值字面量的可讀性求妹。 [link]
# 差 - 有幾個零乏盐?
num = 1000000
# 好 - 方便人腦理解
num = 1_000_000
- 使用 &&= 預(yù)先檢查變量是否存在,如果存在制恍,則做相應(yīng)動作父能。使用 &&= 語法可以省去 if檢查。 [link]
# 差
if something
something = something.downcase
end
# 差
something = something ? something.downcase : nil
# 勉強(qiáng)可以
something = something.downcase if something
# 好
something = something && something.downcase
# 更好
something &&= something.downcase
- 未被使用的區(qū)塊參數(shù)或局部變量净神,添加 _ 前綴或直接使用 _(盡管表意性略差)何吝。這種做法可以抑制 Ruby 解釋器或 RuboCop 等工具發(fā)出“變量尚未使用”的警告。 [link]
# 差
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 中允許給常量重新賦值(可能是重定向某些流),但解釋器會發(fā)出警告坡慌。 [link]
傾向使用 sprintf或其別名 format而不是相當(dāng)晦澀的 String#% 方法黔酥。 [link]
# 差
'%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'
- 當(dāng)你希望處理的變量類型是數(shù)組,但不太確定其是否真的是數(shù)組時洪橘,通過使用 [*var] 或 Array()來替代顯式的數(shù)組類型檢查與轉(zhuǎn)換跪者。 [link]
# 差
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?來替代復(fù)雜的比較邏輯。 [link]
# 差
do_something if x >= 1000 && x <= 2000
# 好
do_something if (1000..2000).include?(x)
# 好
do_something if x.between?(1000, 2000)
- 傾向使用 flat_map而不是 map + flatten的組合熄求。此規(guī)則并不適用于深度超過 2 層的數(shù)組坑夯。舉例來說,如果 users.first.songs == ['a', ['b','c']]成立抡四,則使用 map + flatten的組合而不是 flat_map。flat_map只能平坦化一個層級仗谆,而 flatten能夠平坦化任意多個層級指巡。 [link]
# 差
all_songs = users.map(&:songs).flatten.uniq
# 好
all_songs = users.flat_map(&:songs).uniq
- 傾向使用 reverse_each 而不是 reverse.each隶垮,因?yàn)槟承┗烊?Enumerable 模塊的類可能會提供 reverse_each 的高效版本藻雪。即使這些類沒有提供專門特化的版本,繼承自 Enumerable 的通用版本至少能保證性能與 reverse.each 相當(dāng)威始。 [link]
# 差
array.reverse.each { ... }
# 好
array.reverse_each { ... }
關(guān)于注釋的一些說明
使用 TODO 標(biāo)記應(yīng)當(dāng)加入的特征與功能木西。 [link]
使用 FIXME 標(biāo)記需要修復(fù)的代碼叼丑。 [link]
使用 OPTIMISE 標(biāo)記可能引發(fā)性能問題的低效代碼星立。 [link]
使用 HACK 標(biāo)記代碼異味胧沫,即那些應(yīng)當(dāng)被重構(gòu)的可疑編碼習(xí)慣南蹂。 [link]
使用 REVIEW 標(biāo)記需要確認(rèn)與編碼意圖是否一致的可疑代碼疗疟。比如,REVIEW: Are we sure this is how the client does X currently?霜第。 [link]
類定義
- 在類定義中刃榨,使用一致的結(jié)構(gòu)枢希。 [link]
class Person
# 首先是 extend 與 include
extend SomeModule
include AnotherModule
# 內(nèi)部類
CustomErrorKlass = Class.new(StandardError)
# 接著是常量
SOME_CONSTANT = 20
# 接下來是屬性宏
attr_reader :name
# 跟著是其他宏(如果有的話)
validates :name
# 公開的類方法接在下一行
def self.some_method
end
# 初始化方法在類方法和實(shí)例方法之間
def initialize
end
# 跟著是公開的實(shí)例方法
def some_method
end
# 受保護(hù)及私有的方法等放在接近結(jié)尾的地方
protected
def some_protected_method
end
private
def some_private_method
end
end
- 如果嵌套類數(shù)目較多桌吃,進(jìn)而導(dǎo)致外圍類定義較長,則將它們從外圍類中提取出來苞轿,分別放置在單獨(dú)的以嵌套類命名的文件中茅诱,并將文件歸類至以外圍類命名的文件夾下。 [link]
# 差
# 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
- 定義只有類方法的數(shù)據(jù)類型時搬卒,傾向使用模塊而不是類让簿。只有當(dāng)需要實(shí)例化時才使用類。 [link]
# 差
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
- 當(dāng)你想將模塊的實(shí)例方法變成類方法時秀睛,傾向使用 module_function而不是 extend self。 [link]
# 差
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
- 優(yōu)先考慮通過工廠方法的方式創(chuàng)建某些具有特定意義的實(shí)例對象莲祸。 [link]
class Person
def self.create(options_hash)
# 省略主體
end
end
- 盡可能隱式地使用 begin/rescue/ensure/end區(qū)塊蹂安。 [link]
# 差
def foo
begin
# 主邏輯
rescue
# 異常處理邏輯
end
end
# 好
def foo
# 主邏輯
rescue
# 異常處理邏輯
end
- 當(dāng)創(chuàng)建一組元素為單詞(沒有空格或特殊字符)的數(shù)組時,傾向使用 %w而不是 []锐帜。此規(guī)則只適用于數(shù)組元素有兩個或以上的時候田盈。 [link]
# 差
STATES = ['draft', 'open', 'closed']
# 好
STATES = %w(draft open closed)
- 當(dāng)創(chuàng)建一組符號類型的數(shù)組(且不需要保持 Ruby 1.9 兼容性)時,傾向使用 %i 缴阎。此規(guī)則只適用于數(shù)組元素有兩個或以上的時候允瞧。 [link]
# 差
STATES = [:draft, :open, :closed]
# 好
STATES = %i(draft open closed)
- 當(dāng)訪問集合中的元素時,傾向使用對象所提供的方法進(jìn)行訪問,而不是直接調(diào)用對象屬性上的 [n]方法述暂。這種做法可以防止你在 nil 對象上調(diào)用 []痹升。 [link]
# 差
Regexp.last_match[1]
# 好
Regexp.last_match(1)
- 當(dāng)你需要構(gòu)造巨大的數(shù)據(jù)塊時,避免使用 String#+畦韭,使用 String#<<來替代疼蛾。String#<<通過修改原對象進(jìn)行拼接工作,其比 String#+ 效率更高艺配,因?yàn)楹笳咝枰a(chǎn)生一堆新的字符串對象察郁。 [link]
# 差
html = ''
html += '<h1>Page title</h1>'
paragraphs.each do |paragraph|
html += "<p>#{paragraph}</p>"
end
# 好 - 并且效率更高
html = ''
html << '<h1>Page title</h1>'
paragraphs.each do |paragraph|
html << "<p>#{paragraph}</p>"
end
- 當(dāng)存在更快速、更專業(yè)的替代方案時转唉,不要使用 String#gsub皮钠。 [link]
url = 'http://example.com'
str = 'lisp-case-rules'
# 差
url.gsub('http://', 'https://')
str.gsub('-', '_')
# 好
url.sub('http://', 'https://')
str.tr('-', '_')
- 使用 Ruby 2.3 新增的 <<~ 操作符來縮排 heredocs 中的多行文本。 [link]
# 差 - 使用 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
- 當(dāng)你不需要分組結(jié)果時赠法,使用非捕獲組麦轰。 [link]
# 差
/(first|second)/
# 好
/(?:first|second)/
- 避免使用 Perl 風(fēng)格的、用以代表最近的捕獲組的特殊變量(比如 $1期虾、$2 等)原朝。使用 Regexp.last_match(n) 來替代。[link]
/(regexp)/ =~ string
...
# 差
process $1
# 好
process Regexp.last_match(1)
- 小心使用 ^ 與 $ 镶苞,它們匹配的是一行的開始與結(jié)束喳坠,而不是字符串的開始與結(jié)束。如果你想要匹配整個字符串茂蚓,使用 \A 與 \z壕鹉。(注意,\Z 實(shí)為 /\n?\z/) [link]
string = "some injection\nusername"
string[/^username$/] # 匹配成功
string[/\Ausername\z/] # 匹配失敗
- 對于復(fù)雜的正則表達(dá)式聋涨,使用 x 修飾符晾浴。這種做法不但可以提高可讀性,而且允許你加入必要的注釋牍白。注意的是脊凰,空白字符會被忽略。 [link]
regexp = /
start # some text
\s # white space char
(group) # first group
(?:alt1|alt2) # some alternation
end
/x
- 只有當(dāng)字符串中同時存在插值與雙引號茂腥,且是單行時狸涌,才使用 %()(%Q 的簡寫形式)。多行字符串最岗,傾向使用 heredocs帕胆。 [link]
# 差 - 不存在插值
%(<div class="text">Some text</div>)
# 應(yīng)當(dāng)使用 '<div class="text">Some text</div>'
# 差 - 不存在雙引號
%(This is #{quality} style)
# 應(yīng)當(dāng)使用 "This is #{quality} style"
# 差 - 多行字符串
%(<div>\n<span class="big">#{exclamation}</span>\n</div>)
# 應(yīng)當(dāng)使用 heredocs
# 好 - 同時存在插值與雙引號,且是單行字符串
%(<tr><td class="name">#{name}</td>)
- 避免使用 %q般渡,除非字符串同時存在 ' 與 "懒豹。優(yōu)先考慮更具可讀性的常規(guī)字符串芙盘,除非字符串中存在大量需要轉(zhuǎn)義的字符。 [link]
# 差
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?"'
quote = %q(<p class='quote'>"What did you say?"</p>)
- 只有當(dāng)正則表達(dá)式中存在一個或以上的 / 字符時脸秽,才使用 %r儒老。 [link]
# 差
%r{\s+}
# 好
%r{^/(.*)$}
%r{^/blog/2011/(.*)$}
避免使用 method_missing。它會使你的調(diào)用棧變得凌亂豹储;其方法不被羅列在 #methods 中贷盲;拼錯的方法可能會默默地工作(nukes.launch_state = false)。優(yōu)先考慮使用委托剥扣、代理巩剖、或是 define_method
來替代。如果你必須使用 method_missing 的話钠怯,務(wù)必做到以下幾點(diǎn): [link]
- 確保同時定義了 respond_to_missing?佳魔。
- 僅僅捕獲那些具有良好語義前綴的方法,像是 find_by_*——讓你的代碼愈確定愈好晦炊。
- 在語句的最后調(diào)用 super鞠鲜。
- 委托到確定的、非魔術(shù)的方法断国,比如:
# 差
def method_missing?(meth, *params, &block)
if /^find_by_(?<prop>.*)/ =~ meth
# ... 一堆處理 find_by 的代碼
else
super
end
end
# 好
def method_missing?(meth, *params, &block)
if /^find_by_(?<prop>.*)/ =~ meth
find_by(prop, *params, &block)
else
super
end
end
# 最好的方式可能是在每個需要支持的屬性被聲明時贤姆,使用 define_method 定義對應(yīng)的方法