Dividing and conquering is important when it comes to development. I find that there are lots of reasons why you want to split your tasks up into smaller sub-tasks. This becomes more important the bigger the team you work on as the code base evolves more rapidly and you want to take advantage of being able to parallelise larger features. In this post, I am going to go through some specific reasons why smaller is better and a few tips from my personal experience.
Motivation
Everyone commits to the mainline every day
Like the never ending expansion of the universe, branches diverge. Histories accelerate away from each other and the longer they are kept apart, the more distant they end up. Hence comes, in my view, the lesser talked about but nevertheless very important mindset of Continuous Integration; everyone commits to the mainline every day. As with all absolutes in this industry, following it to the T is not always a good idea but warning alarms should be sounding if it gets to 4.30 and you haven’t merged anything. It is an indication that you have bitten off more than you can chew and with every ticking second, the risk of conflicts increases. The rest of the team will also be thankful for the incremental availability of your code rather than waiting a week for the final dump.
Extend the single responsibility principle
Only try to change one thing at a time. It will keep the brain focused and the more confused the scope of your branch, the more likely you are to muddle concepts in your implementation. Putting in some time up front to divide your feature up into separate subtasks can help you conceptually split up the task at hand and make your implementation cleaner.
Think about the reviewer
There is nothing worse than being assigned a merge request with 20 modified files including a week's worth of frantic head scratching and widespread chaos. The effort to do a thorough review will put a lot of peers off and bugs slip through the cracks. Not to mention that you are saving one of your team mates an afternoon squinting at diffs. Split up the task into bite-size chunks and put in two or three smaller merge requests each representing a single aspect of the feature. You can even base the branches on each other, indicate an order for review and merge sequentially. You can always rebase onto the others if you have some rework.
How to do it
Plan ahead
The first point to make here is that this is not always trivial. Not all features are splittable in obvious ways. You need to plan ahead before you start writing code. Getting a clear picture in your head of the steps it will take is not wasted effort as you will likely need to do the same thinking during your implementation. In my view it is best to put the effort in up front instead of after you have started to limit the risk of rework.
Keep it inaccessible
The initial instinct is to dive into the existing implementation and start coding. The issue with this is that you can break everything to the point where you end up having to make widespread changes just to make the application compile. We have all been there; you change the signature of one method, the interface needs updating. You update the interface, the other implementations break. You update the other implementations, you break all the places they are used. You finally fix the main application, the unit tests are all broken.
Try writing your feature in a separate method. This has a couple of advantages:
- You don't enter into the compile error "whack a mole" situation straight away. This immediately limits the potential scope of integrating your code.
- Until you call the method, no one can use it. You can bite off a manageable chunk of the feature and submit it for review without polluting the merge request with all the changes required to integrate your feature. You can write unit tests. You can even deploy it to production. It takes a lot of the pressure off completing a complex feature.
There are variations on this theme; write a service but don't inject it anywhere, add an endpoint but don't call it anywhere (depending on how public your API is...), add a part of the UI but don't give anyone permission to navigate to it, or even add a good old feature flag. Have a go, you may find it liberating.
Think parallel
"Minimize work in progress". One of the pillars of Kanban is the driving force behind this one. Once a big User Story is opened, it is preferable to put multiple developers on it to push it through as opposed to having everyone working on their own subject. Try and think about how you can split up the logic in such a way that you can all work on it at the same time. Maybe merge early with some stubbed methods to give others access to an interface. Add dummy endpoints to let your front end developers plug in the UI. When working in parallel, I always think that the smaller is better so everyone can punch their merge requests in quickly and more regularly integrate their work. I have been in several situations where 2 or 3 devs have taken out a shared branch that stays open for a week. It accumulates lots of horrible merge commits as people push and pull and some poor soul is laboured with the task of peer reviewing this behemoth. I think this way of working should be avoided at all costs.
Another personal preference is that I don't like it when people split up implementation and unit tests. I don't think anything should be merged to the mainline without being unit tested. What if you find out it doesn't work? What if you forget about it? What if it turns out that the code you have written is completely untestable? It also prevents you from being able to revert the commit as a bunch of tests in a later commit depend on it. I find it also demotes the writing of unit tests to an afterthought. Some people do it but I don't like it and I find it very hard to press approve on a merge request where someone assures me that they will test it later.
Beware of the creep and know when to split
Abort, abort. The feeling is all too familiar when you realise you have gone too deep trying to integrate your new code and now you find yourself needing to significantly rework classes in some far flung corner of your code base. Know when to stop yourself. You can go back a couple of steps, tidy up and create a merge request. Nothing is stopping you from redividing up your Jira tickets after you have started. Your scrum master will be happy to see things move to the right in any case. If you find your merge request touching 10+ files, think about how you can head off the changes without breaking everything and resume in another branch. I find this step back often gives me back the clarity and perspective that I lost whilst desperately trying to get it to compile.
This article has summarised my thoughts on splitting up tasks. There are several compelling reasons to do it and hopefully you find some of my approaches are useful. There is definitely a non-negligible effort to this and one-man teams may not justify this. However, I believe that being a good member of a development team is not just about being able to get things done quickly as an individual but helping your team work well as a whole.