When Trouble Strikes

悲观地讲,就算使用 Ruby 编写程序也不能避免出现许多问题。即使对谈论起这个问题感到很抱歉。

但不需要担心!Ruby 拥有一些可以帮助我们调试的特性。稍后我们会看到这些特性,并且我们也会展示你使用 Ruby 时通常会遇到的问题,以及如何解决这些问题。

Ruby 调试器

Ruby 本身带有调试器,并且可以在基础系统上很方便地构建。你可以通过 -r debug 调用解释器将调试器运转,也可以添加其他 Ruby 参数以及脚本名称。

ruby -r debug [
            options
            ] [
            programfile
            ] [
            arguments
            ]

Ruby 调试器支持一般情况下你期望调试器拥有的功能,包括设置断点,步入和下一步以及展示栈结构和变量。

它也可以列举指定对象或类定义的实例方法列表,还允许你列举和控制 Ruby 中的线程。131 页的表格 12.1 列举了在调试器中可用的命令。

如果你的 Ruby 也支持 readline,你可以将光标移回历史命令的前方,然后用编辑行的方式修改之前输入的命令。

下面通过一些简单的场景让你看看 Ruby 调试器是什么样的。

% 
              ruby -rdebug t.rb
            
Debug.rb
Emacs support available.
t.rb:1:def fact(n)
(rdb:1) 
              list 1-9
            
[1, 10] in t.rb
=> 1  def fact(n)
   2    if n <= 0
   3      1
   4    else
   5      n * fact(n-1)
   6    end
   7  end
   8
   9  p fact(5)
(rdb:1) 
              b 2
            
Set breakpoint 1 at t.rb:2
(rdb:1) 
              c
            
breakpoint 1, fact at t.rb:2
t.rb:2:  if n <= 0
(rdb:1) 
              disp n
            
  1: n = 5
(rdb:1) 
              del 1
            
(rdb:1) 
              watch n==1
            
Set watchpoint 2
(rdb:1) 
              c
            
watchpoint 2, fact at t.rb:fact
t.rb:1:def fact(n)
1: n = 1
(rdb:1) 
              where
            
--> #1  t.rb:1:in `fact'
    #2  t.rb:5:in `fact'
    #3  t.rb:5:in `fact'
    #4  t.rb:5:in `fact'
    #5  t.rb:5:in `fact'
    #6  t.rb:9
(rdb:1) 
              del 2
            
(rdb:1) 
              c
            
120

交互式 Ruby

如果你希望能运用 Ruby,那它拥有被称作交互式 Ruby 的能力—— 也就是 irb。irb 本质上就是 Ruby 的 shell,它与操作系统的 shell 非常相似(都是通过任务控制完成)。并且它提供了一个你可以在真实环境中使用 Ruby 操作的环境。你可以通过命令提示符运行它。

irb [
            irb-options
            ] [
            ruby_script
            ] [
            options
            ]

irb 会显示你完成的每个表达式的值。有如下例子:

% irb
irb(main):001:0> 
              a = 1 +
            
irb(main):002:0* 
              2 * 3 /
            
irb(main):003:0* 
              4 % 5
            
2
irb(main):004:0> 
              2+2
            
4
irb(main):005:0> 
              def test
            
irb(main):006:1> 
              puts "Hello, world!"
            
irb(main):007:1> 
              end
            
nil
irb(main):008:0> 
              test
            
Hello, world!
nil
irb(main):009:0> 

irb 也允许你创建子会话,每个子会话也有自己的上下文。例如,你可以用与原始会话一致的上下文创建子会话,或者通过特定类或实例的上下文完成对子会话的创建。普通的会话在 126 页图 12.1 中展示,而且也显示了如何创建子会话和对它们进行切换。

对于所有关于 irb 支持的命令的描述可以查看 517 页开始的内容。

和调试器一样,如果你的 Ruby 版本是构建于 GNU Readline 支持之上,你便可以使用箭头键或者 vi 风格绑定的方向键编辑单独行,也可以回到之前的命令行再执行或编辑它,就如同 shell 命令一样。

irb 是一个非常好的学习工具,如果你想快速验证一个想法并看到它运行,irb 是一个非常便捷的工具。

支持的编辑器

