Ensure

无论是否发生异常(Exception),你可能会在某些情况下采取某些特定操作。例如,每当你处理某种不可预测的输入/输出时 - 例如,在使用磁盘上的文件和目录时 - 总是有可能位置(磁盘或目录)或数据源(文件)根本不存在或者可能发生其它类型的问题 - 例如当你尝试写入时磁盘已满,或者尝试读取时可能包含一个错误类型的数据。

无论你是否遇到任何问题,你可能需要执行一些最终的“清理”(cleanup)过程 - 例如登录到特定的工作目录或关闭先前打开的文件。你可以通过在 begin..rescue 代码块后跟随一个以 ensure 关键字开头的另一个块的来执行此操作。ensure 块中的代码将始终会执行 - 无论之前是否发生异常。

最后,我想确保我的工作目录(由 Dir.getwd 提供)始终恢复到其原始位置。我通过在 startdir 变量中保存原始目录并再次在 ensure 块中将其作为工作目录来完成此操作:

ensure.rb
  1. startdir = Dir.getwd
  2. begin
  3. Dir.chdir( "X:\\" )
  4. puts( `dir` )
  5. rescue Exception => e
  6. puts e.class
  7. puts e
  8. ensure
  9. Dir.chdir( startdir )
  10. end

现在让我们看看如何处理从文件中读取错误数据的问题。如果数据损坏,或者你不小心打开了错误的文件,或者很简单 - 你的程序代码包含错误(bug)时,则可能会发生这种情况。

这里我有一个文件 test.txt,包含六行内容。前五行是数字(numbers);第六行不是。我的代码会打开此文件并读入所有六行内容:

ensure2.rb
  1. f = File.new( "test.txt" )
  2. begin
  3. for i in (1..6) do
  4. puts("line number: #{f.lineno}")
  5. line = f.gets.chomp
  6. num = line.to_i
  7. puts( "Line '#{line}' is converted to #{num}" )
  8. puts( 100 / num )
  9. end
  10. rescue Exception => e
  11. puts( e.class )
  12. puts( e )
  13. ensure
  14. f.close
  15. puts( "File closed" )
  16. end

这些行作为字符串读入(使用 gets),尝试将它们转换为整数(使用 to_i)。转换失败时不会产生错误;Ruby 会返回值 0。

问题出现在下一行代码中,它尝试按转换后的数字进行除法运算。输入文件的第六行包含字符串 “six”,当尝试转换为整数时产生 0 - 并且当在除法运算中使用该值时不可避免地会导致错误发生。

在外部打开数据文件后,无论是否发生错误我都想确保文件会关闭。例如,如果我只通过将 for 循环中的范围编辑为 (1..5) 来读取前五行,那么就没有异常。我仍然想要关闭该文件。

但是将文件关闭代码(f.close)放在 rescue 子句中并不好,因为在这种情况下它不会被执行。然而,通过将它放在 ensure 子句中,无论是否发生异常,我都可以确定该文件将被关闭。