Previous Page
Next Page

6.4. Negative Control Statements

Don't use unless or until at all.

Perl is unusual amongst programming languages in that it provides not only positive conditional tests (if and while), but also their negative counterparts (unless and until). Some people find that these keywords can make certain control structures read more naturally to them:

    RANGE_CHECK:
    until ($measurement > $ACCEPTANCE_THRESHOLD) {
        $measurement = get_next_measurement(  );
        redo RANGE_CHECK unless defined $measurement;
        # etc.
    }

However, for many other developers, the relative unfamiliarity of these negated tests actually makes the resulting code harder to read than the equivalent "positive" version:


    RANGE_CHECK:
    while ($measurement <= $ACCEPTANCE_THRESHOLD) {
        $measurement = get_next_measurement(  );
        redo RANGE_CHECK if !defined $measurement;
        
# etc.
}

More importantly, the negative tests don't scale well. They almost always become much harder to comprehend as soon as their condition has two or more components, especially if any of those components is itself expressed negatively. For example, most people have significantly more difficulty understanding the double negatives in:

    VALIDITY_CHECK:
    until ($measurement > $ACCEPTANCE_THRESHOLD && ! $is_exception{$measurement}) {
        $measurement = get_next_measurement(  );
        redo VALIDITY_CHECK unless defined $measurement && $measurement ne '[null]';
        # etc.
    }

So unless and until are inherently harder to maintain. In particular, whenever a negative control statement is extended to include a negative operator, it will have to be re-cast as a positive control, which requires you to change both the keyword and the conditional:

    VALIDITY_CHECK:
    while ($measurement < $ACCEPTANCE_THRESHOLD && $is_exception{$measurement}) {
        $measurement = get_next_measurement(  );
        redo VALIDITY_CHECK if !defined $measurement || $measurement eq '[null]';
        # etc.
    }

Not only is that extra effort, it's error-prone effort. Reworking conditionals in that way is an excellent opportunity to introduce new and subtle bugs into your program...just as the previous example did. The original until condition was:

    until ($measurement > $ACCEPTANCE_THRESHOLD && ! $is_exception{$measurement}) {

The corresponding "positive" version should have been:


    while ($measurement <= $ACCEPTANCE_THRESHOLD || $is_exception{$measurement}) {

This kind of mistake is unfortunately common, but very easy to avoid. If you never use an unless or until, then you'll never have to rewrite it as an if or while. Moreover, if your control statements are always "positive", your code will generally be more comprehensible to all readers[*], regardless of the complexity of its logical expressions. Even in the simple cases, something like:

[*] Including yourself in six months' time . . .


    croak "$value is missing" if !$found;

is not appreciably harder to comprehend than:

    croak "$value is missing" unless $found;

unless or until are minor conveniences that can easily become major liabilities. They're never necessary, and frequently counterproductive. Don't use them.

Don't Fail Not to Use Chained Negatives

The "Negative Control Statements" guideline has proved to be one of the most controversial in this book. Though few people will miss until, many very good Perl programmers feel that unless is a valid choice, which improves readability in a limited number of circumstances.

If you, or your team, are persuaded by such arguments and decide to allow the use of unless as part of your coding practices, then make sure that you only ever use it in ways that genuinely improve the readability of your code. The best way to ensure that is to require that the conditional expression of any negative control statement must always itself be expressed using "positive" logic.

That is, never write an unless (or an until) in which the condition includes a "negative" operator, an inequality, or any kind of ordering test. Don't use any of the following operators with an unless or an until:

    !   not                 # Negation of value
    !~  !=  ne              # Inequality of value
    <   >   <=  >=  <=>     # Numerical ordering
    lt  gt  le  ge  cmp     # String ordering

Abiding by that rule would permit the following limited uses:

    next NAME unless $name eq 'Harry';
    redo NAME unless $name =~ m/Mud{1,2}/xms;

    unless ($count == $MAX_COUNT) {
        carp 'Search terminated prematurely';
    }

but would still prohibit confusing double-negatives and reversed inequalities like:

    last NAME  unless $name ne 'Harry';
    exit $FAIL unless $name !~ m/Mud{1,2}/xms;

    unless ($count != $MAX_COUNT) {
        carp "Still searching...\n";
    }
    return $count
        unless $count < 10 || $tries >= $MAX_TRIES;

    until ($input ne $EMPTY_STR) {
        $input = prompt '> ';
    }

Remember too that, if you do choose to allow unless or until, exTRa attention will be required whenever a conditional expression is extended. In particular, if the extension adds a "negative" of any kind, then you will need to convert the negative control statement to the appropriate positive form, and rewrite of the original condition accordingly.

The added degree of complexity and awareness that this transformation always requires and the risk of introducing errors that it inevitably involves are two good reasons to reread the "Negative Control Statements" guideline, and to reconsider banning unless and until altogether.


    Previous Page
    Next Page