Perl coding standards
General Principles
Foswiki is a large project which, over the years, has had a
lot of contributors. It is also mainly written in perl, which has a reputation for not being the most readable language. It is therefore
critical that contributors write code that is clear, concise, well structured and well documented. Contributors are strongly recommended to discover and use the principles of
literate programming i.e. to write code that is self-explanatory, by using good variable names, common programming idioms, and well-known patterns, and to avoid obscure idioms. If you
have to use obfuscated perl (and we all do, on occasion), then make sure it's clearly commented.
Foswiki started out life as a suite of disconnected perl CGI scripts, and that legacy is still present in the codebase today. Over the years there has been a steady drive to move towards a more object-oriented style of perl, implementing a MVC architecture. Contributors are asked to constantly bear this in mind when adding new code.
If you find yourself adding code that duplicates something that is already there, then don't be afraid to refactor the existing code to improve sharing. Refactoring is an essential part of continuous improvement. However please make sure you have a clear, well documented, reason for refactoring, and make doubly sure that you don't compromise any published APIs when you do so.
If you are unsure if your approach is good, then there are plenty of people ready to critique on IRC. There are also tools (described below) that can help you develop a good perl style.
System Requirements
Core code should be based on the
SystemRequirements.
Avoid adding dependencies on external Perl modules not shipped with standard Perl. This is because in some intranet environments, Internet access is difficult or non-existent, making it hard to install add-on modules directly from
CPAN. Also, the installer is often not the server administrator, requiring someone else to perform the module installation (or a more complex CPAN configuration). You can check which modules are available in which perl releases by looking at the
perl
module on CPAN.
If you find you do have to add a dependency from core code on an external perl module, make sure it is discussed to death first.
Foswiki Coding Conventions
Please follow these coding conventions when contributing to core code. This is to make source code more consistent and readable. Please provide your feedback in
CodingStandardsDiscussions.
(Note that the existence of a file called
TIDY
anywhere in the file hierarchy above your source code will automatically enforce a format check when you try to commit a code change to git. Listen to what it tells you!)
Note: the general rule is that readable code is more important than slavish adherence to coding standards. The format of the POD headers for exported functions is important, though, as they are automatically extracted to form the source code documentation in the release.
-
use strict
-
use warnings
-
use Assert
- This defines ASSERT, a function that causes a
die
if a condition is false. ASSERT is used as follows: ASSERT( $condition ) if DEBUG; This is conditionally compiled i.e. is enabled only during development.
- Function names:
- Use bumpy words that start with lower case, i.e.
sillyOperation
- Prepend private function names with
_
, i.e. _secretOperation
- Function documentation:
- Use the documentation templates below for header documentation for functions.
- Variable names:
- Use bumpy words that start with lower case, i.e.
$sillyVariable
- Always initialize, even if it's to
undef
.
- Avoid global variables like the plague, to avoid problems with mod_perl
-
use vars qw( $VERSION $RELEASE ...);
is deprecated, and should be replaced with "our" declarations when required.
-
our $VERSION = ...;
is used to declare globals, and should only be used for variables that must be accessed from other modules.
-
my $someVariable;
when used at the Package or file scope (outside of any {brace enclosed block}
are accessible from anywhere within the module, but cannot be accessed externally unless returned from a subroutine call.
- See this Perl Monks discussion on variable scoping.
- Subclassing:
- Define the @ISA variable using the
our
keyword and set it — for example, our @ISA = 'Parent::Class';
- White space:
- Follow the style of the current Perl scripts for indentation (four characters per level of indent), spacing around brackets and placement of braces (e.g. of '{}')
- Use only spaces for indent, don't use tabs. Mixing tabs and spaces often causes problems, especially with patches. To simplify doing this with specific editors, see VimEditor, EmacsCPerlMode...
- Comments:
- Use comments to describe what you are doing where appropriate
- Special comments: In case some parts of your code need later attention, state that in your comments as:
-
# SMELL:
for unclean code that looks like a bug, or violates common coding practices, or misses some corner case
- Open a Task for the
# SMELL:
and reference the task item in the Smell so that it doesn't get overlooked. If it is critical for correct operation, be sure to mark the task urgent and set the correct release so that it blocks the release.
- Regular expression modifier
/o
- Do not use this modifier. It is very little value in modern perl, there are always better alternatives anyway, and
/o
is a known source of bugs.
=pod
---+ package Namespace::Vector
Objects of this type represent 3D vectors
SMELL: quaternions are more flexible
=cut
package Namespace::Vector;
Example Class Method
A class method is an OO static method that is invoked using indirection off the package object e.g. Foswiki::Vector->new() or new Namespace::Vector()
=pod
---++ ClassMethod new( $x, $y, $z )
Constructor for a new vector object
* =$x= - X component
* =$y= - Y component
* =$z= - Z component
=cut
sub new {
my( $class, $x, $y, $z ) = @_;
my $this = bless( {}, $class );
$this->{x} = $x; $this->{y} = $y; $this->{z} = $z;
return $this;
}
Note how $class is
not listed in the parameter documentation.
Example Object Method
An object method is a OO method that is invoked using indirection off a blessed object of the package type e.g. my $c = new Namespace::Coord(1,2,3); $c->normalise(); or Namespace::Coord::normalise( $c )
=pod
---++ ObjectMethod normalise() -> $length
Normalise the vector to have length 1 i.e. become a direction vector.
Return the magnitude of the original vector.
=cut
sub normalise {
my( $this ) = @_;
ASSERT( $this->isa("Namespace::Vector") ); # it is always best to make sure....
my $mag = sqrt( $this->{x} * $this->{x} + $this->{y} * $this->{y} + $this->{z} * $this->{z} );
$this->{x} /= $mag; $this->{y} /= $mag; $this->{z} /= $mag;
return $mag;
}
Note how $this is
not listed in the parameter documentation.
Example exported static method
Header for a static method intended to be visible outside the package. A static method is any non-OO method.
=pod
---++ StaticMethod dot( $vectorObject, $vectorObject ) -> $number
Return the dot-product of two vectors.
=cut
sub dot {
my ( $a, $b ) = @_;
ASSERT( $a->isa( "Foswiki::Vector" )) if DEBUG;
ASSERT( $b->isa( "Foswiki::Vector" )) if DEBUG;
return $a->{x} * $b->{x} + $a->{y} * $b->{y} + $a->{z} * $b->{z};
}
Private methods
headers for private methods (private class methods, static methods and object methods) should follow the same pattern as exported methods except that they should be documented using # comments rather than POD. For example,
# ---++ ObjectMethod _crush( \@x ) -> $boolean
# Crush the vector with an array.
# Note use of \@ to indicate an array reference
# Note also use of $boolean to indicate the result type.
# Remember that undef, "" and 0 are all FALSE in perl, and any non-zero number or non-empty string is TRUE.
sub _crush {
my( $this, $x ) = @_;
ASSERT( ref($this) eq "Namespace::Vector" );
...
Internationalisation support
See:
InternationalisationGuidelines
You don't need to know anything about internationalisation (
I18N) to make your code work with international characters in
WikiWords and much more, and it also makes your code more readable. The guidelines cover both core code and plugins.
Options to ensure consistent formatting:
- Perl
- use PerlTidy (
perltidy -npro -b filename.pm
will modify the file inplace using the default settings)
- If you want to use
perltidy
on a file, be sure to perform a distinct checkin containing only its reformatting changes
- If you have BuildContrib locally (recommended) then you can easily configure
perltidy
to use it. Simply export PERL5LIB=/the/path/to/BuildContrib/lib
and run perltidy
as usual (Linux bash, other OS's/shells will have a similar feature).
- use PerlCritic to learn how to avoid common coding issues
- As a general rule, it is helpful to run
perlcritic --brutal
or even perlcritic --cruel
for educational purposes; and also to ensure you're not unnecessarily adding new criticisms to the code at these levels (even if they aren't shown by default perlcritic
settings).
- EmacsCPerlMode
Gotchas and Wotchers
Perl is a vast and complex language, and supports some hairy programming constructs. Some lead you by the hand into common errors, so here are some common gotchas and things to watch out for:
- If you need to be sure that a value is defined (e.g. you are going to use it in a condition) then test it with
defined $blah
, don't use $blah || ''
to assign it a known value. This is because zero (0) tests as false, so will end up being overwritten if the ||
syntax is used.
- Validate all URL parameters, to avoid any risk of code injection. URL parameters need to be checked to ensure their values are legal, and cannot carry a code payload that could compromise the server. Avoid blind untainting
$value =~ /^(.*)$/; $value = $1
if possible, and comment it to death if you do use it to explain why you think it's safe.
- Use
use Module ()
in preference to use Module
in the head of a module. By forcing all calls to refer explicitly to the defining module, you avoid polluting namespaces and minimise the risk of nasty accidents.
- Avoid obscure, obfuscated perl, regardless of how clever it makes you feel. If you think you can save a few picoseconds by coding something in a really clever, but somewhat obscure, way, then don't. Remember the poor schmuck who has to read and understand your code. If you find a case where some horrible perl construct is required, then document the life out of it.
CSS coding standards
See:
CssCodingStandards
Javascript coding standards
See:
JavascriptCodingStandards
See also:
It is important when working with other people to make sure source code is consistently formatted. To help with this, the source code repository automatically checks that files have been consistently formatted before allowing them to be checked in. Any code belonging to the Foswiki core, or any of the extensions shipped by default with the core, is required to be correctly formatted or the checkin will be rejected. Other extensions may be 'flagged' as requiring checking by placing a file called
TIDY
in the directory tree containing the sources. Similar to Apache's
.htaccess
, the existence of this file affects all sources in the file tree in and below the directory where it was found.
-
TIDY
does not exist -- check nothing (code can be formatted however you want)
-
TIDY
exists and is empty -- check everything - all source files must be formatted according to the standard
-
TIDY
exists and contains only the word OFF or contains (possibly among other things) a line: <language> OFF
-- disables checking <language> files for this subtree e.g. perl OFF
-
TIDY
exists and contains (possibly among other things) a line such as: perl -q -l=0 -i=2 -pt=2 -nsfs -ce -novalign
-- check using these formatting options instead of the standard - not recommended (please use the defaults)
If you use the
BuildContrib (highly recommended) then you can use
perl build.pl tidy
to run all available formatters on your code.
The Foswiki standard for formatting perl source files is to run the standard
perltidy
command
without any formatting options (ensured by the
-npro
option)
perltidy -npro -b <filename>.pm
Note that individual perl source files can contain special beginning and ending comment markers around code to be passed to the output without formatting. The beginning marker is
#<<<
and the ending marker is
#>>>
Are your commits being rejected even though they're tidied? You may need to upgrade perltidy, or use the version shipped with BuildContrib. See PerlTidy#Versions.
At this point in time Javascript sources are not checked. However in the future we are likely to use
js-beautify for this (the engine behind
http://jsbeautifier.org)
At this point in time CSS sources are not checked.