views:

197

answers:

4

I have a Perl module for a project. I have maybe a dozen programs hanging off it and a lot of it is garbage. I hadn't spent much close personal time with DBI before, so that part is fixable, but the big thing is that it is big. Literally 2KLOCs.

It would be easy to break up this function (let's call it Dumb.pm) into separate modules ( Dumb::FormTools , Dumb::Database, etc.) except, as I said, there's lots of programs that already 'use Dumb ;'

I would like to export Dumb::Database's exportable functions through Dumb without having to have variations of this over and over again:

sub my_dumb_function { return Dumb::Database::my_dumb_function( @_ ) ; }

It isn't that I'm above that. It's just that this seems like the dumb and inelegant way of handling the issue. I used the "Don't know no better" excuse once, and once is really more than you get. Help?

+3  A: 

Not sure how you are currently using it (does it currently export methods?), but you can set up the new child modules to allow you to import their functions (using Exporter), and then just have the original module explicitly import the now broken out pieces. Something like:

package Dumb;

use Dumb::Database qw(my_dumb_function);

1;

package Dumb::Database;

use base qw(Exporter);

our @EXPORT_OK = qw(my_dumb_function);

sub my_dumb_function { 1; }

1;
macabail
I was only able to get it to work if I used "use Exporter qw(import) ;", but that does mean I was able to get it to work. Thank you!
VarLogRant
You can also inherit from Exporter, which is what I meant it to do there. I've corrected for that, sorry.
macabail
+5  A: 

It's difficult to give you specific advice because different code bases require different strategies. I refactor a module with 500-line subroutines differently than one with small subroutines and a lot of repeated code. If I need to change the interface too, there are different strategies for that.

  1. Get everything into source control. You need to keep around the original and intermediate versions.
  2. If you don't already have a test suite, write one. Get the test coverage as high as you can. This test suite is the baseline for preserving the same behavior in the future versions, bugs and all. You're probably going to encounter a program that depends on a bug in the original module.
  3. Start hacking away. At each step, check that the rest still passes the original tests and that the published interface still results in the same behavior.

I think your actual question, though, is "How to I export to the original module that loaded Dumb?". You can provide your own import routine that uses Exporter's import_to_level method. You can import to higher levels than the immediate one that loaded you. The Dumb::Database import can thus load its exports into the namespace that loaded Dumb even though it's Dumb that loads Dumb::Database.

brian d foy
I don't see why you're recommending `import_to_level`. `Dumb` is going to be a backwards-compatibility module until `use Dumb;` can be replaced by the individual modules that particular program needs. Why should you write a custom `import` in each new module that has to decide what level to export to when the stock `import` will allow `Dumb` to re-export the functions it imports from the new modules?
cjm
If `Dumb` needs to be split into separate modules and the top level programs still want to get the exports from those separate modules just by using `Dumb`, then you don't want to import in `Dumb`. However, I'm not recommending it as the solution if the OP wants to do something else. There are a lot of ways to go here. Your answer works, but I think it's inelegant that you have to import into Dumb just so you can export the same thing to the higher level.
brian d foy
As I understood the OP's question, he has a giant module that does a bunch of different things. He wishes he'd used individual modules, so a script could load only the ones it actually used. But he doesn't want to have to track down every program that says `use Dumb` and fix it to import the correct modules. He needs a Dumb.pm with the existing exports for backwards compatibility, so he can gradually fix the programs that `use Dumb` to use only the modules that they actually need.
cjm
That's one way to see the question, but I think that's adding some assumptions that aren't in the question. I didn't make the same assumption as you. Those might be warranted, but only he can clarify want he wants to do and which answer suits him better. :)
brian d foy
+2  A: 

I assume that Dumb.pm currently uses Exporter. Assuming that you don't want to rename the functions (just split them into separate modules), you should be able to keep the existing @EXPORT definitions, import everything from your submodules, and simply re-export the functions.

package Dumb;
use Dumb::FormTools ':all';
use Dumb::Database  ':all';

use Exporter 'import';

our @EXPORT = ...;    # Unchanged from original version
our @EXPORT_OK = ...; # Unchanged from original version

1;

The :all tag is not defined by default. You have to define it manually (in each submodule).

our %EXPORT_TAGS = ( all => [ @EXPORT, @EXPORT_OK ] );
# or, for a module that doesn't export anything by default:
our %EXPORT_TAGS = ( all => \@EXPORT_OK );

On the other hand, if a submodule has no @EXPORT_OK functions, then you can skip the :all tag and just say use Dumb::Submodule;.

cjm
A: 

You might also want to look into Sub::Exporter

Adam Flott