Congratulations on having a completely honest name! It is a huge step to simply represent exactly what the code does. All of the steps until now collected information into the name. In making the name complete, we have now made it possible to reason over the responsibilities of the class/method/variable without having to read its full implementation. This sets us up to change its responsibilities, so now we’re going to use the name to guide chunking the code.
Part 1: Look only at the name
At this step, I look only at the name. I ignore both its usage sites and its body. The only question to ask is “does this name make sense?”
Part 2: Look for one responsibility to separate
The easiest way to find a responsibility to separate is to look for a name clause we want to eliminate. There are typically two kinds of clauses we want to eliminate. Each is found by one key question:
- Is this clause unrelated to other clauses in the name?
- Is this clause a concern we want to encapsulate?
Part 3: Write down by structurally refactoring
Writing it down requires structural refactoring by extracting or encapsulating one behavior of the thing. We need to keep the name completely honest. If we don’t like the name then we have to change what the thing does so that we can use a different name.
Can we get more specific than “structural refactoring???”
There are hundreds of different code patterns and destination patterns that each call for a different refactoring. Code by Refactoring is my attempt to gather those context-specific practices into an overall technique that implements Naming as a Process described in this blog series as well as several other key legacy coding ideas. While handling every possible structural change requires all of Code by Refactoring, the patterns common in your specific codebase require only some of the shifts.
Two especially common goals are 1) safely moving a responsibility from a method and 2) encapsulation. Code by Refactoring resolves these two goals in the Fix Complex Methods Changes Series. This particular series of habits help you refactor a complex method into a new system with identical behavior, but in a tidy readable way.
Once the habits have been learned within Fix Complex Methods, a developer might respond to a particular instance with an approach like these.
Safely moving a responsibility from a method
The most common cause is that my method does too many things. I want to split the responsibilities and update all the callers to use them separately. The challenge is flowing the data between the methods without creating a mess elsewhere.
An example is the handling of the XML and the database in
parseXmlAndStoreFlightToDatabaseAndLocalCacheAndShowOnScreenIfVisible(). I’d like to split that into 2 parts: one that turns XML into a flight, and one that operates on a flight.
The solution is to find a series of refactorings that split methods and cluster their data as we go. This might entail moving methods between classes, transforming methods to take data via parameters instead of using fields, or introducing new classes. Fortunately, the impacts tend to be localized to just the method we are working on and all of its direct callers, which is easily handled as long as we have a good technique for Inline Method.
Here is a common case, used when we start with a simple static method.
- Extract method all the stuff after the part I want to break out.
- Introduce a parameter object for everything going into the new method.
- Extract method the part I want to break out. Have it return the new parameter object.
- Introduce a parameter object for this new method.
- Extract method all the stuff before the part I want to break out. Have it return the new parameter object.
- Split the name of the outer method up into names for the 3 parts based on what does what chunks.
- Add any missing clauses required to make the names complete. For example, the above split may divide a calculation from the part that writes it somewhere. The outer method may just be named
...AndRecordYield...(). So I now have one inner part named
...AndCalculateYield...()and another named
...AndRecordYieldToService(). The methods are smaller so it becomes more obvious how to be specific in their names.
- Inline Method the outer method. Now all call sites use the 3 methods directly and you have split out the functionality.
Another common case is to want to encapsulate something. This usually entails not just removing a clause from the name, but actually encapsulating the behavior so callers can’t see the effect. The usual problem is that the behavior is performed on one of the parameters.
An example is the handling of the local cache in
parseXmlAndStoreFlightToDatabaseAndLocalCacheAndShowOnScreenIfVisible(). I’d like to encapsulate that cache and use it as a read-through cache just for performance.
The solution is to find some sequence of refactorings to shift that parameter to be a field. Sometimes this involves creating a new class, splitting a class, or moving the method we are working with to a different class. It also usually involves changing object lifetime and removing references to the value from all callers of the method, often several layers up the stack.
The easiest way to do this is typically to refactor until the method uses everything via a field instead of a parameter, then use the auto-fix to remove unused parameter, then go up the call-stacks and continue removing unused parameter as far as you can.
Here is one common sequence, used when we start with a static method.
- Use Introduce Parameter Object. Select just the one parameter you want to encapsulate. Name the class
Applesauceand the parameter self.
- Use Convert To Instance Method on the static. Select the parameter you just introduced.
- Improve the class name (
Applesauce) to at least the Honest level.
- Go to the caller of the method. Select the creation of the new type. Introduce parameter to push it up to the caller’s caller.
- Convert any other uses of the parameter you are encapsulating to use the field off the new class.
- When the last usage is gone, use the Autofix for remove unused parameter to remove the now-encapsulated field.
- Select any usage of the public field in the calling method and Extract Method the related statements / expression.
- Convert to Static on the extracted method, then Convert to Instance Method to move it to the new type.
- Repeat 7 & 8 until the calling method no longer uses the field we are encapsulating.
- Walk up the stack to the caller of this method. Repeat from step 4. Stop when you get to the initial fetch / creation of the value you are encapsulating.
- Move that fetch / creation to be a factory method on the new type (via Extract, Make Static, Convert to Instance).
- Repeat from step 4 for any other callers of your original method.
- At this point the only references to the value you are encapsulating will be within your new class. The only users of the constructor that takes that value will be factory methods / other constructors.
- Convert the constructor to private.
- Inline the public property to access the value you are encapsulating. Now all the class methods will just use the field.
The recipe is long, but each step takes 1-2 seconds with proper tooling. No step requires editing code. You can encapsulate even a highly-used value in 2-10 minutes with practice.
You can also encapsulate multiple related values at once. And a variation can be used when you want to split a class or handle similar cases.
Breaking up God Classes
On the surface, breaking up a God Class appears to mean executing the Split Class refactoring many times. In practice, however, it’s a lot more complicated than that. God Classes are problematic not only for the methods of the class but also cause a number of common antipatterns in all the code around them. These two problem areas – the class and the surrounding methods – tend to reinforce each other. It is very difficult to fix either while the other remains broken.
Here are some of the common problems:
- The God Class attracts related data. This steals data from surrounding methods, creating utility classes and primitive obsession.
- The God class becomes the obvious place for responsibilities related to its data. This makes it hard to see and start other classes, so the surrounding code becomes unclustered.
- The God Class becomes a point of coupling between unrelated parts of the system. They start to account for this coupling with special case logic making it difficult to separate them.
- Different responsibilities on the God Class share fields, making it hard to pull them apart.
- Data gets passed off the God Class via primitive parameters, creating data aliasing issues, some of which are intentional and must be maintained.
That’s why people end up rewriting their God Classes away.
The largest Change Series in Code by Refactoring, Fix God Classes, provides an incremental approach. This can be combined with the Fix Utility Classes Change Series if your codebase has both antipatterns snarled together. The techniques from these Change Series will address the above 4 problems plus several more.