Ruby 被设计为一次读取一个程序,这意味着你可以传递一个完整的程序给 Ruby 的标准输入,它将正常运转。

我们在编辑器中运行 Ruby 代码时也会从这个特性获益。例如,在 Emacs 中,你可以选择一块 Ruby 字符并且用命令 Meta-| 运行它。Ruby 解释器会将选择区域作为标准输入,然后输出至叫做 「Shell Commad Output」的缓冲区。在编写这本书的时候这个特性让我们工作很顺手,比如可以选择一段中的几行代码并运行它们。

你还可以通过 vi 编辑器实现类似的功能,vi 编辑器中可以使用 「:!ruby」将运行输出替换程序字符,或者使用「:w !ruby」可以不受缓冲影响地显示输出。其他编辑器也有类似的特性。

当我们讨论这个主题时,也许是个好机会,我们可以了解该改造版中包含了一种用于 Emacs 的 Ruby 模式也就是 misc/Ruby-mode.el。在 vim 中也有一些语法高亮,jed 以及其他的编辑器也非常好用。可以查看 Ruby 的 FAQ 了解相应情况和可用性。

它不运行了!

你已经阅读了足够多的书籍,你也开始编写自己的 Ruby 程序,但是它无法运转。这里列举了常见的陷阱和其他的技巧。

  • 属性的设置方法无法正常调用。在对象中,Ruby 将 setter= 解析为对本地变量的赋值,并不会作为方法调用。需要通过 self.setter= 指明方法的调用。

  • 在源文件的最后一行解析报错说明缺少 end 关键字。

  • 确认使用对象的类型是否与认为的一致。如果有所怀疑,可以用 Object#type 检查对象的类型。

  • 确认你的方法是以小写字母开头,并且类和常量是以大写字母开头。

  • 如果你在参数列表中忘记写「,」,程序将会产生一些奇怪的错误信息。

  • 代码块参数是真实的局部变量。如果当代码块执行时已经存在一个同名的局部变量,此变量将被调用的代码块修改。这可能是件好事也可以成为一件坏事。

  • 需要当心优先级问题,特别是在使用 {} 替代 do/end 时。

  • 确认方法参数列表的左括号与方法名相连,中间不存在空格。

  • 输出到终端的信息可能进行缓冲区。这意味着你无法立即看见自己输出的信息。而且如果你通过 $stdout$stderr 输出信息,输出的内容可能不会按照你预期的顺序出现。如果是输出调试信息要使用无缓冲的 I/O (设置 sync=true)。

  • 如果关于数字的操作总是不正确,或许它们是字符串。从文件中读取的字符都是 String,在 Ruby 中也不会自动转换为数字。如果通过 to_i 调用后将正常运转。如果是 Perl 程序员可能就常会犯这样的错误。

while gets
  num1, num2 = split /,/
  # ...
