Difference between revisions of "Perl Style Guide"

From Proxmox VE
Jump to: navigation, search
m (Spacing and syntax usage: add some if/else hints)
m (Indentation)
 
(6 intermediate revisions by 2 users not shown)
Line 1: Line 1:
= PVE Perl Style Guide =
+
== PVE Perl Style Guide ==
  
 
Various of our files have inconsistent styles due to historical growth as well
 
Various of our files have inconsistent styles due to historical growth as well
Line 29: Line 29:
 
}
 
}
 
</pre>
 
</pre>
 
Please try to avoid lines longer than 80 characters unless it really ruins
 
the code layout (which often means you want to factorize your code better).
 
 
The vim configuration would be `<tt>:set ts=8 sts=4 sw=4 noet cc=80</tt>`
 
  
 
Like all editors for some reason I'll never understand we do not distinguish
 
Like all editors for some reason I'll never understand we do not distinguish
 
between indentation and alignment, so if you split up an expression over
 
between indentation and alignment, so if you split up an expression over
 
multiple lines we still use the same "all 8 spaces are 1 tab" pattern.
 
multiple lines we still use the same "all 8 spaces are 1 tab" pattern.
 +
 +
The vim configuration would be <code>:set ts=8 sts=4 sw=4 noet</code>
 +
 +
== Breaking long lines and strings ==
 +
 +
The preferred limit on the length of a single line is 80 columns.
 +
 +
Statements longer than 80 columns should be broken into sensible chunks, unless exceeding 80 columns significantly increases readability and does not hide information.
 +
The maximal line length tolerated for those exceptions is 100 columns.
 +
 +
The vim configuration would be <code>:set cc=81</code> or, to show both soft and hard limit: <code>:set cc=81,101</code>
  
 
== Spacing and syntax usage ==
 
== Spacing and syntax usage ==
Line 120: Line 126:
 
* use <tt>$foo->[subscript]</tt> instead of <tt>$$foo[subscript]</tt>
 
* use <tt>$foo->[subscript]</tt> instead of <tt>$$foo[subscript]</tt>
 
* use <tt>$foo->{subscript}</tt> instead of <tt>$$foo{subscript}</tt>
 
* use <tt>$foo->{subscript}</tt> instead of <tt>$$foo{subscript}</tt>
 +
 +
When not accessing an element but simply dereferencing *once*, the dereferencing sigil can be put in front with braces, eg. <tt>${stuff}</tt> or <tt>@{stuff}</tt>, provided <tt>stuff</tt> is easy enough to read. Otherwise pull <tt>stuff</tt> out into a local variable first.
 +
 
* prefer <tt>$foo = value if !defined($foo);</tt>
 
* prefer <tt>$foo = value if !defined($foo);</tt>
 
*: over <tt>$foo //= value;</tt>
 
*: over <tt>$foo //= value;</tt>
 
