The benefits of keeping functions short
There are undeniable benefits to limiting the scope and size of our functions. I don’t like to focus so much on ‘keeping them short’, as I believe this can often lead to decision making based on cosmetics alone, but it’s a quick way to get the message across nonetheless. Regardless of which way you word it though, it’s an important piece of the clean code puzzle and it’s one which in my experience is often overlooked. What’s particularly frustrating about this, is that as a concept/rule it’s so easy to adhere to. Unlike SOLID for example, which you could argue takes an ongoing mental effort, or at least a fair bit of practice to pick-up.
Anyway before this post turns into a rant, let’s look at some of the benefits – and if you’re one of those full-page-function kind of developers, maybe (hopefully) I can inspire you to think twice next time around.
Functions should focus on one task
Before I get into the benefits, let’s start with a simple statement. Functions should focus exclusively on a single task – similar to how the single responsibility principle states that classes should be single focused. If a function is responsible for more than one thing (vague way to put it, i know) then it’s likely too long and should be broken down into smaller more focused functions. This I believe, is what we should be focused on – we should not be aiming for an arbitrary max line count per function, for example.
Hopefully we can agree that's a valid strategy for breaking up functions into smaller blocks. Now let’s look at the actual benefits of doing so.
Short functions can be self documenting
In the past, I often wrote long functions that would get so unruly that I’d write inline documentation alongside the code. This type of documentation can be often be made redundant by breaking the code down into smaller functions with descriptive names.
The function below, whilst not exactly lengthy itself, demonstrates this (Disclaimer: this code isn't great and is used only to demonstrate a point).
function showMessages() {
/// check if user is logged and has admin privileges
if ($_SESSION["logged"] == 1 && $_SESSION["privileges"] == 1) { /
echo "Hi Admin";
}
echo "Do something else here...";
}
While the comment maybe helpful, it’s possible to achieve the same level of clarity by separating the ‘is admin’ check into another function. This also brings other benefits which will be discussed later.
function isAdmin() {
if ($_SESSION["logged"] == 1 && $_SESSION["privileges"] == 1) {
return true;
}
return false;
}
function showMessages() {
if (isAdmin()) {
echo "Hi Admin";
}
echo "Do something else here...";
}
Although the function was already small, hopefully the rework communicates the desired message: You can instantly look at the second function and without the need for further thought, understand that the message will be shown to admin users only.
Short functions are easier to comprehend & follow
When writing code it’s always a good idea to make it as comprehensible as possible. Why? Well, when writing the code you can probably understand it at a glance. However, when you come back to it later, without it fresh in your memory, you may find yourself wasting precious time having to step through the function line-by-line just to grasp the intention.
To make your code more comprehensible, break it down into small specific blocks. The next guy to read over it will thank you; remember the next guy may very well be you!
Just think about it, imagine looking at an unfamiliar codebase & seeing a mammoth function which spans 1000 lines. I know, it’s an extreme example, but at some point while looking over that function you are going to lose your trail of thought and will have to backtrack to regain your train of thought.
…you’re bound to run into an upper limit of understandability as you pass 200 lines of code – Steve McConnell, Code Complete.
Short functions are easier to test
Simple put, this is a biggie. First of all the larger the function, the larger the number of possible outcomes. When writing tests you’ll need to account for all of these outcomes and when there’s a lot of them it’s going to increase cognitive load.
Obviously, if you distribute the logic from one huge function to many small functions, you still need to write tests to cover all of the logic. The essential difference though is that you can do it one step at a time. You don’t need to be aware of everything at once. What’s important to add here is that it’s easier for others review your code to ensure that every outcome is covered too. If one of your colleagues has got to review your code in 5-10 minutes, they might not have the same luxury of taking their time to understand all of the ins-and-outs. At a glance, it’s a lot easier to ensure all outcomes have been accounted for when you have many small functions with 1-2 possible outcomes, than one huge function with many more outcomes.
In my experience the longer the function the more mocking you have to do to test a specific piece of logic too. This again increases cognitive load and can make testing a grueling task. As an example, I’ve recently added tests to a project retroactively and in some instances it’s taken me 2 hours to test a piece of logic that took 10 minutes to write. Gaining an understanding of a single, huge block of logic, while mocking services you’re not familiar with is a combo best avoided.
Short functions allow for easy re-use (DRY)
‘DRY’ stands for ‘do not repeat yourself’, it’s a software principle aimed at reducing repetition. It’s a very simple but powerful principle, and it’s actually very easy to adhere to. Are you repeating logic somewhere in your application? Then it’s very probably you’re doing something wrong and you should reconsider your code.
Applying the principle to your functions will help keep them short, concise and re-usable. As an example, consider the ‘isAdmin’ function mentioned earlier. Initially the logic used to check if a user had admin privileges, was held within the ‘showMessages’ function. If the logic was needed elsewhere, for example, to determine whether or not a user was allowed access to a certain page, it would have to be duplicated. However, after being refactored, it would be possible to simply call the ‘isAdmin()’ function.
Tips & Advice
I’d like to explicitly note that I’m not saying you should arbitrarily break up your functions just to make them shorter. You should however break them up where it makes sense. For example, if you have a huge function, let’s imaginatively call it ‘hugeFunction()’. Don’t just randomly pick a section and then break ‘hugeFunction()’ down into steps such as ‘hugeFunctionPartOne()’ and ‘hugeFunctionPartTwo()’. Take the time to look at your logic and break it up where it makes sense.
For example, imagine a function that allows users to authenticate. A hugely simplified example might verify user details and then set a session variable to indicate they are logged in. In this example, you can easily identify two separate tasks, authentication and handling sessions. It would be sensible to separate these tasks into separate functions.
To conclude then, writing short specific functions makes code easier to comprehend, easier to test, reduces repetition, and as such makes the code easier to maintain. In addition to that, it decreases the cognitive load of the author and makes it easier to review/work with unfamiliar code.