end
  • 计划外的混叠。如果你使用一个对象作为散列表的键,需要确认它不会修改到它的散列值(或者如果它会对列表值进行修改可以调用 Hash#rehash)。

  • 当变量值改变时通过 trace_var 查看。

  • 使用调试器。

  • 使用 Object#freeze。如果你怀疑一些未知的代码伪造了变量值,你可以尝试将变量冻结。在代码再次尝试对变量进行修改时将被捕捉到。

这是一些让你编写 Ruby 更加轻松愉悦的主要技巧。立即开发你的应用吧。写一些代码然后运行它们。再写一些,再运行它们。无类型语言的一个主要好处就是在你使用它们之前你不需要完善它们。

它太慢了!

Ruby 是可被解释的,高级的语言,这也意味着它不可能像低级别语言比如 C 语言那样执行速度快。这部分我们会学习一些提升性能的基本知识,或者你也可以查看 Performance 索引下的其他知识点。

创建本地边界封锁

在代码块执行前试图通过代码块定义变量。当遍历大量元素时你可以预声明迭代器变量提高执行速率。在下面的第一个例子中,Ruby 必须在每次迭代中创建新的 x 和 y 变量,但第二个版本中并不是这样做的。我们可以用 Ruby Application Archive 中的 benchmark 包比较循环:

require "benchmark"
include Benchmark
n = 1000000
bm(12) do |test|
  test.report("normal:")    do
    n.times do |x|
      y = x + 1
    end
  end
  test.report("predefine:") do
    x = y = 0
    n.times do |x|
      y = x + 1
    end
  end
end

结果是:

                  user     system      total        real
normal:       2.450000   0.020000   2.470000 (  2.468109)
predefine:    2.140000   0.020000   2.160000 (  2.155307)

使用分析器

Ruby 也自带代码分析器(详情可从 454 页开始查看)。自带代码分析器本身并没有什么让人惊讶的,但如果你了解到它是由 50 行 Ruby 代码编写而成时,你会感叹这是多么让人惊叹的语言呀。

你可以在通过命令行参数 -r profile 或在代码中加入 require "profile" 向代码添加分析器。例如:

require "profile"
class Peter
  def initialize(amt)
    @value = amt
  end

  def rob(amt)
    @value -= amt
    amt
  end
end

class Paul
  def initialize
    @value = 0
  end

  def pay(amt)
    @value += amt
    amt
  end
end

peter = Peter.new(1000)
paul = Paul.new
1000.times do
  paul.pay(peter.rob(10))
end

运行这段代码你将获得如下的结果:

time   seconds   seconds    calls  ms/call  ms/call  name
 32.14     0.27      0.27        1   270.00   840.00  Fixnum#times
 30.95     0.53      0.26     1000     0.26     0.27  Paul#pay
 29.76     0.78      0.25     1000     0.25     0.30  Peter#rob
  5.95     0.83      0.05     1000     0.05     0.05  Fixnum#-
  1.19     0.84      0.01     1000     0.01     0.01  Fixnum#+
  0.00     0.84      0.00        1     0.00     0.00  Paul#initialize
  0.00     0.84      0.00        2     0.00     0.00  Class#inherited
  0.00     0.84      0.00        4     0.00     0.00  Module#method_added
  0.00     0.84      0.00        1     0.00     0.00  Peter#initialize
  0.00     0.84      0.00        1     0.00   840.00  #toplevel
  0.00     0.84      0.00        2     0.00     0.00  Class#new

使用分析器我们可以快速定位和修复瓶颈。记住在检查代码时不要使用分析器,由于分析器会将速度降低导致掩盖其他问题。

Ruby 是一种透明度高且表达力强的语言,但它并不能解决程序员使用其他语言的通病,比如创建不需要的对象,执行不需要的过程,以及编写臃肿的代码。

调试命令

CommandComment
b[reak] [file:]lineSet breakpoint at given line in file (default current file).
b[reak] [file:]nameSet breakpoint at method in file.
b[reak]Display breakpoints and watchpoints.
wat[ch] exprBreak when expression becomes true.
del[ete] [nnn]Delete breakpoint nnn (default all).
disp[lay] exprDisplay value of nnn every time debugger gets control.
disp[lay]Show current displays.
undisp[lay] [nnn]Remove display (default all).
c[ont]Continue execution.
s[tep] nnn=1Execute next nnn lines, stepping into methods.
n[ext] nnn=1Execute next nnn lines, stepping over methods.
fi[nish]Finish execution of the current function.
q[uit]Exit the debugger.
w[here]Display current stack frame.
f[rame]Synonym for where.
l[ist] [start–end]List source lines from start to end.
up nnn=1Move up nnn levels in the stack frame.
down nnn=1Move down nnn levels in the stack frame.
v[ar] g[lobal]Display global variables.
v[ar] l[ocal]Display local variables.
v[ar] i[stance] objDisplay instance variables of obj.
v[ar] c[onst] NameDisplay constants in class or module name.
m[ethod] i[nstance] objDisplay instance methods of obj.
m[ethod] NameDisplay instance methods of the class or module name.
th[read] l[ist]List all threads.
th[read] [c[ur[rent]]]Display status of current thread.
th[read] [c[ur[rent]]] nnnMake thread nnn current and stop it.
th[read] stop nnnMake thread nnn current and stop it.
th[read] resume nnnResume thread nnn.
[p] exprEvaluate expr in the current context. expr may include assignment to variables and method invocations.
emptyA null command repeats the last command.

本文翻译自《Programming Ruby》,主要目的是自己学习使用,文中翻译不到位之处烦请指正,如需转载请注明出处

本章原文为 When Trouble Strikes

©️2020 CSDN 皮肤主题: 像素格子 设计师:CSDN官方博客 返回首页