Refactoring, the project best friend
Helping you to improve your code conception
Hi! Today we're going to talk about refactoring.
In computer programming and software design, code refactoring is the process of restructuring existing computer code – changing the factoring – without changing its external behaviour. Refactoring is intended to improve the design, structure, and/or implementation of the software (its non-functional attributes), while preserving its functionality. Wikipedia
The content of this article is based on Martin Fowler's Refactoring book with some additional notes that I took while I was reading the book, and text revisions by Cesar Morigaki. I strongly recommend that you read it for further more information, it provides several methods (the catalogue) for refactoring your code and also some insights for good code conception.
Any fool can write code that a computer can understand. But only good developers can write code that humans can understand.
1: Test legacy code when possible.
Make sure that the code that you are going to refactor has tests, at least unit test. If you change the code, and you do not have tests, you cannot be sure if the behaviour of the new code is the same as before. Working on legacy projects sometimes is difficult to have tests due to the architecture of the project, but you should try to write some anyway, this can avoid future bugs;
2: Run tests after each change
Test after each change. When you have small changes to tests, it is easier to find bugs, so this will save you some time of debugging. Also, if I can refactor my code and be quite sure that I didn't introduce any bugs because my tests went green, then I can consider myself satisfied with sufficiently good tests;
3: Leave the code better than you find it
Always leave the code better than you find it. Classic, this does not mean that you need to refactor all the code according to the best patterns or architectures, just leave better than you find, improve names (of functions variables), remove some code that is not needed … Small changes together have big impact. (Atomic Habits by James Clear, it is a different context but works the same.);
4: Should be easy to maintain.
The true test for your code is how easy he is to change, (keep this in mind). A normal project always have new features, or changes to implement, so how easy it is to do this in your project? Also, we structure our software to facilitate changes, after all, software must be soft;
5: Open-closed
When implementing new features in the project, you should not need to change the current code, (SOLID O Open Closed) Clean Architecture. Also, when you are doing a refactor the goal is not add new code, only restructure the actual code. If a change in a record causes a field in another record to change, it is a sign that there is a field in the wrong place;
6: Hold the pressure
When (N) people are pressing you to simply walk faster, it is sometimes necessary to say. Wait, I have to consult the map and find the fastest way;
7: KISS (keep it simple, stupid)
Implement software that solves only the needs currently understood, but makes the design of that software excellent for those needs. As my understanding of user's needs changes, I use refactoring to adapt the architecture according to new demands;
8: Tweak afterwards
Refactoring can certainly slow software down, but it can also make it more conducive to performance tweaks. Performance some times can be handled only when you understand what is happening;
9: Measure
Don't speculate about problems in your software, make measurements and understand what's going on. You can not speak about something that you do not understand;
10: Iterate quickly
A well factored code provides time to invest in performance-oriented adjustments, I can also add functionality more quickly. It gives me more time to stay in the performance. Running a profiler ensures that I spend that time in the right place;
11: Comment only the necessary
A block of code with a comment that tells what it does can be replaced by a method whose name is based on the comment. Also, when you need to write a comment, try to refactor the code first, so that any comment becomes superfluous, comments help explain why you did something;
12: Code smells
Avoid leaving code smells in your code like mysterious name, duplicate code, long function, long parameter list, global data, changeable data or mutable data.
Code smells are when you see some code and something on it does not smell good, for example, two duplicated methods may work as they are, but they will be nothing more than fertile ground that will pray for bugs in the future. Whenever there is duplication, there is a risk that a change in one copy will not be made in the other. In general, duplicates are difficult to find …
13: Rely on git to remember the code history
I think I will need this kind of thing someday, if all these resources are being used it is worth it, but otherwise get rid of them. Do not always think in everything in thee future, think in the present also, sometimes leave dead code, to use it in the future can create a mess. Git is here, use it;
14: Immutability
Instead of returning the instance of objects from a data source, always return a copy, so you prevent someone from changing the current instance. Also write unit tests to ensure that when you return an instance of an object it will not be modified, when you do return the instance you cannot know what will happen with that data.
I can pass on an immutable piece of data to other parts of the program and not worry about the fact that it can change without the method that includes it being aware of the change;
15: Create structured data
Encapsulating data helps us to see when data structures are modified, when you access data directly in a variable for example it is difficult to track and know who is using or changing it, when you have it encapsulated in a method for example, you can handle this easier.
The secret to good modular design is encapsulation. An encapsulation means that the modules need to know less about other parts of the system. To achieve this modularity, it is necessary to ensure that the related software elements are grouped and the links between them are easy to find and understand;
16: Composable solution
When it is necessary to replace an algorithm, I have to make sure that I decompose the method as much as possible. Replacing a large and complex algorithm is very difficult, just making it simpler can I make substitution more controlled, like extracting in more methods or creating a structure for it.
17: SRP (Single Responsibility principle)
Any variable with more than one responsibility must be replaced by several variables, one for each responsibility. Using a variable for two different tasks is very confusing for the reader.
18: Do not modify the input object
Avoid modifying input parameters of a function, you have the risk of modifying the value and whoever called the function does not notice that the value has been changed and causes inconsistencies in behaviour, for example the same value can be passed to your function, and after it be used and another function;
19: Nested conditionals
Nesting conditionals in some cases mask the true meaning of what is happening in the function, try using guard cases to get out of function in those cases. Example: if (number < 2) return, in this way you do not need to add if validations in your functions;
20: Avoid parameters flags
Always make explicit functions available for the tasks to be performed that are a clearer option, than using flag parameters to indicate which execution should be done;
Some quotes:
It is better to write and run incomplete tests than not to run complete tests.
Refactoring means never having to regret, I just make the correction.
Command query separation, a method must perform an action or return a value, but not both at the same time.
That's it, let's code? I hope this article helps you! See you in the next post.