牛骨文教育服务平台(让学习变的简单)
  • 使用 :: 引用常量(包括类和模块)和构造器 (比如 Array() 或者 Nokogiri::HTML())。永远不要使用 :: 来调用方法。

    # 差
    SomeClass::some_method
    some_object::some_method
    
    # 好
    SomeClass.some_method
    some_object.some_method
    SomeModule::SomeClass::SOME_CONST
    SomeModule::SomeClass()
    
  • 使用 def 时,有参数时使用括号。方法不接受参数时,省略括号。

    # 差
    def some_method()
      # 此处省略方法体
    
    # 好
    def some_method
      # 此处省略方法体
    
    # 差
    def some_method_with_parameters param1, param2
      # 此处省略方法体
    
    # 好
    def some_method_with_parameters(param1, param2)
      # 此处省略方法体
    end
    
  • 永远不要使用 for ,除非你很清楚为什么。大部分情况应该使用迭代器。for 是由 each 实现的。所以你绕弯了,而且 for 没有包含一个新的作用域 (each 有 ),因此它区块中定义的变量将会被外部所看到。

    arr = [1, 2, 3]
    
    # 差
    for elem in arr do
      puts elem
    end
    
    # 注意 elem 会被外部所看到
    elem #=> 3
    
    # 好
    arr.each { |elem| puts elem }
    
    # elem 不会被外部所看到
    elem #=> NameError: undefined local variable or method `elem"
    
  • 永远不要在多行的 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 结构更为常见也更精准。

    # 差
    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; ... 使用三元操作符。

  • 利用 if 和 case 是表达式的特性。

    # 差
    if condition
      result = x
    else
      result = y
    end
    
    # 好
    result =
      if condition
        x
      else
        y
      end
    
  • 单行情况使用 when x then ...。另一种语法 when x: ... 已经在 Ruby 1.9 被移除了。

  • 永远不要使用 when x: ...。参考前一个规则。

  • 使用 ! 替代 not

    # 差 - 因为操作符有优先级,需要用括号。
    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 这两个关键字被禁止使用了。 总是使用 && 和 || 来取代。

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

  • 单行主体用 if/unless 修饰符。另一个好的方法是使用 &&/|| 控制流程。

    # 差
    if some_condition
      do_something
    end
    
    # 好
    do_something if some_condition
    
    # 另一个好方法
    some_condition && do_something
    
  • 避免在多行区块后使用 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
    
  • 否定判断时,unless(或控制流程的 ||)优于 if(或使用 || 控制流程)。

    # 差
    do_something if !some_condition
    
    # 差
    do_something if not some_condition
    
    # 好
    do_something unless some_condition
    
    # 另一个好方法
    some_condition || 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
    
  • 在多行 while/until 中不要使用 while/until condition do 。

    # 差
    while x > 5 do
      # 此处省略语句体
    end
    
    until x > 5 do
      # 此处省略语句体
    end
    
    # 好
    while x > 5
      # 此处省略语句体
    end
    
    until x > 5
      # 此处省略语句体
    end
    
  • 单行主体时尽量使用 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
    
  • 无限循环用 Kernel#loop,不用 while/until 。

    # 差
    while true
      do_something
    end
    
    until false
      do_something
    end
    
    # 好
    loop do
      do_something
    end
    
  • 循环后条件判断使用 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
    
  • 忽略围绕方法参数的括号,如内部 DSL (如:Rake, Rails, RSpec),Ruby 中带有“关键字”状态的方法(如:attr_readerputs)以及属性存取方法。所有其他的方法呼叫使用括号围绕参数。

    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
    
  • 省略可选哈希参数的外部花括号。

    # 差
    user.set({ name: "John", age: 45, permissions: { read: true } })
    
    # 好
    User.set(name: "John", age: 45, permissions: { read: true })
    
  • 如果方法是内部 DSL 的一部分,那么省略外层的花括号和圆括号。

    class Person < ActiveRecord::Base
      # 差
      validates(:name, { presence: true, length: { within: 1..10 } })
    
      # 好
      validates :name, presence: true, length: { within: 1..10 }
    end
    
  • 如果方法调用不需要参数,那么省略圆括号。

    # 差
    Kernel.exit!()
    2.even?()
    fork()
    "test".upcase()
    
    # 好
    Kernel.exit!
    2.even?
    fork
    "test".upcase
    
  • 当被调用的方法是只有一个操作的区块时,使用Proc

    # 差
    names.map { |name| name.upcase }
    
    # 好
    names.map(&:upcase)
    
  • 单行区块倾向使用 {...} 而不是 do...end。多行区块避免使用 {...}(多行串连总是​​丑陋)。在 do...end 、 “控制流程”及“方法定义”,永远使用 do...end (如 Rakefile 及某些 DSL)。串连时避免使用 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)
    

    某些人会争论多行串连时,使用 {...} 看起来还可以,但他们应该扪心自问——这样代码真的可读吗?难道不能把区块内容取出来放到小巧的方法里吗?

  • 显性使用区块参数而不是用创建区块字面量的方式传递参数给区块。此规则对性能有所影响,因为区块先被转化为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
    
  • 避免在不需要控制流程的场合时使用 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)
        self.status = :in_progress
      end
      status == :verified
    end
    
  • 避免局部变量 shadowing 外部方法,除非它们彼此相等。

    class Foo
      attr_accessor :options
    
      # 勉强可以
      def initialize(options)
        self.options = options
        # 此处 options 和 self.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
      end
    end
    
  • 不要在条件表达式里使用 = (赋值)的返回值,除非条件表达式在圆括号内被赋值。这是一个相当流行的 Ruby 方言,有时被称为“safe assignment in condition”。

    # 差 (还会有个警告)
    if (v = array.grep(/foo/))
      do_something(v)
      ...
    end
    
    # 差 (MRI 仍会抱怨, 但 RuboCop 不会)
    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 时,把名字设为 Bozhidar。
    name ||= "Bozhidar"
    
  • 不要使用 ||= 来初始化布尔变量。 (想看看如果现在的值刚好是 false 时会发生什么。)

    # 差——会把 `enabled` 设成真,即便它本来是假。
    enabled ||= true
    
    # 好
    enabled = true if enabled.nil?
    
  • 使用 &&= 可先检查是否存在变量,如果存在则做相应动作。这样就无需用 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 台面下所用的操作符,在case 语句外的场合使用,会产生难以理解的代码。

    # 差
    Array === something
    (1..100) === 7
    /something/ === some_string
    
    # 好
    something.is_a?(Array)
    (1..100).include?(7)
    some_string =~ /something/
    
  • 避免使用 Perl 风格的特殊变量(像是 $:$; 等)。它们看起来非常神秘,除非用于单行脚本,否则不鼓励使用。使用 English 库提供的友好别名。

    # 差
    $:.unshift File.dirname(__FILE__)
    
    # 好
    require "English"
    $LOAD_PATH.unshift File.dirname(__FILE__)
    
  • 永远不要在方法名与左括号之间放一个空格。

    # 差
    f (3 + 2) + 1
    
    # 好
    f(3 + 2) + 1
    
  • 如果方法的第一个参数由左括号开始的,则此方法调用应该使用括号。举个例子,如 f((3+2) + 1)

  • 总是使用 -w 来执行 Ruby 解释器,如果你忘了某个上述的规则,它就会警告你!

  • 用新的 lambda 字面语法定义单行区块,用 lambda 方法定义多行区块。

    # 差
    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
    
  • 当定义一个简短且没有参数的 lambda 时,省略参数的括号。

    # 差
    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)
    
  • 未使用的区块参数和局部变量使用 _ 前缀或直接使用 _(虽然表意性差些) 。Ruby解释器和RuboCop都能辨认此规则,并会抑制相关地有变量未使用的警告。

    # 差
    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/STDINSTDOUT/STDERR/STDIN 是常量,虽然在 Ruby 中是可以给常量重新赋值的(可能是重定向到某个流),但解释器会警告。

  • 使用 warn 而不是 $stderr.puts。除了更加清晰简洁,如果你需要的话, warn 还允许你压制(suppress)警告(通过 -W0 将警告级别设为 0)。

  • 倾向使用 sprintf 和它的别名 format 而不是相当隐晦的 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 而不是相当隐晦的使用字符串作参数的 Array#*

    # 差
    %w(one two three) * ", "
    # => "one, two, three"
    
    # 好
    %w(one two three).join(", ")
    # => "one, two, three"
    
  • 当处理你希望将变量作为数组使用,但不确定它是不是数组时, 使用 [*var] 或 Array() 而不是显式的 Array 检查。

      # 差
      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? 来替换复杂的逻辑比较。

    # 差
    do_something if x >= 1000 && x < 2000
    
    # 好
    do_something if (1000...2000).include?(x)
    
    # 好
    do_something if x.between?(1000, 2000)
    
  • 尽量用判断方法而不是使用 == 。比较数字除外。

    # 差
    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 区块。

  • 使用 Kernel#at_exit 。永远不要用 END 区块。

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

  • 避免使用嵌套的条件来控制流程。 当你可能断言不合法的数据,使用一个防御从句。一个防御从句是一个在函数顶部的条件声明,这样如果数据不合法就能尽快的跳出函数。

      # 差
        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 而不是条件区块。

      # 差
      [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 。这不是一个硬性要求;如果使用别名增加了可读性,使用它没关系。这些有押韵的方法名是从 Smalltalk 继承而来,在别的语言不通用。鼓励使用 select 而不是 find_all 的理由是它跟 reject 搭配起来是一目了然的。

  • 不要用 count 代替 size。除了Array其它Enumerable对象都需要遍历整个集合才能得到大小。

    # 差
    some_hash.count
    
    # 好
    some_hash.size
    
  • 倾向使用 flat_map 而不是 map + flatten 的组合。 这并不适用于深度大于 2 的数组,举个例子,如果users.first.songs == ["a", ["b", "c"]] ,则使用 map + flatten 的组合,而不是使用 flat_map。 flat_map将数组变平坦一个层级,而 flatten 会将整个数组变平坦。

      # 差
      all_songs = users.map(&:songs).flatten.uniq
    
      # 好
      all_songs = users.flat_map(&:songs).uniq
    
  • 使用 reverse_each ,不用 reverse.each 。 reverse_each 不会重新分配新数组。

    # 差
    array.reverse.each { ... }
    
    # 好
    array.reverse_each { ... }