Clean code tip: small functions bring smarter exceptions
Smaller functions help us write better code, but have also a nice side effect: they help us to understand where an exception was thrown. Let’s see how!
Table of Contents
Just a second! ๐ซท
If you are here, it means that you are a software developer. So, you know that storage, networking, and domain management have a cost .
If you want to support this blog, please ensure that you have disabled the adblocker for this site. I configured Google AdSense to show as few ADS as possible - I don't want to bother you with lots of ads, but I still need to add some to pay for the resources for my site.
Thank you for your understanding.
- Davide
Small functions not only improve your code readability but also help to debug faster your applications in case of unhandled exceptions.
Take as an example the program listed below: what would happen if a NullReferenceException
is thrown? Would you be able to easily understand which statement caused that exception?
static void Main()
{
try
{
PrintAllPlayersInTeam(123);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
}
public static void PrintAllPlayersInTeam(int teamId) {
Feed teamFeed = _sportClient.GetFeedForTeam(teamId);
Team currentTeam = _feedParser.ParseTeamFeed(teamFeed.Content.ToLower());
Feed playerFeed = _sportClient.GetPlayersFeedForTeam(currentTeam.TeamCode.ToUpper());
var players = _feedParser.ParsePlayerFeed(playerFeed.Content.ToLower()).ToList();
foreach (var player in players)
{
string report = "Player Id:" + player.Id;
report += Environment.NewLine;
report += "Player Name: " + player.FirstName.ToLower();
report += Environment.NewLine;
report += "Player Last Name: " + player.LastName.ToLower();
Console.WriteLine(report);
}
}
With one, single, huge function, we lose the context of our exception. The catch
block intercepts an error that occurred in the PrintAllPlayersInTeam
function. But where? Maybe in teamFeed.Content.ToLower()
, or maybe in player.FirstName.ToLower()
.
Even the exception’s details won’t help!
Object reference not set to an instance of an object.
at Program.PrintAllPlayersInTeam(Int32 teamId)
at Program.Main()
Yes, it says that the error occurred in the PrintAllPlayersInTeam
. But where, exactly? Not a clue!
By putting all together inside a single function, PrintAllPlayersInTeam
, we are losing the context of our exceptions.
So, a good idea is to split the method into smaller, well-scoped methods:
static void Main()
{
try
{
PrintAllPlayersInTeam(123);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
}
public static void PrintAllPlayersInTeam(int teamId)
{
Team currentTeam = GetTeamDetails(teamId);
var players = GetPlayersInTeam(currentTeam.TeamCode);
foreach (var player in players)
{
string report = BuildPlayerReport(player);
Console.WriteLine(report);
}
}
public static string BuildPlayerReport(Player player)
{
string report = "Player Id:" + player.Id;
report += Environment.NewLine;
report += "Player Name: " + player.FirstName.ToLower();
report += Environment.NewLine;
report += "Player Last Name: " + player.LastName.ToLower();
return report;
}
public static Team GetTeamDetails(int teamId)
{
Feed teamFeed = _sportClient.GetFeedForTeam(teamId);
Team currentTeam = _feedParser.ParseTeamFeed(teamFeed.Content.ToLower());
return currentTeam;
}
public static IEnumerable<Player> GetPlayersInTeam(string teamCode)
{
Feed playerFeed = _sportClient.GetPlayersFeedForTeam(teamCode.ToUpper());
var players = _feedParser.ParsePlayerFeed(playerFeed.Content.ToLower()).ToList();
return players;
}
Of course, this is not a perfect code, but it give you the idea!.
As you can see, I’ve split the PrintAllPlayersInTeam
method into smaller ones.
If now we run the code again, we get a slightly more interesting stack trace:
Object reference not set to an instance of an object.
at Program.GetTeamDetails(Int32 teamId)
at Program.PrintAllPlayersInTeam(Int32 teamId)
at Program.Main()
Now we know that the exception is thrown on the GetTeamDetails
method, so we reduced the scope of our investigations to the following lines:
Feed teamFeed = _sportClient.GetFeedForTeam(teamId);
Team currentTeam = _feedParser.ParseTeamFeed(teamFeed.Content.ToLower());
return currentTeam;
It’s easy to understand that the most probable culprits are teamFeed
and teamFeed.Content
!
Of course, you must not exaggerate! Don’t create a method for every single operation you do: in that way, you’ll just clutter the code without adding any value.
Downsides
Yes, adding new functions can slightly impact the application performance. In fact, every time we call a function, a stack operation is performed. This means that the more nested methods we call, the more stack operations we perform. But does it really impact the application performance? Or is it better to write cleaner code, even if we lose some nanoseconds? If you want to see the different standpoints, head to my article Code opinion: performance or clean code?
Conclusion
Writing smaller functions not only boosts the code readability but also helps us debug faster (and smarter). As usual, we must not move every statement in its own function: just find the right level of readability that works for you.
๐ Let’s discuss it on Twitter or on the comment section below!
๐ง
ABOUT THE AUTHOR
Davide Bellone is a software developer with more than 10 years of professional experience with Microsoft platforms and frameworks.
He loves learning new things and sharing these learnings with others: thatโs why he writes on this blog and is involved as speaker at tech conferences.
He's a Microsoft MVP ๐ and content creator on LinkedIn๐.