Top Banner
Exporter::Proxy Splitting up modules to help your sanity. Steven Lembark Workhorse Computing
19

Exporter Proxy

May 27, 2015

Download

Technology

This helps break up bloated modules -- or keeping new ones manageable -- by simplifying variable and method export and adding package-space dispatchers to modules. The talk uses the example of an overgrown Query module.
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Exporter Proxy

Exporter::Proxy

Splitting up modules to help your sanity.

Steven LembarkWorkhorse Computing

Page 2: Exporter Proxy

Stuck in Lodi Again

● We've all been there: the 4000­line module.● It began small enough... then it grew.● Canned­query 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.

Page 3: Exporter Proxy

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.

Page 4: Exporter Proxy

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.

Page 5: Exporter Proxy

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.

Page 6: Exporter Proxy

Un­lazy 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...

Page 7: Exporter Proxy

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?

Page 8: Exporter Proxy

Un­Lazy Top­Half 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 un­maintainable if­block, an un­readable n­ary 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.

Page 9: Exporter Proxy

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 top­half dispatcher that combines the moduels.● The bottom­half modules that actually do the work.● Common shared variables, all stored in one place.

Page 10: Exporter Proxy

“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:

● $verbose, &verbose, @verbose, %verbose, all you have to export is “verbose”.

my $source = qualify_to_ref $_, $source;my $install = qualify_to_ref $_, $caller;*$install = *$source;

Page 11: Exporter Proxy

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;

Page 12: Exporter Proxy

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.

Page 13: Exporter Proxy

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.

Page 14: Exporter Proxy

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.

Page 15: Exporter Proxy

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

Page 16: Exporter Proxy

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.

Page 17: Exporter Proxy

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 bottom­half, shared variables, or utility sub's that aren't exported by the bottom­half modules into the API.

● This is what makes the Top Half dumb: the caller makes their own decisions on what to call.

Page 18: Exporter Proxy

Lazy Growth

● Re­factoring 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 );

Page 19: Exporter Proxy

Summary

● True Lazyness is a virtue.● Dispatching interfaces offer a simple way to 

segregate the classes.● Top­half classes can implement mnemonic API's.● Bottom duty can be bearable with Exporter::Proxy.