Monday, August 25, 2014

Java Code Refactoring


I recently browsed again the excellent book Code Complete from Steve McConnell while in the middle of several Java code reviews. Although the book is nor specifically dedicated to Java code, I still found it quite useful and inspiring.

The chapter on refactoring in particular offers a very practical perspective that all programmer should have in mind when it comes to infuse a dose of evolution in the life cycle of their software.

What I found particularly valuable are the checklists that the author has put together: reasons to refactor, specific re-factorings (data level, statement level, routine-level, class-implementation, class-interface, system-level), refactoring safely, strategies, summary.

Since I do not have time to comment every item in these checklists, I decided to pick-up those that are not trivial or obvious and might bring the best return on your refactoring investment.

In a future post, I would like to illustrate some of these refactoring techniques with java code snippets, but that will be later :)


Checklist: Reasons to Refactor
  • Code is duplicate
  • A routine is too long
  • A loop is too long or too deeply nested
  • A class/interface/method has poor cohesion ⇐
  • A class interface does not provide a consistent level of abstraction ⇐
  • A parameter list has too many parameters ⇐
  • Changes within a class tend to be compartmentalized
  • Changes require parallel modifications to multiple classes ⇐
  • Inheritance hierarchies have to be modified in parallel
  • Related data items that are used together are not organized into classes ⇐
  • A routine uses more features of another class than of its own class
  • A primitive data type is overloaded
  • A class doesn't do very much
  • A chain of routines passes tramp data ⇐
  • A middle man object isn't doing anything
  • One class is overly intimate with another
  • A routine has a poor name
  • Data members are public
  • A subclass uses only a small percentage of its parents' routines
  • Comments are used to explain difficult code
  • Global variables are used
  • A routine uses setup code before a routine call or takedown code after a routine call
  • A program contains code that seems like it might be needed someday

Checklist: Summary of Refactorings
  • Data Level Refactoring
  • 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 multi-use 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
  • 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 with polymorphism (especially repeated case statements) ⇐
  • 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 ⇐
  • Replace inheritance with delegation
  • Replace delegation with inheritance
  • Remove a middle man ⇐
  • Introduce a foreign routine
  • Introduce a class extension
  • 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
  • Duplicate 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