http://qs321.pair.com?node_id=304404

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

(This might be more appropriate as a Meditation, or a Perl Gripe, if this site had such a section. But maybe someone has some Perl Wisdom to offer that could help me work around this issue...)

<GRUMP>

I ran into an annoying problem today, again.

Whenever I code, I always use strict, -w, and code defensively. I try to test as much as I can, and I always make sure at a minimum that code is -c clean after any editing pass.

Great.

But I still get bit, on occasion, by typos in function names.

The only way you'll find out that there's a typo in

sub handleNewYear { # do something really useful } # ... lotsa logic... if($mday==1 && $month==1) { handelNewYear(); }
is at run time, when the app croaks because "Undefined subrouting &main::handelNewYear called at...", and only if you happen to excercise that branch of your code.

If it happens to be your new year's day special case handling logic, I guess you'll get beeped out of the party :)

I realize there's no way -w could catch this; there could be autoloads providing the subroutine, or it could have been dynamically generated at runtime in an eval, or something.

But it would be nice if there was a way to catch such cases, without having to write a test suite that exercises every code branch, which isn't always (or even usually) possible.

</GRUMP>


Mike

Replies are listed 'Best First'.
•Re: When -w and use strict aren't enough...
by merlyn (Sage) on Nov 04, 2003 at 13:43 UTC
    without having to write a test suite that exercises every code branch, which isn't always (or even usually) possible.
    Quite the contrary. In a well-maintained system, a test suite that tests every code branch is mandatory. And as they say, "you can pay me now, or you can pay me later" with respect to testing.

    Your code should be designed so that testing every branch is possible. If you're writing code that's hard to test, you can't test it, and therefore it's quite possible that some bug will show up in the future. Bad code, Bad code, what you gonna do?

    Read Test::Tutorial. Learn how to write tests, and you'll gradually start writing code that can always be tested.

    -- Randal L. Schwartz, Perl hacker
    Be sure to read my standard disclaimer if this is a reply.

      What tutorial you are talking about? I've not found anything on CPAN except "Here should be extensive documentation on what unit testing is, why it is useful, and how to do it with the Test::Unit collection of modules. Sorry for not implementing this yet." :(
      http://search.cpan.org/~aspiers/Test-Unit-0.24/lib/Test/Unit/Tutorial.pm

      Please post the full URL.

      --dda

Re: When -w and use strict aren't enough...
by Abigail-II (Bishop) on Nov 04, 2003 at 14:21 UTC
    If you leave the parenthesis off, like this:
    if ($mday == 1 && $month == 1) { handelNewYear; # Typo in function name. }
    then the code won't compile under 'use strict'.

    Leaving parens off isn't always possible without writing uncommon idiom, but in many cases you can leave the parens off, and it'll capture many mistyped sub names.

    Abigail

      Very cool, thanks Abigail-II.

      Exactly the kind of wisdom I was hoping for :)


      Mike
Re: When -w and use strict aren't enough...
by Ovid (Cardinal) on Nov 04, 2003 at 14:43 UTC

    Just as a new Perl programmer might get tired of hearing "use strict" or "use CGI", you're going to get tired of hearing "write tests" if you have a problem which testing tends to solve. Use Devel::Cover to develop a code coverage profile and you have a good start on writing your tests. I'll even help:

    use strict; use warnings; use Test::More 'no_plan'; my $module = 'Foo.pm'; use_ok($module) or die; can_ok($module, 'handleNewYear');

    Then, just fill in "can_ok" stubs for your functions. As you uncover bugs, write a test that exploits them. It's not quite the same as test driven development, but I'm scratching my head trying to think of ways to get people to test who otherwise wouldn't :)

    Cheers,
    Ovid

    New address of my CGI Course.

Re: When -w and use strict aren't enough...
by adrianh (Chancellor) on Nov 04, 2003 at 13:55 UTC
    But it would be nice if there was a way to catch such cases...

    Well, you've answered your own question when you said:

    "there could be autoloads providing the subroutine, or it could have been dynamically generated at runtime in an eval, or something. "

    The flexibility of Perl comes with a price. This is one of them.

    However, I would strongly disagree when you said:

    without having to write a test suite that exercises every code branch, which isn't always (or even usually) possible.

    If you can't test your code then something is wrong with it. Fix the design so you can test it. If you have problems doing this, then post a SOPW - I'm sure somebody could help ;-)

    Seriously, 100% test coverage is an attainable goal with surprisingly little effort.

      You can say that it has been the price, but I see a good niche for adding the ability to turn on stronger checking in this area.

      Even with subs dynamically generated at runtime or handled via AUTOLOAD, offhand I can think of no case where there couldn't be at least a declaration at hand at compile time. It is Unfortunate that sub declarations aren't common enough in CPAN modules to allow this to be a default check.

        Of course it is technically possible to see if an appropriate entry exists in the symbol table at runtime. However, forcing everybody to add declarations for any runtime-generated subroutines strikes me as both un-perlish and extremely unlikely to happen.

