Learn when splitting large methods into smaller ones makes the impossible possible.
Introduction
As a software developer, you have always heard someone telling you that you should not have bulky methods. In other words, you should avoid having too much logic or too many lines of code inside one method.
I can hear you now saying:
Yes, the same bla bla bla, give me a break. I am not an amateur.
Calm down my friend, I am not going to repeat the same thing you are always hearing about this.
I am not going to tell you that if your method is bulky and bloated with too much logic this would be a good indication that it is doing too much, or, may be the whole class is doing too much. In this case you would be violating the Single Responsibility Principle of the (S)OLID Principles.
I know that you already know that. However, I am going to show you something interesting that you might be missing.
In the next sections of this article, I am going to show you that splitting large methods into smaller ones could make a huge difference. Actually, it could make the impossible possible.
What Happens When A Method Is Called
Most probably you know that for each method call a stack frame is created and allocated inside the Stack memory.
Note: If you don’t know much about the Stack memory, may be you can check the article Memory Management In .NET. It would help you understand.
So, let’s say that we have the following simple piece of code.
Assuming that the comments actually represent some code to get executed, when we start calling Function1, this is what is going to happen.
As you can see, at the start of every function, a Stack frame is created and this frame is not deallocated till the function is fully executed. When a child function is called, a child frame is created and so on,…
Worth to mention here, while consecutive and nested Stack frames are being created and allocated into the Stack memory, the allocated memory continues to build up till some frames are deallocated.
Therefore, unless you have an infinite Stack memory, you could have out of memory exceptions, or let’s call it; Stack Overflow exceptions.
You don’t believe me? Let me show you.
Large Method
In this code sample, we are going to simulate a Stack Overflow exception by using Recursion.
Note: If you don’t know much about Recursion, may be you can check the article Curse of Recursion in .NET C#. It would help you understand.
What we can notice here:
We are calling a recursive method which should generate 20,000 method calls.
The logic is not fancy and we should not care much about it for now. It is just for demonstration.
Now, running this code would yield the following result:
This has happened because for each of the 20,000 method call, a new Stack frame is allocated into the Stack memory when the previous one is not cleared yet. Eventually, we ran out of the Stack memory.
Ok, now you might ask:
Then what should I do if my business logic actually mandates that I perform the 20,000 calls? Should I just drop it?
Whew, I was afraid you would never ask. I have built the whole article based on this question 😁
Let me answer your question; simply no. You don’t drop it but you should bend it a little bit to make it work.
Proceed to the next section and I will show you something interesting…
Small Methods
We are going to apply a so simple/trivial change to our previous code. It is simple/trivial to the extent that you might not believe it would make any difference.
Enough with the talking, let’s see some code.
What we can notice here:
We modified the GetResultRecursively method to get an additional parameter which is the lower boundary iteration number.
This way we can call the GetResultRecursively method and decide where to start counting and where to end.
Then we split the one call of the GetResultRecursively method into two calls. Now instead of having one huge call of 20,000 iterations, it would be two calls where each is of 10,000 iterations only.
The first call starts at 20,000 and ends at 10,000.
While the second call starts at 10,000 and ends at 0.
This way, we are still covering the whole 20,000 range.
I can hear you now saying:
Are you a dummy? It would still yield the same problem. At the end we would have 20,000 iterations inside the Main method.
Are you sure? Let’s run the new code and see.
Oh, it worked!!! How is that even possible?!!
Yes my friend, it is possible. The thing is, when we split the 20,000 iterations into 2 calls, the Stack frames allocated inside the Stack memory are only accumulated for 10,000 calls, then cleared relieving some Stack memory, and then the next 10,000 calls are performed and by then there is enough Stack memory.
So, following this pattern, you can actually do more 10,000 calls as you wish like this:
And it would still work. Is it magic? no, just basic knowledge of how method calls work.
Summary
Ok, now I know that you might be confused a little bit, so let me summarize it for you.
Splitting the logic of one method call into two consecutive method calls would return the same logical output but would have different impact on the Stack memory.
Therefore, my advice to you, if you really need a single method to have too much logic, try to split this logic into multiple methods. This would help the Stack memory to breath between the methods calls and eventually avoid encountering a Stack Overflow exception. The only challenge you could encounter is how to reconstruct the final result from these multiple calls.
However, for brevity, you should still think of why your method or class should take care of that much of logic in the first place. This is a sign of something wrong.
Final Thoughts
In this article we proved that splitting large methods into smaller ones is not just a design rule to beautify your code. It could actually make the impossible possible.
Finally, I hope you enjoyed reading this article as I enjoyed writing it.
Comments