Functions inside other functions... Worth it?
Warning: the word function appears more often than you are probably used to. Sorry.
Alright, let's begin: nested functions are functions defined inside other functions (promised and delivered). You saw them in many forms, like this one:
Here, the function
multiply is defined inside its parent,
pow, although it does not have to be this way.
Or does it? If nothing else uses multiply, are there any reasons to move it out?
This topic regularly ignites lengthy bike-shedding conversations regardless of the programming language (stack overflow about python, reddit about swift), so I want to jump in and list some more pros and cons which you can apply for your needs.
Why you should not use nested function
Nested functions reduce readability
The bigger a function is, the harder it is to read it.
Look at this example:
By defining three additional functions inside
showNews, you make the reader's job harder: they have to scan all of them before reaching the part with actual logic.
It might be much shorter:
The reader still can jump to any functions to see how they work, but now it's optional.
Nested functions influence future growth
One may argue that not all nested functions affect readability.
In the previous example, you had three big helpers — pretty bad. What if there was only one?
loadNews inside the primary function cause any harm? After all, it is just several extra lines.
The danger of this approach shows itself when someone needs to add more functionality. You should not be surprised when your colleagues follow the same style and add other nested functions next to the existing one. After all, they just follow the rules you defined as an author.
All it takes to degrade a good codebase is one example of a poor pattern and people's intent to maintain the code style.
Check the article about "Reckless Reuse" for more examples of how code becomes unmaintainable without anyone noticing.
When nested functions are good
Now you may have an idea that all nested functions are evil and should be avoided by any means.
This is not true.
In some cases, they bring some benefits that can outweigh the disadvantages.
Sometimes you do not want others to use your functions
Yes, functions are meant to be reused. But you may not want it, e.g. if a function handles your particular case but is not suitable for everyone:
There are many cases in which
isEqual returns an unexpected result:
But it may be the exact behaviour you need.
In this case, it will be safer to define this function inside
saveNews so that nobody has to deal with its peculiarities.
Sometimes you need access to an outer scope
Many languages give nested functions access to the variables from the parent scope and even allow to modify them. You may need to explicitly declare such variables (Python) or capture them by reference (C++), but the benefits are the same:
In this case, the alternative would be passing all arguments to
handleError every time we call it, using
Function.prototype.bind or wrapping it into a factory. While I prefer different things depending on the situation, I see why somebody may use nested functions here.
Some implementation details are helpful
From the reader's point of view, there are few things worse than jumping between functions to understand how the whole thing works.
Try to understand this piece:
onError do not only hide implementation details but also obscure their purpose. You know something happens, but you don't know what exactly.
In this case, it would be better to define
onError within the primary function so that the reader knows what is happening without leaving it:
Two things to note here:
- Implementation details are still hidden: you know what is happening but not how
- Try to avoid situations like this by better naming and defining better APIs. The downsides of nested functions are still there.
So, what to do?
I usually try to avoid nested functions because they make their parents bigger and, as a result, harder to read. Even if a nested function is concise and the impact is negligible, it can potentially become a slippery slope and serve as a bad example for adding similar ones.
But, as you can see, there are some situations when going this path does worth the downsides.
I hope that now you can defend any side in this holy war and justify the style you prefer. Happy coding!