Clean, best, friendly, excellent code - we are all trying to find the right words for the good code. And I found that we never use the term - refactorable code. I will describe why this is a primary quality feature that we should achieve.
We can rely on some metrics, but we should think about the code’s refactorable ability at first. If it will be hard to change, then no matter what metrics you are using. The code will be unmaintainable.
Nub: - Hey, my code coupling metric is very low. This code is fantastic.
Pro: - But this function is impure. How will you move it to another class if we will need it there?
Nub: - Emmm, yeah, it will not be so easy.
Some programming languages, or most of them, have a magick mechanism. You can write wired things, and you should think twice before doing that - because you will have a problem with maintainability in the future.
How to measure
There are two problems with the metrics. Hard to get some numbers, and we do not know what to do with them. In our case, I would like to suggest a list of questions that you should ask yourself before pushing the code to the master main branch.
- Would it be easy to move classes to another location?
- How to split that class into several parts?
- Is it easy to replace this class with another implementation?
- Would it be easy to move this function to another class?
- Is it easy to add parameters to the function?
- How to extract a statement to the function?
If you think that these questions are mild, you need to check some parts of your code. Probably not everything is okay.
Is it possible to move your class?
IDEs are perfect for boosting productivity. Y ou can move your class (or even several ones) to another package/location/module just with a few shortcuts. But we are developers, and sometimes our architecture is full of unusual things. IDE can’t resolve references, and we should use the old god find and replace feature. If it is true - refactor your code right now.
Another problem with modern solutions is a text format YAML/JSON/INI. We put class FQNs in the configuration files and blaming IDE for the inability to refactor them. Use build-in language futures and avoid hardcoding CFQNs or other mappings into the strings. Some excellent languages have the support of the DSL. Use it, and you will have less pain tomorrow.
Our classes can be small or huge. Some developers create classes with 1k lines of code. They said that this logic should be in one place. Maybe, who knows. I saw some examples when after 1k lines, developers continue to put more and more statements to the class because no one wants to deal with that crap. Often it is troublesome to split class if all methods rely on properties and state that can be changed anywhere in the class (or more horrible outside of it)
Make your classes small, and it will be easy to add, maintain, write tests, and replace. We can spread logic across multiple classes or leave it in one. You can always have a holy war about that. The smaller classes are - the better. Every smart developer would agree about that, no doubts.
When we keep our classes small - then it is easier to replace them. Sometimes it becomes beneficial. Imagine you want to change your caching layer (from Memcache to Redis). The more methods we have in class - the more problems we will get.
Often people argue about “replaceable solutions”. You would never be able to change Mysql to Mongo, so there is no need to write a maintainable DB layer. But it doesn’t mean that you should build crappy code. You need to think about future evolution and make classes small, simple, and easy to replace. For the last four years, I rewrote some layers of the application. Emails - change API provider. DB layer change - from Mysql to Mongo. And every time, I was thinking about making the future refactoring process more efficient.
Think about the next evolution of your classes, how they will be refactored and replaced. This will shift your code to another level.
Simple function is the core of everything
Is it easy to change big functions? What to do if the function depends on some global data? Too many parameters, but we need to add one more? Before writing big functions think about future maintainability. Will it be easy to split and move some statements to other functions? Every great developer knows about things that should stay away: impure functions, global state, and a massive amount of statements.
We have great tools nowadays, but often we are writing the code in a specific manner, and these Tools can not help us. Here are a few examples of how I solved the real-world problems of maintainability.
Php arrays configuration
The first programming language was PHP. Php arrays can contain any kind of data and can have a custom structure. When I was trying to configure my application with arrays, I found a problem with future refactorability. I need to find documentation about the structure of that data, what items I can put there, and whatnot. Imagine the following data:
What attributes I can put there?
Where I should find an implementation for the
Where is documentation about this structure?
Instead, I can write plain PHP Objects
If I need to add a new validator rule, IDE will give me a hint, and I will easily find all validation rules that can be applied to the string. There is no need for the documentation.Everything is in the code. You will also have fewer errors related to the validator names because you can not pass int validators to our CompositeStringValidator. It is a simple type of system that is done in PHP.
Scala implicity feature.
Implicits in scala is like auto resolve values based on types.
When you invoke
notFound function, you just pass one parameter - message. ResponseBuiler implicit parameter gets resolved automatically.
When you are building some DSLs it is great and can guide you to a beautiful code.
I found a huge pain with using this feature. There is no possible way to move function to another class with IDE, and this is a big drawback for me.
Instead of using a few shortcuts, I need to copy/paste the code and type many symbols on my keyboard to perform some refactoring. Now, I’m very careful with implicit features - because it slows me down in the future.
After some time, I have realized that refactorable code is the best code. Because changes are always following us, and we need to be ready for that.