Re: Best practice for handling subroutines that are conditionally loaded?
by kcott (Archbishop) on Mar 08, 2024 at 16:13 UTC
|
Not really part of what you're asking; however, I'd question the naming of some of the symbols you're exporting.
Some general pointers:
-
Use more meaningful names.
-
dd() conflicts with Data::Dump's dd()
— a possible problem now or down the track.
-
Exporting variable names is not recommended.
See "Exporter: What Not to Export".
-
Consider using tags such that the export/import lists are controlled in one place;
i.e. you don't need to make changes to every script when developing/maintaining,
just to the module with the debug routines.
See "Exporter: Specialised Import Lists".
For your specific question, could you set up a Test::Utils::Dump::Noop
whose debug routines have the same names as those in Test::Utils::Dump but are NO-OPs.
Your test script(s) could then have something like:
use if $ENV{MYMOD_DEBUG_LEVEL}, 'Test::Utils::Dump' => ':debug';
use if ! $ENV{MYMOD_DEBUG_LEVEL}, 'Test::Utils::Dump::Noop' => ':debug
+';
| [reply] [d/l] [select] |
|
Thanks for the input. As far as meaningful names go, I just need something quick and dirty to type. And I type these so frequently, I'm not worried about forgetting them and I'm not sharing my debug module so I don't care. And I use a wrapper for Data::Dump in my debug module so I'm not worried about stomping on it. Besides, being able to break rules is what can make you a bad ass if you know what you are doing (or just an ass if you don't). :)
On point three, I didn't know exporting vars was a bad idea. I'll keep that in mind.
Point 4: yes, on my todo list.
Regarding solution, I went with the solution in the comment above which is super clean, just one line. But it basically works like your suggestion. Thanks.
| [reply] |
Re: Best practice for handling subroutines that are conditionally loaded?
by ikegami (Patriarch) on Mar 08, 2024 at 14:40 UTC
|
| [reply] |
Re: Best practice for handling subroutines that are conditionally loaded?
by etj (Priest) on Mar 09, 2024 at 08:21 UTC
|
An idiom I learned from an ingy module, almost certainly Pegex, is:
use constant DEBUG => $ENV{MY_DEBUG};
# ...
DEBUG and print "something\n";
The benefit is that constant-folding means that if it's not in debug mode, the Perl interpreter will simply optimise the debugging code out entirely. | [reply] [d/l] |
|
| [reply] |
Re: Best practice for handling subroutines that are conditionally loaded?
by SankoR (Prior) on Mar 08, 2024 at 14:42 UTC
|
It's early morning so I'm only coming up with awkward solutions but my brain says 'try UNIVERSAL::can.' You could do ... if __PACKAGE__->can('dd'), for example. This doesn't seem like a great idea though. I'd rather just call them and have them become no-op functions internally. You could turn them on and off at runtime that way as well. | [reply] [d/l] [select] |
Re: Best practice for handling subroutines that are conditionally loaded?
by LanX (Saint) on Mar 08, 2024 at 14:57 UTC
|
There must be information missing...
...if the tests are all at one place, why not put them inside the same conditional if ($ENV{MYMOD_DEBUG_LEVEL}){...} ?
Even better why don't you refactor the tests into a separate module which is used if ?
You could even put the essential use Module there as long as you run it inside the same package
| [reply] [d/l] [select] |
|
package Some::Useful::Module;
sub my_mod_func {
my ($self) = @_
dd $self;
}
So I want to conditionally load my debug code for this module.
| [reply] [d/l] |
|
Either unsprinkle them or generate the empty function stubs automatically inside a BEGIN block.¹
Having code all over the place calling "Schrödinger" functions doesn't strike me as a good design decision.
The next maintainer might lock you in a box with randomly activated poison. ;)
Update
¹) something like BEGIN { *{$_} = sub {} for @imports }
NB: you can reuse @imports for your use if to keep it DRY
| [reply] [d/l] [select] |
|
|
|
| [reply] |
Re: Best practice for handling subroutines that are conditionally loaded?
by Danny (Chaplain) on Mar 08, 2024 at 17:08 UTC
|
For each routine in Test::Utils::Dump couldn't you just do something like:
sub d0 {
if($ENV{MYMOD_DEBUG_LEVEL} >= $my_level) {
doThis;
} else {
doThat;
}
}
In general, it might help to name your routines with a common tag so they are easy to find. For example my_debug_d0. | [reply] [d/l] |
Re: Best practice for handling subroutines that are conditionally loaded?
by nysus (Parson) on Mar 08, 2024 at 15:55 UTC
|
With a little help from my AI friend and after holding their hand a bit, I got this:
package Test::Utils::Dump::Load;
use v5.10;
use strict;
use warnings;
use Exporter 'import';
our @EXPORT = qw(d d0 d1 dd di);
my $debug = $ENV{MYMOD_DEBUG_LEVEL};
sub d { _load_and_call('d', @_) if $debug }
sub d0 { _load_and_call('d0', @_) if $debug }
sub d1 { _load_and_call('d1', @_) if $debug }
sub dd { _load_and_call('dd', @_) if $debug }
sub di { _load_and_call('di', @_) if $debug }
sub _load_and_call {
my $sub_name = shift;
state $imported = 0;
if (!$imported) {
require Test::Utils::Dump;
Test::Utils::Dump->import(qw(d d0 d1 dd di));
$imported = 1;
}
no strict 'refs'; ## no critic
&{"Test::Utils::Dump::$sub_name"}(@_);
}
1;
So now in my module with debug code I can just add this one line:
use Test::Utils::Dump::Load;
| [reply] [d/l] [select] |
|
package Log::Maybe {
use strict;
use warnings;
use parent 'Exporter';
our %EXPORT_TAGS = ( all => [ our @EXPORT = qw[info debug debugf $
+LOG_LEVEL] ] ); # Don't do this...
our $LOG_LEVEL = $ENV{LOG_MAYBE};
+ # 0: off, 1: debug, 2: info
use Data::Dump qw[];
use Carp;
$Carp::CarpLevel = 1;
sub info {
return 2 unless @_;
return unless $LOG_LEVEL >= 2;
Carp::cluck join ' ', map { ref $_ ? Data::Dump::dump($_) : $_
+ } @_;
}
sub debug {
return 1 unless @_;
return unless $LOG_LEVEL >= 1;
Carp::cluck '[' . localtime . '] ' . join ' ', map { ref $_ ?
+Data::Dump::dump($_) : $_ } @_;
}
sub debugf {
return 1 unless @_;
return unless $LOG_LEVEL >= 1;
Carp::cluck '[' . localtime . '] ' . sprintf shift, map { ref
+$_ ? Data::Dump::dump($_) : $_ } @_;
}
};
1;
Which is used like so:
use strict;
use warnings;
use lib './lib';
use Log::Maybe; # uses $ENV var
$LOG_LEVEL = debug; # enable/change log leve
+l
info('oh, yeah, baby!'); # prints only if log lev
+el is info
debug('hi'); # prints if log level is
+ debug or info
debug( 'wow', \%ENV ); # dumps non-scalars
debugf( 'name: %s, age: %d', 'Jack', 23 ); # dumpf takes a sprintf
+form
$LOG_LEVEL = 0; # disable logging at run
+time
debug('nothing is logged'); # what it says on the ti
+n
I was feeling clever so the debug function gives you a stack trace. info and debug can also be used to return values used by $LOG_LEVEL. This is simple enough but something like Log::Any would be a wise choice here.
Edit: Added debugf as an example of things you could do. | [reply] [d/l] [select] |
|
> > With a little help from my AI friend
(Well honestly 🤮)
I recently saw a good metaphor for AI code.
Someone used AI to create fake images of Trump surrounded by "happy" black voters for campaign reasons.¹
Alas one pic had fantasy letters on the base caps, on the other pic Trump was missing fingers on one hand.
Human perception might accept these, but computers won't correct fantasy code.
Looking forward to the day the GOP nominates an AI for president, which nukes countries based on statistical grounds because of ... covfefe?
¹) https://www.bbc.com/news/world-us-canada-68440150
| [reply] |
|
|
|
This looks overly convoluted to me.
Just write an import (Don't necessarily need Exporter for that) routine which conditionally decides
what to export at compile time.
If you don't want to wire the ENV flag into the original module, just pass it's state as a flag while use -ing.
I'm reluctant to provide code since your requirements are "drifting". (And you're friends with better qualified AI anyway)
| [reply] |