How much actual attention and focus is needed to “get the codebase right"?
Around a year ago, I gave my “Sanity” talk to our local ruby user group - PRUG.
TL;DR - the goal of the presentation was to share my thoughts about how much actual attention and focus is needed to “get the codebase right”. The thought got clearer over the time, and I’ve come up with a story to share how developers must wrangle code, and negotiate changes in direction as the code and requests change over time.
Take a look at this feature story:
With a simple codebase like this, it’s OK to use AR callbacks for sending a “notifications” email. A few months later, however, the requirements change and something else is needed. Let’s say that we need the admin to be notified too. Let’s do that.
Still simple, still fine. But again, a few months later, new requirements, new code. We get push notifications, and now there is more than one admin for the group.
This code is not so pretty anymore. Also, we have a bug, we are notifying new admins that we accepted twice: once with notification for a user and a second time with a notification to the admin. Let’s fix it:
Now, once this is deployed, everybody is complaining about speed and timeouts. Time to make it async:
That’s better for the end user, but we got a bug report that sometimes admins did not get emails with notifications. After investigating, we realize the timeouts are now happening in the background, in resque—the push notification service lives in a very unstable network. Let’s fix this at least partially, by sending every notification separately:
Now we have a lot of classes for just a simple notifications feature. The fact that we are skipping one admin got a little bit covered with all those classes, and that’s a part of our logic. It’s still slow for a group with 1000 admins.
We need refactoring now: introduce proper notification classes, with target users, that will dispatch notifications correctly. Making push notification and email notification quack the same methods will also help us get rid of some complexities. This refactoring will also ensure next developer after you will not make a big mess of it when next feature request comes in (like sending an email to group moderators).
- Step 1 was fine.
- Step 6 is not.
- Refactoring is needed.
When did it go wrong then? 2? 3? 6?
The answer is: all of them. The first timeouts happened when we did number 2, but a simple dispatcher could have been used for number 3. With this method, 4 would be easier to fix, and 5 would probably not happen. We would never even land on 6.
Sadly, the problem with this story is that it’s fairly easy to see the problem and better solution by being on step 6. But if you are on step 1, 2, or 3, the “better way” to go is not so clear yet. Since it’s not possible to see the future, and quite difficult to change the code-past, a developer has to be focused and aware that at every moment we may be going in a better/worse direction.
A good developer is always making his/hers past design decisions work with what they think future is going to bring, when delivering what’s needed now. Uncompromised compromise between past, present and future - all the time, at every step.