抢先一睹 Ruby 的新调试器!
debug
是 Ruby 的新调试器,将包含在 Ruby 3.1 中。由于我参与贡献和使用已经有一段时间了,我觉得是时候在它1.0
正式发布之前先让大家抢先体验一下了 🙂
(由于尚未正式发布,本文中提到的任何功能仍可能在发布版本中被修改/删除)
(更新:该项目的首席开发人员@ko1已经开始了关于调试器的博客系列。请查看😉)
介绍
正如我之前提到的,它计划成为 Ruby 3.1 的标准库。目前,你可以将其作为 gem 安装,例如:
$ gem install debug --pre
或者
# Gemfile
# it's under active development, so I suggest using GitHub as source when possible
gem "debug", github: "ruby/debug"
功能上,debug
类似于著名的GDB
调试器和 Ruby 的byebug
gem。它提供了丰富的调试命令,并具有一些独特的功能。
摘自其自述文件:
New debug.rb has several advantages:
- Fast: No performance penalty on non-stepping mode and non-breakpoints.
- Remote debugging: Support remote debugging natively.
- UNIX domain socket
- TCP/IP
- VSCode/DAP integration (VSCode rdbg Ruby Debugger - Visual Studio Marketplace)
- Extensible: application can introduce debugging support with several ways:
- By `rdbg` command
- By loading libraries with `-r` command line option
- By calling Ruby's method explicitly
- Misc
- Support threads (almost done) and ractors (TODO).
- Support suspending and entering to the console debugging with `Ctrl-C` at most of timing.
- Show parameters on backtrace command.
以下是我最喜欢的功能:
- 是彩色的。
- 当使用命令显示回溯时
backtrace
,它还会显示方法参数、块参数和返回值。
=>#0 Foo#forth_call(num1=20, num2=10) at target.rb:20 #=> 30
#1 block {|ten=10|} in second_call at target.rb:8
- 可以使用脚本编写调试命令
binding.break
,减少手动操作。(请参阅组合部分的示例) - 有多个命令可以设置在不同条件下触发的断点,例如
break
、catch
和watch
。
binding.break
(别名binding.b
:)
如果您和pry
我一样是重度用户,您可以像往常一样使用熟悉的binding.break
(或只是binding.b
)来启动调试会话。
但binding.break
实际上比更强大binding.pry
,因为它可以接受命令!
例如:
binding.b(do: "catch CustomException")
- 调试器将执行命令(catch customExeption
)并继续程序。binding.b(pre: "catch CustomException")
- 调试器将执行命令(catch customExeption
)并在该行停止。
(要执行多个命令,请使用;;
作为分隔符"cmd1 ;; cmd2 ;; cmd3"
:)
常用命令
新的调试器有很多强大的命令。以下是我最常用的命令:
break
(别名b
:)
class A
def foo; end
def self.bar; end
end
class B < A; end
class C < A; end
B.bar
C.bar
b1 = B.new
b2 = B.new
c = C.new
b1.foo
b2.foo
c.foo
基本用法
b A#foo
b1.foo
- 当、b2.foo
、 和c.foo
被调用时停止b A.bar
B.bar
- 当被C.bar
调用时停止b B#foo
b1.foo
- 当被b2.foo
调用时停止b B.bar
B.bar
-被调用时停止b b1.foo
b1.foo
-被调用时停止
命令
b b1.foo do: cmd
cmd
- 被调用时执行b1.foo
但不停止b b1.foo pre: cmd
cmd
- 在被调用时执行b1.foo
并停止
catch
class FooException < StandardError; end
class BarException < StandardError; end
def raise_foo
raise FooException
end
def raise_bar
raise BarException
end
raise_foo
raise_bar
catch StandardError
StandardError
- 当出现任何实例时停止,包括FooException
和BarException
catch FooException
-FooException
升起时停止
backtrace
(别名bt
)
示例输出:
=>#0 Foo#forth_call(num1=20, num2=10) at target.rb:20 #=> 30
#1 block {|ten=10|} in second_call at target.rb:8
#2 Foo#third_call_with_block(block=#<Proc:0x00007f9283101568 target.rb:7>) at target.rb:15
#3 Foo#second_call(num=20) at target.rb:7
#4 Foo#first_call at target.rb:3
#5 <main> at target.rb:23
bt
- 显示堆栈上的所有帧bt 10
- 仅显示前 10 帧bt /my_lib/
- 仅显示路径匹配的帧my_lib
outline
(别名ls
)
类似于或ls
中的命令。irb
pry
binding.b
+ 命令组合
binding.b(do: "b Foo#bar do: bt")
它允许您检查方法调用的回溯,而无需触及方法定义或手动输入命令。
脚本:
binding.b(do: "b Foo#bar do: bt")
class Foo
def bar
end
end
def some_method
Foo.new.bar
end
some_method
输出:
DEBUGGER: Session start (pid: 75555)
[1, 10] in target.rb
=> 1| binding.b(do: "b Foo#bar do: bt")
2|
3| class Foo
4| def bar
5| end
6| end
7|
8| def some_method
9| Foo.new.bar
10| end
=>#0 <main> at target.rb:1
(rdbg:binding.break) b Foo#bar do: bt
uninitialized constant Foo
#0 BP - Method (pending) Foo#bar do: bt
DEBUGGER: BP - Method Foo#bar at target.rb:4 do: bt is activated.
[1, 10] in target.rb
1| binding.b(do: "b Foo#bar do: bt")
2|
3| class Foo
=> 4| def bar
5| end
6| end
7|
8| def some_method
9| Foo.new.bar
10| end
=>#0 Foo#bar at target.rb:4
#1 Object#some_method at target.rb:9
# and 1 frames (use `bt' command for all frames)
Stop by #0 BP - Method Foo#bar at target.rb:4 do: bt
(rdbg:break) bt
=>#0 Foo#bar at target.rb:4
#1 Object#some_method at target.rb:9
#2 <main> at target.rb:12
binding.b(do: "b Foo#bar do: info")
它允许您在调用时检查方法的环境(例如参数):
脚本:
binding.b(do: "b Foo#bar do: info")
class Foo
def bar(a)
a
end
end
def some_method
Foo.new.bar(10)
end
some_method
输出:
DEBUGGER: Session start (pid: 75924)
[1, 10] in target.rb
=> 1| binding.b(do: "b Foo#bar do: info")
2|
3| class Foo
4| def bar(a)
5| a
6| end
7| end
8|
9| def some_method
10| Foo.new.bar(10)
=>#0 <main> at target.rb:1
(rdbg:binding.break) b Foo#bar do: info
uninitialized constant Foo
#0 BP - Method (pending) Foo#bar do: info
DEBUGGER: BP - Method Foo#bar at target.rb:4 do: info is activated.
[1, 10] in target.rb
1| binding.b(do: "b Foo#bar do: info")
2|
3| class Foo
4| def bar(a)
=> 5| a
6| end
7| end
8|
9| def some_method
10| Foo.new.bar(10)
=>#0 Foo#bar(a=10) at target.rb:5
#1 Object#some_method at target.rb:10
# and 1 frames (use `bt' command for all frames)
Stop by #0 BP - Method Foo#bar at target.rb:4 do: info
(rdbg:break) info
%self = #<Foo:0x00007fdac491c200>
a = 10
我是一名 Rails 开发人员,因此我通常将组合代码放在控制器操作的开头,例如:
class SomeController < ApplicationController
def index
binding.b(pre: "b User#buggy_method do: info")
# other code
end
end
然后,调试器就会执行命令并/或停在我预期的方法上。
我再也不用在多个文件之间跳转来添加binding.pry
或puts
删除文件了😎
一个小缺点
然而,新的调试器目前还不够完美。与byebug
或 不同pry
,您无法在调试会话中直接评估 Ruby 表达式:
(rdbg) 1 + 1
unknown command: 1 + 1
要评估表达式,您需要使用p
或pp
命令:
(rdbg) p 1 + 1
=> 2
但根据项目维护者@ko1的评论,表达式求值可能会在正式1.0
发布之前得到支持。
更新
随着https://github.com/ruby/debug/pull/227的合并,这个问题不再存在了
最后的想法
虽然它还没有正式发布,但我已经开始在日常工作中使用它了。我相信它很快就会成为每个 Ruby 程序员工具箱里的必备工具。所以,如果你对它的功能感兴趣,我鼓励你尝试一下 😉
文章来源:https://dev.to/st0012/a-sneak-peek-of-ruby-s-new-debugger-5caa