語法

2018-02-24 16:10 更新
  • 使用?::?引用常量(包括類和模塊)和構(gòu)造器 (比如?Array()?或者?Nokogiri::HTML())。永遠(yuǎn)不要使用?::?來調(diào)用方法。

    # 差
    SomeClass::some_method
    some_object::some_method
    
    # 好
    SomeClass.some_method
    some_object.some_method
    SomeModule::SomeClass::SOME_CONST
    SomeModule::SomeClass()
  • 使用?def?時(shí),有參數(shù)時(shí)使用括號(hào)。方法不接受參數(shù)時(shí),省略括號(hào)。

    # 差
    def some_method()
      # 此處省略方法體
    
    # 好
    def some_method
      # 此處省略方法體
    
    # 差
    def some_method_with_parameters param1, param2
      # 此處省略方法體
    
    # 好
    def some_method_with_parameters(param1, param2)
      # 此處省略方法體
    end
  • 永遠(yuǎn)不要使用?for?,除非你很清楚為什么。大部分情況應(yīng)該使用迭代器。for?是由?each?實(shí)現(xiàn)的。所以你繞彎了,而且?for?沒有包含一個(gè)新的作用域 (each?有 ),因此它區(qū)塊中定義的變量將會(huì)被外部所看到。

    arr = [1, 2, 3]
    
    # 差
    for elem in arr do
      puts elem
    end
    
    # 注意 elem 會(huì)被外部所看到
    elem #=> 3
    
    # 好
    arr.each { |elem| puts elem }
    
    # elem 不會(huì)被外部所看到
    elem #=> NameError: undefined local variable or method `elem'
  • 永遠(yuǎn)不要在多行的?if/unless?中使用?then

    # 差
    if some_condition then
      # 此處省略語句體
    end
    
    # 好
    if some_condition
      # 此處省略語句體
    end
  • 總是在多行的?if/unless?中把條件語句放在同一行。

    # 差
    if
      some_condition
      do_something
      do_something_else
    end
    
    # 好
    if some_condition
      do_something
      do_something_else
    end
  • 三元操作符?? :?比?if/then/else/end?結(jié)構(gòu)更為常見也更精準(zhǔn)。

    # 差
    result = if some_condition then something else something_else end
    
    # 好
    result = some_condition ? something : something_else
  • 三元操作符的每個(gè)分支只寫一個(gè)表達(dá)式。即不要嵌套三元操作符。嵌套情況使用?if/else?結(jié)構(gòu)。

    # 差
    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
  • 永遠(yuǎn)不要使用?if x: ...——它已經(jīng)在 Ruby 1.9 被移除了。使用三元操作符。

    # 差
    result = if some_condition: something else something_else end
    
    # 好
    result = some_condition ? something : something_else
  • 永遠(yuǎn)不要使用?if x; ...?使用三元操作符。

  • 利用 if 和 case 是表達(dá)式的特性。

    # 差
    if condition
      result = x
    else
      result = y
    end
    
    # 好
    result =
      if condition
        x
      else
        y
      end
  • 單行情況使用?when x then ...。另一種語法?when x: ...?已經(jīng)在 Ruby 1.9 被移除了。

  • 永遠(yuǎn)不要使用?when x: ...。參考前一個(gè)規(guī)則。

  • 使用?!?替代?not。

    # 差 - 因?yàn)椴僮鞣袃?yōu)先級(jí),需要用括號(hào)。
    x = (not something)
    
    # 好
    x = !something
  • 避免使用?!!

    # 差
    x = 'test'
    # obscure nil check
    if !!x
      # body omitted
    end
    
    x = false
    # double negation is useless on booleans
    !!x # => false
    
    # 好
    x = 'test'
    unless x.nil?
      # body omitted
    end
  • and?和?or?這兩個(gè)關(guān)鍵字被禁止使用了。 總是使用?&&?和?||?來取代。

    # 差
    # 布爾表達(dá)式
    if some_condition and some_other_condition
      do_something
    end
    
    # 控制流程
    document.saved? or document.save!
    
    # 好
    # 布爾表達(dá)式
    if some_condition && some_other_condition
      do_something
    end
    
    # 控制流程
    document.saved? || document.save!
  • 避免多行的?? :(三元操作符);使用?if/unless?來取代。

  • 單行主體用?if/unless?修飾符。另一個(gè)好的方法是使用?&&/||?控制流程。

    # 差
    if some_condition
      do_something
    end
    
    # 好
    do_something if some_condition
    
    # 另一個(gè)好方法
    some_condition && do_something
  • 避免在多行區(qū)塊后使用?if?或?unless。

    # 差
    10.times do
      # multi-line body omitted
    end if some_condition
    
    # 好
    if some_condition
      10.times do
        # multi-line body omitted
      end
    end
  • 否定判斷時(shí),unless(或控制流程的?||)優(yōu)于?if(或使用?||?控制流程)。

    # 差
    do_something if !some_condition
    
    # 差
    do_something if not some_condition
    
    # 好
    do_something unless some_condition
    
    # 另一個(gè)好方法
    some_condition || do_something
  • 永遠(yuǎn)不要使用?unless?和?else?組合。改寫成肯定條件。

    # 差
    unless success?
      puts 'failure'
    else
      puts 'success'
    end
    
    # 好
    if success?
      puts 'success'
    else
      puts 'failure'
    end
  • 不要使用括號(hào)圍繞?if/unless/while?的條件式。

    # 差
    if (x > 10)
      # 此處省略語句體
    end
    
    # 好
    if x > 10
      # 此處省略語句體
    end
  • 在多行?while/until?中不要使用?while/until condition do?。

    # 差
    while x > 5 do
      # 此處省略語句體
    end
    
    until x > 5 do
      # 此處省略語句體
    end
    
    # 好
    while x > 5
      # 此處省略語句體
    end
    
    until x > 5
      # 此處省略語句體
    end
  • 單行主體時(shí)盡量使用?while/until?修飾符。

    # 差
    while some_condition
      do_something
    end
    
    # 好
    do_something while some_condition
  • 否定條件判斷盡量使用?until?而不是?while?。

    # 差
    do_something while !some_condition
    
    # 好
    do_something until some_condition
  • 無限循環(huán)用?Kernel#loop,不用?while/until?。

    # 差
    while true
      do_something
    end
    
    until false
      do_something
    end
    
    # 好
    loop do
      do_something
    end
  • 循環(huán)后條件判斷使用?Kernel#loop?和?break,而不是?begin/end/until?或者?begin/end/while。

    # 差
    begin
     puts val
     val += 1
    end while val < 0
    
    # 好
    loop do
     puts val
     val += 1
     break unless val < 0
    end
  • 忽略圍繞方法參數(shù)的括號(hào),如內(nèi)部 DSL (如:Rake, Rails, RSpec),Ruby 中帶有“關(guān)鍵字”狀態(tài)的方法(如:attr_reader,puts)以及屬性存取方法。所有其他的方法呼叫使用括號(hào)圍繞參數(shù)。

    class Person
      attr_reader :name, :age
    
      # 忽略
    end
    
    temperance = Person.new('Temperance', 30)
    temperance.name
    
    puts temperance.age
    
    x = Math.sin(y)
    array.delete(e)
    
    bowling.score.should == 0
  • 省略可選哈希參數(shù)的外部花括號(hào)。

    # 差
    user.set({ name: 'John', age: 45, permissions: { read: true } })
    
    # 好
    User.set(name: 'John', age: 45, permissions: { read: true })
  • 如果方法是內(nèi)部 DSL 的一部分,那么省略外層的花括號(hào)和圓括號(hào)。

    class Person < ActiveRecord::Base
      # 差
      validates(:name, { presence: true, length: { within: 1..10 } })
    
      # 好
      validates :name, presence: true, length: { within: 1..10 }
    end
  • 如果方法調(diào)用不需要參數(shù),那么省略圓括號(hào)。

    # 差
    Kernel.exit!()
    2.even?()
    fork()
    'test'.upcase()
    
    # 好
    Kernel.exit!
    2.even?
    fork
    'test'.upcase
  • 當(dāng)被調(diào)用的方法是只有一個(gè)操作的區(qū)塊時(shí),使用Proc。

    # 差
    names.map { |name| name.upcase }
    
    # 好
    names.map(&:upcase)
  • 單行區(qū)塊傾向使用?{...}?而不是?do...end。多行區(qū)塊避免使用?{...}(多行串連總是??丑陋)。在?do...end?、 “控制流程”及“方法定義”,永遠(yuǎn)使用?do...end?(如 Rakefile 及某些 DSL)。串連時(shí)避免使用?do...end。

    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)

    某些人會(huì)爭論多行串連時(shí),使用?{...}?看起來還可以,但他們應(yīng)該捫心自問——這樣代碼真的可讀嗎?難道不能把區(qū)塊內(nèi)容取出來放到小巧的方法里嗎?

  • 顯性使用區(qū)塊參數(shù)而不是用創(chuàng)建區(qū)塊字面量的方式傳遞參數(shù)給區(qū)塊。此規(guī)則對(duì)性能有所影響,因?yàn)閰^(qū)塊先被轉(zhuǎn)化為Proc。

    require 'tempfile'
    
    # 差
    def with_tmp_dir
      Dir.mktmpdir do |tmp_dir|
        Dir.chdir(tmp_dir) { |dir| yield dir }  # block just passes arguments
      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
  • 避免在不需要控制流程的場合時(shí)使用?return?。

    # 差
    def some_method(some_arr)
      return some_arr.size
    end
    
    # 好
    def some_method(some_arr)
      some_arr.size
    end
  • 避免在不需要的情況使用?self?。(只有在調(diào)用一個(gè) self write 訪問器時(shí)會(huì)需要用到。)

    # 差
    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
  • 避免局部變量 shadowing 外部方法,除非它們彼此相等。

    class Foo
      attr_accessor :options
    
      # 勉強(qiáng)可以
      def initialize(options)
        self.options = options
        # 此處 options 和 self.options 都是等價(jià)的
      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
  • 不要在條件表達(dá)式里使用?=?(賦值)的返回值,除非條件表達(dá)式在圓括號(hào)內(nèi)被賦值。這是一個(gè)相當(dāng)流行的 Ruby 方言,有時(shí)被稱為“safe assignment in condition”。

    # 差 (還會(huì)有個(gè)警告)
    if (v = array.grep(/foo/))
      do_something(v)
      ...
    end
    
    # 差 (MRI 仍會(huì)抱怨, 但 RuboCop 不會(huì))
    if v = array.grep(/foo/)
      do_something(v)
      ...
    end
    
    # 好
    v = array.grep(/foo/)
    if v
      do_something(v)
      ...
    end
  • 變量自賦值用簡寫方式。

    # 差
    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
  • 如果變量未被初始化過,用?||=?來初始化變量并賦值。

    # 差
    name = name ? name : 'Bozhidar'
    
    # 差
    name = 'Bozhidar' unless name
    
    # 好 僅在 name 為 nil 或 false 時(shí),把名字設(shè)為 Bozhidar。
    name ||= 'Bozhidar'
  • 不要使用?||=?來初始化布爾變量。 (想看看如果現(xiàn)在的值剛好是?false?時(shí)會(huì)發(fā)生什么。)

    # 差——會(huì)把 `enabled` 設(shè)成真,即便它本來是假。
    enabled ||= true
    
    # 好
    enabled = true if enabled.nil?
  • 使用 &&= 可先檢查是否存在變量,如果存在則做相應(yīng)動(dòng)作。這樣就無需用?if?檢查變量是否存在了。

    # 差
    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 equality operator)。從名稱可知,這是?case?臺(tái)面下所用的操作符,在case?語句外的場合使用,會(huì)產(chǎn)生難以理解的代碼。

    # 差
    Array === something
    (1..100) === 7
    /something/ === some_string
    
    # 好
    something.is_a?(Array)
    (1..100).include?(7)
    some_string =~ /something/
  • 避免使用 Perl 風(fēng)格的特殊變量(像是?$:$;?等)。它們看起來非常神秘,除非用于單行腳本,否則不鼓勵(lì)使用。使用?English?庫提供的友好別名。

    # 差
    $:.unshift File.dirname(__FILE__)
    
    # 好
    require 'English'
    $LOAD_PATH.unshift File.dirname(__FILE__)
  • 永遠(yuǎn)不要在方法名與左括號(hào)之間放一個(gè)空格。

    # 差
    f (3 + 2) + 1
    
    # 好
    f(3 + 2) + 1
  • 如果方法的第一個(gè)參數(shù)由左括號(hào)開始的,則此方法調(diào)用應(yīng)該使用括號(hào)。舉個(gè)例子,如?f((3+2) + 1)。

  • 總是使用?-w?來執(zhí)行 Ruby 解釋器,如果你忘了某個(gè)上述的規(guī)則,它就會(huì)警告你!

  • 用新的 lambda 字面語法定義單行區(qū)塊,用?lambda?方法定義多行區(qū)塊。

    # 差
    lambda = lambda { |a, b| a + b }
    lambda.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
  • 當(dāng)定義一個(gè)簡短且沒有參數(shù)的 lambda 時(shí),省略參數(shù)的括號(hào)。

    # 差
    l = ->() { something }
    
    # 好
    l = -> { something }
  • 用?proc?而不是?Proc.new。

    # 差
    p = Proc.new { |n| puts n }
    
    # 好
    p = proc { |n| puts n }
  • 用?proc.call()?而不是?proc[]?或?proc.()。

    # 差 - 看上去像枚舉訪問
    l = ->(v) { puts v }
    l[1]
    
    # 也不好 - 不常用的語法
    l = ->(v) { puts v }
    l.(1)
    
    # 好
    l = ->(v) { puts v }
    l.call(1)
  • 未使用的區(qū)塊參數(shù)和局部變量使用?_?前綴或直接使用?_(雖然表意性差些) 。Ruby解釋器和RuboCop都能辨認(rèn)此規(guī)則,并會(huì)抑制相關(guān)地有變量未使用的警告。

    # 差
    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 中是可以給常量重新賦值的(可能是重定向到某個(gè)流),但解釋器會(huì)警告。

  • 使用?warn?而不是?$stderr.puts。除了更加清晰簡潔,如果你需要的話,?warn?還允許你壓制(suppress)警告(通過?-W0?將警告級(jí)別設(shè)為?0)。

  • 傾向使用?sprintf?和它的別名?format?而不是相當(dāng)隱晦的?String#%?方法.

    # 差
    '%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?而不是相當(dāng)隱晦的使用字符串作參數(shù)的?Array#*。

    # 差
    %w(one two three) * ', '
    # => 'one, two, three'
    
    # 好
    %w(one two three).join(', ')
    # => 'one, two, three'
  • 當(dāng)處理你希望將變量作為數(shù)組使用,但不確定它是不是數(shù)組時(shí), 使用?[*var]?或?Array()?而不是顯式的?Array?檢查。

      # 差
      paths = [paths] unless paths.is_a? Array
      paths.each { |path| do_something(path) }
    
      # 好
      [*paths].each { |path| do_something(path) }
    
      # 好(而且更具易讀性一點(diǎn))
      Array(paths).each { |path| do_something(path) }
  • 盡量使用范圍或?Comparable#between??來替換復(fù)雜的邏輯比較。

    # 差
    do_something if x >= 1000 && x < 2000
    
    # 好
    do_something if (1000...2000).include?(x)
    
    # 好
    do_something if x.between?(1000, 2000)
  • 盡量用判斷方法而不是使用?==?。比較數(shù)字除外。

    # 差
    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
  • 除非是布爾值,不用顯示檢查它是否不是?nil?。

    # 差
    do_something if !something.nil?
    do_something if something != nil
    
    # 好
    do_something if something
    
    # 好——檢查的是布爾值
    def value_set?
      !@some_boolean.nil?
    end
  • 避免使用?BEGIN?區(qū)塊。

  • 使用?Kernel#at_exit?。永遠(yuǎn)不要用?END?區(qū)塊。

    # 差
    
    END { puts 'Goodbye!' }
    
    # 好
    
    at_exit { puts 'Goodbye!' }
  • 避免使用 flip-flops 。

  • 避免使用嵌套的條件來控制流程。 當(dāng)你可能斷言不合法的數(shù)據(jù),使用一個(gè)防御從句。一個(gè)防御從句是一個(gè)在函數(shù)頂部的條件聲明,這樣如果數(shù)據(jù)不合法就能盡快的跳出函數(shù)。

      # 差
        def compute_thing(thing)
          if thing[:foo]
            update_with_bar(thing)
            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?而不是條件區(qū)塊。

      # 差
      [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?。這不是一個(gè)硬性要求;如果使用別名增加了可讀性,使用它沒關(guān)系。這些有押韻的方法名是從 Smalltalk 繼承而來,在別的語言不通用。鼓勵(lì)使用?select?而不是?find_all?的理由是它跟?reject?搭配起來是一目了然的。

  • 不要用?count?代替?size。除了Array其它Enumerable對(duì)象都需要遍歷整個(gè)集合才能得到大小。

    # 差
    some_hash.count
    
    # 好
    some_hash.size
  • 傾向使用?flat_map?而不是?map?+?flatten?的組合。 這并不適用于深度大于 2 的數(shù)組,舉個(gè)例子,如果users.first.songs == ['a', ['b', 'c']]?,則使用?map + flatten?的組合,而不是使用?flat_map。?flat_map將數(shù)組變平坦一個(gè)層級(jí),而?flatten?會(huì)將整個(gè)數(shù)組變平坦。

      # 差
      all_songs = users.map(&:songs).flatten.uniq
    
      # 好
      all_songs = users.flat_map(&:songs).uniq
  • 使用?reverse_each?,不用?reverse.each?。?reverse_each?不會(huì)重新分配新數(shù)組。

    # 差
    array.reverse.each { ... }
    
    # 好
    array.reverse_each { ... }
以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)