Final work with the code can be a real pain in the… neck. It is often a long and tedious process of going through and verifying all the changes which can bring you to a moment when frustration is directly proportional to the time spent on writing the code. Is there a way to tidy up the work and avoid those tears under the shower at the thought of another implementation? Yes, there is. You can work with small chunks.
What’s the prob, dev?
Imagine waking up after tens of minutes (or even hours!) of coding trance and seeing 50 changes in your editor. Changes were made in totally different places of the application.
You look at this code-battlefield and ask yourself *the same question the Ant-Man did. *“What the hell happened here?”... To make matters worse, not everything is working and you have a lot more to do because you poked your thumb in too many pies. Part of the changes has to be verified again and it will pull further changes. On top of that - you haven’t corrected even one test.
You know that means one thing - a need to go through and verify all the changes, remove the unwanted code, and fix the tests. Well, you could probably say that you’re starting iteration number 2. The problem is, you are tired and you lean towards leaving work for tomorrow. After all, you did a great job today. Isn’t it enough? ;)
Eventually, that approach drives me to the moment I don’t feel like working at all. It’s like complaining about other people’s work and making a much worse mess than the others. Frustrating, isn't it?
That’s why I try to organize my process of creating software by the little approach. Small, but thought through and effective steps.
Let me guide you through all the stages of the work and point out its important elements.
Let’s start with a good plan
Think over carefully how to approach the problem to finish the task as a whole. Focus only on your goal and only those changes that will lead you to achieving it.
What’s important - I rarely pay attention to the tasks I’ll be working on next week or in two weeks' time. I focus on it when the time is right. Not once and not twice I had prepared implementation for the future changes and finally, there was no need of doing all that work because requirements changed in the meantime. All the work that was done turned out to be a waste of time.
It’s hard because you’re often overwhelmed with tasks. You hear about SOLID, DRY, and the rest of the acronyms that make the whole thing messy and abstract. You end up wasting your time instead of just working on a specific piece of code.
This is the moment when you need to start making decisions. For instance, you need to decide if you’re implementing the strategy pattern or not. But on top of all that is if in the future you will be able to easily change/ replace the elements you are currently working on. Without deleting half of your code. I focus on coupling the changes with the rest of the code.
I tend to point out the work phases on a sticky note. After completing each task I can cross it out. But the real moment of little glory comes when all the tasks are completed and the crushed paper lands in the bin.
The plan isn’t a carbon copy with the changes I’m implementing. It is because you can subdivide them into even smaller steps.
It is my high-level work plan and not the plan for the particular changes.
One change at a time
This is where it gets interesting. After preparing the plan it’s time for a serious fight - decomposing the work into smaller pieces.
Examination of borders and drawing a line where I start and finish the work is just a heuristic. In that matter, I rely on two factors.
A type of change is the first factor. I’ve defined a catalog of 5 different potential changes:
- Change in the file and directory structure - changing names and moving them
- Refactorization - preparing the ground in the accordance with Uncle Bob’s 'Clean Code' principle
- Implementation - the most atomic and independent part of the function you’re working on
- Fixing an error
- Deleting an unnecessary, unused part of the code
I always try to make only one change at a time. The exception to this rule is when the changes are mutually exclusive, for example - changing the file name that fixes the error.
Unfortunately, there is a trap here because it’s impossible to describe decomposition in just five easy points.
Second factor. I verify if the change meets the assumptions below:
- To be atomic
- To be as independent of the future changes as possible. What I mean - it doesn’t require something that doesn’t exist yet
- To be dependent on things I added earlier. What I mean - it can use previously added functions
- To change one specific function
- To apply only one specific file (ideal situation)
- To easily describe the changes in just a few words (it makes creating commit messages a lot easier)
- To start from the deepest changes, the ones at the end of the calls chain
Let me show you an example:
- I add a function to calculate the X. It’s not used anywhere yet (new MathCalculations function)
- I add a new endpoint that I’ll be taking actions in (only Controller action and route)
- I add the validation of data coming to an endpoint
- I add business logic (e.g. in UseCase)
- I connect triggering logic with the endpoint
Theoretically and practically I am able to merge my changes with the main development branch. Nothing should happen because various elements are independent of the next steps I plan to take.
This is, of course, just an example and we may find that to point 4 we would need to add a new entity, repository, or generate a new table in the database. I would think about these as separate changes.
It’s important to remember that theoretically, one single change can require dividing it into many smaller pieces and each one of them follows exactly the same process. I was thinking about 3 changes… and I ended up making 7 :)
Decomposition is the key part here. Either you have it or the process itself won’t get you anywhere.
Earning the ability to do that won’t always mean it is going to be easy (but also no one promised it will be ;) ). You often need to know the system you’re modifying, its architecture, code structure, and the main principles it follows. Without them, working little by little is a lot more difficult. What’s important is that each new task is knowing the code and how the software was built. This is what makes us move around much quicker and with higher confidence.
In this step, I’m not afraid to use comments // @ TODO. But only to make my work in the next step easier and finally to replace it with an appropriate implementation.
Everything needs to work
Everything needs to work after the changes you made. Every added, deleted line or modification can’t have any side effects, e.g. damaged automated tests, defective SCA, or malfunctioning software. Everything needs to be compiled correctly.
What’s the next step? To check if everything works. I verify not only the app but also everything needed to implement the next change in the environment.
I tell more about the changes testing approach in my 'Programmer, Test!' article. You’ll find a list of points worth remembering in the process of changes verification.
Write it down!
Preparing the storytelling about changes in the right words is the final step. Remember, you need to explain what you have done and why.
- I add files to the Staging Area (git add)
- I make the Git Commit and the created history I put into the Commit Message
This is where I can make the next step. Part of the work I consider done has been secured and I can safely take the next step in this case
What Have I achieved?
First of all, using only one tool (git) I can track/monitor the progress of my work. Even if I had a difficult weekend due to my friend’s bachelor party I could still easily remember what I had done on Friday. And after getting rid of the hangover I am able to quickly get back to work on Monday morning… ;)
And what’s important - I can easily undo my experiments. All I need to do is to get back to a specific commit.
In my opinion, it’s important to take care of proper history creation used in Commit Message. You get this feeling of sweet harmony in your work. You know what you changed and most importantly why you had done it.
But on the other hand, if you don’t care about this you can use Git Squash to simply merge a few commits in one. That way you will describe the final result, not how you achieved it.
Obviously, as always, the right approach depends on the project you’re working on or simply your preferences.
Do I push every commit created?
It depends. Sometimes you co-work with someone on the same branch and your partner is waiting for your changes. The other time you will just upload them from time to time.
Over time you will get this on your list of habits and you will realize that the work is getting done faster. Next thing you know you have a bunch of tasks marked with the green ‘done’ tick. In the end - practice makes perfect, or rather “practice makes practitioner” ;)
In the end, I would like to draw your attention to 'Commit Often, Perfect Later, Publish Once: Git Best Practices'. Actually, I think this is where my adventure with the little by little work and Git approach had begun.