Существует, в последнем стандарте, ключевое слово (export
), которое помогло бы облегчить эту проблему, но это не реализовано ни в каком компиляторе, о котором я знаю кроме Comeau.
Посмотрите облегченный FAQ об этом.
У меня несколько иной подход:
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
Несколько замечаний по поводу этого решения:
method_added
) без необходимости использования псевдонимов. class_eval
вместо define_method
для определения нового метода, чтобы иметь возможность поддерживать методы, которые принимают блоки. Это также потребовало использования alias_method
.
или вызывается комментарий
, который затем применяется при добавлении метода. Как и в предыдущем решении, ловушка method_added
используется для отслеживания добавления нового метода, но вместо того, чтобы каждый раз удалять ловушку, ловушка проверяет наличие переменной экземпляра. Переменная экземпляра удаляется после применения АОП, поэтому применяется только один раз. Если этот же метод использовался несколько раз, его можно было бы далее абстрагировать. Отличный вопрос. Это моя быстрая попытка реализации (я не пытался оптимизировать код). Я взял на себя смелость добавить метод профиля
в
Модуль
класс. Таким образом, он будет доступен в каждом определении класса и модуля. Было бы даже лучше
чтобы извлечь его в модуль и смешать с классом Module
всякий раз, когда он вам понадобится.
Я также не знал, был ли смысл в том, чтобы заставить метод профиля
вести себя как общедоступные
/ защищенные
/ частные
ключевые слова Ruby,
но я все равно так реализовал. Все методы, определенные после вызова 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
Теперь вы можете использовать его следующим образом:
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
Вызовите методы, как обычно:
foo = Foo.new
foo.foo(1, 2, 3)
foo.bar(2)
foo.baz(3)
И получить результаты тестирования (и результат исходного 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
Следует отметить, что невозможно динамически получить имя аргументов с помощью Мета-программирование на Ruby. Вам нужно будет проанализировать исходный файл Ruby, что, безусловно, возможно, но немного сложнее. См. parse_tree и ruby_parser драгоценные камни для деталей.
Забавным улучшением была бы возможность определять такое поведение с помощью метода класса в классе Module
. Было бы здорово иметь возможность сделать что-нибудь вроде:
class Module
method_wrapper :profile do |*arguments|
# Do something before calling method.
yield *arguments # Call original method.
# Do something afterwards.
end
end
Я оставлю это упражнение по мета-мета-программированию на другой раз. : -)