Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

What habits do you have to avoid introducing bugs?

by superfrink (Curate)
on Feb 06, 2008 at 04:30 UTC ( #666463=perlquestion: print w/ replies, xml ) Need Help??
superfrink has asked for the wisdom of the Perl Monks concerning the following question:

I created a fork bomb by accident.
for my $i (@list) { my $status = fork(); if ($status < 0) { # error condition } elsif ($status = 0) { # in child } else { # in parent } }
The bug is a typo. The conditional: ($status = 0) Should be: ($status == 0)

As soon as I saw it I thought "I know better. I should have put the constant on the left" like: (0 = $status) because it will produce an error instead of a conditional that fails.

I suppose I could have used ranges and left zero to be the default.
if ($status < 0) elsif ($status > 0) else

What habits do you have to prevent making mistakes when you write code?

Comment on What habits do you have to avoid introducing bugs?
Select or Download Code
Re: What habits do you have to avoid introducing bugs?
by Util (Priest) on Feb 06, 2008 at 05:20 UTC

    In production code:   use warnings;
    In development code:  use warnings 'all';

    It would have caught your typo:

    Found = in conditional, should be == at pm_666463_01.pl line...

      ++

      I did not have the -w on the #! line even though I did have use strict; . Good catch.

      That said the warnings do report the mistake to STDERR but do not stop the program from running. In this case the fork bomb still happens.

      Update: Also perl -w -c does not report this mistake.

        do not stop the program from running ... fork bomb still happens
        True!

        After futher consideration, I came up with many more habits, but few that are akin to the OP. I don't even like it (0 == $foo) myself; I use it in C, but am glad to not need it in Perl.
        More of my habits:

        • Strict and warnings, of course!
        • Write in a Syntax Highlighting editor.
        • Write at least one test ASAP, and run it frequently (roughly each time I hit Save).
        • If I can't write normal tests (maintenance on a huge web package), at least get (WWW::Mechanize) the complete output of one sample run, and compare against *that* as my test.
        • Align similar code, and write code that is alignable (until refactoring makes sense).
        • Prefer to write Table or Data-driven code where possible.
        • (Except when skipping out with next/last) Never write an if{}elsif{} without an else, even if it is just a documented '# Do nothing'.
        • (During early development) Die at the drop of a hat. Don't just die where you see possible errors; use assertions on *everything* that you can predict the state of. Die on "Can't happen"!
        • Make whitespace visible on diagnostic output: die "Foo '$foo' has an invalid value"; the single-quotes show me that $foo contains trailing whitespace.
        • I copy a template to create each new program. In its top block is this line, to foil non-printables and buffering issues that confound debugging. The whole line is either commented on uncommented together: #use Data::Dumper; $Data::Dumper::Useqq=1; $|=1;.
        • Examine the data during development, rather than waiting for debugging:
          # I want to write this: my @rows = @{ $dbh->selectall_arrayref($sql) }; # Instead, I always write this, and remove the dump # after verifying the data and its format: my $rows_aref = $dbh->selectall_arrayref($sql) or die; print Dumper $rows_aref; my @rows = @{$rows_aref};
          Hence I see early that each row is an arrayref (not the expected scalar), even though I only asked for one field in the select. 1 minute of checking my assumptions saved 15 minutes of later debugging.

        In this case the fork bomb still happens.

        Make it fatal:

        use warnings FATAL => 'all';

        lodin

        -w -c doesn't catch it? Yes it does:

        $ perl -w -c -e 'if($status = 0) { $status = 1 }' Found = in conditional, should be == at -e line 1. -e syntax OK

        That's using 5.8.8, BTW.

Re: What habits do you have to avoid introducing bugs?
by marcussen (Pilgrim) on Feb 06, 2008 at 05:40 UTC
    In general I would say that using:
    use strict; use warnings;

    And perlcritic is a very good starting point.

    while ( whoring ){ for ( xp ){ grep /the source/,@perlmonks; }}

      I think that in jumping right to perlcritic, you miss a step. I'd first pick up a copy of Perl Best Practices.

      Much of the value of perlcritic/PBP is wrapped up in the introspection: PBP makes you ask "Why do I code this way?" invites you to explore different ways of doing things. Which, in my case, were often better ways of doing things.

Re: What habits do you have to avoid introducing bugs?
by pc88mxer (Vicar) on Feb 06, 2008 at 07:41 UTC
    One thing to do is to make sure that you are using the functions correctly. fork returns undef on an error. When successful, to the parent process it returns a non-zero number (the pid of the child), and to the child it returns 0. At least that's the way it works under Unix-like versions of perl.
Re: What habits do you have to avoid introducing bugs?
by andreas1234567 (Vicar) on Feb 06, 2008 at 09:48 UTC
    In addition to what's been already said, I'd use Log::Log4perl and Log::Dispatch::FileRotate to detect errors and enable fast and easy debugging by the flip of a flag (i.e. change log level):
    $ cat 666463.pl use strict; use warnings; use Log::Log4perl; use Log::Dispatch::FileRotate; my $conf = q( log4perl.category.foo = DEBUG, FileRotateAppender log4perl.appender.FileRotateAppender = Log::Dispatch::File +Rotate log4perl.appender.FileRotateAppender.filename = foo.log log4perl.appender.FileRotateAppender.mode = append log4perl.appender.FileRotateAppender.size = 100000 log4perl.appender.FileRotateAppender.max = 5 log4perl.appender.FileRotateAppender.layout = PatternLayout log4perl.appender.FileRotateAppender.layout.ConversionPattern=[%p] % +d %M %F:%L:- %m%n ); Log::Log4perl::init( \$conf ); my $log = Log::Log4perl::get_logger("foo"); sub check_something { my $something = shift; $log->debug(qq{something:'$something'}) if $log->is_debug(); if (!defined($something)) { $log->error(qq{something is nothing!}) if $log->is_error(); } return $something; } # ------ main ------ check_something(q{hello}); check_something(); __END__
    Run it:
    $ perl 666463.pl Use of uninitialized value in concatenation (.) or string at 666463.pl + line 23.
    Inspect log:
    $ tail foo.log [DEBUG] 2008/02/06 10:45:00 main::check_something 666463.pl:23:- somet +hing:'hello' [DEBUG] 2008/02/06 10:45:00 main::check_something 666463.pl:23:- somet +hing:'' [ERROR] 2008/02/06 10:45:00 main::check_something 666463.pl:24:- somet +hing is nothing!
    --
    Andreas
Re: What habits do you have to avoid introducing bugs?
by apl (Monsignor) on Feb 06, 2008 at 10:57 UTC
    I try to use lots of small procedures rather than a few large ones. It enables a bigger picture of the logical flow, and prevents you from getting bogged down in details.

    Whenever I see duplicate occurrences of the same code, I try to use a sub instead. (If there's a problem with it, a fix to the one sub fixes all occurrences; you know the code is supposed to be identical.)

    These prevent logical as opposed to physical problems...

    Revised:Always test error returns! Always display error returns with associated parameter(s) (as opposed to simple text)!

      [..] small procedures rather than a few large one
      This also has the added benefit that is will probably be easier to test. chromatic writes (IntroTestMore.pdf, p32 of 69):
      writing testable code
      • the smaller the unit, the better
      • the simpler the unit, the better
      Be sure to read his Perl Testing: A Developer's Notebook.
      --
      Andreas
Re: What habits do you have to avoid introducing bugs?
by Prof Vince (Friar) on Feb 06, 2008 at 11:13 UTC
    Reading the documentation. That would have make you realize that fork returns undef on error.
Re: What habits do you have to avoid introducing bugs?
by grinder (Bishop) on Feb 06, 2008 at 14:20 UTC

    I have a number of habits that I use regularly to prevent syntax and certain simple semantic errors.

    Firstly, I always type the parens first, then go back an fill in the contents:

    if () if () { } if () { } else { } if ($foo) { } else { }

    Similarly, I write file-reading loops outside-in:

    open my $file, ... open my $file, ... close $file; open my $file, ... while () { } close $file; open my $file, ... while (<$file>) { chomp; } close $file;

    Or fire-and-forget database connections:

    my $db = DBI->connect(...); END {$db and $db->rollback and $db->disconnect}

    This way I can do race ahead doing what needs to be done, and I know that no matter how my programs ends, everything will be undone. It's only after I've tested that the desired results are achieved do I go back afterwards and change the END to commit the db changes.

    I know there are auto-completion editors that generate this boilerplate for you, but I've never come across one I could really come to terms with. The main point is that your code should always be runnable (assuming you finish the statement you're writing).

    I find this cuts out a lot of edit-compile-kaboom cycles. Once you are confident that the program will run each time without syntax errors, I find you can concentrate a lot more on the problem itself, and this accrued concentration naturally helps you pay more attention to the the larger issues. I hate wasting five minutes trying to track down a poorly-closed heredoc. The time wasted on this issue is an interruption in concentration about the problem you're really trying to solve. Get that sorted out, and you reduce the chance of creating subtle errors in the larger picture.

    • another intruder with the mooring in the heart of the Perl

      ++

      The editor I use (NEdit), as I'm sure every editor with a modicum of intelligence, allows me to create macros which automatically create common empty structures as you have mentioned (if-elsif-else, etc.). The small investment in time it takes to set these up pays off big in the long run.

Re: What habits do you have to avoid introducing bugs?
by zentara (Archbishop) on Feb 06, 2008 at 14:46 UTC
    My method is more mundane(and direct) than the others. After I add a few lines of code, or even make a few small changes, I see if the script runs well (as I would expect). As I code, I make many,many test runs of incomplete code. In your case, I would have noticed my system bogging down pretty quickly, killed off the script, and zeroed in on the fork bomb.

    I'm not really a human, but I play one on earth. Cogito ergo sum a bum
Re: What habits do you have to avoid introducing bugs?
by ultibuzz (Monk) on Feb 06, 2008 at 20:43 UTC

    i like to code in a debug enviorment, wich monitore memory access and also thread generation.
    saved me sever times from killing my machine with endless threads that grow and eat up all cpu + memory.
    for coding bigger things i like to use the editor debugger komodo from active state,i like it's warnings and suggestion how to write some things better.

    kd ultibuzz

Re: What habits do you have to avoid introducing bugs?
by loris (Hermit) on Feb 07, 2008 at 14:19 UTC

    Bearing in mind that every line of code can contain errrors, I try to write as little code as possible.

    This of course has to be weighed up against the danger of writing overly terse code, which might be hard to maintain.

    loris


    "It took Loris ten minutes to eat a satsuma . . . twenty minutes to get from one end of his branch to the other . . . and an hour to scratch his bottom. But Slow Loris didn't care. He had a secret . . ." (from "Slow Loris" by Alexis Deacon)
Re: What habits do you have to avoid introducing bugs?
by blucap (Initiate) on Feb 07, 2008 at 16:17 UTC
    Use Editpad Pro.
Re: What habits do you have to avoid introducing bugs?
by dragonchild (Archbishop) on Feb 21, 2008 at 16:47 UTC
    Some have already been mentioned, but:
    • My template is:
      use 5.6.0; use strict; use warnings FATAL => 'all'; __END__
    • Write everything outside-in, matching close to open immediately.
    • Proper nesting (my editor helps)
    • Syntax highlighting
    • No code without a test
    • No refactoring with many tests.

    I have still never done the constant-on-the-left thing. I suppose I should ...


    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://666463]
Approved by McDarren
Front-paged by andreas1234567
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others studying the Monastery: (5)
As of 2014-10-02 03:39 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    What is your favourite meta-syntactic variable name?














    Results (46 votes), past polls