The document discusses using Exporter::Proxy to split up large Perl modules into smaller, more manageable chunks by functionality. It proposes splitting modules into "top-half" dispatcher modules that export symbols and dispatch calls to "bottom-half" modules that implement the specific functionality. This allows for a clean separation of interfaces from implementations and makes modules easier to maintain and extend over time.
2. Stuck in Lodi Again
● We've all been there: the 4000line module.
● It began small enough... then it grew.
● Cannedquery modules are classics for this:
● In the beginning was “userid”, and it was good, enough.
● Then it was split into get_userid/set_userid.
● Then it branched into get_dept_userid,
get_general_userid and set_*.
● That was then, your cleanup is now.
3. Welcome to the Machine
● You have to clean up the module.
● You need to split the thing into usable chunks.
● Separating out namespaces.
● Keeping shared variables in a reasonable place.
● Keeping utility subs out of the way.
● By tomorrow.
In parallel universes, in constant time.
4. This is Perl, however.
● So you will practice True Lazyness (c).
● This means doing it once.
● Doing it right.
● And never having to do it again.
5. Lazyness is a virtue
● Exporting in Perl should be easy, but Exporter is
complicated (and error prone).
● Breaking up large modules into manageable chunks
often takes too much work.
● Both of these should be easy.
6. Unlazy Shared Variables.
● Exporter allows sharing variables.
● If you inherit from it.
● If you keep proper track of EXPORT_OK,
EXPORT, EXPORT_TAGS.
● If, hopefully, @foo never changes to %foo.
● There has to be a better way...
7. False Lazyness:
● Break the module up by function, the names don't
immediately collide: Lookup::userid, Modify::userid
● But, you can't inherit both classes without collisions.
● You could try Lookup::Dept::userid and
Modify::Dept::userid.
● But do you really want to see:
$qryobj->Lookup::Dept::userid( ... );
Let alone type it?
8. UnLazy TopHalf Modules
● Another way to split up the modules is a single God
Object dispatcher that “figures out” how to dispatch
into the various pieces of the original module.
● This ends up being either a unmaintainable if
block, an unreadable nary ternary op, or the
Switch From Hell (tm).
● The problem is that the caller knows what they
want: expose them to the decisions and let them
make the proper one.
9. Exporter::Proxy
● This is Perl, there is a lazy way.
● E::P simplifies you work by:
● Exporting symbols.
● Installing an import sub to export the symbols.
● Optionally installing a dispatch sub.
● This allows you to split your modules into layers:
● A tophalf dispatcher that combines the moduels.
● The bottomhalf modules that actually do the work.
● Common shared variables, all stored in one place.
10. “The simplest interface is none at all”
● Instead of having to define a set of variables for your
set of variables, just:
use Exporter::Proxy qw( verbose debug foobar );
● Whatever names you provide are exported as
symbols via Symbol:
my $source = qualify_to_ref $_, $source;
my $install = qualify_to_ref $_, $caller;
*$install = *$source;
● $verbose, &verbose, @verbose, %verbose, all you
have to export is “verbose”.
11. Lazy Dispatcher
● These look pretty much the same: decide where the
call goes, send it there, and get out of the way.
● This is done in Exporter::Proxy with the “dispatch=”
argument.
● The dispatcher is exported by default.
● Each one dispatches calls within its own package:
my $name = splice @_, 1, 1;
my $dest = $package->can( $name )
or croak “$package cannot '$name'”;
goto &$dest;
12. Splitting Up The Interface
● You've probably witnessed *NIX device drivers .
● The top half validates and dispatches the call, the
bottom half just does things.
● The top half doesn't care what does on, it just sends
things where they belong.
● The bottom half doesn't care why it does things, it
just does them.
13. Breaking Up A Module
● You can usually break the combined interface up
into functional groups:
● “modify” vs. “lookup”
● “department” vs. “office” vs. “location”.
● “Taxid”, “GenBank”, “Medline”, “MeSH”.
● You can also combine any shared variables into a
single module that exports them as needed.
14. Looking At A Query Module
● “modify” and “lookup” are probably good divisions.
● The SQL for canned queries is probably sharable.
● The query methods don't care who their caller is.
● The dispatcher doesn't care why the methods were
called, it just has to hand back the data.
● Code something like:
$query->lookup( department => @argz );
$query->modify( department => @argz );
● Is reasonably mnemonic and easily extended.
15. Bottom Half Does the Work.
● The bottom half implements methods for specific
tasks and exports a single dispatcher for them.
● Break the module up into Query::Lookup,
Query::Modify, Query::Shared.
● Lookup & Modify install a dispatcher:
use Exporter::Proxy qw( dispatch=lookup );
use Exporter::Proxy qw( dispatch=query );
● They pull in shared content by Using Query::Shared:
package Query::Modify;
use Query::Shared qw( modify_dml ); # SQL hash
16. The Public Interface is Truly Lazy
● Bottom halves export their dispatchers: just collect
them together into a single API.
package Query;
use Query::Lookup;
use Query::Modify;
use Query::Shared qw( verbose );
sub verbose { @_ ? $verbose = shift : $verbose }
42
__END__
● The Top Half is DUMB: there are no decisions here,
no special cases, no edge cases.
17. What the Caller Sees
● Anyone using “Query” just knows that its API
includes “lookup”, “modify”, and “verbose”.
● The methods take a first argument of what to lookup
or modify, and whatever arguments it needs.
● They don't have to know about the bottomhalf,
shared variables, or utility sub's that aren't exported
by the bottomhalf modules into the API.
● This is what makes the Top Half dumb: the caller
makes their own decisions on what to call.
18. Lazy Growth
● Refactoring the interface into subject areas is also
straightforward:
● Add new modules “department”, “office”, “frobnicate”.
● Each has a mnemonic dispatcher, say, “dept”, “office”,
and “frob”.
● They implement whatever methods describe the action.
● They can share the existing SQL or define their own.
● The caller uses, say,
$query->dept( id => $dept_name );
19. Summary
● True Lazyness is a virtue.
● Dispatching interfaces offer a simple way to
segregate the classes.
● Tophalf classes can implement mnemonic API's.
● Bottom duty can be bearable with Exporter::Proxy.