Friday morning ... You are almost done with your coding task and just need to complete a unit test. So, before starting the day, you update your "branch" with the remote repository, to avoid any problems. Then, the unthinkable happens: Your code is completely out of alignment with the remote version! Not just for trivial reasons, such as changes in style, but for architectural reasons!
After talking to colleagues, you discover that someone has decided to Refactor the code to make it more beautiful and presentable, and/or more flexible.
But that "refactoring" was done in only part of the code, leaving other parts (including the test cases) incompatible.
You know the caption for this photo, right? (what the...)
This little story may seem bad, but what is really bad is that it is real and it happens to all developers.
What is "working code"? How can you prove that software is "working"? Simple. The software passed the tests, including acceptance tests.
The source code that makes up the software has only one purpose: to implement the functions that it must perform, correctly. It doesn't matter if the source code is aesthetically pleasing or not.
Some developers resort to the "maintainability and flexibility" argument, trying to give importance to the aesthetics of the source code, but ... First of all, the software must work before we think about maintenance or flexibility.
A "beautiful" source code is one that is working and passing automated tests, including acceptance tests. Trying to refactor the software before considering it to be working, can mean "premature optimization", to paraphrase Donald Knuth:
“The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.” - The Art of Computer Programming
Perhaps he was only referring to the performance of the source code, but we can extend this meaning to anything, including the aesthetic itself or future features.
The worst thing a developer can do, in addition to commiting code that does not compile, is to break the source code of the test cases.
If you did something that broke a test, then you should fix it right away! Perhaps the test was "getting to know" the structure of the software too much, or perhaps you have changed something that is a basic understanding of how the software works.
The point is: If the test is broken, then the software is no longer working. It doesn't matter how beautiful the source code has become.
Test-driven development is the most widely used agile method for software development. And this is no accident! Its most sacred principle is its workflow (according to wikipedia):
1. Add a test: The adding of a new feature begins by writing a test that passes if the feature's specifications are met. The developer can discover these specifications by asking about use cases and user stories. A key benefit of test-driven development is that it makes the developer focus on requirements before writing code. This is in contrast with the usual practice, where unit tests are only written after code.
2. Run all tests. The new test should fail for expected reasons: This shows that new code is actually needed for the desired feature. It validates that the test harness is working correctly. It rules out the possibility that the new test is flawed and will always pass.
3. Write the simplest code that passes the new test: Inelegant or hard code is acceptable, as long as it passes the test. The code will be honed
anyway in Step 5. No code should be added beyond the tested
functionality.
4. All tests should now pass: If any fail, the new code must be revised until they pass. This ensures the new code meets the test requirements and does not break existing features.
5. Refactor as needed, using tests after each refactor to ensure that functionality is preserved: Code is refactored for readability
and maintainability. In particular, hard-coded test data should be removed. Running the test suite after each refactor helps ensure that no existing functionality is broken.
Did you read steps 3 and 5? Step 3 says, "Write the simplest code that passes the new test", and goes further, explaining that inelegant code is acceptable. Refactoring should only happen in step 5 after all tests have passed without problems.
But there are also integration and acceptance tests that often cannot be performed in the early stages of coding. Software should only be refactored when it passes ALL test levels.
Whenever I can, I preach this principle about refactoring: Refactoring is a controlled technique for improving the style of an existing codebase.
Refactoring is a controlled technique for improving the style of an existing code base.
What does "existing" mean? It is logical to deduce that it would be working code. It does not make sense to refactor what is not working.
Martin Fowler, in his article on refactoring, says something very significant about the way to refactor source code:
Its essence is applying a series of small behavior-preserving transformations, each of which "too small to be worth doing". However the cumulative effect of each of these transformations is quite significant. By doing them in small steps you reduce the risk of introducing errors. You also avoid having the system broken while you are carrying out the restructuring - which allows you to gradually refactor a system over an extended period of time.
Did you read the explanation well? "Behavior preserving transformations" means that the code cannot break due to its refactoring.
If you are doing a radical transformation in the source code, then you have thrown away all the work done and are going back to the coding step. This is not refactoring at all!
YAGNI means "You Ain't Gonna Need It". In software engineering, it is a working guideline that suggests programmers not to add functionality to a program's source code until it is really needed.
I also call this "developer anxiety" a habit of modifying the source code to accommodate things that are not yet a reality. They justify putting the blame on the Client, saying that these things were requested for future implementation.
It is as pilots usually say: "Do not chase the gauge", that is, do not modify the code due to changes in some instruments. Try to keep the flight stable and assess how much these changes will measure your trajectory, in this case, your software.
I have a guideline that I usually follow when I see these ad-hoc requests from Clients:
If it works in one case, it can be adapted to work in another case. Now, if it doesn't work at all, it's better to throw it away and start over.
In other words, the software must be ready with its source code working before we think about future modifications. This is what the backlog and sprints are for.
Putting structures and functionality into the source code just because you will need them in the future, even if the software has not yet been delivered to the Customer, is a gross mistake that many developers make today.
You may still remember the Unified Software Process chart, according to which we had separate phases and disciplines for analysis, design, and conducting (we knew it as a "whale chart"). Now, especially with Scrum, we don't have that anymore.
Generally, tasks are estimated at "story points" and included in a Sprint, without considering the disciplines of analysis and design. You need to build the simplest code that works (like an MVP) and only refactor when it passes all tests. There is no point in wanting to do classes and components design later and try to "fit" the code already made.
Always remember this:
Do not refactor code that is not yet working.