I’m refactoring several classes of over 2000 lines each. These are classic god-classes that mediate a handful of stubby little data-centric classes around them. I reduced the first, primary domain class by over 1100 lines, to under 1500, by simply pushing responsibilities around. Adding tests after the fact (using WELC techniques, of course) gave me the confidence to refactor. In just a few hours, I’ve been able to completely eliminate about 700 lines so far out of the 1100 that were shifted elsewhere. I’m loving it!
After cleaning up some really ugly code, I ended up with better (not great) code:
private boolean containsThreeOptionsSameType() { Map<String, Integer> types = new HashMap<String, Integer>(); for (Option option : options) increment(types, option.getType()); return count(types, Option.ALPHA) >= 3 || count(types, Option.BETA) >= 3 || count(types, Option.GAMMA) >= 3 || count(types, Option.DELTA) >= 3; } private int count(Map<String, Integer> map, String key) { if (!map.containsKey(key)) return 0; return map.get(key); } private void increment(Map<String, Integer> map, String key) { if (!map.containsKey(key)) map.put(key, 1); else map.put(key, map.get(key) + 1); }
I could clean up the complex conditional on the
statement (perhaps calling a method to loop through all types). Also, the
and
methods contain a bit of duplication.
More importantly, the
and
methods represent a missed abstraction. This is very common: We tend to leave little, impertinent, SRP-snubbing, OCP-violating methods lying about, cluttering up our classes.
(Aside: I’m often challenged during reviews about exposing things so I can test them. Long-term, entrenched developers are usually very good about blindly following the simple rule about making things as private as possible. But they’re perfectly willing to throw OO concept #1, cohesion, completely out the window.)
I decided to test-drive the soon-to-be-no-longer-missing abstraction, CountingSet. I ended up with an improved implementation of
by eliminating the redundancy I spotted above:
import java.util.*; public class CountingSet<T> { private Map<T, Integer> map = new HashMap<T, Integer>(); public int count(T key) { if (!map.containsKey(key)) return 0; return map.get(key); } public void add(T key) { map.put(key, count(key) + 1); } }
Some developers would be appalled: “You built a cruddy little class with just four lines of real code? And for only one client?” And further: “It’s only a subset of a counting set, where are all the other methods?”
Sure thing, buddy. Here’s my client:
private boolean containsThreeOptionsSameType() { CountingSet<String> types = new CountingSet<String>(); for (Option option : options) types.add(option.getType()); return types.count(Option.ALPHA) >= 3 || types.count(Option.BETA) >= 3 || types.count(Option.GAMMA) >= 3 || types.count(Option.DELTA) >= 3; }
My client now only contains intent. The small ugliness with the parameterized map and boxing is now “information hidden.” I also have a simple reusable construct–I know I’ve had a similar need several times before. As for building and/or using a fully-functional CountingSet, there’s something to be said for creating “adapter” classes with the smallest possible, and most meaningful, interface.
Good enough for now. Don’t hesitate to create your own cute little abstractions!