18.10. Overriding Strictures
Sometimes you really do need to implement something arcane; something that would cause use strict or use warnings to complain. In this case, because you'll always be using both those pragmas (see the previous three guidelines, "Strictures", "Warnings", and "Correctness"), you'll need to turn them off temporarily. The key to doing that without compromising the robustness of your code is to turn off warnings and strictures in the smallest possible scope. And to turn off only the particular warnings you intend to cause or those specific strictures that you're intentionally violating. For example, suppose you needed a Sub::Tracking module that, when passed the name of a subroutine, would modify that subroutine so that any subsequent call to it was logged. For example: use Digest::SHA qw( sha512_base64 ); use Sub::Tracking qw( track_sub ); track_sub('sha512_base64'); Such a module might be implemented as in Example 18-1. Example 18-1. A module for tracking subroutine calls package Sub::Tracking; use version; our $VERSION = qv(0.0.1); use strict; use warnings; use Carp; use Perl6::Export::Attrs; use Log::Stdlog {level => 'trace'}; The _make_tracker_for( ) utility subroutine creates a new anonymous subroutine that first logs the fact that it has been called: print {*STDLOG} trace => "Called $sub_name(@_) from package $package at '$file' line $line"; then turns itself into the original subroutine instead[*]:
goto &{$orig_sub_ref}; The Sub::Tracking::track_sub( ) subroutine expects to be passed the name of the subroutine to be tracked. It takes that name, prepends the caller's package name ($caller.'::'.$sub_name), and then looks up that fully qualified name to see if there is a corresponding subroutine entry in the caller's symbol table (*{$full_sub_name}{CODE}). The result of this look-up will be either a reference to the named subroutine or undef (if no such subroutine exists). track_sub( ) then creates a new tracking version of the subroutine: my $tracker_ref = _make_tracker_for($sub_name, $sub_ref); and installs it back in the caller's symbol table: *{$full_sub_name} = $tracker_ref; The problem here is that both the symbol table look-up and the symbol table assignment use a string ($full_sub_name) as the name of the symbol table entry, rather than a hard reference to it. Using a string instead of a real reference would normally incur the wrath of use strict, but the no strict 'refs' declarations tell the compiler to turn a blind eye. Of course, it's particularly tedious to have to set up those tiny block scopes to contain the no strict declarations, especially when you could get the same effect simply by omitting the use strict at the start of the module: package Sub::Tracking; # use strict -- Disabled because symbolic references needed below use warnings; use Carp; use Stdlog; use version; our $VERSION = qv(0.0.1); # etc. But that's a bad practice, because it would remove the strictures not only from the two lines where they're not wanted, but from every other line as well. That could easily mask other strictness violations that you would still like to be informed of. Nor would it have been acceptable to turn off strict references throughout the track_sub( ) subroutine: sub track_sub : Export { my ($sub_name) = @_; no strict 'refs'; # Locate the (currently untracked) subroutine in the caller's symbol table... my $caller = caller; my $full_sub_name = $caller.'::'.$sub_name; my $sub_ref = *{$full_sub_name}{CODE}; # Or die trying... croak "Can't track nonexistent subroutine '$full_sub_name'" if !defined $sub_ref; # Then build a tracked version of it... my $TRacker_ref = _make_tracker_for($sub_name, $sub_ref); # And install that version back in the caller's symbol table... *{$full_sub_name} = $tracker_ref; return; } That would still exclude far more code from strictness-checking than was (ahem) strictly necessary. Wrapping extra do blocks or raw blocks tightly around any statement that is deliberately violating strictness is tedious, but not as tedious as spending an hour debugging some unexpected symbolic reference, unauthorized package variable, or undeclared subroutine that use strict would otherwise have caught. |