Re: When -w and use strict aren't enough...
by pg (Canon) on Nov 04, 2003 at 17:00 UTC

    So as merly and everyone else have pointed out, at the end of the day, you have to have test cases cover all braches. Brach could mean different things for different testing phases. For the unit testing that you should do, branch means each logic branch of all your scripts.

    On the other hand, I agree that it would be nice, if Perl can help you to avoid some of those pitfalls.

    Now, how many times have you run into nasty problems by misspelling your hash keys? (In this case -w and strict does not help) But in this in this case Perl provides a great tool: Hash::Util:

    foo.pm: package foo; use Hash::Util; use strict; use warnings; sub new { my $self = {}; $self->{XBCDE} = 1; $self->{AXCDE} = 2; $self->{ABXDE} = 3; $self->{ABCXE} = 4; $self->{ABCDX} = 5; bless($self); Hash::Util::lock_keys(%{$self}); return $self; } sub bar_1 { my $self = shift; $self->{AXCDE} = 10; } sub bar_2 { my $self = shift; print $self->{AXCDE}; } sub bar_3 { my $self = shift; $self->{XXXXX} = 10; } sub bar_4 { my $self = shift; print $self->{XXXXX}; } 1; foo.pl: use foo; use strict; use warnings; my $foo = new foo; $foo->bar_1; #okay as the key exists $foo->bar_2; #okay as the key exists $foo->bar_3; #no good, comment out this, and try for the second time $foo->bar_4; #no good, comment out this, and try for the third time
Re: When -w and use strict aren't enough...
by tilly (Archbishop) on Nov 04, 2003 at 22:19 UTC
    There is a way to do what you want, but you probably don't want to do it. Here goes:
    my $handleNewYear = sub { # do something really useful }; # ... lotsa logic... if (1==$mday && 1==$month) { $handelNewYear->(); }
    All that I have done is stored the subroutine in a variable. Do that with all of your subroutines and all of your typos in subroutines become typos in variable names which strict.pm catches.

    You may note that I also reversed the order of your == comparisons. The reason for doing that is so that if you type = instead of == some day, Perl will complain because you can't assign to a constant. This is a useful habit in many C-like languages.

      I read this thread earlier today and I have to admit that I skimmed over the bit about == comparisons and then just now I was reading about the back door attempt on the Linux kernel and way down there in the comments (sorry I can't see a way to link directly to it) are two comments by "legobuff" and "Mr_Z" on the exact same topic. A real world example how a simple change in coding practice can potentially make a profound difference (presuming of course some-one writes the auditing tools). ++tilly its a good meme to spread.

      --
      Do not seek to follow in the footsteps of the wise. Seek what they sought. -Basho

      This intrigues me.
      but
      Why do you say "Probably don't want to..."?
      because
      "it's too much work"?
      or because
      "it's dangerous/bad/ugly"?
        Don't know, but one downside (upside?) is that your functions are no longer accessible from outside the package. That can easily be fixed by doing one of the following for exportable functions:
        *handleNewYear = my $handleNewYear = sub { # do something really useful };
        sub handleNewYear { # do something really useful } my $handleNewYear = \&handleNewYear;
        Because it is ugly. And will be sure to generate negative comments from any maintainance programmer. And cannot be mixed with OO techniques.

        Other than that, it is a veru good idea. But its goodness is simply not worth the arguments that you're bound to generate.

Re: When -w and use strict aren't enough...
by fletcher_the_dog (Friar) on Nov 04, 2003 at 18:40 UTC
    Even if there wasn't AUTOLOAD and eval, perl's loose typing makes it impossible to check method names at compile time. Consider this snippet:
    sub foo{ my $thing = shift; print $thing->to_xml; }
    $thing could be anything, there is no way at compile time for perl to know if $thing has a "to_xml" method
Re: When -w and use strict aren't enough...
by bl0rf (Pilgrim) on Nov 05, 2003 at 01:16 UTC
    I know not all the intricacies of Perl but I will
    suggest this:

    Snarf in your script from another script. The second
    script will declare any sub it comes across:

    if( $line =~ m!sub\s+(\S+){ eval "sub $1 {}" }
    Then the second script will store every sub called
    in an array, it will check %main:: and CORE:: to see
    whether the subroutines have been, or will be, declared
    at any point in your original script.

    I hope my advice helps.
    -Jacob
    Eat Perl, live well.
    My site

Re: When -w and use strict aren't enough...
by dada (Chaplain) on Nov 06, 2003 at 12:18 UTC
    RMGir, meet B::Xref. B::Xref, meet RMGir :-)

    I've published elsewhere a little script which parses the output of B::Xref to catch unused subs. if you just change the last four lines to:

    foreach my $sub (keys %subused) { next if exists $subdef{ $sub }; print "UNDEFINED SUB: $subused{ $sub } $sub\n"; }
    you have something that will catch your handelNewYear error :-)

    cheers,
    Aldo

    King of Laziness, Wizard of Impatience, Lord of Hubris

      Thanks!

      I think both the unused and undeclared options look pretty useful.

      Your post is a reply to Warnings for unused subs, an interesting thred I'd missed.


      Mike

      PS: I liked that side trip to Re: Re: ESMTP, linefeed and <CRLF> before you fixed your link; it made me wonder if I'd accidentally gotten decaf this morning :)

Re: When -w and use strict aren't enough...
by graff (Chancellor) on Nov 17, 2003 at 04:58 UTC
    A while back, someone posted an SoPW about having trouble maintaining someone else's complicated perl app (lots of separate perl files, lots of subs, etc), and it lead me to write and post this simple-minded tool, based on an idea that had worked pretty well for me when I had the same problem handling packages in C: scan all the source code files and build a listing that shows where subs are declared, and where they're called.

    Of course, Perl is a bit tougher to parse than C, so my perl version of the tool is "less deterministic"... Still, I wonder if something like this might help in your case, at least to give a decent starting point for the diagnostics that you really want. Basically, it's a matter of comparing the sub calls against the sub declarations to see if there are any "outliers".