Как определить источник LocalJumpError?
Вопрос
Как я могу легко и программно определить, является ли LocalJumpError
возникло из-за немедленной неспособности вызывающей стороны предоставить необходимый блок методу или из-за более глубоких глубин этого метода и других, которые он вызывает?
Под «легко» я имею в виду, что мне бы хотелось избежать проверки/регулярного выражения строк в $!.backtrace
.Решение, применимое к версиям 1.8 и 1.9, также является предпочтительным.
Мотивация: Когда я неудачно вызываю метод в Ruby, это обычно происходит из-за того, что я неправильно напечатал метод (NoMethodError
), неправильно определил количество аргументов (ArgumentError
) или забыли передать необходимый блок (LocalJumpError
).
Для проксирования или украшения объекта-обертки в Ruby я хотел бы различать эти ошибки вызывающего абонента или API из ошибки реализации или среды это может привести к тем же классам ошибок.Например:
...
def method_missing(sym, *args, &block)
@wrapped.__send__(sym, *args, &block)
rescue NoMethodError
raise MyApp::BadInvocation, "duh - no such method" unless @wrapped.respond_to?(sym)
raise
rescue ArgumentError
raise MyApp::BadInvocation, "duh - wrong arg count" \
unless _args_fit_arity?(@wrapped.method(sym), args)
raise
rescue LocalJumpError
# XXX - what is the test?
raise
end
Решение
Чтобы выяснить, была ли ошибка LocalJumpError вызвана тем, что пользователь забыл передать блок, вам нужно знать две вещи:Предоставил ли пользователь блок и нужен ли блок методу.Первое просто:просто проверьте, равен ли blk нулю.Однако второе невозможно (по крайней мере, в простом рубине).
Поэтому я думаю, что лучше всего вам подойдет анализ трассировки стека.
Другие советы
Можно изучить относительная глубина backtrace
чтобы приложить все усилия, чтобы отличить ошибку вызывающего абонента от последующей ошибки, расположенной глубже в стеке вызовов:
def lje_depth_from_send
Class.new { def lje; yield end }.new.__send__ :lje
rescue LocalJumpError
return $!.backtrace.size - caller(0).size
end
def method_missing(sym, *args, &block)
...
rescue LocalJumpError
if !block_given? and ($!.backtrace.size - caller(0).size) == lje_depth_from_send
raise MyApp::BadInvocation, "duh - you forgot to supply a block"
end
raise
end
Интересно, что этот расчет относительной глубины меняется с MRI 1,8 на MRI 1,9 - прежний трек отправлять, последний, кажется, молча опускает это (аля Perl goto &sub
, возможно?), например.(В версии 1.9 обратная трассировка LJE более мелкий чем caller(0)
стек, потому что 1.9 явно учитывает rescue
блок как дискретный кадр стека).
Возможно, это не сработает и без MRI, но я сомневаюсь, что анализ стека вызовов можно будет переносить из одной реализации в другую.