In the last 3 months I tried to talk about different subjects presented in Clean Code. Even if this is the 4th article about this topic, I have the feeling that there are so many things that we should talk about when we are talking about a clean and good written code.
We could say that the ‘Clean Code’ book, written by Robert C. Martin, has set the standards in our industry from this perspective. It is the developers’ Bible and many times it is used as the ‘law’ of the code. I don’t want to go deeper into this subject, but I promise that one day I will talk in detail about why we should (not) use this book as THE Bible.
In this article I will try to talk about Objects and Data Structure, XXXX. I expect to go from the code format, to what the code should look like and how we should implement different features.
I think that all of us remember the University courses, when teachers tried to explain to us that we should only expose from a class the information that is needed by others. But, because current programing language gives us so easily the possibility to expose data outside a class, we often end up with a lot of private data exposed to the system.
One of my colleagues called the getter and setter the tool of the devil. It is funny, but sometimes it is true.
It is not so important if we are using a getter/setter of a method. The most important thing is to expose the data in an abstract way that doesn’t expose implementation details. For example, if we need to expose information related to the weight of a person, we can use a getter or a simple method. Both solutions are good as long as we don’t give any kind of implementation details.
public class Person
public double WeightInKg
public double GetWeightInKg()
Outside this class, you don’t know how the data look like and what its format is. If we add getters and setters everywhere, what would the value of encapsulation be? … NONE.
Be aware that there is a big difference between data structures and objects. You should keep this in mind when you start to write code. The best thing that you can do is to keep a clear line between these two.
Data Structure exposes only data, but without any kind of functionality, in contrast with objects, that expose only functionality. Of course, we need to keep in mind that the balance between these two is hard to keep. You will need a data structure that exposes functionality. You only need to keep in mind what data you want to expose and where the functionality implementation should be added.
When you implement a functionality, you should talk only with friends and never with strangers (The Law of Demeter). This means a function should only access/call:
Hybrid structures are objects that contain also data structure. The problem with them is that it is pretty hard to add new functionality or data to them. This creates confusion because you don’t know what you should add there. It can indicate that the purpose of that entity is not clear, nor if data protection was needed.
They are used a lot when we need to store data somewhere (DB) or send data over the wire. They are called DTO and usually don’t have any kind of functionality. Their purpose is good, but we should keep in mind that we should use them only for the purpose they were created for. Otherwise a change in the data structure would trigger a lot of changes in our code.
On top of DTO we have Active Records, that are similar to DTOs, but have methods used for navigation like Find, Save, Delete, Send and so on. This functionality is usually offered by DB for example. The problem with them is that developers usually use them like objects and other functionality is added to them. Because of this we end up with an Active Record that has business logic inside.
What should we do? Create Active Records that store only the data structure and use separate objects to store the business rules.
Why do we need to talk about error handling? Because even if the main purpose of a code is not error handling, but the functionality that is implemented, we end up with code where only error handling can be seen and it is almost impossible for us to find the details about the real functionality that is exposed there.
To avoid these situations, there are small things that can be done at code level. First of all, avoid using error codes. This adds a lot of code to your methods and hides information behind the exception itself. Also, the caller needs to check every time the return code and implement a custom handler for different error codes.
Exception can be used with success with try/catch blocks that can be seen as a ‘transaction’ block, where you expect exception and you are ready to handle them. Also, there is a clear separation between the functionality and the exception handling.
} catch (FantaException nullEx)
} catch (CokeException nullEx)
It is important to remember that the function n that throws an expectation, should provide enough content about the source of the error. We should try to define specific exceptions based on caller’s needs. Why? Because the main purpose of them is to help the caller to find out the root of the issues.
You should never do two things. Pass a NULL to another function, because the function will need to check if it is null or not and to manage this situation. You basically pass the problem to another function, but without resolving it.
And you should NEVER return a NULL. The caller will need to check the result if it is null or not and add a specific handler. You should better return an exception that can be cached and managed by the caller.
We are surrounded by boundaries. When we are using 3rd parties libraries, code implemented by another team, core API and so on. In all these situations, we have a boundary that is set and a set of functions that we can use to cross over it.
It is important to know how to keep this boundaries clean and useful. The first thing that we should do when we need to use an external resource is to reserve time to learn and explore it. We need to discover the boundary, how we need to manage and use the functionality exposed by it.
The easiest way to learn is by writing tests that validate different scenarios. In this way we can be 100% sure that different flows will work and we know how to handle them. Also, we will be able to validate that the 3rd party offers what we really need.
When we have external 3rd parties that expose boundaries it is mandatory to define an adapter that would isolate us from the 3rd party library. We will have cases when we will need to fake the behavior or when 3rd party API will change. In this case we don’t want to create a domino effect in all our code.
The adapter should not expose 1:1 the functionality exposed by the 3rd party. It should expose the functionality what we need, not the one that is offered. All details implemented should be put in the adapter itself.
There are so many other things to say about the topics that were touched in this article. Here are 3 things that I would like you to remember from this article:
And YES, all of us should read ‘Clean Code’.
by Mircea Vădan
by Călin Lupo