Думаю, любой программист на Perl довольно регулярно добавляет в программу вспомогательный код, который не должен выполняться всегда. Это может быть отладочный код, сбор статистики о скорости работы разных частей кода, вывод в лог, и т.д. С этим кодом связано сразу несколько проблем:
- Он мешает читать основной код.Он замедляет выполнение программы.Из-за первых двух причин его зачастую удаляют, как только необходимость в нём пропадает… только, к сожалению, необходимость в нём регулярно возникает снова, и этот код, матерясь, снова пишут… чтобы через несколько часов снова удалить.Борьба с первой проблемой, как правило, обречена на неудачу. Ибо если код должен выполняться, то он должен быть написан. А если он написан, то он царапает глаза, разрывает основной код, раздувает код, отвлекает и раздражает. Решить эту проблему, как правило, удаётся только тогда, когда этот код должен быть написан в самом начале и/или конце функции — тогда можно автоматически сгенерировать функцию-обёртку, которая спрячет внутри себя этот код.
А вот со второй проблемой бороться можно вполне успешно:
warn "i=$i\n" if $DEBUG;
В этом случае потери производительности когда$DEBUG==0
ограничиваются проверкой одного скаляра, т.е. фактически код получается такой:
0;
К сожалению, это решение приводит к тому, что «лишний» код становится ещё больше: в каждой такой строке добавляетсяif $DEBUG
. С этим пытаются иногда бороться, перенося этотif
внутрь вызываемой функции:
sub log { return if !$DEBUG; warn "$_[0]\n"; }
log("i=$i");
К сожалению, производительность при этом падаёт значительно сильнее, т.к. к проверке скаляра добавляется ещё и вход/выход в функцию. А поскольку смысл существования$DEBUG
в том, чтобы производительность не падала, то этот способ обычно не пользуется популярностью.
Ещё один вариант — использование технологии source filters. С её помощью можно изменить код программы перед тем, как perl начнёт её компилировать и выполнять. Изменить как любой другой текст, обычно с помощью регулярных выражений. Например, код превратить в комментарии, либо наоборот — это позволит полностью избежать замедления программы (например, посмотрите модуль Smart::Comments). Первая проблема этого подхода в том, что синтаксис языка Perl очень сложный, а регулярные выражения использующиеся для модификации кода программы (обычно довольно простые) иногда ошибаются… и искать и исправлять такие баги довольно сложно. Вторая проблема заключается в том, что появляется ощущение потери контроля над своим кодом — вы уже не знаете точно, какой именно код выполняется, т.к. ваш код был как-то модифицирован. Парадокс, но вторая проблема полностью сводит на нет смысл существования таких модулей — ведь они изначально появились чтобы облегчить работу с кодом убрав из него «лишние» команды, а в результате работа с кодом наоборот, усложнилась.
Я хочу предложить вашему вниманию другой способ решения этой проблемы:
BEGIN {
*log = $ENV{DEBUG} ? sub {warn "@_"} : sub () {0};
}
log+ "data", 123;
Идея в том, что при отключенном отладочном режиме функцияlog
определяется как константа, а функции-константы perl оптимизирует и просто подставляет в код их значения вместо того, чтобы их вызывать. (КодBEGIN { *log = sub () {0} }
идентичен кодуuse constant log => 0;
.) В результате реальный код, который выполнит perl будет:
log( +"data", 123 ); # if $ENV{DEBUG}
0 + "data", 123; # if !$ENV{DEBUG}
В результате отif $DEBUG
мы избавились, и функция не вызывается, когда отладочный режим выключен. (А когда отладочный режим включен — лишний унарный плюс никак испортить первый аргумент функции не должен.)
К сожалению, у этого изврата есть два побочных эффекта. Первый — use warnings ругается на попытки сложения не-чисел. Второй — этот способ всё же немного медленнее варианта сif $DEBUG
, т.к. аргументы функции log вычисляются даже в том случае, когда отладочный режим отключен.
Впрочем, несмотря на недостатки, этот способ имеет право на существование, и, возможно, будет кому нибудь полезен. К тому же, придумал я его всего пару часов назад, и возможно его ещё удастся развить и избавить от недостатков.