Ïðèãëàøàåì ïîñåòèòü
Êëþåâ (klyuev.lit-info.ru)

5.5 Selective Disabling

Previous Table of Contents Next

5.5 Selective Disabling

As you might have guessed by now, I am in favor of cranking up warnings and strictness to their maximum levels. Sometimes, however, it makes sense to silence them because you're violating the rules intentionally and you know what you're doing.

5.5.1 Learn How to Say no

There's no need to turn off strictness or warnings altogether just because they get in your way. If you can't make a reasonable adjustment to your code to avoid triggering them, disable just the part that's in the way, and only for long enough to get the job done. You can do this with the no strict pragma; for example:


no strict 'refs';

no is the opposite of use; it disables the specified part of the pragma for its lexical scope. All other parts of the pragma remain in force.

5.5.2 Turning Off Strictness

no strict 'vars' I cannot think of a single case where this would be necessary. Any time you feel the urge to turn off strict 'vars', use our or, in perls that precede the introduction of our, use vars. It's certainly legitimate to want to avoid fully qualifying package variables in situations where you don't want to hard-code the package name, or might not even know what it is, but our/use vars will still do the job without turning off strictness.

I have looked at many programs that used no strict 'vars', and in every case, they could have easily been written without it.

no strict 'subs' There is even less reason to resort to this. The last remotely good reason to do so was to pass filehandles to subroutines:


open(REPORT, "evil-empire.takeovers");

my @totals = extract_totals(REPORT);

# [...]

sub extract_totals

