rir has asked for the wisdom of the Perl Monks concerning the following question:

For those of you who instrument your code with debug statements, how do you do it?
And why is your choice the best for you?

Are there any command line conventions for setting the debug level of multiple packages?

My interest relates to fair sized projects.

I plan to use code like the below. One issue is somewhat unsettled, the trade-off between having all the debugging code in the target module versus putting as much as possible in a Debug module. Since debugging code falls outside the domain of normal code normal standards may not apply. Does the encapsulation versus redundancy equation change?

In smaller projects I have often used a simple $DEBUG variable mostly as a boolean. When used as a bit field the only thing I've done that may be unusual is to have a TRASH level to enable the debugging code that is just too embarassing to admit to having used. So TRASH in my code is a weaker version of XXX. When things break in a way I do not expect I quickly get to a point of tossing anything into the debugging output; on review some of the checks seem ridiculous.

For those unfamiliar with XXX. It is an old C convention for marking bad code. I only learned of it at a distance so my usage may be deviant. In my code XXX indicates broken code, ideally there should be no XXX's in my released code. Wrote a stub, mark it with XXX; Algorithm in doubt, XXX; Screen layout deferred, XXX. Some programming editors recognize XXX as a syntactic element of languages.

debugging
Debugging in packages
Perl debugging - a look at 'beta' mechanism
Devel::TrackSub - Subroutine wrappers for debugging

Debugging Perl Program
debugging /logging help

package Off_hand_code; use Exporter; use Carp; our @ISA = "Exporter"; our @EXPORT = qw/ routine /; our @EXPORT_OK = qw/ set_debug /; our $debug; # current debug level # debugging level definitions use constant BUG => 1; use constant TRACE => 2; use constant ALL => 3; # Set the $debug variable, complain if new setting is # not an integer or is out of range (0 thru ALL). # Tri-nature function. # sub set_debug { $debug = shift; $debug = shift if ref($debug) || $debug eq __PACKAGE__; no warnings; carp qq/Unexpected debug level "$debug"./ if ( $debug < 0 || $debug > ALL ) || ($debug =~ /\D/ ); } # With no args return the current debugging level for package. # With an arg return 0 unless arg is part of current debugging level. # sub _debug { carp "Wrong number of args to ",__PACKAGE__,"::_debug" if @_ > 1; return $debug unless @_; return $_[0] if $_[0] & $debug; return 0; } # XXX set_debug_output( $output_method) # XXX debug( $level, $output_method, $error_message) # instead of Carp::.* etc. # a routine # sub routine { warn "debug message" if _debug && "something"; warn "trace message" if _debug(TRACE); }

Replies are listed 'Best First'.
Re: Instrumenting code for debugging.
by adrianh (Chancellor) on Mar 13, 2003 at 10:37 UTC

    This is somewhat personal style - but these are the levels I tend to migrate through.

    First of all I write all of my code test first. I moved over to this style a couple of years ago. I find it gives me a far better knowledge of my code state, so I spend far less time sprinkling debug statements around.

    The stuff that you mark with XXX I would mark by a failing test or a TODO test (the latter being an exceptionally neat feature of Perl's testing framework not found in the systems I have used in other languages).

    If I've got a quick question about what $foo is at some point in my code I'll just add:

    warn "foo is $foo";

    and delete it as soon as I have an answer to my question.

    I only do this when I'm sorting unit tests. Code with debug warnings in never hits the CVS tree.

    If I need to leave the debug statements in for a bit I stick a constant into the module.

    use constant DEBUG => 1; ... warn "foo is $foo" if DEBUG; warn "bar is $bar" if DEBUG > 2; ...

    Again, I only use this when I'm sorting unit tests. Code with warnings can hit the CVS tree, but only when DEBUG => 0.

    If I have bugs during integration and need to switch debugging info on and off in several modules I will use some variant of Poor man's logging.

    Anything more complex means that I'm in the realm of logging, rather than debugging so I'll use a proper logging module like Log::Log4Perl.

      I write all of my code test first. ... I find it gives me a far better knowledge of my code state, so I spend far less time sprinkling debug statements around.

      I find this also. Though I don't have it down to a habit yet. I may write test or code first. I also find testing to eliminate much debugging effort. I usually have an editor on the test file and the code file, write a little, test a little, and vice versa. If I know where I'm going I'm apt to write the test first. When I'm in cut and fit mode, writing tests first seems inefficient.

      Part of this is a prejudice toward testing small chunks of code, then writing tests for those pieces as a unit.

      I will consider using TODO tests, thanks adrianh. My XXX's mark the location of the bad code, and it is known to be bad. (That statement demonstrates that I have not internalized the XP attitude to testing.)

      It is more flexible to use bitfields instead of additive levels.

      Thanks Poor man's logging is one nice answer to my command line query.

      More and more, I lean towards leaving in all the debugging code. If I wanted to check warn "foo is $foo"; why wouldn't I want to leave that pointer for the next reader. There are times that the code is changed and a debug statement becomes superfluous. But in general it is like marking a trail for the next person.

        I find this also. Though I don't have it down to a habit yet. I may write test or code first. I also find testing to eliminate much debugging effort. I usually have an editor on the test file and the code file, write a little, test a little, and vice versa. If I know where I'm going I'm apt to write the test first. When I'm in cut and fit mode, writing tests first seems inefficient.

        I went through that stage too, but I now test first for everything. It really made a difference to my code quality. Give it a try for a couple of weeks. You'll like it (why do I feel like a pusher :-)

        When writing new code I always have three windows open. One for my test script. One for my module. One for my test output.

        I've a little script that runs my tests every time I change a file, so I just spend my development time switching between the test window and the code window. Write test. Hit save. Watch test fail. Write code. Hit save. Watch test pass. Warm fuzzies. Repeat until done. Fun.

        It is more flexible to use bitfields instead of additive levels.

        True. However I find if I need that level of flexibility for debug statements it's a sign that:

        • There is something seriously wrong with my code
        • I should be using a proper logging module.
        More and more, I lean towards leaving in all the debugging code. If I wanted to check warn "foo is $foo"; why wouldn't I want to leave that pointer for the next reader. There are times that the code is changed and a debug statement becomes superfluous. But in general it is like marking a trail for the next person.

        I personally don't find that's true. If the statements are there for debugging purposes (as opposed to logging) I find that they just clutter up the code. If the next person needs the debug statements to understand the code I would start thinking about refactoring the code to make it clearer.

        Something else I do more now is use things like Hook::LexWrap to add instrumentation so I don't have to alter the original code at all!

      Could you give a pointer to the testing framework you mentioned?
Re: Instrumenting code for debugging.
by dash2 (Hermit) on Mar 14, 2003 at 12:15 UTC
    If you want to keep debugging code in the production codebase, which may often be a good idea, I thought up a nifty idea, although I've never implemented it:

    sub debug ($) {0;} # constant subroutine gets inlined for speed sub set_debug { # set your options... no warnings; *{My::Package::debug}{SUB} = sub { # your debugging subroutine goes here } }

    The idea being that unless set_debug is called, your debug calls don't take up any time because they are inlined by the compiler. For this you do require (1) constant-length argument lists to your debug subroutine and (2) NOT using a debug 'object', because then (I think) subroutine argument signatures don't work.

    andramoiennepemousapolutropon