* use <tt>if (!cond) {</tt>
 
* use <tt>if (!cond) {</tt>
 
*: over <tt>unless (cond) {</tt>
 
*: over <tt>unless (cond) {</tt>
* use <tt>foreach</tt> instead of <tt>for</tt> when looping over a list of elements.
+
* use either of <tt>foreach</tt> or <tt>for</tt> when looping over a list of elements.
 +
* prefer <tt>foo($a, $b);</tt>
 +
*: over <tt>foo $a, $b</tt>
  
Regarding the first three: When not accessing an element but simply dereferencing *once*, the dereferencing sigil can be put in front with braces, eg. <tt>${stuff}</tt> or <tt>@{stuff}</tt>, provided <tt>stuff</tt> is easy enough to read. Otherwise pull <tt>stuff</tt> out into a local variable first.
+
Function calls should favor the use of parentheses. Omitting them is ''only'' allowed in simple cases.
 +
 
 +
<pre>
 +
foo($a, $b); # ok
 +
foo $a, $b; # okay, but discouraged
 +
print $a, $b; # okay, print is commonly used this way
 +
print($a, $b); # preferred
 +
delete $a->{b}; # okay, common
 +
my $var = delete($hash->{key}) // "default"; # ok
 +
my $var = delete $hash->{key} // "default"; # NOT ok!
 +
</pre>
 +
 
 +
As a rule of thumb, when operators are involved, or when calling functions other than <tt>print, map</tt> or <tt>grep</tt>, always use parentheses for function calls.
  
 
=== Blocks and multi-line statements ===
 
=== Blocks and multi-line statements ===
Line 177: Line 200:
 
* short comments can stay in the same line as the to-be-commented statement
 
* short comments can stay in the same line as the to-be-commented statement
 
* try to keep comment and its related statement(s) together location-wise, so that it's clear what the comment target is.
 
* try to keep comment and its related statement(s) together location-wise, so that it's clear what the comment target is.
 +
 +
== Regular Expressions ==
 +
 +
=== Matching Digits Fallacy ===
 +
 +
For matching digits normally <code>\d</code> is used, but this has some implications which may not be expected.
 +
Namely, it matches all "Unicode Characters" (code points) from the '[https://www.fileformat.info/info/unicode/category/Nd/list.htm Number, Decimal Digit]' Category.
 +
 +
If you think that this could have any implication on security or long term functionality then '''do not use''' <code>\d</code> but use the <code>[0-9]</code> character class.
 +
 +
=== Non-Capturing Groups ===
 +
 +
Normally you should use non capturing groups <code>(?:)</code> as long as you do not need to use the value in parenthesis.
 +
They can make the regex execution faster and simpler do understand.

Latest revision as of 12:58, 2 September 2020

PVE Perl Style Guide

Various of our files have inconsistent styles due to historical growth as well as because of the mixed styles we got from various contributors.

Please try to follow this guide if you're working on code for PVE to avoid adding to the style mess.

Here's a summary of our style (which is somewhat unusual at least when it comes to the mixed indentation).

Indentation

We indent by 4 spaces, assume tabs to be 8 spaces and convert all groups of 8 spaces into tabs at the beginning of a line. Here's an example, with tabs represented via '>........'

sub foo {
    my ($arg1, $arg2, $arg3) = @_;
    if ($arg1) {
>.......print($arg2, "\n");
>.......if ($arg3) {
>.......    die "Exceptions should end with a newline in most cases\n"
>.......>.......if $arg3 ne $arg2;
>.......    print("Another line\n");
>.......}
    }
}

Like all editors for some reason I'll never understand we do not distinguish between indentation and alignment, so if you split up an expression over multiple lines we still use the same "all 8 spaces are 1 tab" pattern.

The vim configuration would be :set ts=8 sts=4 sw=4 noet

Breaking long lines and strings

The preferred limit on the length of a single line is 80 columns.

Statements longer than 80 columns should be broken into sensible chunks, unless exceeding 80 columns significantly increases readability and does not hide information. The maximal line length tolerated for those exceptions is 100 columns.

The vim configuration would be :set cc=81 or, to show both soft and hard limit: :set cc=81,101

Spacing and syntax usage

Spaces around parenthesis with syntactical words are inserted as you would in regular text (one before the opening parenthesis, one after the closing parenthesis). Similarly for curly braces:

  • use if (cond) {
  • use while (cond) {
  • not if(cond) {
  • not if (cond){
  • not if(cond){

BUT: no space between a function`s name and its parameter list:

  • func(params)
  • not func (params)

Use spaces around braces of lists:

  • use my $list = [ 1, 2, 3 ];
  • use my $hash = { one => 1, two => 2 };

No spaces for unary operators or sigils which are directly connected to one another, or in array/hash accesses (here the contents of the brackets or curly braces represent content of the expression/variable to its left, so it makes sense to "group" them):

  • use !$foo
  • not ! $foo
  • use $foo->{text}
  • use $foo{text}
  • use $foo->[index]
  • use $foo[index]
  • use $foo->(index)
  • not &$foo(args)
  • not & $foo(args)

In general: use spaces in arithmetic expressions in a way which makes sense, eg. you can skip them on a short single binary operation, or if it helps reading the expression by grouping it so that the operator precedence is emphasized. Do not add spaces in a way which conflicts with the operators' precedences:

  • use a + b
  • not a+b
  • may use a*3 + b*4
  • must not use a+3 * b+4

In if-else blocks a else or elsif should be on it's own line together with its curly braces.

# Good
if (expr) {
    code();
} else {
    code();
}

# Bad
if (expr) {
    code();
}
else {
    code();
}

# Bad
if (expr)
{
    code();
}
else
{
    code();
}

Perl syntax choices

Most of these are chosen for semantic clarity and should make it easier to understand the code for people who don't use much perl or simply aren't used to our code base:

  • use $foo->(args) instead of &$foo(args)
  • use $foo->[subscript] instead of $$foo[subscript]
  • use $foo->{subscript} instead of $$foo{subscript}

When not accessing an element but simply dereferencing *once*, the dereferencing sigil can be put in front with braces, eg. ${stuff} or @{stuff}, provided stuff is easy enough to read. Otherwise pull stuff out into a local variable first.

  • prefer $foo = value if !defined($foo);
    over $foo //= value;
  • use if (!cond) {
    over unless (cond) {
  • use either of foreach or for when looping over a list of elements.
  • prefer foo($a, $b);
    over foo $a, $b

Function calls should favor the use of parentheses. Omitting them is only allowed in simple cases.

foo($a, $b); # ok
foo $a, $b; # okay, but discouraged
print $a, $b; # okay, print is commonly used this way
print($a, $b); # preferred
delete $a->{b}; # okay, common
my $var = delete($hash->{key}) // "default"; # ok
my $var = delete $hash->{key} // "default"; # NOT ok!

As a rule of thumb, when operators are involved, or when calling functions other than print, map or grep, always use parentheses for function calls.

Blocks and multi-line statements

While there certainly are way-too-long lines in our code already, please try to avoid adding more of them.

Generally, when a line is long, try to split it up in a reasonable manner, and when there's an followup block or statement afterwards (eg. the contents of an if or while-loop), please *separate* the block visually:

sub foo {
    my ($arg1, $arg2, $arg3) = @_;

    # Good:
    if ($arg1->{foo}->{bar} && $arg2 * $arg1->{xzy} > $arg3->{baz}
        && $arg3->{more})
    {
>.......code();
    }
    # Bad: (block not visually distinct from the code())
    if ($arg1->{foo}->{bar} && $arg2 * $arg1->{xzy} > $arg3->{baz}
        && $arg3->{more}) {
>.......code();
    }

    # Good:
    if ($arg1->{foo}->{bar}
        && $arg2 * $arg1->{xzy} > $arg3->{baz}
        && $arg3->{more})
    {
>.......code();
    }
    # Bad: (inconsistent '&&' placement)
    if ($arg1->{foo}->{bar}
        && $arg2 * $arg1->{xzy} > $arg3->{baz} &&
        $arg3->{more})
    {
>.......code();
    }
}

If a language permits leaving out braces for single statements in an if for example, *do* use braces when the condition spans multiple lines.

Comments

Besides the fact that your comments should not explain what happens for a code hunk, but rather why it is they way it is, or why it even exists if one could question that, you should adhere to the following rules:

  • at least a single space after the initial comment hash symbol (#), more can be added for formatting purposes
  • try to stay below the 80 character per line length limit
  • do not add newlines early, use the full width (80cc) available
  • short comments can stay in the same line as the to-be-commented statement
  • try to keep comment and its related statement(s) together location-wise, so that it's clear what the comment target is.

Regular Expressions

Matching Digits Fallacy

For matching digits normally \d is used, but this has some implications which may not be expected. Namely, it matches all "Unicode Characters" (code points) from the 'Number, Decimal Digit' Category.

If you think that this could have any implication on security or long term functionality then do not use \d but use the [0-9] character class.

Non-Capturing Groups

Normally you should use non capturing groups (?:) as long as you do not need to use the value in parenthesis. They can make the regex execution faster and simpler do understand.