Skip to main content

Clean Code - Functions

http://vunvulearadu.blogspot.ro/2014/08/clean-code-naming.html
In the last blog post we discover the universe of Clean Code written by Robert C. Martin. We had the opportunity to go deeper on the naming topic and see how easily small things like meaningful names or naming that revel intention can improve the quality and readability of the code itself.
Today we will talk dive deeper in Clean Code and we will talk about ‘Functions’. This basic and simple mechanism used to write programs can impact not only how easily a program can be maintain and extended but also the mental health of developers. Don’t forget that long methods will make your eyes bleed.
Imagine a book where all the paragraphs are mixed, the font size is different for each of it and a part of them has 20 pages. How easily you can read a book like that. Code should be written in a way that gives the opportunity to people to read it like a book, from to bottom, where each different logic is grouped separately.

Happy story

I remember one time when I had to extend an existing code written by somebody else. When I open the solution I found only one class, with 2 or 3 methods that had in total around 4.000 lines of code. My estimations for that task were:

  • 4 days on refactoring
  • 1 days to add the new functionality

My PM from that period accepted this estimation and gave me green light to work on it. But in the end, I needed 4X more time to finish the task because the methods themselves were too long and I couldn’t do anything there without having nightmares during the nights.
So, what we can do to improve the quality of developers and our software from functions/methods perspective.

Small

This is the first and the most important rule related to functions. You should keep them as short as possible. The explanation is pretty simple: a shorter function will do less (only one simple thing). Additionally to this, it will be more easily to understand and manage it.
The natural question that pops in our mind is “How short?”.

  • 100 lines?
  • 50 lines?
  • 10 lines?
  • 5 lines?

