编辑:我稍微改变了规格,以更好地匹配我想象的那样做.
好吧,我真的不想伪造C#属性,我想要一次性并支持AOP.
鉴于该计划:
class Object
def Object.profile
# magic code here
end
end
class Foo
# This is the fake attribute, it profiles a single method.
profile
def bar(b)
puts b
end
def barbar(b)
puts(b)
end
comment("this really should be fixed")
def snafu(b)
end
end
Foo.new.bar("test")
Foo.new.barbar("test")
puts Foo.get_comment(:snafu)
Run Code Online (Sandbox Code Playgroud)
期望的输出:
Foo.bar was called with param: b = "test" test Foo.bar call finished, duration was 1ms test This really should be fixed
有没有办法实现这个目标?
Yeh*_*atz 11
我有一个不同的方法:
class Object
def self.profile(method_name)
return_value = nil
time = Benchmark.measure do
return_value = yield
end
puts "#{method_name} finished in #{time.real}"
return_value
end
end
require "benchmark"
module Profiler
def method_added(name)
profile_method(name) if @method_profiled
super
end
def profile_method(method_name)
@method_profiled = nil
alias_method "unprofiled_#{method_name}", method_name
class_eval <<-ruby_eval
def #{method_name}(*args, &blk)
name = "\#{self.class}##{method_name}"
msg = "\#{name} was called with \#{args.inspect}"
msg << " and a block" if block_given?
puts msg
Object.profile(name) { unprofiled_#{method_name}(*args, &blk) }
end
ruby_eval
end
def profile
@method_profiled = true
end
end
module Comment
def method_added(name)
comment_method(name) if @method_commented
super
end
def comment_method(method_name)
comment = @method_commented
@method_commented = nil
alias_method "uncommented_#{method_name}", method_name
class_eval <<-ruby_eval
def #{method_name}(*args, &blk)
puts #{comment.inspect}
uncommented_#{method_name}(*args, &blk)
end
ruby_eval
end
def comment(text)
@method_commented = text
end
end
class Foo
extend Profiler
extend Comment
# This is the fake attribute, it profiles a single method.
profile
def bar(b)
puts b
end
def barbar(b)
puts(b)
end
comment("this really should be fixed")
def snafu(b)
end
end
Run Code Online (Sandbox Code Playgroud)
关于这个解决方案的几点:
method_added),而不需要别名.class_eval而不是define_method定义新方法,以便能够支持带块的方法.这也需要使用alias_method.profile或comment被调用,当加入的方法中,然后施用.与前面的解决方案一样,method_added钩子用于跟踪何时添加新方法,但钩子不是每次都删除钩子,而是检查实例变量.应用AOP后将删除实例变量,因此它仅应用一次.如果多次使用同样的技术,可以进一步抽象.好问题.这是我对实现的快速尝试(我没有尝试优化代码).我冒昧地把这个profile方法添加到
Module课堂上.通过这种方式,它将在每个类和模块定义中可用.将它提取到模块中并Module在需要时将其混合到类中会更好.
我也不知道是不是要让profile方法表现得像Ruby的public/ protected/ private关键字,但无论如何我都是这样实现的.调用后定义的所有方法profile都被分析,直到noprofile被调用.
class Module
def profile
require "benchmark"
@profiled_methods ||= []
class << self
# Save any original method_added callback.
alias_method :__unprofiling_method_added, :method_added
# Create new callback.
def method_added(method)
# Possible infinite loop if we do not check if we already replaced this method.
unless @profiled_methods.include?(method)
@profiled_methods << method
unbound_method = instance_method(method)
define_method(method) do |*args|
puts "#{self.class}##{method} was called with params #{args.join(", ")}"
bench = Benchmark.measure do
unbound_method.bind(self).call(*args)
end
puts "#{self.class}##{method} finished in %.5fs" % bench.real
end
# Call the original callback too.
__unprofiling_method_added(method)
end
end
end
end
def noprofile # What's the opposite of profile?
class << self
# Remove profiling callback and restore previous one.
alias_method :method_added, :__unprofiling_method_added
end
end
end
Run Code Online (Sandbox Code Playgroud)
您现在可以按如下方式使用它:
class Foo
def self.method_added(method) # This still works.
puts "Method '#{method}' has been added to '#{self}'."
end
profile
def foo(arg1, arg2, arg3 = nil)
puts "> body of foo"
sleep 1
end
def bar(arg)
puts "> body of bar"
end
noprofile
def baz(arg)
puts "> body of baz"
end
end
Run Code Online (Sandbox Code Playgroud)
像往常一样调用方法:
foo = Foo.new
foo.foo(1, 2, 3)
foo.bar(2)
foo.baz(3)
Run Code Online (Sandbox Code Playgroud)
并获得基准测试输出(以及原始method_added回调的结果只是为了表明它仍然有效):
Method 'foo' has been added to 'Foo'.
Method 'bar' has been added to 'Foo'.
Method 'baz' has been added to 'Foo'.
Foo#foo was called with params 1, 2, 3
> body of foo
Foo#foo finished in 1.00018s
Foo#bar was called with params 2
> body of bar
Foo#bar finished in 0.00016s
> body of baz
Run Code Online (Sandbox Code Playgroud)
需要注意的一点是,使用Ruby元编程动态获取参数名称是不可能的.你必须解析原始的Ruby文件,这肯定是可能的,但有点复杂.有关 详细信息,请参阅parse_tree和ruby_parser gems.
一个有趣的改进是能够使用类中的Module类方法定义这种行为.能够做类似的事情会很酷:
class Module
method_wrapper :profile do |*arguments|
# Do something before calling method.
yield *arguments # Call original method.
# Do something afterwards.
end
end
Run Code Online (Sandbox Code Playgroud)
我将把这个元元编程练习再次留下.:-)