7 Ways More Methods Can Improve Your Program
A lot of code I come across consists of relatively few, but long, methods. The code does what it is supposed to do. However, it could be improved a lot. Refactoring to use more methods can produce better structured programs that are easier to understand, easier to modify, easier to test and easier to debug. Below are 7 reasons why using more methods can be a good idea.
1. Hide Details
At work I recently came across this section of Java code in a method outputting Call Detail Records (CDRs):
// Check if CDR should be excluded because of sink filter if (profile.isEnableSinkFilter() && profile.getSinkFilter() != null) { String[] sinkFilters = profile.getSinkFilter().split(";"); for (String str : sinkFilters) { if (str.equals(sinkName)) { sinkNameMatch = true; break; } } if (sinkNameMatch && profile.isInvertSinkFilter()) { return result; // Do not output anything } if (!sinkNameMatch && !profile.isInvertSinkFilter()) { return result; // Do not output anything } }
It performs filtering based on sink name. Depending on if there is a match, and if the filter is inverted or not, it can exit (by returning the result), or continue to do more processing. There is nothing wrong with the code. It does exactly what it is supposed to. The problem, though, is that every time I was reading through the enclosing method, I was forced to see all the details of how the filtering was done. But most of the time, it is enough to know that filtering is done. The details are only in the way. So I put the details in a new method called excludeBasedOnSinkName()
if (excludeBasedOnSinkName(profile, sinkName)) { return result; // Do not output anything }
The name of the method tells me what it does, and the enclosing method is now easier to read, since the details of the filtering are hidden in the new method. If I want to know the details, I just jump into the method. Most of the time though, it is enough to just see the name in order to know what happens there.
2. Avoid Duplication
If the same lines of code appear more than once, they should be put into a method instead. There are several good reasons for this. It makes the program shorter. It ensures that all the statements are done, in the same order, every time they are done. And my favorite reason: there is only one place to make a change if something needs to be changed in that area. If you have the same lines of code spread out in 15 different places, it is easy to miss one of them when making a change.
Duplicated code is unfortunately quite common. Often it is the result of copy-paste coding. Copy some code, maybe modify some parts a little, and paste it in somewhere else where the same action is needed. Copy-paste coding is often (slightly) faster than creating a method with the common part, and calling it from both places. It is also worth pointing out that the two cases need not be identical. There may be differences. But in that case, put the common part in the method, and pass in the parts that vary with parameters.
Martin Fowler wrote a really good article in IEEE Software on the subject called Avoiding Repetition.
3. Group Related Behavior
Sometimes, several things need to be done in a certain order. By creating a method and calling it, you ensure that no step is forgotten. For example, if you want to stop a timer if it is running, and then clear it, use a stopTimer() method:
private void stopTimer() { if (this.timer != null) { this.timer.stop(); this.timer = null; } }
If you use the method instead of the individual statements, you know that the timer has been stopped and that the variable is set to null after it has been called. And, it is working on a conceptual level. What you want to do is stopping the timer, not just executing individual statements.
4. Create “Funnels”
When a lot of complex processing is done, it is helpful to have a structure where certain points in the code are always passed, regardless of what else is done. For example, when setting up a mobile phone call, one of the first steps is to check that the subscriber has enough money in the account to place the call. There can be different types of call set up, and the charging check could be done in each of those places separately. However, by making all cases do the checking from one method, all different calls are “funneled” through this one method.
Funnel methods make the program easier to understand. Whatever else happened before, every call setup is going to call this method. They also make trouble shooting and debugging easier. You can have logging code in the funnel methods, and they give you certain check-points. Then it is easy to follow the progress of the call setup process.
5. Simplify Testing
Long methods do a lot of things, which can make testing harder. For example, in our system at work, there is a license the limits the number of concurrent calls that are allowed. Also, you can configure that an alarm should be raised when a given percentage of the capacity has been reached. A simple calculation is needed to find the number of calls that should raise the alarm, given the license value and percentage. By putting the calculation in its own method, it is easy to set up some unit tests for it. Does it give the correct value when the limit is 0%? When it is 100%? When the division yields a remainder? If the calculation is part of a longer method, it can only be tested in combination with other actions, which makes the testing harder.
Sometimes (due to legacy code written before unit-testing was common), it can be complicated to create the objects needed to perform the tests. A simple solution can be to make a static method that gets all the necessary input as arguments. The static method can then be called (and tested) without any complicated object creation.
6. Simplify Logging and Debugging
Is a method containing only one line too short? No. A method like this can be useful:
private void setState(State newState) { this.state = newState; }
At work the other day, I was trouble-shooting a problem. As part of that effort, I wanted to check if everything had been closed properly when going back to state idle. Unfortunately, the state was set to idle in around 25 places, so it took a bit of work to cover all the cases. If a setState-method had been used, I could just have added:
private void setState(State newState) { if (newState == State.IDLE && !everythingClosed()) { logDebug("Going IDLE, but everything not closed"); } this.state = newState; }
A setState-method is also good for logging all state transitions:
private void setState(State newState) { if (logDebugEnabled()) { logDebug("Changing state from " + this.state + " to " + newState); } this.state = newState; }
Seeing all the state transitions in a log usually helps a lot when trying to figure out what is happening in the code.
7. Self-Documenting Code
Code is read much more often than written. Therefore it is important that it is easy to understand what the code does. Well-named methods make the process of understanding a program a lot easier. Also, you don’t need to write a lot of comments, because the names of the methods (and variables) tell the story. Coming up with good names is harder than it sounds, but is one of the essential parts of programming in my opinion. The best test of the names I come up with is when I come back to the code a few months later (when I have forgotten all the details): can I understand what a method does just from its name, or do I have to jump into it and read the code?
I almost never write any Javadoc for the internal methods I write. The name of the method is enough. The benefit is shorter, more compact code – I see more of the program in each screen-full. And if the name is not enough, it is easy to jump into the method and check what it does. The only exception is for APIs. There Javadoc is important, since you may not have the ability to examine the code on the other side of the API.
Why Not?
So why is there so much code that doesn’t take advantage of the benefits of using more methods? Partly I think it is because a lot of code is added on to over a long period of time, by different people. It may not seem worth the effort at each individual update, so nobody does the refactoring.
I also think the tools you use affect you in this regard. For the last several years, I’ve been coding in Java using IntelliJ IDEA. The IDE makes jumping into and out of classes and methods a matter of a keystroke. So using lots of methods is easy. When I was using Emacs and coding in C++, my methods tended to be longer, and my classes bigger. I think the reason was that it took more effort to navigate between classes and methods – switching files or buffers, and using string search on the method name to find it.
Yet another reason is that some people simply prefer to see long chunks of code, instead of several methods. When I read code for the first time, I usually jump into all the methods and read what they do. But once I have done that, I trust the name of the method (if it is a good name), and I don’t need to read the code in it every time I see that method. The name of the method is enough. I prefer seeing method names to seeing all the code – it makes reading faster.
Conclusion
Several of the reasons to create methods mentioned above usually apply at the same time. For example, grouping behavior simultaneously also hides details, avoids duplications and can be self-documenting. All the better – many good reasons to introduce a method in the code. Can it be over-done? Probably. But most code I see is erring on the side of too few methods, not too many. So the next time you are modifying some code, see if it would benefit from a few new methods as well.