Skip navigation

Don’t put the implementation of your program in your program; put it in a module instead. There are at least three reasons for doing things this way:

  • Your code is more testable. You can feed in arbitrary command lines and check the exit code.
  • Your code is reusable. If you need to use your program from another Perl program, there’s no need to use system, backticks, or IPC::System::Simple, just use your module and call the implementing subroutine.
  • Your programs can be better managed by the standard Perl toolchain. Program versions are not checked by CPAN, et. al. so if you create a new version of an existing program, it won’t be upgraded. If all of your code is in a module, then version changes will cause updates to be installed.

A simple way of doing this would be to have a module that looks like


 1 package ProgramImplementation;
 2
 3 use utf8;
 4 use 5.010;
 5
 6 use strict;
 7 use warnings;
 8
 9 use Exporter qw< import >;
10
11 our @EXPORT_OK = qw< run >;
12
13
14 sub run {
15     my (@argv) = @_;
16
17     …
18
19     return 0;
20 }
21
22 1;

and then use that like this:


 1 use utf8;
 2 use 5.010;
 3
 4 use strict;
 5 use warnings;
 6
 7 use ProgramImplementation qw< run >;
 8
 9 return 1 if caller;
10 exit run(@ARGV);

Having the “return 1 if caller” in there means that the program can be required without causing the requiring program to exit. (Whether you ought to actually do that is another matter.)

If you want things to be even more reusable, make your program implementation an object with attributes for the standard file handles:


 1 package ObjectProgramImplementation;
 2
 3 use utf8;
 4 use 5.010;
 5
 6 use Moose;      # Or your favorite object module.
 7
 8 has stdout => (
 9     isa => FileHandle,
10     …
11     default => sub { \*STDOUT },
12 );
13
14 has stderr => (
15     isa => FileHandle,
16     …
17     default => sub { \*STDERR },
18 );
19
20
21 sub run {
22     my ($self, @argv) = @_;
23
24     say { $self->stdout() } Hello there!;
25     …
26
27     return 0;
28 }
29
30 1;

Your regular program would then look like this:


 1 use utf8;
 2 use 5.010;
 3
 4 use strict;
 5 use warnings;
 6
 7 use ObjectProgramImplementation qw< >;
 8
 9 return 1 if caller;
10 exit ObjectProgramImplementation->new()->run(@ARGV);

But you can have other uses like


 1 my $stdout;
 2 my $stderr;
 3 open my $stdout_handle, >, \$stdout;
 4 open my $stderr_handle, >, \$stderr;
 5
 6 my $program =
 7     ObjectProgramImplementation->new(stdout => $stdout, stderr => $stderr);
 8 my $exit_code = $program->run(@ARGV);
 9
10 # Do something with $stdout/$stderr