Refactoring
Or, Object-Oriented Program Algebra and the Art of Elegant Programming
-- use program transformations (algebraic equivalence laws of OOP) to produce
cleaner, better code.
The "bible" of refactoring is Fowler's book, Refactoring. It is a very well-written book which I would have made a required text for the course if it didn't cost $50.
Note that Fowler didn't come up with the idea of refactoring, he just popularized it.
Refactoring by Example
We spent a whole lecture working through the example of Refactoring Chapter 1.
Foundations of Refactoring
Here are the key points of Fowler chapter 2. Much of this we have covered already in lecture so it is review/summarization.Summary of the philosophy: code is a liquid, not a solid!
Why Refactor?
- To improve the quality of the codebase
- Makes software easier to understand
- This in turn helps in finding bugs
- .. and in turn allows you to program faster in the end.
When to Refactor?
- Most important: refactor in order to elegantly incorporate
new functionality.
-- one of the biggest sins is not to refactor in this case, and after several extensions, one arrives at . . . evil spaghetti code! - The rule of three: if you do three things the same time in an implementation, its time to refactor to avoid more repetition in the future
- around a consistently buggy area: bugs are a sign that code is bad, and refactoring will improve that code.
- at "code review" time in the project lifecycle. A code review is a meeting at which the state of the code is reviewed in detail.
- Beethoven was a serious refactorer: he wrote, threw out, redid, patched etc his symphonies
- Mozart wrote great musical code right off the top of his head.
Limitations of Refactoring
- External systems that can't be refactored: if you are using a external database or large code framework, those can't be refactored, limiting the improvements that can be made. (Note however that with shared code you can refactor your team-mates code; this is one of the main strengths of having a shared codebase)
- Interfaces that are published should change as little as possible, but refactoring often wants to change those interfaces. There is a significant tension here.
- If the design is too awful, its not worth trying to refactor---throw it out!
Refactoring and Design
- Refactoring is almost as important in design as in
implementation
Same principles apply, but at higher level. - Don't overdesign, spending too much time on design
- Some problems are more clear when code takes shape
- Instead, code your initial design and redesign/refactor over time as needed
Testing and Refactoring
Unit testing is critical to refactoring. Fowler chapter 4 covers JUnit, the unit testing framework. We covered this in the implementation lecture.Here is the sequence of events to follow to test in the context of a refactoring:
- Make sure you have unit tests covering the code you are about to change; if not write them
- Make sure you are 100% compliant with tests before refactoring
- Refactor
- Re-run tests and get back to 100%
Bad Smells in Code
- See Fowler Chapter 3.
- The idea is Bad Smells and refactorings to clean them up.
- (We are not going to delve into the proposed refactorings below in detail, but the names should give the idea.)
- There is also a general philosophy here: if things look ugly/awkward/smelly, fix it!
- Final goal is elegant, easy-to-understand code.
Here are the smells and fixes.
- Duplicated Code
--extract out the common bits into their own method (extract method) if code is in same class
--if two classes duplicate code, consider extract class to create a new class to hold the shared functionality. - Long Methods
--extract method! - Long Parameter List
--replace parameter with method (dont' pass data the receiver can get on its own accord -- receiver explicitly asks for data itself)
OR: if the parameters are a natural, self-contained grouping, introduce parameter object.
Example: day month, year, hour minute second parameters ==> date parameter - Divergent Change
If you have a fixed class that does distinctly different things consider separating out the varying code into varying classes (extract class) that either subclass or are contained by the non-varying class. This smell is closely related to Martin's Single Responsibility Principle. - Shotgun Surgery
The smell: a change in one class repeatedly requires little changes in a bunch of other classes.
--try to move method and move field to get all the bits into one class since they are obviously highly dependent. - Feature Envy
Method in one class uses lots of pieces from another class.
--move method to move it to the other class. - Data Clumps
Data that's always hanging with each other (e.g. name street zip).
--Extract out a class (extract class) for the data. Will help trim argument lists too since name street zip now passed as one address object. Related to long parameter list above. - Switch (case) statements
--Use inheritance and polymorphism instead (example of this was in Fowler Chapter 1; this is one of the more difficult refactorings) - Lazy Class
Class doesn't seem to be doing anything. Get rid of it!- collapse heirarchy if subclasses are nearly vacuous.
- inline class (stick the class' methods and fields in the class that was using it and get rid of original class).
- Speculative generality
Class designed to do something in the future but never ends up doing it. Thinking too far ahead. Or you though you needed this generality but you didn't.
--like above, collapse hierarchy or inline class - Message chains
Say you want to send a message to object D in class A but you have to go through B to get C and C to get D.
--use hide delegate to hide C and D in B, and add a method to B that does what A wanted to do with D. - Inappropriate Intimacy
Directly getting in and munging with the internals of another class.
--To fix this, move methods, inline methods, to consolidate the intimate bits. - Data Class
We have already talked about this extensively: in data-centric design, there are some data classes which are pretty muchstructs: no interesting methods.
--first don't let other directly get and set fields (make them private) and don't have setter for things outsiders shouldn't change
--look who uses the data and how they use it and move some of that code to the data class via a combination of extract method and move method (see the Fowler chapter 1 example for several examples of this) - Comments
Comments in the middle of methods are deodorant. You should really refactor so each comment block is its own method. Do extract method.
Sample Refactorings
We will cover some particular refactorings of Fowler in more detail in lecture at this point.Eclipse and Refactoring
- Eclipse has a
Refactormenu - use it!! - It will automatically perform many of the most important refactoring operations for you
- Eclipse automatically checks many of the preconditions that a refactoring needs in fact hold (some it can't easily check so its not airtight in that regard).