I've been using refactoring techniques for Perl programming, recently, and I was wondering about others' experiences with it. I'll start by defining refactoring and how I do it... there is an excellent book on the subject, Refactoring: Improving the Design of Existing Code, by Martin Fowler. The examples are in Java, but many of the refactoring techniques described therein can be applied to Perl.
Anyway, like the book title says, refactoring is the process of taking existing programs and improving their designs, step-by-step. I find it helpful when in the course of my job I need to add features to legacy code that someone else (or a previous, less learned version of myself) wrote. You can boil down refactoring to these steps applied repeatedly:
- Pick a patch of code that's desperately in need of improvement. (A subroutine, for example.)
- Write a small test program that tests the current functionality of that piece of code. (NOTE: Not the expected functionality... this is not bug fixing or feature addition. The current functionality.)
- Check your tests and code into some kind of version control system, so that you can always retreat to a previous version if you cause a bug.
- Make a small change that makes the code easier to comprehend and enhance. (For example, pull a small function out of a larger one. Turn a global variable into a function call.)
- Run the test you've just defined, plus any other tests you've defined for the program.
- Repeat.
Using the Test and Test::Harness modules that come along with Perl saves a lot of time while writing the tests.
In his book, Fowler defines a list of "refactorings"-- small procedures for improving code. You can get a list of them at his website, http://www.refactoring.com. I'm working on a similar list focused on Perl, and hope to put up a website collecting them. An example refactoring for Perl would be:
Transform Global Variable To Function Call
Indication: You've got a global or package variable that is being used by a number of different functions to communicate some value.
Solution: Replace the global variable with an exportable function which returns an equivalent value.
Steps:
- Extract the part of code that initializes the global variable into an exportable function.
- Tie the global variable to that function.
- Test.
- Replace instances of the global variable with calls to the new function.
- Test.
- Untie the global variable from the function.
- Test.
Other Perl-oriented refactorings would include:
- Unreinvent Wheel: replace needlessly rewritten code (like a CGI parser or templating system) with a call to a CPAN module.
- Strictify Module: Gradually remove errors under 'use strict', testing as you go, until you can finally run the module under 'strict'.
- Localize Variable: A variable is used only in a single subroutine. Good! Localize it with 'my'.
These would be better with some examples; I'll post some as followup if people are interested. Many of these things are obvious, common-sense things to do, but it's frequently useful to have them written down someplace so that one isn't tempted to skip a step.
Refactoring is extremely useful to me because it gives me a middle-ground alternative between living with incomprehensible code and tossing and rewriting a large Perl project. For small projects it's not such a problem to rewrite from scratch, but I've inherited more than a couple of huge projects consisting of poorly-written RUNNING code. It's my experience that all specs and documentation for such projects are completely out of date. No one knows everything that the software does, but people are out there using it, and management isn't interested in funding a long rewriting project that may lead nowhere... they just want this one feature added. Oh, and this other one. Oh, and this other one, too...
Sorry to have run on for so long, but it's a big topic and I wanted to at least scratch the surface of it. What experiences do people have with this sort of thing? Any refactorings that you find helpful? Any questions?
stephen
Update: I am now maintaining a list of Perl refactorings on my homenode.