24.3. Specific Refactorings

 < Free Open Study > 

In this section, I present a catalog of refactorings, many of which I describe by summarizing the more detailed descriptions presented in Refactoring (Fowler 1999). I have not, however, attempted to make this catalog exhaustive. In a sense, every case in this book that shows a "bad code" example and a "good code" example is a candidate for becoming a refactoring. In the interest of space, I've focused on the refactorings I personally have found most useful.

Data-Level Refactorings

Here are refactorings that improve the use of variables and other kinds of data.

Replace a magic number with a named constant If you're using a numeric or string literal like 3.14, replace that literal with a named constant like PI.

Rename a variable with a clearer or more informative name If a variable's name isn't clear, change it to a better name. The same advice applies to renaming constants, classes, and routines, of course.

Move an expression inline Replace an intermediate variable that was assigned the result of an expression with the expression itself.

Replace an expression with a routine Replace an expression with a routine (usually so that the expression isn't duplicated in the code).

Introduce an intermediate variable Assign an expression to an intermediate variable whose name summarizes the purpose of the expression.

Convert a multiuse variable to multiple single-use variables If a variable is used for more than one purpose common culprits are i, j, temp, and x create separate variables for each usage, each of which has a more specific name.

Use a local variable for local purposes rather than a parameter If an input-only routine parameter is being used as a local variable, create a local variable and use that instead.

Convert a data primitive to a class If a data primitive needs additional behavior (including stricter type checking) or additional data, convert the data to an object and add the behavior you need. This can apply to simple numeric types like Money and Temperature. It can also apply to enumerated types like Color, Shape, Country, or OutputType.

Convert a set of type codes to a class or an enumeration In older programs, it's common to see associations like

const int SCREEN = 0; const int PRINTER = 1; const int FILE = 2;

Rather than defining standalone constants, create a class so that you can receive the benefits of stricter type checking and set yourself up to provide richer semantics for OutputType if you ever need to. Creating an enumeration is sometimes a good alternative to creating a class.

Convert a set of type codes to a class with subclasses If the different elements associated with different types might have different behavior, consider creating a base class for the type with subclasses for each type code. For the OutputType base class, you might create subclasses like Screen, Printer, and File.

Change an array to an object If you're using an array in which different elements are different types, create an object that has a field for each former element of the array.

Encapsulate a collection If a class returns a collection, having multiple instances of the collection floating around can create synchronization difficulties. Consider having the class return a read-only collection, and provide routines to add and remove elements from the collection.

Replace a traditional record with a data class Create a class that contains the members of the record. Creating a class allows you to centralize error checking, persistence, and other operations that concern the record.

Statement-Level Refactorings

Here are refactorings that improve the use of individual statements.

Decompose a boolean expression Simplify a boolean expression by introducing wellnamed intermediate variables that help document the meaning of the expression.

Move a complex boolean expression into a well-named boolean function If the expression is complicated enough, this refactoring can improve readability. If the expression is used more than once, it eliminates the need for parallel modifications and reduces the chance of error in using the expression.

Consolidate fragments that are duplicated within different parts of a conditional If you have the same lines of code repeated at the end of an else block that you have at the end of the if block, move those lines of code so that they occur after the entire ifthen-else block.

Use break or return instead of a loop control variable If you have a variable within a loop like done that's used to control the loop, use break or return to exit the loop instead.

Return as soon as you know the answer instead of assigning a return value within nested if-then-else statements Code is often easiest to read and least error-prone if you exit a routine as soon as you know the return value. The alternative of setting a return value and then unwinding your way through a lot of logic can be harder to follow.

Replace conditionals (especially repeated case statements) with polymorphism Much of the logic that used to be contained in case statements in structured programs can instead be baked into the inheritance hierarchy and accomplished through polymorphic routine calls.

Create and use null objects instead of testing for null values Sometimes a null object will have generic behavior or data associated with it, such as referring to a resident whose name is not known as "occupant." In this case, consider moving the responsibility for handling null values out of the client code and into the class that is, have the Customer class define the unknown resident as "occupant" instead of having Customer's client code repeatedly test for whether the customer's name is known and substitute "occupant" if not.

Routine-Level Refactorings

Here are refactorings that improve code at the individual-routine level.

Extract routine/extract method Remove inline code from one routine, and turn it into its own routine.

Move a routine's code inline Take code from a routine whose body is simple and self-explanatory, and move that routine's code inline where it is used.

Convert a long routine to a class If a routine is too long, sometimes turning it into a class and then further factoring the former routine into multiple routines will improve readability.

Substitute a simple algorithm for a complex algorithm Replace a complicated algorithm with a simpler algorithm.

Add a parameter If a routine needs more information from its caller, add a parameter so that that information can be provided.

Remove a parameter If a routine no longer uses a parameter, remove it.

Separate query operations from modification operations Normally, query operations don't change an object's state. If an operation like GetTotals() changes an object's state, separate the query functionality from the state-changing functionality and provide two separate routines.

Combine similar routines by parameterizing them Two similar routines might differ only with respect to a constant value that's used within the routine. Combine the routines into one routine, and pass in the value to be used as a parameter.

Separate routines whose behavior depends on parameters passed in If a routine executes different code depending on the value of an input parameter, consider breaking the routine into separate routines that can be called separately, without passing in that particular input parameter.

Pass a whole object rather than specific fields If you find yourself passing several values from the same object into a routine, consider changing the routine's interface so that it takes the whole object instead.

Pass specific fields rather than a whole object If you find yourself creating an object just so that you can pass it to a routine, consider modifying the routine so that it takes specific fields rather than a whole object.

Encapsulate downcasting If a routine returns an object, it normally should return the most specific type of object it knows about. This is particularly applicable to routines that return iterators, collections, elements of collections, and so on.

Class Implementation Refactorings

Here are refactorings that improve at the class level.

Change value objects to reference objects If you find yourself creating and maintaining numerous copies of large or complex objects, change your usage of those objects so that only one master copy exists (the value object) and the rest of the code uses references to that object (reference objects).

Change reference objects to value objects If you find yourself performing a lot of reference housekeeping for small or simple objects, change your usage of those objects so that all objects are value objects.

Replace virtual routines with data initialization If you have a set of subclasses that vary only according to constant values they return, rather than overriding member routines in the derived classes, have the derived classes initialize the class with appropriate constant values, and then have generic code in the base class that works with those values.

Change member routine or data placement Consider making several general changes in an inheritance hierarchy. These changes are normally performed to eliminate duplication in derived classes:

  • Pull a routine up into its superclass.

  • Pull a field up into its superclass.

  • Pull a constructor body up into its superclass.

    Several other changes are normally made to support specialization in derived classes:

  • Push a routine down into its derived classes.

  • Push a field down into its derived classes.

  • Push a constructor body down into its derived classes.

Extract specialized code into a subclass If a class has code that's used by only a subset of its instances, move that specialized code into its own subclass.

Combine similar code into a superclass If two subclasses have similar code, combine that code and move it into the superclass.

Class Interface Refactorings

Here are refactorings that make for better class interfaces.

Move a routine to another class Create a new routine in the target class, and move the body of the routine from the source class into the target class. You can then call the new routine from the old routine.

Convert one class to two If a class has two or more distinct areas of responsibility, break the class into multiple classes, each of which has a clearly defined responsibility.

Eliminate a class If a class isn't doing much, move its code into other classes that are more cohesive and eliminate the class.

Hide a delegate Sometimes Class A calls Class B and Class C, when really Class A should call only Class B and Class B should call Class C. Ask yourself what the right abstraction is for A's interaction with B. If B should be responsible for calling C, have B call C.

Remove a middleman If Class A calls Class B and Class B calls Class C, sometimes it works better to have Class A call Class C directly. The question of whether you should delegate to Class B depends on what will best maintain the integrity of Class B's interface.

Replace inheritance with delegation If a class needs to use another class but wants more control over its interface, make the superclass a field of the former subclass and then expose a set of routines that will provide a cohesive abstraction.

Replace delegation with inheritance If a class exposes every public routine of a delegate class (member class), inherit from the delegate class instead of just using the class.

Introduce a foreign routine If a class needs an additional routine and you can't modify the class to provide it, you can create a new routine within the client class that provides that functionality.

Introduce an extension class If a class needs several additional routines and you can't modify the class, you can create a new class that combines the unmodifiable class's functionality with the additional functionality. You can do that either by subclassing the original class and adding new routines or by wrapping the class and exposing the routines you need.

Encapsulate an exposed member variable If member data is public, change the member data to private and expose the member data's value through a routine instead.

Remove Set() routines for fields that cannot be changed If a field is supposed to be set at object creation time and not changed afterward, initialize that field in the object's constructor rather than providing a misleading Set() routine.

Hide routines that are not intended to be used outside the class If the class interface would be more coherent without a routine, hide the routine.

Encapsulate unused routines If you find yourself routinely using only a portion of a class's interface, create a new interface to the class that exposes only those necessary routines. Be sure that the new interface provides a coherent abstraction.

Collapse a superclass and subclass if their implementations are very similar If the subclass doesn't provide much specialization, combine it into its superclass.

System-Level Refactorings

Here are refactorings that improve code at the whole-system level.

Create a definitive reference source for data you can't control Sometimes you have data maintained by the system that you can't conveniently or consistently access from other objects that need to know about that data. A common example is data maintained in a GUI control. In such a case, you can create a class that mirrors the data in the GUI control, and then have both the GUI control and the other code treat that class as the definitive source of that data.

Change unidirectional class association to bidirectional class association If you have two classes that need to use each other's features but only one class can know about the other class, change the classes so that they both know about each other.

Change bidirectional class association to unidirectional class association If you have two classes that know about each other's features but only one class that really needs to know about the other, change the classes so that one knows about the other but not vice versa.

Provide a factory method rather than a simple constructor Use a factory method (routine) when you need to create objects based on a type code or when you want to work with reference objects rather than value objects.

Replace error codes with exceptions or vice versa Depending on your error-handling strategy, make sure the code is using the standard approach.

cc2e.com/2450

Checklist: Summary of Refactorings

Data-Level Refactorings

  • Replace a magic number with a named constant.

  • Rename a variable with a clearer or more informative name.

  • Move an expression inline.

  • Replace an expression with a routine.

  • Introduce an intermediate variable.

  • Convert a multiuse variable to a multiple single-use variables.

  • Use a local variable for local purposes rather than a parameter.

  • Convert a data primitive to a class.

  • Convert a set of type codes to a class or an enumeration.

  • Convert a set of type codes to a class with subclasses.

  • Change an array to an object.

  • Encapsulate a collection.

  • Replace a traditional record with a data class.

Statement-Level Refactorings

  • Decompose a boolean expression.

  • Move a complex boolean expression into a well-named boolean function.

  • Consolidate fragments that are duplicated within different parts of a conditional.

  • Use break or return instead of a loop control variable.

  • Return as soon as you know the answer instead of assigning a return value within nested if-then-else statements.

  • Replace conditionals (especially repeated case statements) with polymorphism.

  • Create and use null objects instead of testing for null values.

Routine-Level Refactorings

  • Extract a routine.

  • Move a routine's code inline.

  • Convert a long routine to a class.

  • Substitute a simple algorithm for a complex algorithm.

  • Add a parameter.

  • Remove a parameter.

  • Separate query operations from modification operations.

  • Combine similar routines by parameterizing them.

  • Separate routines whose behavior depends on parameters passed in.

  • Pass a whole object rather than specific fields.

  • Pass specific fields rather than a whole object.

  • Encapsulate downcasting.

Class Implementation Refactorings

  • Change value objects to reference objects.

  • Change reference objects to value objects.

  • Replace virtual routines with data initialization.

  • Change member routine or data placement.

  • Extract specialized code into a subclass.

  • Combine similar code into a superclass.

Class Interface Refactorings

  • Move a routine to another class.

  • Convert one class to two.

  • Eliminate a class.

  • Hide a delegate.

  • Remove a middleman.

  • Replace inheritance with delegation.

  • Replace delegation with inheritance.

  • Introduce a foreign routine.

  • Introduce an extension class.

  • Encapsulate an exposed member variable.

  • Remove Set() routines for fields that cannot be changed.

  • Hide routines that are not intended to be used outside the class.

  • Encapsulate unused routines.

  • Collapse a superclass and subclass if their implementations are very similar.

System-Level Refactorings

  • Create a definitive reference source for data you can't control.

  • Change unidirectional class association to bidirectional class association.

  • Change bidirectional class association to unidirectional class association.

  • Provide a factory routine rather than a simple constructor.

  • Replace error codes with exceptions or vice versa.


 < Free Open Study > 


Code Complete
Code Complete: A Practical Handbook of Software Construction, Second Edition
ISBN: 0735619670
EAN: 2147483647
Year: 2003
Pages: 334

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net