Logo

dev-resources.site

for different kinds of informations.

Get out early with Perl statement modifiers

Published at
1/19/2022
Categories
perl
exceptions
patterns
antipatterns
Author
mjgardner
Author
9 person written this
mjgardner
open
Get out early with Perl statement modifiers

When I first started writing Perl in my early 20’s, I tended to follow a lot of the structured programming conventions I had learned in school through Pascal, especially the notion that every function has a single point of exit. For example:

sub double_even_number {
    # not using signatures, this is mid-1990's code
    my $number = shift;

    if (not $number % 2) {
        $number *= 2;
    }

    return $number; 
}
Enter fullscreen mode Exit fullscreen mode

This could get pretty convoluted, especially if I was doing something like validating multiple arguments. And at the time I didn’t yet grok how to handle exceptions with eval and die, so I’d end up with code like:

sub print_postal_address {
    # too many arguments, I know
    my ($name, $street1, $street2, $city, $state, $zip) = @_;
    # also this notion of addresses is naive and US-centric

    my $error;

    if (!$name) {
        $error = 'no name';
    }
    else {
        print "$name\n";

        if (!$street1) {
            $error = 'no street';
        }
        else {
            print "$street1\n";

            if ($street2) {
                print "$street2\n";
            }

            if (!$city) {
                $error = 'no city';
            }
            else {
                print "$city, ";

                if (!$state) {
                    $error = 'no state';
                }
                else {
                    print "$state ";

                    if (!$zip) {
                        $error = 'no ZIP code';
                    }
                    else {
                        print "$zip\n";
                    }
                }
            }
        }
    }

    return $error;
}
Enter fullscreen mode Exit fullscreen mode

What a mess. Want to count all those braces to make sure they’re balanced? This is sometimes called the arrow anti-pattern, with the arrowhead(s) being the most nested statement. The default ProhibitDeepNests perlcritic policy is meant to keep you from doing that.

The way out (literally) is guard clauses: checking early if something is valid and bailing out quickly if not. The above example could be written:

sub print_postal_address {
    my ($name, $street1, $street2, $city, $state, $zip) = @_;

    if (!$name) {
        return 'no name';
    }
    if (!$street1) {
        return 'no street1';
    }
    if (!$city) {
        return 'no city';
    }
    if (!$state) {
        return 'no state';
    }
    if (!$zip) {
        return 'no zip';
    }

    print join "\n",
      $name,
      $street1,
      $street2 ? $street2 : (),
      "$city, $state $zip\n";

    return;
}
Enter fullscreen mode Exit fullscreen mode

With Perl’s statement modifiers (sometimes called postfix controls) we can do even better:

    ...

    return 'no name'    if !$name;
    return 'no street1' if !$street1;
    return 'no city'    if !$city;
    return 'no state'   if !$state;
    return 'no zip'     if !$zip;

    ...
Enter fullscreen mode Exit fullscreen mode

(Why if instead of unless? Because the latter can be confusing with double-negatives.)

Guard clauses aren’t limited to the beginnings of functions or even exiting functions entirely. Often you’ll want to skip or even exit early conditions in a loop, like this example that processes files from standard input or the command line:

while (<>) {
    next if /^SKIP THIS LINE: /;
    last if /^END THINGS HERE$/;

    ...
}
Enter fullscreen mode Exit fullscreen mode

Of course, if you are validating function arguments, you should consider using actual subroutine signatures if you have a Perl newer than v5.20 (released in 2014), or one of the other type validation solutions if not. Today I would write that postal function like this, using Type::Params for validation and named arguments:

use feature qw(say state); 
use Types::Standard 'Str';
use Type::Params 'compile_named';

sub print_postal_address {
    state $check = compile_named(
        name    => Str,
        street1 => Str,
        street2 => Str, {optional => 1},
        city    => Str,
        state   => Str,
        zip     => Str,
    );
    my $arg = $check->(@_);

    say join "\n",
      $arg->{name},
      $arg->{street1},
      $arg->{street2} ? $arg->{street2} : (),
      "$arg->{city}, $arg->{state} $arg->{zip}";

    return;
}

print_postal_address(
    name    => 'J. Random Hacker',
    street1 => '123 Any Street',
    city    => 'Somewhereville',
    state   => 'TX',
    zip     => 12345,
);
Enter fullscreen mode Exit fullscreen mode

Note that was this part of a larger program, I’d wrap that print_postal_address call in a try block and catch exceptions such as those thrown by the code reference $check generated by compile_named. This highlights one concern of guard clauses and other “return early” patterns: depending on how much has already occurred in your program, you may have to perform some resource cleanup either in a catch block or something like Syntax::Keyword::Try’s finally block if you need to tidy up after both success and failure.

antipatterns Article's
30 articles in total
Favicon
Backend Red Flags - What NOT to do
Favicon
Microservice Antipatterns: The Shared Client Library
Favicon
Microservices: Avoiding the Pitfalls, Embracing the Potential - A Guide to Anti-Patterns
Favicon
Antipattern: We'll buy it when we come back
Favicon
SQL antipatterns: Diplomatic Immunity
Favicon
The Consumer Conundrum: Navigating Change in Microservices Without Gridlock
Favicon
Top AWS CloudFormation Anti-Patterns & Best Practices
Favicon
Guess-Driven Development
Favicon
AWS Serverless Anti-Patterns: What They Are and How to Avoid Them
Favicon
AWS DR Anti-Patterns: Avoiding Common Mistakes
Favicon
The Strategy of One
Favicon
Microservices anti-patterns
Favicon
Get out early with Perl statement modifiers
Favicon
JavaScript Anti-patterns
Favicon
The two most common DevOps anti-patterns
Favicon
The God 🦸
Favicon
Design Meeting Patterns and Antipatterns
Favicon
Anti-patterns of automated software testing
Favicon
React Anti Patterns Part 1
Favicon
Death by Interfaces?
Favicon
The Antipattern Antipattern
Favicon
Ruby's Array: a Swiss Army Knife?
Favicon
13 ways the Internet is broken - #9 will shock you!
Favicon
A note from a TDD zealot
Favicon
Applications as Frameworks
Favicon
My first React 'aha' moment. Is this an antipattern?
Favicon
Avoiding the Builder Design Pattern in Kotlin
Favicon
The Fallacy of DRY
Favicon
Avoid anemic domain models by empowering your objects
Favicon
微服务的反模式和陷阱

Featured ones: