Pages

Clean Code Series #3 - Functions

 


Part 3 - Functions


As Uncle Bob defines in his book, Clean Code: A Handbook of Agile Software Craftsmanship, functions are the first tear in any program. All the code we write goes into a function, executing some action inside it, returning a result or not. This is the motive why it's so important to understand how to construct a function in a good way.


The First Rule of Functions

"Functions should be small"

The objective of a function is to tell a story in an objective way. It should be focused on doing one thing, and doing it well. The best way to achieve this is by being small.

They are some conventions about the sizing. You can consider that a good function should have between 4~6 lines of code. Of course, it turns out into a big amount of small functions. However, if you remember the Scope Rule, it's better lots of small self-descriptive functions instead of a big one with lots of responsibilities. 

If you have a well-defined function, a well-defined class in a well-defined namespace, you have no motive to worry about the "Sea of Functions".


A Class?

When you have a function that can be divided into several functional areas, and then you have variables that are being used for all these areas, what you should really have is a class. Remember that classes are nothing more than a group of functions (methods) with a set of variables (properties). Be aware of the complexity applied to your functions.


The One Thing Meaning

If a function is composed of many different sections, it's clearly doing more than one thing. The level of abstraction should be considered. 

For example, look at this piece of code:


It's clear that this function has more than one responsibility. You can see that, at a level of abstraction, we are looking into some external property (Email) with the objective to verify if the email is valid (line 22). Just below (line, 26), we are going to a lower level, opening a connection with the DB, and executing a SQL query.

Mixing levels of abstraction within a function causes confusion. If you want to have a function that orchestrates a lower level of abstraction, at least extract and divide the functions into more specific focused ones (like, AddClientIntoDatabase, ValidateEmail).

Consider the rule of Extract Till You Drop, which means you will continue extracting one function from another until you can't get it anymore.

Here is an example of a refactored compound of functions, each one with their intent + correct level of abstraction:


Perceive that the naming rule that we talked about in the previous post is applied here. Lots of os small well-descriptive functions. The hierarchy is top-bottom and you can understand what is happening during your lecture.


Switch Statements

By nature, switch statements are mean to do more than one thing (against SRP). Sometimes there is no way and you should apply some decision structure like this in your code. The idea in this scenario is to add the switch statement in a low-level class and use externally through polymorphism.

Look at this method:


You can see that this method has more than one responsibility. Here we have a source code dependency and a run-time dependency. It means any new type added here will affect directly each place that consumes this method, with the necessity of recompile everything too.

To attend OCP and SRC in a good way, we can use the abstract factory:
 



Now, the runtime dependency is still the same. However, the source-dependency is inverted, providing us a way to deploy independently the changes, without affecting all the dependent methods (that are pointing to the interface). Exposing an interface that can be used as the public contract made it possible for new consumers of this functionality be added, without affecting the ones that are implemented already.

Arguments

Regarding arguments, we have some conventions that can help us:
  • The number of arguments: in a general way, we can say that the maximum of three arguments is acceptable (the same rule applied for constructors). Instead of passing parameters or objects to functions/constructors, you can use setter functions. Don't forget that, when you pass many arguments, you can incur the problem of sending parameters with different levels of abstraction. 
  • Boolean arguments: booleans represent different flows by themselves. Instead of sending a boolean as a parameter, write to specific functions for the true and false paths. 
  • Output arguments: this type of argument is difficult to understand because when we call a function it's normal that we think about sending a parameter and returning a value. However, returning a value from an external variable is not an easy way to approach a problem, turning the code less readable.  
  • Null arguments: passing null arguments are considered worst than passing a boolean as an argument. Use the same approach for boolean arguments, create two functions, one to handle the non-null values, another to handle the null ones. Avoid the use of a null argument as a pseudo-boolean. Uncle Bob doesn’t believe in defensive coding, adding lots of null checks into a function because. You should believe in your tests. If you don’t trust them, you will go for defensive programming as much as possible. If you are always checking for nulls, that means your tests don't prevent you from passing those nulls. 

Command/Query Separation

The concept of command/query separation tells that a function should change the state of an object, or return some information of an object, not do both. For example:


You can see that this function is responsible for setting a value and returns true or false. It's basically a set and a get at the same time. The cleaner solution would be to separate the command from the query and we can get rid of the ambiguity:



I hope you have enjoyed this step. Bellow the list of the other chapters:

---------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------

References:

I'd like to leave with you some references that can be helpful for your development as a software engineer, part of what we talked about above:


Links for books to buy in the USA: 

         



Links for books to buy in Europe:

           


Help us to maintain the MyLifeInDev running:

Fabio Ono

No comments:

Post a Comment