{

  local *FH = shift;

  # [...]

But (imagine a Californian teenager accent here) that is so Perl 4. In fact, even in Perl 4 you could satisfy strictness by passing the typeglob instead:


@totals = extract_totals(*REPORT);

(Well, aside from the fact that Perl 4 didn't have the strict pragma, but you get the idea.) See Section 7.9.1 for more information on how to improve this code with more modern Perl features.

no strict 'refs' Finally we have a candidate for Most Useful Negation of a Strict Pragma. There are indeed times when a symbolic reference really is the right thing to do, and you have to ask strict 'refs' to step aside. For instance, creating a bunch of look-alike accessor methods:


sub _attr

{

  my ($self, $name) = splice @_, 0, 2;

  $self->{$name} = shift if @_;

  return $self->{$name};

}



for my $method (qw(email name telephone address))

{

  no strict 'refs';

  *$method = sub { _attr($_[0], $method, @_[1..$#_] };

}

# Now with a $person object we can call

# $person->email, or $person->name, etc

This approach has the advantage that all the methods share the same piece of code rather than having separate implementations each taking up their own space. Without disabling strict 'refs' Perl will object to the assignment to *$method:


Can't use string ("email") as a symbol ref while "strict refs" in use...

In case you're unfamiliar with the * notation, this is called a typeglob, a frighteningly arcane term meaning all the types of package variable with the name that follows the *. In this code, what follows the * is not a word but a variable, which is therefore used as a symbolic reference. Because we are assigning a reference to the typeglob, Perl can tell the type of variable we want to assign, and therefore populates only that type of package variable and not every entry in the typeglob. In our code, the reference is a code reference, and so we are creating a new subroutine (subroutines are package variables) whose name is whatever is stored in $method.

We have, however, created all of the methods regardless of which ones we end up actually using, so you might prefer to create methods only when they're needed:


1  # Same _attr() method as before, and then:

2  sub AUTOLOAD

3  {

4    (my $method = our $AUTOLOAD) =~ s/.*://;

5    no strict 'refs';

6    *$method = sub { _attr($_[0], $method, @_[1..$#_]) };

7    goto &$method;

8  }

Incidentally, it's line 6 that needs no strict 'refs', not line 7; for what it's worth, calling subroutines indirectly with goto does not violate strict 'refs'.

This just-in-time method creation is fine, but somewhat permissive; we really need to limit the user to just the methods we want. No problem:


1  sub AUTOLOAD

2  {

3    (my $method = our $AUTOLOAD) =~ s/.*://;

4    { map { $_ => 1 } qw(email name telephone address)

5    }->{$method}

6      or croak "Invalid method $method"

7    no strict 'refs';

8    *$method = sub { _attr($_[0], $method, @_[1..$#_]) };

9    goto &$method;

10 }

I've added a highly concise construction in lines 4 through 6 for checking whether the given method is in the allowed list; a level 6 idiom at least. Set membership is best done with a hash, so I made a reference to an anonymous hash whose keys are the allowed methods, then deferenced the element with key $method. If it's in the hash, it'll have the (true) value 1. If it isn't, the hash lookup will return the (false) value undef, and the code will call croak(). A more maintainable module would enumerate its object attributes at the top of the code in a global hash, which we could then reference instead of the literal list in line 4.

Both the all-at-once and the on-the-fly approaches to creating methods suffer from the disadvantage that the methods are created at run time and there fore static analysis tools won't see them. Using the all-at-once approach and wrapping that code in a BEGIN block may help.

There are other legitimate uses of no strict 'refs' besides creating methods, such as exporting a variable to your caller's package (although arguably that's just generalizing method creation to other types of package variable).

5.5.3 Turning Off Warnings

Temporarily disabling lexical warnings is a snap: Find out the name for the class of warning you want to ignore, and wrap a block containing a no warnings pragma for that class around the code that violates it.

Here's an example: Suppose you want to override an existing subroutine and make it do something else. Two good reasons for wanting to do this are temporarily overriding one of your own subroutines for debugging purposes, and overriding a method in a module you didn't write so that you can change its behavior. For instance, I've done this with a graphing module from CPAN when I needed to get it to produce some graphs whose margins were beyond its usual repertoire. The easiest way of overriding a subroutine is just to define it again:


# Adjusting from Celsius for USA-centric users

sub temperature

{

  my $self = shift;

  return ($self->{temperature} + 40) * 9 / 5 - 40;

}

But Perl will warn us that this might be a mistake:


Subroutine temperature redefined at...

We can handily take care of that with:


{

  no warnings 'redefine';

  # Adjusting from Celsius for USA-centric users

  sub temperature

  {

    my $self = shift;

    $self->{temperature} * 9 / 5;

  }

}

Yes, it's possible to redefine a subroutine in such a way as to not need to disable warnings, but the preceding code is perfectly clear. Well, . . . here's how you would do it:


BEGIN

{

  undef &temperature;

  *temperature = sub

  {

    my $self = shift;

    return ($self->{temperature} + 40) * 9 / 5 - 40;

  };

}

First we undefine the old subroutine, then install a new one by assigning to the typeglob. We have to put it in a BEGIN block to get the same effect as we had with our earlier warning-disabling code, because saying "sub temperature" creates a subroutine definition at compile time; but assigning to a typeglob is a run time action and therefore any calls to temperature() before we get to that code will call the old subroutine unless we preemptively hoist the redefinition into the compilation phase.[5]

[5] If we just wanted to replace all calls to temperature() after the run time point of redefinition, then we could say local *temperature = sub { ... }

There; wasn't it easier just to disable the warning?

5.5.4 Disabling Warnings with $^W

If you're using a Perl that precedes the introduction of lexical warnings, their moral equivalent is the special variable $^W. When that flag is set to 1, warnings—all warnings—are enabled, as if you'd typed -w on the shebang line, and when it is set to 0, warnings are off. You can thus enable and disable warnings to your heart's content throughout your code. To have warnings revert to their previous state when you're done modifying them, make the assignment to $^W temporary via local() and put it in a block to limit its scope:


{

  local $^W = 0;  # Disable warnings

  $x = $y + $z;   # Use possibly undef variable

}

Remember, however, that assigning to any variable is a run time action and many warnings happen at compile time; to affect them you must put your assignment in a BEGIN block. Disabling warnings for the subroutine redefinition example is particularly difficult because you cannot just redefine the subroutine the obvious way in the same BEGIN block as the assignment to $^W:


BEGIN

{

  local $^W = 0;            # WON'T WORK

  sub temperature { ... }

}

The subroutine will be compiled before the assignment is executed because that's how things are done, whether they're in BEGIN blocks or not. You either have to use the typeglob assignment from earlier:


BEGIN

{

  local $^W = 0;

  *temperature = sub { ... };

}

or you have to do something even more despicable:


{

  my $warn_save;

  BEGIN

  {

    $warn_save = $^W;

    $^W = 0;

  }

  sub temperature { ... }

  BEGIN

  {

    $^W = $warn_save;

  }

}

This shows you how useful lexical warnings are, now that you see how unpleasant the alternatives can be!

5.5.5 Redirecting Errors and Warnings

There are some warnings you don't want the user to see in detail, though; they might reveal too much about your code. This is even more true of fatal exceptions, which often incorporate (via the croak() or confess() functions of the Carp module) details of function calls and their arguments. And the worst environment for that to happen in is a public web site whose users are not normally entitled to read program source code and among whose number could lurk unsavory characters to whom you'd rather not allow your database password to be exposed.

Randal Schwartz addressed this in a Linux Magazine column[6] with a module called FatalsToEmail, designed to send the text of fatal exceptions in e-mail to the developer while leaving the user with a nondescript apology.

[6] http://www.stonehenge.com/merlyn/LinuxMag/col14.html

Here's my version, a module called EmailErrors designed to fulfill a similar purpose, but with a few key differences. It allows exceptions to propagate normally if the code is running in an eval block, or if it's being run at the command line instead of via the web. It handles warnings as though they were exceptions. However, you'll have to insert some code to handle your specific use of the module where indicated by "##":

Example 5.1. EmailErrors Module

package EmailErrors;

use strict;

use warnings;

BEGIN

{

  # If we're in an eval, or not being used from the web,

  # allow the exception to be handled normally.

  # Otherwise, if we die, send it to an error handler.

  $SIG{__DIE__} = sub

  {

    return unless $ENV{REMOTE_ADDR};

    my $in_eval = 0;

    for (my $stack = 1;

         my $sub = (CORE::caller($stack))[3];

         $stack++)

    {

      $in_eval = 1 if $sub =~ /^\(eval\)/;

    }

    unless ($in_eval)

    {

      goto &throw;

    }

  };





  # For warnings, do the same thing.

  $SIG{__WARN__} = sub

  {

    CORE::warn(@_) and return unless $ENV{REMOTE_ADDR};

    local $SIG{__WARN__};  # Avoid possible recursion

    goto &throw;

  };





  sub throw

  {

    my $text = shift;

    $text = do { require Carp::Heavy; Carp::longmess($text) };

    ## Handle denial of service issues

    ## Print desired text to user

    ## Log error text

    ## Email error text

  }

}



1;

Here's what you'd do with those comments: If you're using the code in a web application and you're worried about bad guys slowing your server down by triggering many e-mails in succession, then keep a first-in, first-out cache of warnings and timestamps; if a warning is repeated too soon, ignore it. Decide what you want the user to see; in a web application, you might use a special error template. If you're keeping logs of what your application does, log the error. Finally, send the e-mail message containing the actual error text to whoever in your organization is unlucky enough to be detailed to handle such things.

5.5.6 Don't Rush to Disable

Don't disable a warning when you don't know why it happens. I have lost count of how many times I have heard someone ask what was wrong with their program, and on inspection, I discovered that they did not have warnings enabled. If they had, they would have received a giant clue as to what the problem was—but they said they had disabled the warnings because of all the messages that were produced. This is plugging your ears when your mentor is yelling, "I THINK THERE'S SOMETHING WRONG HERE!"

    Previous Table of Contents Next