In my previous post, I posed the question Should I Leave that Helper Class? Hopefully I've convinced you that you should not leave, but should refactor the helper class.
Now, I'm going to detail some of the techniques I have used to eliminate helper classes in legacy code.
First, let's set a ground rule: We are not going to just jump into legacy code and eliminate helper classes for the heck of it. Why?
- It doesn't have a good return on investment (ROI) for the time you spend doing it.
- Your manager or general overlord will probably look at you with a disapproving frown, since you're not adding any tangible value to the product.
- If you break something, you will give refactoring a bad name, and be shunned by other developers. You will have to wear a big red scarlet “R”.
- It is not exactly fun. I mean, it shouldn't be fun… Let me put it this way. If this kind of thing is fun for you, then I've got a bunch of other “fun” stuff you can do around my house.
So what are we going to do then? We are going to refactor the helper class into real classes or existing classes when we are modifying or adding functionality to it. Let's get started…
Modifying a method
If you have to modify a method that is in a helper class, the very first step is to move the logic as it is into a concrete class that we can write a unit test for. Here is an example:
Looking at this example, the first thing we need to do is to figure out what real class this helper class's method belongs to. (Quick side note here: notice the helper method in question also uses another helper class. This is likely to be the case in the real world.)
One technique I use to figure this out is to look and see what data this helper method is using. In this simple example it is pretty obvious that the data it is operating on belongs to Person, even though the method is passing in MonsterObject. Usually the correct place to move a helper method is the place where you will maximize the amount of this operators that are used in the method.
In this case, let's move the helper method to person. Here is what it would look like after:
Notice what we did here?
- We eliminated a parameter being passed in.
- We replaced a bunch of calls with this dot.
- We made the method non-static and private.
- And of course we moved it onto person, where it belonged.
We still have a reference to the helper method it was originally calling, but we can eliminate that later down the road. If this logic ends up being complex, we might have a DependantCounter class that takes in a list of relations that our Person method instantiates and calls in order to get the person count.
Our next step here is to write a unit test that tests the current functionality, then check in our code. Finally, after we have that done, we can write a unit test that will fail for the changes we want to make to the method, and then modify the method.
It is much cleaner and easier to do things this way, and we have just eliminated a method in a helper class!
Adding a method
Adding a method to a helper class is much easier. JUST DON'T DO IT!!! Instead figure out what data that method is going to operate on and move it to the class that contains that data.
If the functionality you are going to add is large and seems to have its own responsibility, then go ahead and create a new class.
As you are modifying code and bringing the helper methods into real classes, or adding new methods in classes that would have been in helper classes by convention, you may start to see some of the classes these methods are being moved into grow. That is ok, you are discovering that you need more classes. A helper class is not a spill over class for long methods that you don't want to put into the class they actually belong in. Instead, the appropriate thing to do is to break up the class based on responsibility.
To stick with the current example, imagine a Person class that has some data on it for money. Perhaps there is a private variable called cashOnHand. As you add to the class you may end up bringing in data on their savings account, their outstanding loans. You might bring in methods that operate on their savings account information and their cash on hand. It is ok and good to discover that Person becomes a separate thing than a person's financial data. At that point you might create a class called Financials and a person would have a reference to it.
Refactoring helper classes is about figuring out where things belong.
It is just like cleaning out the junk drawer in your kitchen. You have to go through each item and find out where its real home should be. If there isn't one, you might have to make one.
If a method operates on a piece of data, it belongs as close to that data as possible. Don't try and tackle the huge helper class all at once, but rather eliminate the helper class piece by piece as you change or add functionality.