2010-02-08 29 views
28

我已经继承了大量的Ruby代码,坦率地说,对于像我这样的凡人来说,几乎无法理解。它实际上是Rspec单元测试代码,但是它的结构“很不寻常”可以很好地表达出来。如何记录每个在Ruby程序中调用的方法?

想我能够做的就是运行代码,并已经某处记录以下信息:

  • 每一个被调用,包括定义该方法的类的名称的方法,和已定义调用方法的文件名(也就是说,我们已经在多个不同文件中定义了相同的类/方法,并且很难知道哪个被调用)
  • (可选)传递给每个方法的参数被调用

With那我可以开始尝试重构它。没有它,由于代码库的大小(20k +单元测试用例),将它变成一个非常困难的任务。

我不能进入并对正在运行的代码执行批量编辑,因为当您甚至使用粗糙的语言(即频繁)时它会中断。相反,我需要能够在现有状态下对代码进行测试,或者对现有的代码进行最小限度的更改。

有没有一种方法可以记录此级别的细节而不对代码库进行大规模更改?我看了一下Ruby探查器,看看它是否有帮助,可能会;我很好奇,如果有更好的方法(尤其是记录包含被调用方法的文件名)。

在此先感谢

+0

你有没有考虑一个静态的分析,还是你只是寻找一个实际运行代码的东西吗? Doxygen输出一些很好的调用者/被调用者图形,并没有看到它是否支持Ruby,但调用图形可以证明对理解现有代码非常有用。 – 2010-02-08 04:15:23

+0

我相当确信Doxygen不支持Ruby - 如果确实如此,它肯定对我有用,但我找不到任何有关支持Ruby的Doxygen的信息。 我宁愿有一些实际运行代码的东西,主要是因为处理require需求的顺序会影响给定方法的多个(不同的)定义中的哪一个将被使用。 正如我所说,这是一个丑陋的代码库... – monch1962 2010-02-08 04:24:23

回答

58

这绝对是可能的 - 事实上,甚至有一种方法!

set_trace_func proc { |event, file, line, id, binding, classname| 
    printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname 
} 

你想要的秘密武器来自Kernel#set_trace_func,正如上面所指出的:就在你的代码要开始记录的东西点之前添加这个地方

  • set_trace_func( PROC)=> PROC
  • set_trace_func(无)=>无

proc设置为跟踪处理程序,如果参数为nil,则禁用跟踪。 proc最多需要六个参数:事件名称,文件名,行号,对象ID,绑定和类名称。每当发生事件时都会调用proc。事件是:c-call(调用C语言例程),c-return(从C语言例程返回),call(调用Ruby方法),class(启动类或模块定义),end(完成类或模块定义),line(在新行上执行代码),raise(引发异常)和return(从Ruby方法返回)。跟踪在proc的上下文中被禁用。

这里有一个简便的事例:

class Test 
    def test 
    a = 1 
    b = 2 
    end 
end 

set_trace_func proc { |event, file, line, id, binding, classname| 
    printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname 
} 

t = Test.new 
t.test 

(注意:除非你想文本的一个巨大的滚动屏幕不要尝试这种在irb)输出结果是:

line test.rb:11    false 
    c-call test.rb:11  new Class 
    c-call test.rb:11 initialize Object 
c-return test.rb:11 initialize Object 
c-return test.rb:11  new Class 
    line test.rb:12    false 
    call test.rb:2  test  Test 
    line test.rb:3  test  Test 
    line test.rb:4  test  Test 
    return test.rb:4  test  Test 

你可以使用上面的格式化字符串来获得你想要记录的结果(例如,听起来你只对call事件感兴趣)。希望有所帮助,并祝你能够在所有单元测试中排序!

+0

哇 - 这正是我所希望的。谢谢约翰! – monch1962 2010-02-08 04:36:51

+0

没问题。对于这种瑞士军刀来说,Ruby变得相当方便。 – 2010-02-08 04:48:32

+1

+1为IRB警告。 – 2010-12-06 23:29:01

4

我想包括秒,过去一分钟的情况下,在happend以及多久每个功能

start = DateTime.now.strftime('%Q').to_i/1000.0 
set_trace_func proc { |event, file, line, id, binding, classname| 
    now_ms = DateTime.now.strftime('%Q').to_i/1000.0 
    duration = '%.3f' % (now_ms - start) 
    start = DateTime.now.strftime('%Q').to_i/1000.0 
    printf "%s %s %8s %s:%-2d %10s %8s\n", DateTime.now.strftime("%S.%L"), duration, event, file, line, id, classname 
} 

AdminUser.create(password: "password", password_confirmation: "password", email: email) 

set_trace_func nil 

我试图调试为什么花了这么长时间来创建用户花在并登录到ActiveAdmin。

05.761 0.000 c-return /Users/nperry/.rvm/gems/[email protected]/gems/bcrypt-3.1.7/lib/bcrypt/engine.rb:51  to_s String 
05.761 0.000 c-call /Users/nperry/.rvm/gems/[email protected]/gems/bcrypt-3.1.7/lib/bcrypt/engine.rb:51 __bc_crypt BCrypt::Engine 
09.736 63.975 c-return /Users/nperry/.rvm/gems/[email protected]/gems/bcrypt-3.1.7/lib/bcrypt/engine.rb:51 __bc_crypt BCrypt::Engine 
09.736 0.000 return /Users/nperry/.rvm/gems/[email protected]/gems/bcrypt-3.1.7/lib/bcrypt/engine.rb:59 hash_secret BCrypt::Engine 
09.736 0.000 c-call /Users/nperry/.rvm/gems/[email protected]/gems/bcrypt-3.1.7/lib/bcrypt/password.rb:46  new Class 

从那我知道Ruby花了一分多钟在__bc_crypt

+1

听起来像你有一个相当高的Bcrypt“成本”。 T.J. Shuck有一个很好的视频解释了这个(http://rubyvideos.com/presenters/t-j-schuck)(前15分钟左右)。 – jwadsack 2015-07-30 20:02:05

+0

我相信我们当时确实解决了这个问题,问题肯定是Bcrypt。 – Nate 2015-07-31 14:11:16

3

近日,set_trace_func已被否决:

注:此方法已过时,请使用跟踪点来代替。

我们可以利用跟踪点,它备份set_trace_func,而不是:

trace = TracePoint.new(:call) do |tp| 
    puts "#{tp.defined_class}##{tp.method_id} got called (#{tp.path}:#{tp.lineno})" 
end 

trace.enable 
# do stuff here 
trace.disable 

其实,这甚至比set_trace_func更强大,因为您可以启用和在您方便的禁用。你可以有选择地挂接到以下事件::line, :class, :end, :call, :return, :c_call, :c_return, :raise, :b_call, :b_return, :thread_begin, :thread_end

下面一个完整的例子:

class MyClass 
    def initialize 
    end 
    def y 
    z 
    end 
    def z 
    1 + 1 
    end 
end 

trace = TracePoint.new(:call) do |tp| 
    puts "#{tp.defined_class}##{tp.method_id} got called (#{tp.path}:#{tp.lineno})" 
end 

trace.enable # note 
MyClass.new.y 
trace.disable 
    # MyClass#initialize got called (./trace.rb:4) 
    # MyClass#y got called (./trace.rb:7) 
    # MyClass#z got called (./trace.rb:10) 
相关问题