Coding Standards

From WeBWorK_wiki
Revision as of 02:30, 3 July 2008 by Darnold (talk | contribs) (Fixed some grammar)
Jump to navigation Jump to search

If you commit changes or submit patches to WeBWorK, make sure your work conforms with these standards. (This is not a complete list, just the things that have caused problems in the past.)

Some of these standards are motivated by readability, some by the line-oriented nature of CVS, some by maintainability. (And some by Sam Hathaway's personal preferences and pet peeves.)

Indentation

We use the TAB character to indicate a level of indentation, and SPACE characters to line things up in lists. I understand there is some debate as to whether is it appropriate to use "hard" tabs in text files. We do it because some of the WeBWorK developers prefer to view the code with wider indentation (say, eight columns) and some prefer narrower indentation (say, three or four columns). Thus, we use hard tabs for initial indentation and let each developer chose a tab width using their editor.

Here in an example of when to use tabs and spaces. ---> represents a tab character.

my %errors = (
--->not_found            => "The record was not found.",
--->invalid_id           => "The record ID is invalid.",
--->undefined_or_unknown => "The record was undefined or unknown.",
);

sub bar {
--->my ($self, @recs) = @_;
--->foreach my $rec (@recs) {
--->--->my $Rec = $self->getRec($rec);
--->--->if ($Rec->error) {
--->--->--->warn $errors{$Rec->error};
--->--->} else {
--->--->--->foo($Rec->a, $Rec->b);
--->--->--->baz(
--->--->--->--->origin             => $Rec->origin,
--->--->--->--->destination        => $Rec->destination,
--->--->--->--->widgets_per_second => $Rec->widgets_per_second,
--->--->--->--->score              => $Rec->score,
--->--->--->--->email_address      => $Rec->email_address,
--->--->--->);
--->--->}
--->}
}

As you can see, indentation that is used to "line up" elements on adjacent lines uses spaces, while indentation that is used to indicate some structural hierarchy in the code uses tabs.

Lining up multiline structures

We usually use a single additional level of indentation on the trailing lines of a multi-line statement or expression. We typically do not line the continuation of a statement up with the opening brace or paren or bracket that encloses it. Otherwise, a change in the length first line of the statement will require repositioning the following lines, which is a pain.

Wrong

$some_long_object->callWithALotOfHashArguments(-heliocoper=>$h, -airplane=>$p,
                                               -collisions=>"resolve",
                                               -unresovable_collisions=>sub{my $e = new E::Collision;
                                                                            $e->throw;
                                                                            return 0;},
                                               -arewedoneyet=>"no", -justonemore=>0);

Right

$some_long_object->callWithALotOfHashArguments(
    -heliocoper => $h,
    -airplane => $p,
    -collisions => "resolve",
    -unresovable_collisions => sub {
        my $e = new E::Collision;
        $e->throw;
        return 0;
    },
    -arewedoneyet => "no",
    -justonemore => 0,
);

(I'm not obsessed with lining up =>'s, but you can if you want. I do like to put spaces on either side of that symbol though, since it binds pretty loosely.)

In the example above, the situation could be improved further by defining the unresovable_collisions subroutine in a separate statement and assigning it to a variable, like $throw_coll_except.

Related to this is something that we're not completely sure about, but it's good to think about:

If we've got a multiline call, block, list, or hash, we usually don't put the first statement or item on the same line as the opening brace/bracket/paren/etc. I think this improved readability, since all the arguments are indented the same amount, and nobody gets "lost" at the end of a long line. It's also better for CVS. Likewise, the closing brace/bracket/paren/etc. usually goes on its own line, and not additionally indented.

However, there are exceptions. If the text leading up to the brace/bracket/etc. isn't very long and encloses a lot of short arguments, we sometimes do start the contents of the structure on the same line. There are some cases where it would take up too many lines otherwise. Or just plain look silly. So, this could be all right:

someFunctionCallWithShortArgs($foo, $bar, $baz, $fubar,
    $xyzzy, $fizzy, $silly, \@wakakaka, $lalala, $webwork);

And if you've got hashes going, and you format them like this, you should probably leave out those spaces to save space. I like to keep the space after the comma evern here, but I'm not married to it. Here's a real-life example:

CGI::table({-border=>"0", -cellpadding=>"10", cellspacing=>"10"},
    CGI::Tr(
        CGI::td(...)
    )
);

I've got to think about this one some more. Suggestions welcome.

Comments

Comments should always be indented as far as the code they are next to. Avoid multiline comments that appear to the right of code. Commented-out code should immediately follow the comment character (#$foo...) while comments should have a space after the comment character (# this is...).

Don't freak out with the comment character. Usually, a single # will do it. You should probably never be making huge boxes out of them, and you should never ever do that within a subroutine. I like to use comments prefixed with ##### to separate major sections of code, but you only really need that if the major sections are major enough that a comment prefixed with a single # would get lost.

In general, wrap long comments to 80 characters. You can bend this rule a little bit if you have a comment that's like 90 characters and you don't want to use two lines.

Wrong

sub foo {
    my ($a, $b) = @_;
    my $c = $a * $b; # first, get the
                     # product of $a and $b.
    
#################################   
#####now call a function#########
#################################   
    callFunc($c);
    
# this code is no longer needed
#    oldFunc($a, \$b);
#    $c -= $b;
    
    return $c;
}

Right

sub foo {
    my ($a, $b) = @_;
    
    # first, get the product of $a and $b.
    my $c = $a * $b;
    
    # now call a function
    callFunc($c);
    
    # this code is no longer needed
    #oldFunc($a, \$b);
    #$c -= $b;
    
    return $c;
}