Unfortunately we cannot have a magic number like 5 or 20, because is pretty hard to generalized it. The length of a method depends on multiple factors like the code conventions. For example how often you hit enter to add a new line (for every ‘{‘ or for every logic statement and so on).
In general if you end up with a method longer than 10-15 lines of code you should take a look over it and see why is so long. Because of code conventions or it is because there is too much logic there.

Blocks and Indenting

Thing about an IF, ELSE, WHRE, REPEAT and this kind of functionality. You don’t want to end up with an IF on 10 lines. It is pretty hard to read and understand. For this kind of cases you should extract the check into a separately function and call if from the IF statement. Appling this rule you will end up with statements like IF or WHERE that needs only one line of code.
Additionally to this you will improve the readability and documentation of your own code. For developers will be very easy to understand what the code does and what the check behind that is IF or WHERE supposed to do.

Do One Thing

If you read a long function you realized that more than one thing is done there. For example in the same function you open the DB connection, you execute a query, you convert the result to another type and you handle special cases. Each of this things should be done in a separately places.
This is why a function should do ONLY one things. If you need to do more than one thing, than you should split it into separately functions.
Even if the statement is so simple, it is pretty hard to do this. If you discover in a function different part of code that is grouped or you can extract a part of it into a separate function with a meaningful name than the functions is doing more than one thing.
One level of abstraction per Function
This can be a mechanism that can tell us that the function is doing to many things. For example a function that process an entity and also internally start to split and transfer one of the entity fields has more than one level of abstraction.
It is pretty clear that you should extract the string processing into another function. In this way each function will have only one level of abstraction.

Switch Statement

The store related to switch statements is pretty long and we will talk about with another occasions also. In our case we should extract the logic of each of CASE’s to separate functions. Even if we will do this thing will not be okay because we are violating SRP (Single Responsibility Principle).
We should replace the switch statement with polymorphism. This is the perfect solution, but there are cases when such a solution would add to much extra complexity. When you need to use switch statement you should hide them as deep as possible, in Clean Code the recommendation is behind a abstract factory.

 Use descriptive names

The name of a function should describe exactly what it does. Not less or more. For example a function with name like ‘DO’, ‘ACTION’ are not very helpful, because we don’t know what are there scope.
 A name like ‘TriggerDoorLock’ it gives us all the information that is needed to know what that function is doing.
Finding a good name is pretty hard and can consume a lot of energy. Addition to good names you should be consistent and try to use the same naming pattern when there are similarities.

Function Arguments

How many arguments a function should have? The best value is 0, but not all the time is possible. Each time when you add a new argument think about the role of it.
When you end up with more than 3 or 4 arguments something can be wrong. You should take a look over them and see if you cannot more the function in another location or to add another abstraction level.
The OUT option to arguments is not all the time recommended and it may be a smell that something is wrong there. For example the TryXXX methods usually check if a conversion can be done. If it can be done with success, ‘TRUE’ is retuned and the out parameter will contain the conversion result. This could be a smell that the method is doing   too many things – converting and checking.

Have No Side Effects

This is the case when your function is doing more than one thing, but without telling to the client. For example a ‘Read’ method that read the content of a file and after it delete it without notify the user. In this case the user should be notified about this action or at least he should know about it from the moment when he does the call – ‘ReadAndDelete’.
Because of this side effects we can end up with temporary coupling. For example when function ‘GoLeft’ can be called only if ‘StartEngine’ was called. You should think about a way to expose only the methods that are available at a specific time, without creating temporarily coupling. For example the ‘StartEngine’ could return an object that has only the commands like ‘GoLeft’, ‘GoRight’ and so on.

Command Query Separation

A function should do only one thing. You should never have methods that execute a query and in the same time a command. This two action needs to be separately all the time without exception.

Prefer Exceptions and not Error Codes

Returning an error code created two additional things that needs to be done by developer/client. He needs to know the mapping of each error code and in the same time he has to check the returning error code.
For this cases throwing an exception is better and will simplify the work of clients. Additional to this, the error handing will be 100% separately from your logic.

Extract try/catch blocks 

A code that contains this kind of blocks are pretty ugly and long. Because of this all this blocks of code should be extracted into separately functions. The TRY block can be putted into a function and the catch block can be putted into another functions.

Don’t repeat yourself

All the code that is duplicated should be extracted to a separately function. You will not only reduce the number of lines of code, but you would also male your life easier when a change will need to be done. It is easier to change only one line of code than searching and changing all the locations where the code is duplicated.

Small things

As you can see, small things can make a real difference between a good and a bad function. You don’t need to do or know crazy stuff to be able to write ‘happy’ functions. Taking into consideration this recommendation you will end up with better software that can be maintain after 10 years easier and with less money.

Comments

Popular posts from this blog

Windows Docker Containers can make WIN32 API calls, use COM and ASP.NET WebForms

After the last post , I received two interesting questions related to Docker and Windows. People were interested if we do Win32 API calls from a Docker container and if there is support for COM. WIN32 Support To test calls to WIN32 API, let’s try to populate SYSTEM_INFO class. [StructLayout(LayoutKind.Sequential)] public struct SYSTEM_INFO { public uint dwOemId; public uint dwPageSize; public uint lpMinimumApplicationAddress; public uint lpMaximumApplicationAddress; public uint dwActiveProcessorMask; public uint dwNumberOfProcessors; public uint dwProcessorType; public uint dwAllocationGranularity; public uint dwProcessorLevel; public uint dwProcessorRevision; } ... [DllImport("kernel32")] static extern void GetSystemInfo(ref SYSTEM_INFO pSI); ... SYSTEM_INFO pSI = new SYSTEM_INFO(...

ADO.NET provider with invariant name 'System.Data.SqlClient' could not be loaded

Today blog post will be started with the following error when running DB tests on the CI machine: threw exception: System.InvalidOperationException: The Entity Framework provider type 'System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer' registered in the application config file for the ADO.NET provider with invariant name 'System.Data.SqlClient' could not be loaded. Make sure that the assembly-qualified name is used and that the assembly is available to the running application. See http://go.microsoft.com/fwlink/?LinkId=260882 for more information. at System.Data.Entity.Infrastructure.DependencyResolution.ProviderServicesFactory.GetInstance(String providerTypeName, String providerInvariantName) This error happened only on the Continuous Integration machine. On the devs machines, everything has fine. The classic problem – on my machine it’s working. The CI has the following configuration: TeamCity .NET 4.51 EF 6.0.2 VS2013 It see...

Navigating Cloud Strategy after Azure Central US Region Outage

 Looking back, July 19, 2024, was challenging for customers using Microsoft Azure or Windows machines. Two major outages affected customers using CrowdStrike Falcon or Microsoft Azure computation resources in the Central US. These two outages affected many people and put many businesses on pause for a few hours or even days. The overlap of these two issues was a nightmare for travellers. In addition to blue screens in the airport terminals, they could not get additional information from the airport website, airline personnel, or the support line because they were affected by the outage in the Central US region or the CrowdStrike outage.   But what happened in reality? A faulty CrowdStrike update affected Windows computers globally, from airports and healthcare to small businesses, affecting over 8.5m computers. Even if the Falson Sensor software defect was identified and a fix deployed shortly after, the recovery took longer. In parallel with CrowdStrike, Microsoft provi...