重构
Refactoring, a First Example
How do I begin to write about refactoring? The traditional way to begin talking about something is to outline the history, broad principle, and the like. When someone does that at a conference, I get slight sleepy. My mind starts wandering with a low-priority background process that polls the speaker until he or she gives an example. The example wake me up because it is with example that I can see what is going on. With principle it is too easy to make generalizations, too hard to figure out hou to apply things. An example helps make things clear.
So I’m going to start this book with an example of refactoring. During the process I’ll tell you a lot about how refactoring works and give you a sense of the process of refactoring. I can then provide the usual principle-style introduction.
With an introductory example, however, I run into a big problem. If I pick a large program, describing it and how it is refactored is too complicated for any reader to work through. (I tried and even a slight complicated example runs to more than a hundred pages.) However, if I pick a program that is small enough to be comprehensible,refactoring does not look like it is worthwhile.
Thus I’m in the classic bind of anyone who wants to describe techniques that are useful for real-world programs. Frankly it is not worth the effort to do the tefactoring that I’m going to use. But if the code I’m showing you is part of a larger system, then the refactoring soon becomes important. So I have to ask you to look at this and imagine it in the context of a much larger system.
The Starting Point
The example program is very simple. It is a program to calculate and print a statement of a customer’s charges at a video store. The program is told which movies a customer rented and for how long. It then calculates the charges,which depend on how long the movie is rented, and identifies the type of movies There are three kinds of movies: regular, children’s,and new releases. In addition to calculating charges, the statement also computes frequent renter points,which vary depending on whether the film is a new release.
Several classes represent various video elements. Here’s a class diagram to show them (Figure 1.1).
I’ll show the code for each of these classes in turn.
Movie
Movie is just a simple data class.
Figure 1.1 Class diagram of the starting-point classes. Only the most important features are shown. The notation os Unified Modeling Language UML[Fowler,UML].
6
Comments on the Starting Program
What are you impressions about the design of this program? I would describe it as not weell designed and certainly not object oriented. For a simple program like this, that does not really matter. There’s nothing wrong with a quick and dirty simple program. But if this is a representative fragment of a more complex routine in the Customer class does far too much. Many of the things that it does should really be done by the other classes.
Even so the program works. Is this not just an aesthetic judgment, a dislike of ugly code? It is until we want to change the system. The compiler doesn’t care whether the code is ugly or clean. But when we change the system. The compiler doesn’t a human involved, and humans do care. A poorly designed system is hard to change. Hard because it is hard to figure out where the changes are needed. If it is hard to figure out what to change. There is a strong chance that the programmer will make a mistake and introduce bugs.
In this case we have a change that the users would like to make. First they want a statement printed in HTML so that the statement can be Web enabled and fully buzzwoud compliant. Consider the impact this change would have.As you look at the code you can see that it is impossible to reuse any of the behavior of the current statement method for an HTML statement. Your only recourse is to write a whole new method that duplicates much of the behavior of statement. Now, of course, this is not too onerous. You can just copy the statement method and make whatever changes you need.
But what happens when the charging rules change? You have to fix both statement and htmlStatements and ensure the fixes are consistent. The problem with copying and pasting code comes when you have to change it later. If you are writing a program that you don’t expect to change, then cut and paste is fine. If the program is long lived and likely to change, then cut and paste is a menace.
This brings me to a second change. The users want to make changes to the way they classify movies, but they haven’t yet decided on the change they are going to make. They have a number of changes in mind. These changes will affect both the way renters are charged for movies and the way that frequent reenter points are calculated. As an experienced developer you are sure that whatever scheme users come up with, the only guarantee you’re going to have is that they will change it again within six months.
The statement method is where the changes have to be made to deal with changes on classification and charging rules. If, however, we copy the statement to an HTML statement, we need to ensure that any changes are completely consistent. Furthermore, as the rules grow in complexity it’s going to be harder to figure out where to make the changes and harder to make them without making a mistake.
You may be tempted to make the fewest possible changes to the program; after all, it works fine. Remember the old engineering adage: “if it ain’t broke, don’t fix it.” The program may not be broken, but it does hurt. It is making your life more difficult because you find it hard to make the changes your users want. This is where refactoring comes in.
The Forst Step in Refactoring
Whenever I do refactoring, the first step is always the same. I need to build a solid set of tests for that section of code. The tests are essential because even though I follow refactorings structured to avoid most of the opportunities for introducing bugs, I’m still human and still make mistakes. Thus I need solid tests.
Because the statement result produces a sting, I create a few customers, give each customer a few rentals of varous kinds of films, and generatee the statement strins. I then do a string comparison between the new string and some reference strings that I have hand checked. I ser up all of these tests so I can run them from one Java command on the command line. The tests take only a few seconds to run, and as you will see, I run them ofren.
An important part of the tests is the way they repout their results. They either say “OK,” meaning that all the strings are identical to the reference strings, or they print a list of failures: lines that turned out differently. The tests are thus self0checking. It is vital to make tests self-checking. If you don’t, you end up spending time hand checking some numbers from the test against some numbers of a desk pad, and that slows you down,
As we do the refactoring, we will lean on the tests. I’m going to be relying on the tests to tell me whether I introduce a bug. It is essential for refactoring that you have good tests. It’s worth spending the time to build the tests, because the tests give you the security you need to change the program later. This is such an important part of refactoing that I go into more detail on testing in Chapter 4.
Decomposing and Redistributing the Statement Method
The obvious first target of my attention is the overly long statement method. When I look at a long method like that, I am looking to decompose the method into smaller pieces. Smaller pieces of code tend to make things more manageable. They are easier to work with and move around.
The first phase of the refactorings in this chapter show how I split up the long method and move the pieces to better classed. My aim is to make it easier to write an HTMLstatement method with much less duplication of code.
My first step is to find a logical clump of code and use Extract Method (110).
An obvious piece here is the switch statement. This looks like it would make a good chunk to extract into its own method.
When I extract a method, as in any refactoring. I need to know what can go wrong. If I do the extraction badly, I could introduce a bug into the program. So before I do the refactoring I need to figure out how to do it safely. I’ve dome this refactoring a few times before, so I’ve written down the safe steps in the catalog.
Forst I need to look in the fragment for any variables that are local in scope to the method we are looking at, the local variables and parameters. Thus segment of code uses two: each and thisAmount. Of these each is not modified by the code but thisAmount is modified. Any non-modified variable I can pass in as a parameter,. Modified variables need more care. If there is only onem I can return it. The temp is initialized to 0 each time around the loop and is not altered until the seotch gets to it. So I can just assign the result.
The next two pages show the code before and after refactoring. The before code is on the left, the resulting code on the tright. The code I’m extracting from the original and any changes in the new code that I don’t think are immediately obvious are in bold face type. As I continue with this chapter, I’ll continue with this left-right convention.