份 Ruby 風格指南推薦的是 Ruby 的最佳實踐,現(xiàn)實世界中的 Ruby 程序員據(jù)此可以寫出可維護的高質(zhì)量代碼。我們只說實際使用中的用法。指南再好,但里面說的過于理想化結(jié)果大家拒絕使用或者可能根本沒人用,又有何意義。
本指南分為幾個小節(jié),每一小節(jié)由幾條相關(guān)的規(guī)則構(gòu)成。我盡力在每條規(guī)則后面說明理由(如果省略了說明,那是因為其理由顯而易見)。
這些規(guī)則不是我憑空想象出來的——它們中的絕大部分來自我多年以來作為職業(yè)軟件工程師的經(jīng)驗,來自 Ruby 社區(qū)成員的反饋和建議,以及幾個評價甚高的 Ruby 編程資源,像《Programming Ruby》以及《The Ruby Programming Language》。
Ruby 社區(qū)尚未就某些規(guī)則達成明顯的共識,比如字符串字面量的引號、哈希字面量兩端是否應該添加空格、多行鏈式方法調(diào)用中 .
操作符的位置。對于這種情況,本指南列出了所有可選的流行風格,你可以任選其一并堅持使用。
本指南會一直更新,隨著 Ruby 本身的發(fā)展,新的規(guī)則會添加進來,過時的規(guī)則會被剔除。
許多項目有其自己的編程風格指南(往往是源于本指南而創(chuàng)建)。當項目的風格指南與本指南發(fā)生沖突時,應以項目級的指南為準。
你可以使用 Pandoc 生成本指南的 PDF 或 HTML 版本。
RuboCop 工具會自動檢查你的 Ruby 代碼是否符合這份 Ruby 風格指南。
源代碼排版
所有風格都又丑又難讀,自己的除外。幾乎人人都這樣想。把“自己的除外”拿掉,他們或許是對的...
——Jerry Coffin(論縮排)
使用 UTF-8
作為源文件的編碼。[link]
每個縮排層級使用兩個空格。不要使用制表符。[link]
# 差 - 四個空格def some_method do_somethingend# 好def some_method do_somethingend
使用 Unix 風格的換行符。(*BSD/Solaris/Linux/OS X 系統(tǒng)的用戶不需擔心,Windows 用戶則要格外小心。)[link]
如果你使用 Git,可用下面這個配置來保護你的項目不被 Windows 的換行符干擾:
$ git config --global core.autocrlf true
不要使用 ;
隔開語句與表達式。推論:一行一條語句。[link]
# 差puts 'foobar'; # 不必要的分號puts 'foo'; puts 'bar' # 一行里有兩個表達式# 好puts 'foobar'puts 'foo'puts 'bar'puts 'foo', 'bar' # 僅對 puts 適用
對于沒有主體的類,傾向使用單行定義。[link]
# 差class FooError < StandardErrorend# 勉強可以class FooError < StandardError; end# 好FooError = Class.new(StandardError)
定義方法時,避免單行寫法。盡管這種寫法有時頗為普遍,但其略顯古怪的定義語法容易使人犯錯。無論如何,至少保證單行寫法的方法不應該擁有一個以上的表達式。[link]
# 差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 bodyend
這個規(guī)則的一個例外是空方法。
# 好def no_op; end
操作符前后適當?shù)靥砑涌崭瘢诙禾?,
、冒號 :
及分號 ;
之后。盡管 Ruby 解釋器(大部分情況下)會忽略空格,但適量的空格可以增強代碼的可讀性。[link]
sum = 1 + 2a, b = 1, 2class FooError < StandardError; end
(對于操作符)唯一的例外是當使用指數(shù)操作符時:
# 差e = M * c ** 2# 好e = M * c**2
(
、[
之后,]
、)
之前,不要添加任何空格。在 {
前后,在 }
之前添加空格。[link]
# 差some( arg ).other [ 1, 2, 3 ].each{|e| puts e}# 好some(arg).other [1, 2, 3].each { |e| puts e }
{
與 }
需要額外說明,因為它們可以同時用在區(qū)塊、哈希字面量及字符串插值中。
對于哈希字面量,有兩種可被接受的風格。第一種風格更具可讀性(在 Ruby 社區(qū)里似乎更為流行)。第二種風格的優(yōu)點是,在視覺上使得區(qū)塊與哈希字面量有所區(qū)分。無論你選擇何種風格,務必在使用時保持連貫性。
# 好 - { 之后 與 } 之前有空格{ one: 1, two: 2 }# 好 - { 之后 與 } 之前無空格{one: 1, two: 2}
對于插值表達式,括號內(nèi)兩端不要添加空格。
# 差"From: #{ user.first_name }, #{ user.last_name }"# 好"From: #{user.first_name}, #{user.last_name}"
!
之后,不要添加任何空格。[link]
# 差! something# 好!something
范圍的字面量語法中,不要添加任何空格。[link]
# 差1 .. 3'a' ... 'z'# 好1..3'a'...'z'
把 when
與 case
縮排在同一層級。這是《Programming Ruby》與《The Ruby Programming Language》中早已確立的風格。[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.playend# 好casewhen 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.playend
當將一個條件表達式的結(jié)果賦值給一個變量時,保持分支縮排在同一層級。[link]
# 差 - 非常費解kind = case yearwhen 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'endresult = if some_cond calc_somethingelse calc_something_elseend# 好 - 結(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' endresult = 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' endresult = if some_cond calc_something else calc_something_else end
在各個方法定義之間添加空行,并且將方法分成若干合乎邏輯的段落。[link]
def some_method data = initialize(options) data.manipulate! data.resultenddef some_method resultend
在各個段落之間,使用一個空行分隔。[link]
# 差 - 使用了兩個空行some_method some_method# 好some_method some_method
在屬性修飾器之后,使用一個空行分隔。[link]
# 差class Foo attr_reader :foo def foo # 做一些事情 endend# 好class Foo attr_reader :foo def foo # 做一些事情 endend
在不同縮進的代碼之間,不要使用空行分隔。[link]
# 差class Foo def foo begin do_something do something end rescue something end endend# 好class Foo def foo begin do_something do something end rescue something end endend
避免在方法調(diào)用的最后一個參數(shù)之后添加逗號,尤其當參數(shù)沒有分布在同一行時。[link]
# 差 - 盡管移動、新增、刪除參數(shù)頗為方便,但仍不推薦這種寫法some_method( size, count, color, )# 差some_method(size, count, color, )# 好some_method(size, count, color)
當給方法的參數(shù)賦予默認值時,在 =
前后添加空格。[link]
# 差def some_method(arg1=:default, arg2=nil, arg3=[]) # 做一些事情end# 好def some_method(arg1 = :default, arg2 = nil, arg3 = []) # 做一些事情end
盡管有幾本 Ruby 書籍推薦使用第一種風格,但第二種在實踐中更為常見(而且似乎更具可讀性)。
避免在非必要的情形下使用續(xù)行符 \
。在實踐中,除了字符串拼接,避免在其他任何地方使用續(xù)行。[link]
# 差result = 1 - \ 2# 好 - 但仍然丑到爆result = 1 \ - 2long_string = 'First part of the long string' \ ' and second part of the long string'
使用統(tǒng)一的風格進行多行鏈式方法調(diào)用。在 Ruby 社區(qū)中存在兩種流行的風格:前置 .
(風格 A)與后置 .
(風格 B)。[link]
兩種風格各自優(yōu)點查閱這里。
(風格 A) 當多行鏈式方法調(diào)用需要另起一行繼續(xù)時,將 .
放在第二行開頭。
# 差 - 需要查看第一行才能理解第二行在做什么one.two.three. four# 好 - 立刻能夠明白第二行在做什么one.two.three .four
(風格 B) 將 .
放在第一行末尾,以表示當前表達式尚未結(jié)束。
# 差 - 需要查看第二行才能知道鏈式方法調(diào)用是否結(jié)束one.two.three .four# 好 - 立刻能夠明白第二行還有其他方法調(diào)用one.two.three. four
當方法調(diào)用參數(shù)過長時,將它們排列在多行并對齊。若對齊后長度超過行寬限制,將首個參數(shù)位置挪到下一行進行縮排也是可以接受的。[link]
# 初始(行太長了)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
當構(gòu)建數(shù)組時,若元素跨行,應當保持對齊。[link]
# 差 - 沒有對齊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']
使用 _
語法改善大數(shù)的數(shù)值字面量的可讀性。[link]
# 差 - 有幾個零?num = 1000000# 好 - 方便人腦理解num = 1_000_000
當數(shù)值需要前綴標識進制時,傾向使用小寫字母。使用 0o
標識八進制,使用 0x
標識十六進制,使用 0b
標識二進制。十進制數(shù)值無需前綴(0d
)標識。[link]
# 差num = 01234num = 0O1234num = 0X12ABnum = 0B10101num = 0D1234num = 0d1234# 好 - 方便區(qū)分數(shù)值前綴與具體數(shù)字num = 0o1234num = 0x12ABnum = 0b10101num = 1234
使用 RDoc 及其慣例來編寫 API 文檔。注意,不要在注釋與 def
之間添加空行。[link]
將單行長度控制在 80 個字符內(nèi)。[link]
避免行尾空格。[link]
文件以空白行結(jié)束。[link]
不要使用區(qū)塊注釋。它們不能被空白字符引導,且不如常規(guī)注釋容易辨認。[link]
# 差=begincomment lineanother comment line=end# 好# comment line# another comment line
語法
使用 ::
引用常量(包括類與模塊)與構(gòu)造器(比如 Array()
、Nokogiri::HTML()
)。不要使用 ::
調(diào)用常規(guī)方法。[link]
# 差SomeClass::some_method some_object::some_method# 好SomeClass.some_method some_object.some_methodSomeModule::SomeClass::SOME_CONSTSomeModule::SomeClass()
使用 def
定義方法時,如果有參數(shù)則使用括號,如果無參數(shù)則省略括號。[link]
# 差def some_method() # 省略主體end# 好def some_method # 省略主體end# 差def some_method_with_parameters param1, param2 # 省略主體end# 好def some_method_with_parameters(param1, param2) # 省略主體end
方法調(diào)用應當使用括號包裹參數(shù),尤其是第一個參數(shù)以 (
開頭時,比如 f((3 + 2) + 1)
;[link]
x = Math.sin y # 差x = Math.sin(y) # 好array.delete e # 差array.delete(e) # 好temperance = Person.new 'Temperance', 30 # 差temperance = Person.new('Temperance', 30) # 好
但在下述情況下可以省略括號:
無參調(diào)用
# 差Kernel.exit!()2.even?()fork()'test'.upcase()# 好Kernel.exit!2.even?fork'test'.upcase
內(nèi)部 DSL 的組成部分(比如 Rake、Rails、RSpec)
validates(:name, presence: true) # 差validates :name, presence: true # 好
具有“關(guān)鍵字”特性的方法
class Person attr_reader(:name, :age) # 差 attr_reader :name, :age # 好 # 省略主體endputs(temperance.age) # 差puts temperance.age # 好
定義可選參數(shù)時,將可選參數(shù)放置在參數(shù)列表尾部。如果可選參數(shù)出現(xiàn)在列表頭部,則此方法在調(diào)用時可能會產(chǎn)生預期之外的結(jié)果。[link]
# 差def some_method(a = 1, b = 2, c, d) puts "#{a}, #{b}, #{c}, #ag4ik86"endsome_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}, #ug4o444"endsome_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'
定義變量時,避免并行賦值。但當右值為方法調(diào)用返回值,或是與 *
操作符配合使用,或是交換兩個變量的值,并行賦值也是可以接受的。并行賦值的可讀性通常不如分開賦值。[link]
# 差a, b, c, d = 'foo', 'bar', 'baz', 'foobar'# 好a = 'foo'b = 'bar'c = 'baz'd = 'foobar'# 好 - 交換兩個變量的值a = 'foo'b = 'bar'a, b = b, aputs a # => 'bar'puts b # => 'foo'# 好 - 右值為方法調(diào)用返回值def multi_return [1, 2]endfirst, 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]
除非必要,否則避免在并行賦值時使用單字符的 _
變量。優(yōu)先考慮前綴形式的下劃線變量,而不是直接使用 _
,因為前者可以提供一定的語義信息。但當賦值語句左側(cè)出現(xiàn)帶 *
操作符的變量時,使用 _
也是可以接受的。[link]
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
實現(xiàn)的,所以你繞彎了。另外,for
沒有引入一個新的作用域 (each
有),因此在它內(nèi)部定義的變量在外部仍是可見的。[link]
arr = [1, 2, 3]# 差for elem in arr do puts elemend# 注意,elem 可在 for 循環(huán)外部被訪問elem # => 3# 好arr.each { |elem| puts elem }# 注意,elem 不可在 each 塊外部被訪問elem # => NameError: undefined local variable or method `elem'
永遠不要在多行 if
/unless
中使用 then
。[link]
# 差if some_condition then # 省略主體end# 好if some_condition # 省略主體end
在多行 if/unless
中,總是把條件表達式與 if/unless
放置在同一行。[link]
# 差if some_condition do_something do_something_elseend# 好if some_condition do_something do_something_elseend
傾向使用三元操作符(?:
)而不是 if/then/else/end
結(jié)構(gòu)。前者更為常見且簡練。[link]
# 差result = if some_condition then something else something_else end# 好result = some_condition ? something : something_else
三元操作符的每個分支只寫一個表達式。即不要嵌套三元操作符。嵌套情況請使用 if/else
結(jié)構(gòu)。[link]
# 差some_condition ? (nested_condition ? nested_something : nested_something_else) : something_else# 好if some_condition nested_condition ? nested_something : nested_something_elseelse something_elseend
永遠不要使用 if x; ...
。使用三元操作符來替代。[link]
# 差result = if some_condition; something else something_else end# 好result = some_condition ? something : something_else
利用“if
與 case
是表達式”的這個特性。[link]
# 差if condition result = xelse result = yend# 好result = if condition x else y end
在 case
表達式中,單行情況使用 when x then ...
語法。另一種語法 when x: ...
在 Ruby 1.9 之后被移除了。[link]
不要使用 when x; ...
語法。參考前一條規(guī)則。[link]
使用 !
而不是 not
。[link]
# 差 - 因為操作符的優(yōu)先級,這里必須使用括號x = (not something)# 好x = !something
避免使用 !!
。[link]
!!
會將表達式結(jié)果轉(zhuǎn)換為布爾值,但對于流程控制的表達式通常并不需要如此顯式的轉(zhuǎn)換過程。如果需要做 nil
檢查,那么調(diào)用對象的 nil?
方法。
# 差x = 'test'# 令人費解的 nil 檢查if !!x # 省略主體end# 好x = 'test'if x # 省略主體end
永遠不要使用 and
與 or
關(guān)鍵字。使用 &&
與 ||
來替代。[link]
# 差# 布爾表達式ok = got_needed_arguments and arguments_are_valid# 流程控制document.save or fail(RuntimeError, "Failed to save document!")# 好# 布爾表達式ok = got_needed_arguments && arguments_are_valid# 流程控制fail(RuntimeError, "Failed to save document!") unless document.save# 流程控制document.save || fail(RuntimeError, "Failed to save document!")
避免使用多行三元操作符(?:
)。使用 if
/unless
來替代。[link]
對于單行主體,傾向使用 if
/unless
修飾語法。另一種方法是使用流程控制 &&
/||
。[link]
# 差if some_condition do_somethingend# 好do_something if some_condition# 好 - 使用流程控制some_condition && do_something
避免在多行區(qū)塊后使用 if
/unless
修飾語法。[link]
# 差10.times do # 省略多行主體end if some_condition# 好if some_condition 10.times do # 省略多行主體 endend
避免使用嵌套 if
/unless
/while
/until
修飾語法。適當情況下,使用 &&
/||
來替代。[link]
# 差do_something if other_condition if some_condition# 好do_something if some_condition && other_condition
對于否定條件,傾向使用 unless
而不是 if
(或是使用流程控制 ||
)。[link]
# 差do_something if !some_condition# 差do_something if not some_condition# 好do_something unless some_condition# 好some_condition || do_something
不要使用 unless
與 else
的組合。將它們改寫成肯定條件。[link]
# 差unless success? puts 'failure'else puts 'success'end# 好if success? puts 'success'else puts 'failure'end
不要使用括號包裹流程控制中的條件表達式。[link]
# 差if (x > 10) # 省略主體end# 好if x > 10 # 省略主體end
這個規(guī)則的一個例外是條件表達式中的安全賦值。
在多行 while/until
中,不要使用 while/until condition do
。[link]
# 差while x > 5 do # 省略主體enduntil x > 5 do # 省略主體end# 好while x > 5 # 省略主體enduntil x > 5 # 省略主體end
對于單行主體,傾向使用 while/until
修飾語法。[link]
# 差while some_condition do_somethingend# 好do_something while some_condition
對于否定條件,傾向使用 until
而不是 while
。[link]
# 差do_something while !some_condition# 好do_something until some_condition
對于無限循環(huán),使用 Kernel#loop
而不是 while/until
。[link]
# 差while true do_somethingenduntil false do_somethingend# 好loop do do_somethingend
對于后置條件循環(huán)語句,傾向使用 Kernel#loop
與 break
的組合,而不是 begin/end/until
或 begin/end/while
。[link]
# 差begin puts val val += 1end while val < 0# 好loop do puts val val += 1 break unless val < 0end
對于可選參數(shù)的哈希,省略其外圍的花括號。[link]
# 差user.set({ name: 'John', age: 45, permissions: { read: true } })# 好user.set(name: 'John', age: 45, permissions: { read: true })
對于 DSL 的內(nèi)部方法調(diào)用,同時省略其外圍的圓括號與花括號。[link]
class Person < ActiveRecord::Base # 差 validates(:name, { presence: true, length: { within: 1..10 } }) # 好 validates :name, presence: true, length: { within: 1..10 }end
當被調(diào)用方法是當前區(qū)塊中唯一操作時,傾向使用簡短的傳參語法。[link]
# 差names.map { |name| name.upcase }# 好names.map(&:upcase)
對于單行主體,傾向使用 {...}
而不是 do...end
。對于多行主體,避免使用 {...}
。對于“流程控制”或“方法定義”(比如 Rakefile、其他 DSL 構(gòu)成片段),總是使用 do...end
。避免在鏈式方法調(diào)用中使用 do...end
。[link]
names = %w[Bozhidar Steve Sarah]# 差names.each do |name| puts nameend# 好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)
某些人可能會爭論在多行鏈式方法調(diào)用時使用 {...}
看起來還可以。但他們應該捫心自問——這樣的代碼真的可讀嗎?難道不能把區(qū)塊內(nèi)容提取出來放到小巧的方法里嗎?
優(yōu)先考慮使用顯式區(qū)塊參數(shù),以避免某些情況下通過創(chuàng)建區(qū)塊的手法來傳遞參數(shù)給其他區(qū)塊。此規(guī)則對性能有所影響,因為區(qū)塊會被轉(zhuǎn)換為 Proc
對象。[link]
require 'tempfile'# 差def with_tmp_dir Dir.mktmpdir do |tmp_dir| Dir.chdir(tmp_dir) { |dir| yield dir } # 通過創(chuàng)建區(qū)塊的手法來傳遞參數(shù) endend# 好def with_tmp_dir(&block) Dir.mktmpdir do |tmp_dir| Dir.chdir(tmp_dir, &block) endendwith_tmp_dir do |dir| puts "dir is accessible as a parameter and pwd is set: #{dir}"end
避免在不需要流程控制的情況下使用 return
。[link]
# 差def some_method(some_arr) return some_arr.sizeend# 好def some_method(some_arr) some_arr.sizeend
避免在不需要的情況下使用 self
。(只有在調(diào)用 self
的修改器、以保留字命名的方法、重載的運算符時才需要)[link]
# 差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 == :verifiedend# 好def ready? if last_reviewed_at > last_updated_at worker.update(content, options) self.status = :in_progress end status == :verifiedend
避免局部變量遮蔽方法調(diào)用,除非它們具有相同效果。[link]
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 endend
不要在條件表達式中使用 =
(賦值語句)的返回值,除非賦值語句包裹在括號之中。這種慣用法被稱作條件表達式中的安全賦值。[link]
# 差 - 會出現(xiàn)警告if v = array.grep(/foo/) do_something(v) ...end# 好 - 盡管 Ruby 解釋器仍會出現(xiàn)警告,但 RuboCop 不會if (v = array.grep(/foo/)) do_something(v) ...end# 好v = array.grep(/foo/)if v do_something(v) ...end
優(yōu)先考慮簡短的自我賦值語法。[link]
# 差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]
# 差name = name ? name : 'Bozhidar'# 差name = 'Bozhidar' unless name# 好 - 當且僅當 name 為 nil 或 false 時,設置 name 的值為 'Bozhidar'name ||= 'Bozhidar'
不要使用 ||=
對布爾變量進行初始化。[link]
# 差 - 設置 enabled 的值為 true,即使其原本的值是 falseenabled ||= true# 好enabled = true if enabled.nil?
使用 &&=
預先檢查變量是否存在,如果存在,則做相應動作。使用 &&=
語法可以省去 if
檢查。[link]
# 差if something something = something.downcaseend# 差something = something ? something.downcase : nil# 勉強可以something = something.downcase if something# 好something = something && something.downcase# 更好something &&= something.downcase
避免使用 case
語句等價操作符 ===
。從名稱可知,這是 case
語句隱式使用的操作符,在 case
語句外的場合中使用,會產(chǎn)生難以理解的代碼。[link]
# 差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? some_str# 好'ruby' == some_str1.0.eql? x # 當需要區(qū)別 Fixnum 1 與 Float 1.0 時,eql? 是具有意義的
避免使用 Perl 風格的特殊變量(比如 $:
、$;
等)。它們看起來非常神秘,但除了單行腳本,其他情況并不鼓勵使用。建議使用 English
程序庫提供的友好別名。[link]
# 差$:.unshift File.dirname(__FILE__)# 好require 'English'$LOAD_PATH.unshift File.dirname(__FILE__)
永遠不要在方法名與左括號之間添加空格。[link]
# 差f (3 + 2) + 1# 好f(3 + 2) + 1
運行 Ruby 解釋器時,總是開啟 -w
選項來。如果你忘了某個上述某個規(guī)則,它就會警告你![link]
不要在方法中嵌套定義方法,使用 lambda 方法來替代。 嵌套定義產(chǎn)生的方法,事實上和外圍方法處于同一作用域(比如類作用域)。此外,“嵌套方法”會在定義它的外圍方法每次調(diào)用時被重新定義。[link]
# 差def foo(x) def bar(y) # 省略主體 end bar(x)end# 好 - 作用同前,但 bar 不會在 foo 每次調(diào)用時被重新定義def bar(y) # 省略主體enddef foo(x) bar(x)end# 好def foo(x) bar = ->(y) { ... } bar.call(x)end
對于單行區(qū)塊,使用新的 lambda 字面量定義語法。對于多行區(qū)塊,使用 lambda
定義語法。[link]
# 差l = lambda { |a, b| a + b } l.call(1, 2)# 好 - 但看起來怪怪的l = ->(a, b) do tmp = a * 7 tmp * b / 50end# 好l = ->(a, b) { a + b } l.call(1, 2) l = lambda do |a, b| tmp = a * 7 tmp * b / 50end
定義 lambda 方法時,如果有參數(shù)則使用括號。[link]
# 差l = ->x, y { something(x, y) }# 好l = ->(x, y) { something(x, y) }
定義 lambda 方法時,如果無參數(shù)則省略括號。[link]
# 差l = ->() { something }# 好l = -> { something }
傾向使用 proc
而不是 Proc.new
。[link]
# 差p = Proc.new { |n| puts n }# 好p = proc { |n| puts n }
對于 lambda 方法或代碼塊,傾向使用 proc.call()
而不是 proc[]
或 proc.()
。[link]
# 差 - 看上去像是枚舉器的存取操作l = ->(v) { puts v } l[1]# 差 - 極少見的調(diào)用語法l = ->(v) { puts v } l.(1)# 好l = ->(v) { puts v } l.call(1)
未被使用的區(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]
使用 warn
而不是 $stderr.puts
。除了更加簡練清晰外,warn
允許你在需要時通過設置解釋器選項(使用 -W0
將警告級別設置為 0)來抑制警告。[link]
傾向使用 sprintf
或其別名 format
而不是相當晦澀的 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'
傾向使用 Array#join
而不是相當晦澀的帶字符參數(shù)的 Array#*
方法。[link]
# 差%w[one two three] * ', '# => 'one, two, three'# 好%w[one two three].join(', ')# => 'one, two, three'
當你希望處理的變量類型是數(shù)組,但不太確定其是否真的是數(shù)組時,通過使用 Array()
來替代顯式的數(shù)組類型檢查與轉(zhuǎn)換。[link]
# 差paths = [paths] unless paths.is_a? Arraypaths.each { |path| do_something(path) }# 差 - 總是構(gòu)建新的數(shù)組對象[*paths].each { |path| do_something(path) }# 好Array(paths).each { |path| do_something(path) }
通過使用范圍或 Comparable#between?
來替代復雜的比較邏輯。[link]
# 差do_something if x >= 1000 && x <= 2000# 好do_something if (1000..2000).include?(x)# 好do_something if x.between?(1000, 2000)
傾向使用謂詞方法而不是 ==
操作符。但數(shù)值比較除外。[link]
# 差if x % 2 == 0endif x % 2 == 1endif x == nilend# 好if x.even?endif x.odd?endif x.nil?endif x.zero?endif x == 0end
不做顯式的 non-nil
檢查,除非檢查對象是布爾變量。[link]
# 差do_something if !something.nil? do_something if something != nil# 好do_something if something# 好 - 檢查對象是布爾變量def value_set? !@some_boolean.nil?end
避免使用 BEGIN
區(qū)塊。[link]
永遠不要使用 END
區(qū)塊。使用 Kernel#at_exit
來替代。[link]
# 差END { puts 'Goodbye!' }# 好at_exit { puts 'Goodbye!' }
避免使用 flip-flops 操作符。[link]
流程控制中,避免使用嵌套條件。[link]
傾向使用防御從句進行非法數(shù)據(jù)斷言。防御從句是指處于方法頂部的條件語句,其能盡早地退出方法。
# 差def compute_thing(thing) if thing[:foo] update_with_bar(thing[:foo]) if thing[:foo][:bar] partial_compute(thing) else re_compute(thing) end endend# 好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
循環(huán)中,傾向使用 next
而不是條件區(qū)塊。
# 差[0, 1, 2, 3].each do |item| if item > 1 puts item endend# 好[0, 1, 2, 3].each do |item| next unless item > 1 puts itemend
傾向使用 map
而不是 collect
,find
而不是 detect
,select
而不是 find_all
,reduce
而不是 inject
以及 size
而不是 length
。這不是一個硬性要求,如果使用別名可以增強可讀性,使用它也沒關(guān)系。這些別名方法繼承自 Smalltalk 語言,但在別的語言并不通用。鼓勵使用 select
而不是 find_all
的理由是前者與 reject
搭配起來一目了然。[link]
不要使用 count
作為 size
的替代方案。除了 Array
外,其他 Enumerable
對象都需要通過枚舉整個集合才可以確定數(shù)目。[link]
# 差some_hash.count# 好some_hash.size
傾向使用 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
,因為某些混入 Enumerable
模塊的類可能會提供 reverse_each
的高效版本。即使這些類沒有提供專門特化的版本,繼承自 Enumerable
的通用版本至少能保證性能與 reverse.each
相當。[link]
# 差array.reverse.each { ... }# 好array.reverse_each { ... }
命名
程序設計的真正難題是替事物命名及使緩存失效。
——Phil Karlton
標識符使用英文命名。[link]
# 差 - 標識符使用非 ASCII 字符заплата = 1_000# 差 - 標識符使用拉丁文寫法的保加利亞單詞zaplata = 1_000# 好salary = 1_000
符號、方法、變量使用蛇底式小寫(snake_case
)。[link]
# 差:'some symbol':SomeSymbol:someSymbolsomeVar = 5var_10 = 10def someMethod ...enddef SomeMethod ...end# 好:some_symbolsome_var = 5var10 = 10def some_method ...end
給符號、方法、變量命名時,避免分隔字母與數(shù)字。[link]
# 差:some_sym_1some_var_1 = 1def some_method_1 # 做一些事情end# 好:some_sym1some_var1 = 1def some_method1 # 做一些事情end
類與模塊使用駝峰式大小寫(CamelCase
)。(HTTP、RFC、XML 等首字母縮寫應該仍舊保持大寫形式)[link]
# 差class Someclass ...endclass Some_Class ...endclass SomeXml ...endclass XmlSomething ...end# 好class SomeClass ...endclass SomeXML ...endclass XMLSomething ...end
文件名使用蛇底式小寫,如 hello_world.rb
。[link]
目錄名使用蛇底式小寫,如 lib/hello_world/hello_world.rb
。[link]
盡量使一個源文件中只有一個類或模塊。文件名就是類名或模塊名,但使用蛇底式小寫而不是駝峰式大小寫。[link]
其他常量使用尖叫蛇底式大寫(SCREAMING_SNAKE_CASE)。[link]
# 差SomeConst = 5# 好SOME_CONST = 5
謂詞方法(返回布爾值的方法)的名字應當以問號結(jié)尾。(比如 Array#empty?
)。不返回布爾值的方法不應以問號結(jié)尾。[link]
謂詞方法的名字應當避免使用 is
、does
、can
等助動詞作為前綴。這些助動詞在實際場景中顯得冗余,且與標準庫的命名習慣(比如 empty?
、include?
)很不一致。[link]
# 差class Person def is_tall? true end def can_play_basketball? false end def does_like_candy? true endend# 好class Person def tall? true end def basketball_player? false end def likes_candy? true endend
具有潛在危險性的方法,當其存在對應安全版本的方法時,其名字應當以驚嘆號結(jié)尾。(比如修改 self
或參數(shù)值的方法、相對 exit
方法不會在退出時運行 finalizers 執(zhí)行清理工作的 exit!
方法等)[link]
# 差 - 沒有對應安全版本的方法class Person def update! endend# 好class Person def update endend# 好class Person def update! end def update endend
盡量根據(jù)危險方法來定義對應安全版本的方法。[link]
class Array def flatten_once! res = [] each do |e| [*e].each { |f| res << f } end replace(res) end def flatten_once dup.flatten_once! endend
當定義二元操作符時,將參數(shù)命名為 other
(<<
與 []
例外,因為其語義與此不同)。[link]
def +(other) # 省略主體end
注釋
良好的代碼自身就是最佳的文檔。當你要添加一個注釋時,捫心自問,“如何改善代碼讓它不需要注釋?” 改善代碼,再寫相應文檔使之更清楚。
——Steve McConnell
編寫讓人一目了然的代碼然后忽略這一節(jié)的其它部分。我是認真的![link]
使用英文編寫注釋。[link]
前導 #
與注釋文本之間應當添加一個空格。[link]
注釋超過一個單詞時,句首字母應當大寫,并在語句停頓或結(jié)尾處使用標點符號。句號后添加一個空格。[link]
避免無謂的注釋。[link]
# 差counter += 1 # Increments counter by one.
及時更新注釋。過時的注釋比沒有注釋還要糟糕。[link]
好的代碼就像是好的笑話 —— 它不需要解釋。
——Russ Olsen
避免替爛代碼編寫注釋。重構(gòu)它們使其變得一目了然。(要么做,要么不做,不要只是試試看。——Yoda)[link]
注解
注解應該直接寫在相關(guān)代碼之前那行。[link]
注解關(guān)鍵字后面,跟著一個冒號及空格,接著是描述問題的文本。[link]
如果需要用多行來描述問題,后續(xù)行要放在 #
后面并縮排兩個空格。[link]
def bar # FIXME: This has crashed occasionally since v3.2.1. It may # be related to the BarBazUtil upgrade. baz(:quux)end
當問題是顯而易見時,任何文檔都是多余的,注解應當放在有問題的那行末尾且不帶任何多余說明。這個用法應該算是例外而不是規(guī)則。[link]
def bar sleep 100 # OPTIMIZEend
使用 TODO
標記應當加入的特征與功能。[link]
使用 FIXME
標記需要修復的代碼。[link]
使用 OPTIMIZE
標記可能引發(fā)性能問題的低效代碼。[link]
使用 HACK
標記代碼異味,即那些應當被重構(gòu)的可疑編碼習慣。[link]
使用 REVIEW
標記需要確認與編碼意圖是否一致的可疑代碼。比如,REVIEW: Are we sure this is how the client does X currently?
。[link]
適當情況下,可以自行定制其他注解關(guān)鍵字,但別忘記在項目的 README
或類似文檔中予以說明。[link]
Magic Comments
Place magic comments above all code and documentation. Magic comments should only go below shebangs if they are needed in your source file.[link]
# good# frozen_string_literal: true# Some documentation about Personclass Personend# bad# Some documentation about Person# frozen_string_literal: trueclass Personend
# good#!/usr/bin/env ruby# frozen_string_literal: trueApp.parse(ARGV)# bad# frozen_string_literal: true#!/usr/bin/env rubyApp.parse(ARGV)
Use one magic comment per line if you need multiple.[link]
# good# frozen_string_literal: true# encoding: ascii-8bit# bad# -*- frozen_string_literal: true; encoding: ascii-8bit -*-
Separate magic comments from code and documentation with a blank line.[link]
# good# frozen_string_literal: true# Some documentation for Personclass Person # Some codeend# bad# frozen_string_literal: true# Some documentation for Personclass Person # Some codeend
類與模塊
在類定義中,使用一致的結(jié)構(gòu)。[link]
class Person # 首先是 extend 與 include extend SomeModule include AnotherModule # 內(nèi)部類 CustomError = Class.new(StandardError) # 接著是常量 SOME_CONSTANT = 20 # 接下來是屬性宏 attr_reader :name # 跟著是其他宏(如果有的話) validates :name # 公開的類方法接在下一行 def self.some_method end # 初始化方法在類方法和實例方法之間 def initialize end # 跟著是公開的實例方法 def some_method end # 受保護及私有的方法等放在接近結(jié)尾的地方 protected def some_protected_method end private def some_private_method endend
在混入多個模塊時,傾向使用多行語法。[link]
# 差class Person include Foo, Barend# 好class Person include Foo include Barend
如果嵌套類數(shù)目較多,進而導致外圍類定義較長,則將它們從外圍類中提取出來,分別放置在單獨的以嵌套類命名的文件中,并將文件歸類至以外圍類命名的文件夾下。[link]
# 差# foo.rbclass Foo class Bar # 定義 30 多個方法 end class Car # 定義 20 多個方法 end # 定義 30 多個方法end# 好# foo.rbclass Foo # 定義 30 多個方法end# foo/bar.rbclass Foo class Bar # 定義 30 多個方法 endend# foo/car.rbclass Foo class Car # 定義 20 多個方法 endend
定義只有類方法的數(shù)據(jù)類型時,傾向使用模塊而不是類。只有當需要實例化時才使用類。[link]
# 差class SomeClass def self.some_method # 省略主體 end def self.some_other_method # 省略主體 endend# 好module SomeModule module_function def some_method # 省略主體 end def some_other_method # 省略主體 endend
當你想將模塊的實例方法變成類方法時,傾向使用 module_function
而不是 extend self
。[link]
# 差module Utilities extend self def parse_something(string) # 做一些事情 end def other_utility_method(number, string) # 做一些事情 endend# 好module Utilities module_function def parse_something(string) # 做一些事情 end def other_utility_method(number, string) # 做一些事情 endend
當設計類的層次結(jié)構(gòu)時,確保它們符合里式替換原則。[link]
讓你的類盡量滿足 SOLID 原則 。[link]
總是替那些用以表示領域模型的類提供一個適當?shù)?to_s
方法。[link]
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}" endend
使用 attr
系列方法來定義瑣碎的存取器或修改器。[link]
# 差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 endend# 好class Person attr_reader :first_name, :last_name def initialize(first_name, last_name) @first_name = first_name @last_name = last_name endend
對于訪問器方法,避免使用 get_
作為名字前綴;對于更改器方法,避免使用 set_
作為名字前綴。Ruby 語言中,通常使用 attr_name
作為訪問器的方法名,使用 attr_name=
作為更改器的方法名。[link]
# 差class Person def get_name "#{@first_name} #{@last_name}" end def set_name(name) @first_name, @last_name = name.split(' ') endend# 好class Person def name "#{@first_name} #{@last_name}" end def name=(name) @first_name, @last_name = name.split(' ') endend
避免使用 attr
。使用 attr_reader
與 attr_accessor
來替代。[link]
# 差 - 創(chuàng)建單個存取方法(此方法在 Ruby 1.9 之后被移除了)attr :something, trueattr :one, :two, :three # 類似于 attr_reader# 好attr_accessor :somethingattr_reader :one, :two, :three
優(yōu)先考慮使用 Struct.new
。它替你定義了那些瑣碎的訪問器、構(gòu)造器及比較操作符。[link]
# 好class Person attr_accessor :first_name, :last_name def initialize(first_name, last_name) @first_name = first_name @last_name = last_name endend# 更好Person = Struct.new(:first_name, :last_name) doend
不要擴展 Struct.new
實例化后的對象。對它進行擴展不但引入了毫無意義的類層次,而且在此文件被多次引入時可能會產(chǎn)生奇怪的錯誤。[link]
# 差class Person < Struct.new(:first_name, :last_name)end# 好Person = Struct.new(:first_name, :last_name)
優(yōu)先考慮通過工廠方法的方式創(chuàng)建某些具有特定意義的實例對象。[link]
class Person def self.create(options_hash) # 省略主體 endend
傾向使用鴨子類型而不是繼承。[link]
# 差class Animal # 抽象方法 def speak endend# 繼承父類class Duck < Animal def speak puts 'Quack! Quack' endend# 繼承父類class Dog < Animal def speak puts 'Bau! Bau!' endend# 好class Duck def speak puts 'Quack! Quack' endendclass Dog def speak puts 'Bau! Bau!' endend
避免使用類變量(@@
)。類變量在繼承方面存在令人生厭的行為。[link]
class Parent @@class_var = 'parent' def self.print_class_var puts @@class_var endendclass Child < Parent @@class_var = 'child'endParent.print_class_var # => 此處打印的結(jié)果為 'child'
如你所見,在類的層次結(jié)構(gòu)中所有類都會共享同一類變量。通常情況下,傾向使用類實例變量而不是類變量。
根據(jù)方法的目的與用途設置適當?shù)目梢娂墑e(private
、protected
)。不要什么都不做就把所有方法設置為 public
(默認值)。畢竟我們寫的是 Ruby 而不是 Python。[link]
把 public
、protected
、private
與其作用的方法縮排在同一層級。且在其上下各留一行以強調(diào)此可見級別作用于之后的所有方法。[link]
class SomeClass def public_method # ... end private def private_method # ... end def another_private_method # ... endend
使用 def self.method
定義類方法。這種做法使得在代碼重構(gòu)時,即使修改了類名也無需做多次修改。[link]
class TestClass # 差 def TestClass.some_method # 省略主體 end # 好 def self.some_other_method # 省略主體 end # 在需要定義多個類方法時,另一種便捷寫法 class << self def first_method # 省略主體 end def second_method_etc # 省略主體 end endend
在類的詞法作用域中定義方法別名時,傾向使用 alias
。因為定義期間 alias
與 self
指向的都是詞法作用域,除非明確說明,否則該別名所引用的方法不會在運行期間被改變,或是在任何子類中被修改。[link]
class Westerner def first_name @names.first end alias given_name first_nameend
因為 alias
與 def
一樣都是關(guān)鍵字,傾向使用裸字而不是符號或字符串。也就是說,使用 alias foo bar
而不是 alias :foo :bar
。
另外需要了解 Ruby 是如何處理別名和繼承的:別名所引用的原始方法是在定義期間被指定的,而不是運行期間。
class Fugitive < Westerner def first_name 'Nobody' endend
在這個例子中,Fugitive#given_name
仍然調(diào)用原先的 Westerner#first_name
方法,而不是 Fugitive#first_name
。如果想要覆寫 Fugitive#given_name
,必須在子類中重新定義。
class Fugitive < Westerner def first_name 'Nobody' end alias given_name first_nameend
在運行期間定義模塊方法、類方法、單件方法的別名時,總是使用 alias_method
。在上述情況下,使用 alias
可能會導致預期之外的結(jié)果。[link]
module Mononymous def self.included(other) other.class_eval { alias_method :full_name, :given_name } endendclass Sting < Westerner include Mononymousend
在模塊方法,或是類方法內(nèi)部調(diào)用自身其他方法時,通常省略模塊名/類名/self
。[link]
class TestClass # 差 def self.call(param1, param2) TestClass.new(param1).call(param2) end # 差 def self.call(param1, param2) self.new(param1).call(param2) end # 好 def self.call(param1, param2) new(param1).call(param2) end # 省略其他方法end
異常
對于異常處理,傾向使用 raise
而不是 fail
。[link]
# 差fail SomeException, 'message'# 好raise SomeException, 'message'
不要在帶雙參數(shù)形式的 raise
方法中顯式指定 RuntimeError
。[link]
# 差raise RuntimeError, 'message'# 好 - 默認就是 RuntimeErrorraise 'message'
傾向使用帶異常類、消息的雙參數(shù)形式調(diào)用 raise
方法,而不是使用異常的實例。[link]
# 差 - 并無 raise SomeException.new('message') [, backtraces] 這種調(diào)用形式raise SomeException.new('message')# 好 - 與調(diào)用形式 raise SomeException [, 'message' [, backtraces]] 保持一致raise SomeException, 'message'
永遠不要從 ensure
區(qū)塊返回。如果你顯式地從 ensure
區(qū)塊返回,那么其所在的方法會如同永遠不會發(fā)生異常般的返回。事實上,異常被默默地丟棄了。[link]
def foo raiseensure return 'very bad idea'end
盡可能隱式地使用 begin/rescue/ensure/end
區(qū)塊。[link]
# 差def foo begin # 主邏輯 rescue # 異常處理邏輯 endend# 好def foo # 主邏輯rescue # 異常處理邏輯end
通過使用 contingency 方法(一個由 Avdi Grimm 創(chuàng)造的詞)來減少 begin/rescue/ensure/end
區(qū)塊的使用。[link]
# 差begin something_that_might_failrescue IOError # 處理 IOErrorendbegin something_else_that_might_failrescue IOError # 處理 IOErrorend# 好def with_io_error_handling yieldrescue IOError # 處理 IOErrorendwith_io_error_handling { something_that_might_fail } with_io_error_handling { something_else_that_might_fail }
不要抑制異常。[link]
# 差begin # 拋出異常rescue SomeError # 不做任何相關(guān)處理end# 差do_something rescue nil
避免使用 rescue
修飾語法。[link]
# 差 - 這里將會捕捉 StandardError 及其所有子孫類的異常read_file rescue handle_error($!)# 好 - 這里只會捕獲 Errno::ENOENT 及其所有子孫類的異常def foo read_filerescue Errno::ENOENT => ex handle_error(ex)end
不要將異常處理作為流程控制使用。[link]
# 差begin n / drescue ZeroDivisionError puts 'Cannot divide by 0!'end# 好if d.zero? puts 'Cannot divide by 0!'else n / dend
避免捕獲 Exception
。這種做法會同時將信號與 exit
方法困住,導致你必須使用 kill -9
來終止進程。[link]
# 差 - 信號與 exit 方法產(chǎn)生的異常會被捕獲(除了 kill -9)begin exitrescue Exception puts "you didn't really want to exit, right?" # 處理異常end# 好 - 沒有指定具體異常的 rescue 子句默認捕獲 StandardErrorbegin # 拋出異常rescue => e # 處理異常end# 好 - 指定具體異常 StandardErrorbegin # 拋出異常rescue StandardError => e # 處理異常end
把較具體的異常放在處理鏈的較上層,不然它們永遠不會被執(zhí)行。[link]
# 差begin # 拋出異常rescue StandardError => e # 處理異常rescue IOError => e # 處理異常,但事實上永遠不會被執(zhí)行end# 好begin # 拋出異常rescue IOError => e # 處理異常rescue StandardError => e # 處理異常end
在 ensure
區(qū)塊釋放程序的外部資源。[link]
f = File.open('testfile')begin # .. 文件操作rescue # .. 處理異常ensure f.close if fend
在調(diào)用資源獲取方法時,盡量使用具備自動清理功能的版本。[link]
# 差 - 需要顯式關(guān)閉文件描述符f = File.open('testfile') # ...f.close# 好 - 文件描述符會被自動關(guān)閉File.open('testfile') do |f| # ...end
傾向使用標準庫中的異常類而不是引入新的類型。[link]
集合
對于數(shù)組與哈希,傾向使用字面量語法來構(gòu)建實例(除非你需要給構(gòu)造器傳遞參數(shù))。[link]
# 差arr = Array.newhash = Hash.new# 好arr = [] hash = {}
當創(chuàng)建一組元素為單詞(沒有空格或特殊字符)的數(shù)組時,傾向使用 %w
而不是 []
。此規(guī)則只適用于數(shù)組元素有兩個或以上的時候。[link]
# 差STATES = ['draft', 'open', 'closed']# 好STATES = %w[draft open closed]
當創(chuàng)建一組符號類型的數(shù)組(且不需要保持 Ruby 1.9 兼容性)時,傾向使用 %i
。此規(guī)則只適用于數(shù)組元素有兩個或以上的時候。[link]
# 差STATES = [:draft, :open, :closed]# 好STATES = %i[draft open closed]
避免在數(shù)組與哈希的字面量語法的最后一個元素之后添加逗號,尤其當元素沒有分布在同一行時。[link]
# 差 - 盡管移動、新增、刪除元素頗為方便,但仍不推薦這種寫法VALUES = [ 1001, 2020, 3333, ]# 差VALUES = [1001, 2020, 3333, ]# 好VALUES = [1001, 2020, 3333]
避免在數(shù)組中創(chuàng)造巨大的間隔。[link]
arr = [] arr[100] = 1 # 現(xiàn)在你有一個很多 nil 的數(shù)組
當訪問數(shù)組的首元素或尾元素時,傾向使用 first
或 last
而不是 [0]
或 [-1]
。[link]
當處理的對象不存在重復元素時,使用 Set
來替代 Array
。Set
是實現(xiàn)了無序且無重復元素的集合類型。它兼具 Array
的直觀操作與 Hash
的快速查找。[link]
傾向使用符號而不是字符串作為哈希鍵。[link]
# 差hash = { 'one' => 1, 'two' => 2, 'three' => 3 }# 好hash = { one: 1, two: 2, three: 3 }
避免使用可變對象作為哈希鍵。[link]
當哈希鍵為符號時,使用 Ruby 1.9 的字面量語法。[link]
# 差hash = { :one => 1, :two => 2, :three => 3 }# 好hash = { one: 1, two: 2, three: 3 }
當哈希鍵既有符號又有字符串時,不要使用 Ruby 1.9 的字面量語法。[link]
# 差{ a: 1, 'b' => 2 }# 好{ :a => 1, 'b' => 2 }
傾向使用 Hash#key?
而不是 Hash#has_key?
,使用 Hash#value?
而不是 Hash#has_value?
。[link]
# 差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.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]
heroes = { batman: 'Bruce Wayne', superman: 'Clark Kent' }# 差 - 如果我們打錯了哈希鍵,則難以發(fā)現(xiàn)這個錯誤heroes[:batman] # => 'Bruce Wayne'heroes[:supermann] # => nil# 好 - fetch 會拋出 KeyError 使這個錯誤顯而易見heroes.fetch(:supermann)
當為哈希鍵的值提供默認值時,傾向使用 Hash#fetch
而不是自定義邏輯。[link]
batman = { name: 'Bruce Wayne', is_evil: false }# 差 - 如果僅僅使用 || 操作符,那么當值為假時,我們不會得到預期結(jié)果batman[:is_evil] || true # => true# 好 - fetch 在遇到假值時依然可以正確工作batman.fetch(:is_evil, true) # => false
當提供默認值的求值代碼具有副作用或開銷較大時,傾向使用 Hash#fetch
的區(qū)塊形式。[link]
batman = { name: 'Bruce Wayne' }# 差 - 此形式會立即求值,如果調(diào)用多次,可能會影響程序的性能batman.fetch(:powers, obtain_batman_powers) # obtain_batman_powers 開銷較大# 好 - 此形式會惰性求值,只有拋出 KeyError 時,才會產(chǎn)生開銷batman.fetch(:powers) { obtain_batman_powers }
當需要一次性從哈希中獲取多個鍵的值時,使用 Hash#values_at
。[link]
# 差email = data['email'] username = data['nickname']# 好email, username = data.values_at('email', 'nickname')
利用“Ruby 1.9 之后的哈希是有序的”的這個特性。[link]
當遍歷集合時,不要改動它。[link]
當訪問集合中的元素時,傾向使用對象所提供的方法進行訪問,而不是直接調(diào)用對象屬性上的 [n]
方法。這種做法可以防止你在 nil
對象上調(diào)用 []
。[link]
# 差Regexp.last_match[1]# 好Regexp.last_match(1)
當為集合提供存取器時,盡量支持索引值為 nil
的訪問形式。[link]
# 差def awesome_things @awesome_thingsend# 好def awesome_things(index = nil) if index && @awesome_things @awesome_things[index] else @awesome_things endend
數(shù)值
通過 Integer
檢查對象是否是數(shù)值類型,而不是 Fixnum
或 Bignum
。因為 Fixnum
或 Bignum
表達的數(shù)值大小存在范圍限定。[link]
timestamp = Time.now.to_i# 差timestamp.is_a? Fixnumtimestamp.is_a? Bignum# 好timestamp.is_a? Integer
對于隨機數(shù)的生成,傾向使用 Range 來表示,而不是 Integer + 偏移量,這樣可以更加清晰地表達你的意圖,類比于投擲骰子。[link]
# 差rand(6) + 1# 好rand(1..6)
字符串
傾向使用字符串插值或字符串格式化,而不是字符串拼接。[link]
# 差email_with_name = user.name + ' <' + user.email + '>'# 好email_with_name = "#{user.name} <#{user.email}>"# 好email_with_name = format('%s <%s>', user.name, user.email)
使用統(tǒng)一的風格創(chuàng)建字符串字面量。在 Ruby 社區(qū)中存在兩種流行的風格:默認單引號(風格 A)與默認雙引號(風格 B)。[link]
本指南使用第一種風格。
(風格 A) 當你不需要字符串插值或特殊字符(比如 \t
、\n
、'
)時,傾向使用單引號。
# 差name = "Bozhidar"# 好name = 'Bozhidar'
(風格 B) 除非字符串中包含雙引號,或是你希望抑制轉(zhuǎn)義字符,否則傾向使用雙引號。
# 差name = 'Bozhidar'# 好name = "Bozhidar"
不要使用 ?x
字面量語法。在 Ruby 1.9 之后,?x
與 'x'
(只包含單個字符的字符串)是等價的。[link]
# 差char = ?c# 好char = 'c'
不要忘記使用 {}
包裹字符串插值中的實例變量或全局變量。[link]
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}" endend$global = 0# 差puts "$global = #$global"# 好puts "$global = #{$global}"
在字符串插值中,不要顯式調(diào)用 Object#to_s
方法,Ruby 會自動調(diào)用它。[link]
# 差message = "This is the #{result.to_s}."# 好message = "This is the #{result}."
當你需要構(gòu)造巨大的數(shù)據(jù)塊時,避免使用 String#+
,使用 String#<<
來替代。String#<<
通過修改原始對象進行拼接工作,其比 String#+
效率更高,因為后者需要產(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
當存在更快速、更專業(yè)的替代方案時,不要使用 String#gsub
。[link]
url = 'http://example.com'str = 'lisp-case-rules'# 差url.gsub('http://', 'https://') str.gsub('-', '_')# 好url.sub('http://', 'https://') str.tr('-', '_')
heredocs 中的多行文本會保留各行的前導空白。因此做好如何縮排的規(guī)劃。[link]
code = <<-END.gsub(/^\s+\|/, '') |def test | some_method | other_method |endEND# => "def test\n some_method\n other_method\nend\n"
使用 Ruby 2.3 新增的 <<~
操作符來縮排 heredocs 中的多行文本。[link]
# 差 - 使用 Powerpack 程序庫的 String#strip_margincode = <<-END.strip_margin('|') |def test | some_method | other_method |endEND# 差code = <<-ENDdef test some_method other_methodendEND# 好code = <<~END def test some_method other_method endEND
日期與時間
避免使用 DateTime
,除非你確實需要處理歷法改革(儒略/格里歷的改革),此時通過設置 start
參數(shù)來明確你的意圖。[link]
# 差 - 使用 DateTime 表示當前時間DateTime.now# 好 - 使用 Time 表示當前時間Time.now# 差 - 使用 DateTime 表示近現(xiàn)代日期DateTime.iso8601('2016-06-29')# 好 - 使用 Date 表示近現(xiàn)代日期Date.iso8601('2016-06-29')# 好 - 使用 DateTime 表示日期,通過設置 start 參數(shù)為 Date::ENGLANG 明確表示使用 England 歷法改革版本DateTime.iso8601('1751-04-23', Date::ENGLAND)
正則表達式
有些人在面對問題時,不經(jīng)大腦便認為,“我知道,這里該用正則表達式”。現(xiàn)在他要面對兩個問題了。
——Jamie Zawinski
如果只是在字符串中進行簡單的文本搜索,不要使用正則表達式,比如 string['text']
。[link]
對于簡單的構(gòu)建操作,使用正則表達式作為索引即可。[link]
match = string[/regexp/] # 獲取匹配的內(nèi)容first_group = string[/text(grp)/, 1] # 獲取匹配的分組(grp)的內(nèi)容string[/text (grp)/, 1] = 'replace' # string => 'text replace'
當你不需要分組結(jié)果時,使用非捕獲組。[link]
# 差/(first|second)/# 好/(?:first|second)/
避免使用 Perl 風格的、用以代表最近的捕獲組的特殊變量(比如 、
等)。使用
Regexp.last_match(n)
來替代。[link]
/(regexp)/ =~ string ...# 差process $1# 好process Regexp.last_match(1)
避免使用數(shù)字來獲取分組。因為很難明白它們代表的含義。使用命名分組來替代。[link]
# 差/(regexp)/ =~ string ... process Regexp.last_match(1)# 好/(?<meaningful_var>regexp)/ =~ string ... process meaningful_var
在字符類別中,只有少數(shù)幾個你需要特別關(guān)心的特殊字符:^
、-
、\
、]
,所以你不需要轉(zhuǎn)義 []
中的 .
與中括號。[link]
小心使用 ^
與 $
,它們匹配的是一行的開始與結(jié)束,而不是字符串的開始與結(jié)束。如果你想要匹配整個字符串,使用 \A
與 \z
。(注意,\Z
實為 /\n?\z/
)[link]
string = "some injection\nusername"string[/^username$/] # 匹配成功string[/\Ausername\z/] # 匹配失敗
對于復雜的正則表達式,使用 x
修飾符。這種做法不但可以提高可讀性,而且允許你加入必要的注釋。注意的是,空白字符會被忽略。[link]
regexp = / start # some text \s # white space char (group) # first group (?:alt1|alt2) # some alternation end/x
對于復雜的替換,使用 sub/gsub
與哈希或區(qū)塊組合的調(diào)用形式。[link]
words = 'foo bar'words.sub(/f/, 'f' => 'F') # => 'Foo bar'words.gsub(/\w+/) { |word| word.capitalize } # => 'Foo Bar'
百分號字面量
只有當字符串中同時存在插值與雙引號,且是單行時,才使用 %()
(%Q
的簡寫形式)。多行字符串,傾向使用 heredocs。[link]
# 差 - 不存在插值%(<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>)# 應當使用 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>)
只有當正則表達式中存在一個或以上的 /
字符時,才使用 %r
。[link]
# 差%r{\s+}# 好%r{^/(.*)$}%r{^/blog/2011/(.*)$}
除非調(diào)用的命令使用了反引號(這種情況并不多見),否則不要使用 %x
。[link]
# 差date = %x(date)# 好date = `date`echo = %x(echo `date`)
避免使用 %s
。傾向使用 :"some string"
來創(chuàng)建含有空白字符的符號。[link]
針對不同的百分號字面量,使用不同的括號類型。[link]
# 差%q{"Test's king!", John said.}# 好%q("Test's king!", John said.)# 差%w(one two three)%i(one two three)# 好%w[one two three]%i[one two three]# 差%r((\w+)-(\d+))%r{\w{1,2}\d{2,5}}# 好%r{(\w+)-(\d+)}%r|\w{1,2}\d{2,5}|
針對構(gòu)建字符串的 %q
, %Q
字面量,使用 ()
。
針對構(gòu)建數(shù)組的 %w
, %i
, %W
, %I
字面量,使用 []
,以與常規(guī)的數(shù)組字面量保持一致。
針對構(gòu)建正則的 %r
字面量,使用 {}
,此乃慣例。
針對 %s
, %x
等其他字面量,使用 ()
。
元編程
避免無謂的元編程。[link]
當編寫程序庫時,不要使核心類混亂(不要使用 monkey patch)。[link]
對于 class_eval
方法,傾向使用區(qū)塊形式,而不是字符串插值形式。[link]
當使用字符串插值形式時,總是提供 __FILE__
及 __LINE__
,以使你的調(diào)用棧看起來具有意義:
class_eval 'def use_relative_model_naming?; true; end', __FILE__, __LINE__
傾向使用 define_method
而不是 class_eval { def ... }
當使用 class_eval
(或其他的 eval
)的字符串插值形式時,添加一個注釋區(qū)塊來說明它是如何工作的(來自 Rails 代碼中的技巧)。[link]
# 摘錄自 activesupport/lib/active_support/core_ext/string/output_safety.rbUNSAFE_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 endend
避免使用 method_missing
。它會使你的調(diào)用棧變得凌亂;其方法不被羅列在 #methods
中;拼錯的方法可能會默默地工作(nukes.launch_state = false
)。優(yōu)先考慮使用委托、代理、或是 define_method
來替代。如果你必須使用 method_missing
的話,務必做到以下幾點:[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 endend# 好def method_missing?(meth, *params, &block) if /^find_by_(?<prop>.*)/ =~ meth find_by(prop, *params, &block) else super endend# 最好的方式可能是在每個需要支持的屬性被聲明時,使用 define_method 定義對應的方法
傾向使用 public_send
而不是 send
,因為 send
會無視 private/protected
的可見性。[link]
module Activatable extend ActiveSupport::Concern included do before_create :create_token end private def reset_token ... end def create_token ... end def activate! ... endendclass Organization < ActiveRecord::Base include Activatableendlinux_organization = Organization.find(...)# 差 - 會破壞對象的封裝性linux_organization.send(:reset_token)# 好 - 會拋出異常linux_organization.public_send(:reset_token)
傾向使用 __send__
而不是 send
,因為 send
可能會被覆寫。[link]
require 'socket'u1 = UDPSocket.newu1.bind('127.0.0.1', 4913) u2 = UDPSocket.newu2.connect('127.0.0.1', 4913)# 這里不會調(diào)用 u2 的 sleep 方法,而是通過 UDP socket 發(fā)送一條消息u2.send :sleep, 0# 動態(tài)調(diào)用 u2 的某個方法u2.__send__ ...
其他
總是開啟 ruby -w
選項,以編寫安全的代碼。[link]
避免使用哈希作為可選參數(shù)。這個方法是不是做太多事了?(對象構(gòu)造器除外)[link]
避免單個方法的長度超過 10 行(不計入空行)。理想上,大部分方法應當不超過 5 行。[link]
避免參數(shù)列表數(shù)目多于三或四個。[link]
如果你真的需要“全局”方法,將它們添加到 Kernel
并設為私有。[link]
使用模塊實例變量而不是全局變量。[link]
# 差$foo_bar = 1# 好module Foo class << self attr_accessor :bar endendFoo.bar = 1
使用 OptionParser
來解析復雜的命令行選項。使用 ruby -s
來處理瑣碎的命令行選項。[link]
使用 Time.now
而不是 Time.new
來獲取當前的系統(tǒng)時間。[link]
使用函數(shù)式思維編寫程序,避免副作用。[link]
不要修改參數(shù)值,除非那就是這個方法的作用。[link]
避免使用三層以上的嵌套區(qū)塊。[link]
保持一致性。在理想的世界里,遵循這些準則。[link]
使用常識。[link]
工具
以下的一些工具可以幫助你自動檢查項目中的 Ruby 代碼是否符合這份指南。
RuboCop
RuboCop 是一個基于本指南的 Ruby 代碼風格檢查工具。RuboCop 涵蓋了本指南相當大的部分,其同時支持 MRI 1.9 和 MRI 2.0,且與 Emacs 整合良好。
RubyMine
RubyMine 的代碼檢查部分基于本指南。
TML <base> 元素
指定用于一個文檔中包含的所有相對 URL 的根 URL。一份中只能有一個 <base> 元素。
包含屬性href 和
target 可以指定a 標簽的默認窗口打開行為
<base href="https://www.baidu.com/img/123" target="_banlk"></base> 默認就打開新的窗口 <a href="aaa">123</a>
使用
<base href="https://www.baidu.com/img/"></base> <img src="bd_logo1.png?where=super"></img>
雖然在codepen 的代碼上編寫的但是能夠正確的通過base url + img 的src 定位顯示出圖片
content 標簽
HTML <aside> 元素
表示一個和其余頁面內(nèi)容幾乎無關(guān)的部分,被認為是獨立于該內(nèi)容的一部分并且可以被單獨的拆分出來而不會使整體受影響。
HTML <blockquote> 元素
(或者 HTML 塊級引用元素),代表其中的文字是引用內(nèi)容。通常在渲染時,這部分的內(nèi)容會有一定的縮進(注 中說明了如何更改)。若引文來源于網(wǎng)絡,則可以將原內(nèi)容的出處 URL 地址設置到 cite 特性上,若要以文本的形式告知讀者引文的出處時,可以通過 <cite> 元素。
HTML <figure> 元素
代表一段獨立的內(nèi)容, 經(jīng)常與說明(caption) <figcaption> 配合使用, 并且作為一個獨立的引用單元。當它屬于主內(nèi)容流(main flow)時,它的位置獨立于主體。這個標簽經(jīng)常是在主文中引用的圖片,插圖,表格,代碼段等等,當這部分轉(zhuǎn)移到附錄中或者其他頁面時不會影響到主體。
Inline text semantics
HTML 縮寫元素(<abbr>)
用于展示縮寫,并且可以通過可選的 title 屬性提供完整的描述。
ps: 完整描述樣式貌似不能自定義
HTML鍵盤輸入元素(<kbd>)
用于表示用戶輸入,它將產(chǎn)生一個行內(nèi)元素,以瀏覽器的默認monospace字體顯示。
HTML標記文本元素(< Mark >)
表示為引用或符號目的而標記或突出顯示的文本,這是由于標記的段落在封閉上下文中的相關(guān)性或重要性造成的。
ps:項目中大量使用span 標記的做法不符合html5 的語義化
HTML Ruby Base(<rb>)
元素用于分隔<ruby>注釋的基本文本組件(即正在注釋的文本)。一個<rb>元素應該包裝基本文本的每個單獨的原子段。
ps: 拼音注解
<samp> 元素
用于標識計算機程序輸出,通常使用瀏覽器缺省的 monotype 字體(例如 Lucida Console)。
HTML 中的<small>
元素將使文本的字體變小一號。(例如從大變成中等,從中等變成小,從小變成超小)。在HTML5中,除了它的樣式含義,這個元素被重新定義為表示邊注釋和附屬細則,包括版權(quán)和法律文本。
HTML <sub> 元素定義了一個文本區(qū)域,出于排版的原因,與主要的文本相比,應該展示得更低并且更小。
ps: 下腳標
HTML <sup> 元素定義了一個文本區(qū)域,出于排版的原因,與主要的文本相比,應該展示得更高并且更小。
ps: 上腳標
HTML <u> 元素
使文本在其內(nèi)容的基線下的一行呈現(xiàn)下劃線。在HTML5中, 此元素表示具有未標注的文本跨度,顯示渲染,非文本注釋,例如將文本標記為中文文本中的專有名稱(一個正確的中文標記), 或 將文本標記為拼寫錯誤
HTML <map>
與 <area> 屬性一起使用來定義一個圖像映射(一個可點擊的鏈接區(qū)域).
HTML <track> 元素
被當作媒體元素—<audio> 和 <video>的子元素來使用。它允許指定計時字幕(或者基于時間的數(shù)據(jù)),例如自動處理字幕。
HTML <object> 元素
(或者稱作 HTML 嵌入對象元素)表示引入一個外部資源,這個資源可能是一張圖片,一個嵌入的瀏覽上下文,亦或是一個插件所使用的資源
ps: 支持引入的資源類型
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Complete_list_of_MIME_types
關(guān)于支持資源類型的測試
http://joliclic.free.fr/html/object-tag/en/index.php
CSS網(wǎng)頁布局中,排版是一個麻煩的問題。作為一個優(yōu)秀的網(wǎng)頁設計師和Web前端開發(fā)人員,掌握一些簡單的中文排版技巧是不可或缺的。所以今天小編特意整理了一些簡單實用的技巧,希望對大家有所幫助。
字體
我們可以使用css樣式為網(wǎng)頁中的文字設置字體、字號、顏色等樣式屬性。
body{font-family:"宋體";}
這里注意不要設置不常用的字體,因為如果用戶本地電腦上如果沒有安裝你設置的字體,就會顯示瀏覽器默認的字體。(因為用戶是否可以看到你設置的字體樣式取決于用戶本地電腦上是否安裝你設置的字體。)
現(xiàn)在一般網(wǎng)頁喜歡設置“微軟雅黑”,如下代碼:
body{font-family:"Microsoft Yahei";}
或
body{font-family:"微軟雅黑";}
字號、顏色
可以使用下面代碼設置網(wǎng)頁中文字的字號為12像素,并把字體顏色設置為#666(灰色):
body{font-size:12px;color:#666}
粗體
可以使用下面代碼實現(xiàn)設置文字以粗體樣式顯示出來。
p span{font-weight:bold;}
斜體
以下代碼可以實現(xiàn)文字以斜體樣式在瀏覽器中顯示:
p a{font-style:italic;}
<p>三年級時,我還是一個<a>膽小如鼠</a>的小女孩。</p>
下劃線
有些情況下想為文字設置為下劃線樣式,這樣可以在視覺上強調(diào)文字,可以使用下面代碼來實現(xiàn):
p a{text-decoration:underline;}
<p>三年級時,我還是一個<a>膽小如鼠</a>的小女孩。</p>
刪除線
如果想在網(wǎng)頁上設置刪除線怎么辦,這個樣式在電商網(wǎng)站上常會見到:
上圖中的原價上的刪除線使用下面代碼就可以實現(xiàn):
.oldPrice{text-decoration:line-through;}
縮進
中文文字中的段前習慣空兩個文字的空白,這個特殊的樣式可以用下面代碼來實現(xiàn):
p{text-indent:2em;}
注意:2em的意思就是文字的2倍大小
行間距(行高)
這一小節(jié)我們來學習一下另一個在段落排版中起重要作用的行間距(行高)屬性(line-height),如下代碼實現(xiàn)設置段落行間距為1.5倍。
p{line-height:1.5em;}
中文字間距、字母間距
如果想在網(wǎng)頁排版中設置文字間隔或者字母間隔就可以使用 letter-spacing 來實現(xiàn),如下面代碼:
h1{
letter-spacing:50px;
}
注意:這個樣式使用在英文單詞時,是設置字母與字母之間的間距。
如果我想設置英文單詞之間的間距呢?可以使用 word-spacing來實現(xiàn)。如下代碼:
h1{
word-spacing:50px;
}
...
<h1>welcome to imooc!</h1>
對齊
想為塊狀元素中的文本、圖片設置居中樣式嗎?可以使用text-align樣式代碼,如下代碼可實現(xiàn)文本居中顯示。
h1{
text-align:center;
}
<h1>了不起的蓋茨比</h1>
同樣可以設置居左:
h1{
text-align:left;
}
<h1>了不起的蓋茨比</h1>
還可以設置居右:
h1{
text-align:right;
}
<h1>了不起的蓋茨比</h1>
圖文環(huán)繞
在css中有一個常見的圖文環(huán)繞效果。實現(xiàn)方式主要是通過float標簽,將圖片左浮動,或者右浮動。其相鄰的文字,就會環(huán)繞圖片排列,代碼和效果如下:
豎排文字
使用writing-mode實現(xiàn)。writing-mode屬性有兩個值lr-tb和tb-rl,前者是默認的左-右、上-下,后者是上-下、右-左。
比如:
p{ writing-mode: tb-rl;}
可以結(jié)合direction排版。
首字下沉
偽對象:first-letter配合font-size、float可以制作首字下沉效果。
比如:
p:first-letter{ padding: 6px; font-size: 32pt; float: left;}
漢字注音
如果我們想為漢字注音,就可以使用ruby標簽和ruby-align屬性來實現(xiàn),比如:
<ruby>注音<rt style="font-size:11px;">zhuyin</rt></ruby>
然后通過ruby-align設置其對齊方式。
這是一個比較冷門的技巧,可能平時使用不多,但小編覺得不妨提供給大家預防不時之需。
以上就是小編要跟大家分享的CSS網(wǎng)頁布局中文排版技巧,雖然很簡單,但簡單的過程中其實暗藏玄機,如果大家喜歡還請記得收藏哦~
*請認真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。