風格是從偉大事物中分離出的美好事物。
-- Bozhidar Batsov
作為 Ruby 開發者,有一件總是令我煩心的事 — Python 開發者有一份好的程式風格參考指南(PEP-8) 而我們永遠沒有一份官方指南,一份記錄 Ruby 程式風格及最佳實踐的指南。而我們確信風格很重要。我也相信這些好傢伙們,像我們是 Ruby 開發者,應該可以自己產生一份這夢寐以求的文件。
這份指南開始是作為我們公司內部 Ruby 程式指南(由我所寫的)。進行到某個部分時,我決定要把我的成果貢獻給廣大的 Ruby 社群,而且這個世界需要來自另一個公司內部的一點幫助。然而這個世界也可以從由社群制定及驅動的一系列 Ruby 程式慣例、實踐及風格中受益。
在開始寫這份指南時,我收到世界上很多優秀 Ruby 社群用戶們的反饋。感謝所有的建議及幫助!我們同心協力創造一個能夠讓每一個 Ruby 開發者受益的資源。
順道一提,如果你對 Rails 有興趣,你可以看看這份與之互補的 Ruby on Rails 3 風格指南。
這份 Ruby 風格指南向你推薦現實世界中,Ruby 程式設計師如何寫出可被別的 Ruby 程式設計師維護的程式碼。一份風格指南,反映出現實世界中的用法,並有一個理想,幫助人們避免使用危險的程式碼 — 不管它看起來有多好。
本指南分成數個相關規則的小節。我試著在每個規則後說明理由(如果省略的話,我相信理由相當明顯)。
我沒有想到所有的規則 — 他們大致上是基於,我作為一個專業軟體工程師的廣泛生涯,從 Ruby 社群成員所得到的反饋及建議,和數個高度評價的 Ruby 程式設計資源,像是 "Programming Ruby 1.9" 以及 "The Ruby Programming Language"。
本指南仍在進行改善中 — 某些規則缺乏實例,某些規則沒有例子來清楚地展示它們。在最後交付時,這些議題會被解決 — 就先把它們記在心理吧。
你可以使用 Transmuter 來產生本指南的一份 PDF 或 HTML 副本。
本指南被翻譯成下列語言:
幾乎每人都深信,每一個除了自己的風格都又醜又難讀。把 "除了自己的" 拿掉,他們或許是對的...
-- Jerry Coffin (論縮排)
-
使用
UTF-8作為原始檔案的編碼。 -
每個縮排層級使用兩個空格。不要使用 Hard Tabs。
# 好 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
-
-
使用空格來圍繞運算元,逗點
,、冒號:及分號;之後,圍繞{和}之前。 空格可能對(大部分)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
-
不要有空格在
(、[之後,或]、)之前。some(arg).other [1, 2, 3].length
-
把
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 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
-
在
def之間使用空行,並且把方法分成合乎邏輯的段落。def some_method data = initialize(options) data.manipulate! data.result end def some_method result 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 # 好 def send_mail(source) Mailer.deliver(to: 'bob@example.com', from: 'us@example.com', subject: 'Important message', body: source.text) end
-
Add underscores to big numeric literals to improve their readability.
# 差勁 - 到底是有幾個零? num = 1000000 # 良好 - 容易被人腦解讀。 num = 1_000_000
-
使用 RDoc 以及它的慣例來撰寫 API 文件。不要在註解區塊及
def之前放一個空行。 -
讓每一行保持在少於 80 個字元。
-
避免尾隨的空白(trailing whitesapce)。
-
使用
def時,當有參數時使用括號。當方法不接受任何參數時,省略括號。def some_method # 省略主體 end 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 # 好 arr.each { |elem| puts elem }
-
永遠不要在多行的
if/unless使用then# 不好 if some_condition then # 省略主體 end # 好 if some_condition # 省略主體 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; ...使用三元運算元來取代。 -
一行的情況使用
when x then ...。替代方案的語法when x: ...在 Ruby 1.9 被移除了。 -
永遠不要使用
when x; ...。參考前一個規則。 -
布林表達式使用
&&/||,控制流程使用and/or。(經驗法則:如果你需要使用外部括號,你正在使用錯誤的運算元。)# 布林表達式 if some_condition && some_other_condition do_something end # 控制流程 document.saved? or document.save!
-
避免多行的
? :(三元運算元);使用if/unless來取代。 -
當你有單行的主體時,偏愛
if/unless修飾符。另一個好的方法是使用控制流程的and/or。# 不好 if some_condition do_something end # 好 do_something if some_condition # 另一個好方法 some_condition and do_something
-
否定條件偏愛
unless優於if(或是控制流程or)。# 不好 do_something if !some_condition # 好 do_something unless some_condition # 另一個好方法 some_condition or 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 # 好 if (x = self.next_value) # 省略主體 end
-
當你有一個單行的主體時,偏愛使用
while/until修飾子。# bad while some_condition do_something end # good do_something while some_condition
-
負面條件偏愛
until勝於while。# bad do_something while !some_condition # good do_something until some_condition
-
忽略圍繞方法參數的括號,如內部 DSL (如:Rake, Rails, RSpec),Ruby 中帶有 "關鍵字" 狀態的方法(如:
attr_reader,puts)以及屬性存取方法。所有其他的方法呼叫,使用括號圍繞參數。class Person attr_reader :name, :age # 忽略 end temperance = Person.new('Temperance', 30) temperance.name puts temperance.age x = Math.sin(y) array.delete(e)
-
單行區塊喜好
{...}勝於do..end。多行區塊避免使用{...}(多行串連總是醜陋)。在do...end、 "控制流程" 及"方法定義",永遠使用do...end(如 Rakefile 及某些 DSL)。串連時避免使用do...end。names = ['Bozhidar', 'Steve', 'Sarah'] # 好 names.each { |name| puts name } # 不好 names.each do |name| puts name end # 好 names.select { |name| name.start_with?('S') }.map { |name| name.upcase } # 不好 names.select do |name| name.start_with?('S') end.map { |name| name.upcase }
某些人會爭論多行串連時,使用
{...}看起來還可以,但他們應該問問自己 — 這樣程式碼真的可讀嗎?難道不能把區塊內容取出來放到絕妙的方法裡嗎? -
避免在不需要控制流程的場合時使用
return。# 不好 def some_method(some_arr) return some_arr.size end # 好 def some_method(some_arr) some_arr.size end
-
避免在不需要的情況使用
self。(只有在呼叫一個 self write 存取器時會需要用到。)# 不好 def ready? if self.last_reviewed_at > self.last_updated_at self.worker.update(self.content, self.options) self.status = :in_progress end self.status == :verified end # 好 def ready? if last_reviewed_at > last_updated_at worker.update(content, options) status = :in_progress end status == :verified end
-
避免使用帶有區域變數的 shadowing 方法,除非它們彼此相等。
class Foo attr_accessor :options # ok def initialize(options) self.options = options # both options and self.options are equivalent here 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
-
當賦予預設值給方法參數時,使用空格圍繞
=運算元。# 不好 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
-
使用
=(一個賦值)的回傳值是好的,但用括號環繞賦值。# 好 — 演示賦值的目標用途 if (v = array.grep(/foo/)) ... # 不好 if v = array.grep(/foo/) ... # 也很好 — 演示賦值的目標用途及有正確的優先順序 if (v = self.next_value) == 'hello' ...
-
隨意使用
||=來初始化變數# 僅在name為nil或false時,把名字設為 Bozhidar。 name ||= 'Bozhidar'
-
不要使用
||=來初始化布林變數。(想看看如果現在的值剛好是false時會發生什麼。)# 不好 — 會把 enabled 設成真,即便它本來是假。 enabled ||= true # 好 enabled = true if enabled.nil?
-
避免使用 Perl 風格的特別變數(像是
$0-9,$`, 等等)。它們看起來非常神祕以及不鼓勵使用一行的腳本。 -
避免在方法名與左括號之間放一個空格。
# 不好 f (3 + 2) + 1 # 好 f(3 + 2) + 1
-
如果方法的第一個參數由左括號開始,永遠在這個方法呼叫裡使用括號。舉個例子,寫
f((3+2) + 1)。 -
總是使用
-w來執行 Ruby 直譯器,如果你忘了某個上述的規則,它就會警告你! -
當雜湊的鍵是符號時,偏好使用 Ruby 1.9 雜湊字面語法。
# 不好 hash = { :one => 1, :two => 2 } # 好 hash = { one: 1, two: 2 }
-
Ruby 1.9 偏好使用新的 lambda 字面語法。
# 不好 lambda = lambda { |a, b| a + b } lambda.call(1, 2) # 好 lambda = ->(a, b) { a + b } lambda.(1, 2)
-
未使用的區塊參數使用
_。# 不好 result = hash.map { |k, v| v + 1 } # 好 result = hash.map { |_, v| v + 1 }
程式設計的真正難題是替事物命名及無效的快取。
-- Phil Karlton
-
方法與變數使用蛇底式小寫(snake_case)。
-
類別與模組使用駝峰式大小寫(CamelCase)。(保留像是 HTTP、RFC、XML 這種縮寫為大寫)
-
其他常數使用尖叫蛇底式大寫(SCREAMING_SNAKE_CASE)。
-
判斷式(predicate)方法的名字(回傳布林值的方法)應以問號結尾。(即
Array#empty?) -
有潛在“危險性”的方法,若此 危險 方法有安全版本存在時,應以驚嘆號結尾(即:改動
self或參數、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! end end
-
在短的區塊使用
reduce時,把參數命名為|a, e|(累加器,元素) -
當定義二元運算元時,把參數命名為
other。def +(other) # 省略主體 end
-
偏好
map勝於collect,find勝於detect,select勝於find_all,reduce勝於inject以及size勝於length。這不是一個硬性要求;如果使用別名增加了可讀性,使用它沒關係。這些有押韻的方法名是從 Smalltalk 繼承而來,在別的語言不常見。鼓勵使用select而不是find_all的理由是它跟reject搭配起來是一目了然的。
良好的程式碼是最佳的文件。當你要加一個註解時,捫心自問,
"如何改善程式碼讓它不需要註解?" 改善程式碼然後記錄下來使它更簡潔。
-- 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或類似的地方。
-
當設計類別階層時,確認它們符合 Liskov 代換原則。
-
盡可能讓你的類別越[堅固](http://en.wikipedia.org/wiki/SOLID_(object-oriented_design\))越好。
-
永遠替類別提供一個適當的
to_s方法來表示領域模型(domain model)。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這類函數來定義瑣碎的 accessor 或 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 # 好 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,它替你定義了那些瑣碎的存取器(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 # 較佳 class Person < Struct.new (:first_name, :last_name) end
-
考慮加入工廠方法來提供額外合理的方式,來創造一個特定類別的實體。
class Person def self.create(options_hash) # 省略主體 end end
-
偏好鴨子類型勝於繼承。
# 不好 class Animal # 抽象方法 def speak end end # 繼承高層次的類別 (superclass) class Duck < Animal def speak puts 'Quack! Quack' end end # 繼承高層次的類別 (superclass) 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 # 也有可能且當你要定義多個 # singleton時的便利方法 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
-
永遠不要從
ensure區塊返回。如果你顯式地從ensure區塊中的一個方法返回,那麼這方法會如同沒有異常般的返回。實際上,異常會被默默地丟掉。def foo begin fail ensure return 'very bad idea' end end
-
盡可能使用隱式的
begin區塊。# 不好 def foo begin # main logic goes here rescue # failure handling goes here end end # 好 def foo # main logic goes here rescue # failure handling goes here end
-
透過 contingency 方法 (一個由 Avdi Grimm 創造的詞)來減少
begin區塊的使用。# 不好 begin something_that_might_fail rescue IOError # handle IOError end begin something_else_that_might_fail rescue IOError # handle IOError end # 好 def with_io_error_handling yield rescue # handle IOError end with_io_error_handling { something_that_might_fail } with_io_error_handling { something_else_that_might_fail }
-
不要封鎖異常。
begin # 這裡發生了一個異常 rescue SomeError # 救援子句完全沒有做事 end # bad do_something rescue nil
-
避免在 modifier 形式裡使用
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 # 從 StandardError 中救援一個救援子句, # 不是許多程式設計師所假定的異常。 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
-
偏愛使用標準函式庫的異常處理勝於導入新的異常類別。
-
偏好陣列及雜湊的字面表示法(除非你需要給建構子傳入參數)。
# 不好 arr = Array.new hash = Hash.new # 好 arr = [] hash = {}
-
當你需要使用一個字串的陣列時,偏好使用
%w的字面陣列語法。# 不好 STATES = ['draft', 'open', 'closed'] # 好 STATES = %w(draft open closed)
-
避免在陣列中創造巨大的間隔。
arr = [] arr[100] = 1 # 現在你有一個很多 nil 的陣列
-
當處理獨一無二的元素時,使用
Set來替代Array。Set實現了不重複的無序數值集合。Set是陣列直觀的內部操作功能與雜湊的快速存取的混合體。 -
偏好用符號取代字串作為雜湊的鍵。
# 不好 hash = { 'one' => 1, 'two' => 2, 'three' => 3 } # 好 hash = { one: 1, two: 2, three: 3 }
-
在處理需要出現的雜湊鍵時,使用
fetch。heroes = { 蝙蝠俠: 'Bruce Wayne', 超人: 'Clark Kent' } # 差勁 - 如果我們打錯字的話,我們就無法找到對的英雄了 heroes[:蝙蝠俠] # => "Bruce Wayne" heroes[:超女] # => nil # 棒 - fetch 會拋出一個 KeyError 來體現這個問題 heroes.fetch(:supermann)
-
避免使用可變的物件作為鍵值。
-
當你的 hash 鍵為符號時,偏好使用 Ruby 1.9 新的雜湊字面語法。
# 不好 hash = { :one => 1, :two => 2, :three => 3 } # 好 hash = { one: 1, two: 2, three: 3 }
-
相信這個事實吧,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 }" -
當你不需要插入特殊符號如
\t,\n,', 等等時,偏好單引號的字串。# 不好 name = 'Bozhidar' # 好 name = 'Bozhidar'
-
不要使用
{}圍繞要被插入字串的實體變數。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
-
當你需要建構龐大的資料區段(chunk)時,避免使用
String#+。 使用String#<<來替代。字串串接在對的地方改變字串實體,並且永遠比String#+來得快,String#+創造了一堆新的字串物件。# 好也比較快 html = '' html << '<h1>Page title</h1>' paragraphs.each do |paragraph| html << "<p>#{paragraph}</p>" end
-
如果你只需要在字串中簡單的搜索文字,不要使用正規表示法:
string['text'] -
針對簡單的字串查詢,你可以直接在字串索引中直接使用正規表示法。
match = string[/regexp/] # 獲得匹配正規表示法的內容 first_group = string[/text(grp)/, 1] # 或得分組的內容 string[/text (grp)/, 1] = 'replace' # string => 'text replace'
-
當你不需要替結果分組時,使用非分組的群組。
/(first|second)/ # 不好 /(?:first|second)/ # 好
-
避免使用
$1-9,因為它們很難追蹤它們包含什麼。可以使用命名群組來替代。# 不好 /(regexp)/ =~ string ... process $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可以與區塊或雜湊來使用。
-
隨意使用
%w。STATES = %w(draft open closed)
-
使用
%()給需要插值與嵌入雙引號的單行字串。多行字串,偏好使用 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>)
-
正規表示法要匹配多於一個的
/字元時,使用%r。# 不好 %r(\s+) # 仍不好 %r(^/(.*)$) # 應當是 /^\/(.*)$/ # 好 %r(^/blog/2011/(.*)$)
-
避免
%q,%Q,%x,%s以及%W。 -
偏好
()作為所有%字面的分隔符。
-
避免無謂的元程式設計。
-
寫一個函式庫時不要在核心類別搗亂(不要替它們加 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,-
僅捕捉字首定義良好的方法,像是
find_by_*― 讓你的程式碼愈肯定(assertive)愈好。 -
在最後的敘述句(statement)呼叫
super -
從 delegate 到 assertive, 不神奇的(non-magical)方法:
# 不好 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 裡。
-
避免參數列表長於三或四個參數。
-
如果你真的需要,加入 "全域" 變數到核心以及把它們設為私有的。
-
使用實體變數而不是全域變數。
# 不好 $foo_bar = 1 # 好 class Foo class << self attr_accessor :bar end end Foo.bar = 1
-
當
alias_method可以做到時,避免使用alias。 -
使用
OptionParser來解析複雜的命令行選項及ruby -s來處理瑣碎的命令行選項。 -
用函數式的方法寫程式,在有意義的情況下避免賦值。
-
不要變動參數,除非那是方法的目的。
-
避免超過三行的巢狀區塊。
-
保持一致性。在理想的世界裡,遵循這些準則。
-
使用常識。
在本指南所寫的每個東西都不是定案。這只是我渴望想與同樣對 Ruby 程式設計風格有興趣的大家一起工作,以致於最終我們可以替整個 Ruby 社群創造一個有益的資源。
歡迎開票或發送一個帶有改進的更新請求。在此提前感謝你的幫助!
一份社群策動的風格指南,對一個社群來說,只是讓人知道有這個社群。推特這個指南,分享給你的朋友或同事。我們得到的每個註解、建議或意見都可以讓這份指南變得更好一點。而我們想要擁有的是最好的指南,不是嗎?