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 ‘Clean Code’ book, written by Robert C. set the standards in our industry from this perspective. It is the developers Bible and many times it is a used as the ‘law’ of the code. I don’t want to go deeper into this subject, but I promise that one time I will talk in details why/why not we should use this book as THE Bible.
Today topics
In this article I will try to talk about Objects and Data Structure, Error Handling and Boundaries I expect to go from the code format, to how the code should look like and how we should implement different features.
Objects and Data Structure
I think that all of us remember the University courses, when teachers try to explain us that we should only expose from a class, only the information that is needed by others. But, because current programing language give us so easily the possibility to expose a data outside a class there are many times when we end up with a lot of private data exposed to the system.
One of my colleges called gets and setter the tool of devil. It is funny, but sometimes 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 don’t expose , that don’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. Botch solutions are good as long as we don’t give any kind of implementation details.
Be aware that there is a big difference between data structures and objects. You should keep this in mind when to start to write code. The best things that you can do is to keep a clear like between this 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 this two I hard to keep. You will need a data structure that expose functionality. You only need to keep in mind what data you want 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 mean a function should only access/call:
Hybrids
Hybrid structures are objects that contains also data structure. The problem with them is that is pretty hard to add new functionality or data to it. It’s create a confusion because you don’t know what you should add there. It can indicate that is it not clear the purpose of that entity and if data protection was needed.
Data Structure Object
There 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. The purpose of them is good, but we should keep in mind that we should use them only for the purpose that they were created. 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 with DTOs, but ha 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 we should do? Create Active Records that store only the data structure and use separate objects to store the business rules.
Error Handling
Why we need to talk about error handling? Because even if the main purpose of a code is not error handling, is 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 this 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 hide information 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 blocs that can be seen as a ‘transaction’ block, where you expect exception and you are ready to handle them. Also, it is a clear separation between the functionality and the exception handling.
Nulls
You should never do two things. Pass a NULL to another function, because the function will need to check if 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 is null or not and add a specific handler. You should better return an exception that can be cached and managed by the caller.
Boundaries
We are surrounded by boundaries. When we are using 3rd parties libraries, code implemented by other team, core API and so on. In all this situation, we have a boundary that is set and a set of function 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 expose by it.
The easiest way to learn is by writing test that validate different scenarios. In this way we can be sure 100% 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 expose by 3rd party. It should expose the functionality what we need, not the one that is offered. All details implemented should be putted in the adapter itself.
Conclusion
There are so many other things to say about topics that were touched in this article. There 3 things that I would like you to remember from this article:
And YES, all of us should read ‘Clean Code’.
We could say that ‘Clean Code’ book, written by Robert C. set the standards in our industry from this perspective. It is the developers Bible and many times it is a used as the ‘law’ of the code. I don’t want to go deeper into this subject, but I promise that one time I will talk in details why/why not we should use this book as THE Bible.
Today topics
In this article I will try to talk about Objects and Data Structure, Error Handling and Boundaries I expect to go from the code format, to how the code should look like and how we should implement different features.
Objects and Data Structure
I think that all of us remember the University courses, when teachers try to explain us that we should only expose from a class, only the information that is needed by others. But, because current programing language give us so easily the possibility to expose a data outside a class there are many times when we end up with a lot of private data exposed to the system.
One of my colleges called gets and setter the tool of devil. It is funny, but sometimes 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 don’t expose , that don’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. Botch solutions are good as long as we don’t give any kind of implementation details.
public class Person
{
public double WeightInKg
{
...
}
// OR
public double GetWeightInKg()
{
...
}
}
Outside this class, you don’t know how the data look like and what the format of it is. If we would add getters and setters everywhere, what would be the value of encapsulation … NONE.Be aware that there is a big difference between data structures and objects. You should keep this in mind when to start to write code. The best things that you can do is to keep a clear like between this 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 this two I hard to keep. You will need a data structure that expose functionality. You only need to keep in mind what data you want 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 mean a function should only access/call:
- Methods from the class where is implemented
- Methods from objects created in the methods itself
- Methods from objects that are send as arguments
- Methods from objects help as instances in the class where method is implemented
Hybrids
Hybrid structures are objects that contains also data structure. The problem with them is that is pretty hard to add new functionality or data to it. It’s create a confusion because you don’t know what you should add there. It can indicate that is it not clear the purpose of that entity and if data protection was needed.
Data Structure Object
There 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. The purpose of them is good, but we should keep in mind that we should use them only for the purpose that they were created. 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 with DTOs, but ha 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 we should do? Create Active Records that store only the data structure and use separate objects to store the business rules.
Error Handling
Why we need to talk about error handling? Because even if the main purpose of a code is not error handling, is 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 this 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 hide information 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 blocs that can be seen as a ‘transaction’ block, where you expect exception and you are ready to handle them. Also, it is a clear separation between the functionality and the exception handling.
try
{
// Functionality
} 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.Nulls
You should never do two things. Pass a NULL to another function, because the function will need to check if 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 is null or not and add a specific handler. You should better return an exception that can be cached and managed by the caller.
Boundaries
We are surrounded by boundaries. When we are using 3rd parties libraries, code implemented by other team, core API and so on. In all this situation, we have a boundary that is set and a set of function 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 expose by it.
The easiest way to learn is by writing test that validate different scenarios. In this way we can be sure 100% 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 expose by 3rd party. It should expose the functionality what we need, not the one that is offered. All details implemented should be putted in the adapter itself.
Conclusion
There are so many other things to say about topics that were touched in this article. There 3 things that I would like you to remember from this article:
- The different between an object and a data structure
- Never return a NULL, or forward a NULL to another method
- An adapter for a boundary should expose the functionality that you need, not the one exposed by the 3rd party
And YES, all of us should read ‘Clean Code’.
Comments
Post a Comment