Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight
 
PerlMonks  

Refactoring Perl #1 - Extract Method

by agianni (Hermit)
on Jun 22, 2007 at 04:15 UTC ( #622705=perlmeditation: print w/ replies, xml ) Need Help??

You have a code fragment that can be grouped together

Turn the fragment into a method whose name explains the purpose of the method

(Fowler, p.110)

Fowler's first refactoring pattern is probably the most intuitive and the one I use 90% of the time when I'm refactoring. That's part of the reason I'm going through these patterns, because I know there are plenty of other useful ones out there (there are 72 in the book after all).

Here is Fowler's example in Perl code:

sub print_owing{ my $self = shift; my $e = $self->{_orders}->elements(); my $outstanding = 0.0; # print banner print "**************************\n"; print "***** Customer Owes ******\n"; print "**************************\n"; # calculate outstanding while ( $e->has_more_elements() ){ my $each = $e->next_element(); $outstanding += $each->get_amount(); } # print details print 'name: ', $self->{_name}, "\n"; print "amount: $outstanding\n"; }

becomes:

sub print_owing{ my $self = shift; $self->print_banner(); my $outstanding = $self->get_outstanding(); $self->print_details( $outstanding ); } sub print_banner{ my $self = shift; # print banner print "**************************\n"; print "***** Customer Owes ******\n"; print "**************************\n"; } sub print_details{ my $self = shift; my $outstanding = shift; print 'name: ', $self->{_name}, "\n"; print "amount: $outstanding\n"; } sub get_outstanding{ my $self = shift; my $e = $self->{_orders}->elements(); my $result = 0.0; # calculate outstanding while ( $e->has_more_elements() ){ my $each = $e->next_element(); $result += $each->get_amount(); } return $result; }

Fowler outlines the intricacies through example in three steps but I won't go into that much detail here because I think it's pretty straight forward. You should really buy the book if you're interested. The code with tests include the steps in the refactoring process so you can see the whole thing in Perl.

Instead, I'd like to discuss how I've written the unit tests for the example, as testing is an important part of the refactoring process and writing tests for the refactoring patterns will likely require some decent understanding of Perl unit testing methodologies.

There are three non-standard modules that I use in the tests for this code: Test::MockModule, Test::MockObject and Test::Output. The first two are very useful for working with tests for incomplete modules / classes -- which is the case here -- or situations where you don't want to do a great deal of setup of environmental factors just to test a single method / subroutine. Test::Output is very useful to testing code that generates STDOUT or STDERR as this code does. In order to run these tests, you'll need these three modules. I highly recommend Ian Langworth and chromatic's Perl Testing, A Developer's Notebook if you're not comfortable with writing and maintaining tests.

In the case of the test for Refactoring::ExtractMethod::*, I use Test::MockModule to mock up a new method rather than include it in the class itself. I also used Test::MockObject to put together a quick and dirty object for the _orders object variable. For the purpose of this example it may be overkill, but this is generally how I like to write my tests: isolate the test to the functionality of the method we are testing. Of course this will come back to bite me in the very next refactoring example, but we'll get to that later :)

For now I'll be keeping one tarball of all of the code and I'll update it when I have additional code to share. I'm not sure that's a good long term option, and I'm open to suggestions -- I might CPAN it if I can maintain momentum here.

I welcome comments and conversation on all aspects of this example: implementation of the example in Perl, testing methodology, tactics for maintaining this series of posts, what have you.

perl -e 'split//,q{john hurl, pest caretaker}and(map{print @_[$_]}(joi +n(q{},map{sprintf(qq{%010u},$_)}(2**2*307*4993,5*101*641*5261,7*59*79 +*36997,13*17*71*45131,3**2*67*89*167*181))=~/\d{2}/g));'

Comment on Refactoring Perl #1 - Extract Method
Select or Download Code
Re: Refactoring Perl 1 - Extract Method
by rhesa (Vicar) on Jun 22, 2007 at 11:37 UTC
    Thanks, agianni, looks good.

    A little while ago I stumbled on a perl script to perform this refactoring. I found it through Piers Cawley's blog in this post. The script itself is written by Jesse Vincent, and can be found here. Read the comments on Piers' post for further instructions. I've hooked it into my vimrc, and it's proven to be a nice tool.

      I haven't had a chance to try the script out, but I'd like to get myself set up to use some of these tools that provide refactoring capacity. I have briefly looked at Komodo and Eclipse but my development environment doesn't make them particularly easy to use and I haven't been able to take the time to get myself setup with them. I've been using emacs for years and just haven't seen the value in switching just yet.

      I would definitely be interested to see other's comments on refactoring tools for Perl. I know Devel::Refactor was built specifically for use within Eclipse. Any others out there?

      perl -e 'split//,q{john hurl, pest caretaker}and(map{print @_[$_]}(joi +n(q{},map{sprintf(qq{%010u},$_)}(2**2*307*4993,5*101*641*5261,7*59*79 +*36997,13*17*71*45131,3**2*67*89*167*181))=~/\d{2}/g));'
        After reading this thread, I hooked up a small Devel::Refactor script to emacs at the weekend. It works, but I haven't used it seriously, and I'm not convinced it provides a huge productivity leap

        Anyway, here's how...

        in your .emacs:

        (defun refactor-extract () "Change region into a subroutine" (interactive) (shell-command-on-region (point) (mark) "./refactor-extract" t) )

        and some Perl in refactor-extract:

        #!/usr/bin/perl ### Rewrite code block as a subroutine # This is mostly intended to be used through emacs' # shell-command-on-region # #(defun refactor-extract () # "Change region in to a subroutine" # (interactive) # (shell-command-on-region (point) (mark) "./refactor-extract" t)) # #Based on a Perlmonks node: http://www.perlmonks.org/?node_id=622772 use strict; use warnings FATAL => 'all'; use Devel::Refactor; my $code = join '', <>; my $name = "refactored_" . sprintf( "%04d", rand() * 10000 ); print join "\n", Devel::Refactor->new->extract_subroutine( $name, $cod +e );

        use Disclaimer::Std;
        This was a quick hack, quickly hacked. Use at your peril.

Re: Refactoring Perl 1 - Extract Method
by Arunbear (Parson) on Jun 22, 2007 at 21:54 UTC
    Stating the obvious perhaps, but as use of objects/methods is optional in Perl (but not in Java from which you're translating), and having a code fragment that can be grouped together arises whether or not you're writing in OO style, the answer Turn the fragment into a method ... could just as well be Turn the fragment into a subroutine ..., i.e. refactoring in Perl is not limited to object oriented considerations.

      Absolutely. Many of Fowler's refactoring patterns are specific to OO design, but there are plenty of them that are more generally applicable to most subroutine-driven, imperative programming language. I will try to keep that differentiation in mind as I review future patterns.

      perl -e 'split//,q{john hurl, pest caretaker}and(map{print @_[$_]}(joi +n(q{},map{sprintf(qq{%010u},$_)}(2**2*307*4993,5*101*641*5261,7*59*79 +*36997,13*17*71*45131,3**2*67*89*167*181))=~/\d{2}/g));'
Re: Refactoring Perl 1 - Extract Method
by doom (Deacon) on Jun 24, 2007 at 04:50 UTC
    Test::Output is very useful to testing code that generates STDOUT or STDERR as this code does.
    I've been getting into Test::Trap of late, myself, which I think I've seen mentioned here. It has a pretty simple interface, and you can use it to trap errors as well.

    I highly recommend Ian Langworth and chromatic's Perl Testing, A Developer's Notebook if you're not comfortable with writing and maintaining tests.

    I like that book quite a bit, but what I would say is that if you're not into writing tests yet, you should look at the documentation for Test::More. There's no end of little tricks to writing tests (as in any kind of programming) but there's plenty of material to get started with right there in "Test::More" [1].

    I have this fear that at some point the standard practice for testing perl is going to involve frameworks so elaborate that writing tests is going to turn into a sub-specialty that people are going to be afraid to get involved with...

    [1] I have one complaint about the "Test::More" docs though: it's long since time they should stop directing newbies to "Test::Simple" first... "Test::More" is clearly the new standard.

      I think it's important for developers to figure out what testing scheme works well given the context of the tests they are writing. I use Test::Class for much of my testing these days because I'm working on large projects and T::C helps me keep things organized. But for small projects I still just use Test::More by itself to keep things simple. I will actually include examples of using Test::Class in later writeups. It's actually really easy an intuitive to use and allows for the use of T::M functionality within a XUnit framework.

      perl -e 'split//,q{john hurl, pest caretaker}and(map{print @_[$_]}(joi +n(q{},map{sprintf(qq{%010u},$_)}(2**2*307*4993,5*101*641*5261,7*59*79 +*36997,13*17*71*45131,3**2*67*89*167*181))=~/\d{2}/g));'

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlmeditation [id://622705]
Front-paged by naikonta
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others scrutinizing the Monastery: (17)
As of 2014-10-01 19:08 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    What is your favourite meta-syntactic variable name?














    Results (34 votes), past polls