Home > Articles > Web Development > Perl

  • Print
  • + Share This
  • 💬 Discuss

Item 55. Make flexible output.

When you use hard-coded (or assumed) filehandles in your code, you limit your program and frustrate your users. Some culprits look like these:

print "This goes to standard output\n";
print STDOUT "This goes to standard output too\n";
print STDERR "This goes to standard error\n";

When you put those sorts of statements in your program, you reduce the flexibility of the code, causing people to perform acrobatics and feats of magic to work around it. They shouldn't have to localize any filehandles or redefine standard filehandles to change where the output goes. Despite that, people still code like that because it's quick, it's easy, and mostly, they don't know how easy it is to do it better.

You don't need an object-oriented design to make this work, but it's a lot easier that way. When you need to output something in a method, get the output filehandle from the object. In this example, you call get_output_fh to fetch the destination for your data:

sub output_method {
  my ( $self, @args ) = @_;

  my $output_fh = $self->get_output_fh;

  print $output_fh @args;
}

To make that work, you need a way to set the output filehandle. That can be a set of regular accessor methods. get_output_fh returns STDOUT if you haven't set anything:

sub get_output_fh {
  my ($self) = @_;

  return $self->{output_fh} || *STDOUT{IO};
}

sub set_output_fh {
  my ( $self, $fh ) = @_   ;

  $self->{output_fh} = $fh;
}

With this as part of the published interface for your code, the other programmers have quite a bit of flexibility when they want to change how your program outputs data:

$obj->output_method("Hello stdout!\n");

# capture the output in a string
open my ($str_fh), '>', \$string;
$obj->set_output_fh($str_fh);
$obj->output_method("Hello string!\n");

# send the data over the network
socket( my ($socket), ... );
$obj->set_output_fh($socket);
$obj->output_method("Hello socket!\n");

# output to a string and STDOUT at the same time
use IO::Tee;
my $tee =
  IO::Tee->new( $str_fh, *STDOUT{IO} );
$obj->set_output_fh($tee);
$obj->output_method("Hello all of you!\n");

# send the data nowhere
use IO::Null;
my $null_fh = IO::Null->new;
$obj->set_output_fh($null_fh);
$obj->output_method("Hello? Anyone there?\n");

# decide at run time: interactive sessions use stdout,
# non-interactive session use a null filehandle
use IO::Interactive;
$obj->set_output_fh( interactive() );
$obj->output_method("Hello, maybe!\n");

It gets even better, though. You almost get some features for free. Do you want to have another method that returns the output as a string? You've already done most of the work! You just have to shuffle some filehandles around as you temporarily make a filehandle to a string (Item 54) as the output filehandle:

sub as_string {
  my ( $self, @args ) = @_;

  my $string = '';
  open my ($str_fh), '>', \$string;
  my $old_fh = $self->get_output_fh;
  $self->set_output_fh($str_fh);
  $self->output_method(@args);

  # restore the previous fh
  $self->set_output_fh($old_fh);

  $string;
}

If you want to have a feature to turn off all output, that's almost trivial now. You just use a null filehandle to suppress all output:

$obj->set_output_fh( IO::Null->new )
  if $config->{be_quiet};

Things to remember

  • For flexibility, don't hard-code your filehandles.
  • Give other programmers a way to change the output filehandle.
  • Use IO::Interactive to check if someone will see your output.
  • + Share This
  • 🔖 Save To Your Account

Discussions

comments powered by Disqus