异常


  • 对于异常处理,倾向使用 raise 而不是 fail
    [link]

    1. # 差
    2. fail SomeException, 'message'
    3. # 好
    4. raise SomeException, 'message'

  • 不要在带双参数形式的 raise 方法中显式指定 RuntimeError
    [link]

    1. # 差
    2. raise RuntimeError, 'message'
    3. # 好 - 默认就是 RuntimeError
    4. raise 'message'

  • 倾向使用带异常类、消息的双参数形式调用 raise 方法,而不是使用异常的实例。
    [link]

    1. # 差 - 并无 raise SomeException.new('message') [, backtraces] 这种调用形式
    2. raise SomeException.new('message')
    3. # 好 - 与调用形式 raise SomeException [, 'message' [, backtraces]] 保持一致
    4. raise SomeException, 'message'

  • 永远不要从 ensure 区块返回。如果你显式地从 ensure 区块返回,那么其所在的方法会如同永远不会发生异常般的返回。事实上,异常被默默地丢弃了。
    [link]

    1. def foo
    2. raise
    3. ensure
    4. return 'very bad idea'
    5. end

  • 尽可能隐式地使用 begin/rescue/ensure/end 区块。
    [link]

    1. # 差
    2. def foo
    3. begin
    4. # 主逻辑
    5. rescue
    6. # 异常处理逻辑
    7. end
    8. end
    9. # 好
    10. def foo
    11. # 主逻辑
    12. rescue
    13. # 异常处理逻辑
    14. end

  • 通过使用 contingency 方法(一个由 Avdi Grimm 创造的词)来减少 begin/rescue/ensure/end 区块的使用。
    [link]

    1. # 差
    2. begin
    3. something_that_might_fail
    4. rescue IOError
    5. # 处理 IOError
    6. end
    7. begin
    8. something_else_that_might_fail
    9. rescue IOError
    10. # 处理 IOError
    11. end
    12. # 好
    13. def with_io_error_handling
    14. yield
    15. rescue IOError
    16. # 处理 IOError
    17. end
    18. with_io_error_handling { something_that_might_fail }
    19. with_io_error_handling { something_else_that_might_fail }

  • 不要抑制异常。
    [link]

    1. # 差
    2. begin
    3. # 抛出异常
    4. rescue SomeError
    5. # 不做任何相关处理
    6. end
    7. # 差
    8. do_something rescue nil

  • 避免使用 rescue 修饰语法。
    [link]

    1. # 差 - 这里将会捕捉 StandardError 及其所有子孙类的异常
    2. read_file rescue handle_error($!)
    3. # 好 - 这里只会捕获 Errno::ENOENT 及其所有子孙类的异常
    4. def foo
    5. read_file
    6. rescue Errno::ENOENT => ex
    7. handle_error(ex)
    8. end

  • 不要将异常处理作为流程控制使用。
    [link]

    1. # 差
    2. begin
    3. n / d
    4. rescue ZeroDivisionError
    5. puts 'Cannot divide by 0!'
    6. end
    7. # 好
    8. if d.zero?
    9. puts 'Cannot divide by 0!'
    10. else
    11. n / d
    12. end

  • 避免捕获 Exception。这种做法会同时将信号与 exit 方法困住,导致你必须使用 kill -9 来终止进程。
    [link]

    1. # 差 - 信号与 exit 方法产生的异常会被捕获(除了 kill -9)
    2. begin
    3. exit
    4. rescue Exception
    5. puts "you didn't really want to exit, right?"
    6. # 处理异常
    7. end
    8. # 好 - 没有指定具体异常的 rescue 子句默认捕获 StandardError
    9. begin
    10. # 抛出异常
    11. rescue => e
    12. # 处理异常
    13. end
    14. # 好 - 指定具体异常 StandardError
    15. begin
    16. # 抛出异常
    17. rescue StandardError => e
    18. # 处理异常
    19. end

  • 把较具体的异常放在处理链的较上层,不然它们永远不会被执行。
    [link]

    1. # 差
    2. begin
    3. # 抛出异常
    4. rescue StandardError => e
    5. # 处理异常
    6. rescue IOError => e
    7. # 处理异常,但事实上永远不会被执行
    8. end
    9. # 好
    10. begin
    11. # 抛出异常
    12. rescue IOError => e
    13. # 处理异常
    14. rescue StandardError => e
    15. # 处理异常
    16. end

  • ensure 区块释放程序的外部资源。
    [link]

    1. f = File.open('testfile')
    2. begin
    3. # .. 文件操作
    4. rescue
    5. # .. 处理异常
    6. ensure
    7. f.close if f
    8. end

  • 在调用资源获取方法时,尽量使用具备自动清理功能的版本。
    [link]

    1. # 差 - 需要显式关闭文件描述符
    2. f = File.open('testfile')
    3. # ...
    4. f.close
    5. # 好 - 文件描述符会被自动关闭
    6. File.open('testfile') do |f|
    7. # ...
    8. end

  • 倾向使用标准库中的异常类而不是引入新的类型。
    [link]