(Like this article? Read more Wednesday Wisdom!)
One of the problems with software engineering is that 99.9% of the time, and in the short term, bad code works about as well as good code. It might be slower, it might use more memory, it might be hard to extend, it might be hard to find and fix bugs, but by and large it kinda works. So if the only thing you care about is whether code kinda works here and now, and you are not inclined to change your ways, you might want to stop reading now and read something else instead.
“Mostly working” is not at all what I am interested in though. Code that works here and now (and only in the happy path) is table stakes. I am not here to write code that mostly works today, I am here to write code that always works and that meets significant non-functional as well as functional requirements.
Call me old fashioned if you will…
When I was a much younger engineer, getting the code to work was a challenge in and of itself. When I started working on a problem, it was often not at all obvious to me that I could even write the code. When I eventually managed to do so, and it mostly worked,I felt considerable pride, and after basking in the glow a bit, I moved on to the next thing.
As a more senior engineer, I never have that doubt anymore. It is obvious to me that I can write the code to solve the problem. Instead, all my efforts are now focused on solving the problem in the right way and with beautiful code. To this end, I am spending considerable effort ensuring that the code is logically structured, consistent, well formatted, well documented, commented, easy to understand, efficient, fast, and reliable.
With the possible exception of the code I write for interview prep and AdventOfCode. For these endeavors, and these endeavors only, as soon as the code works, I move on. For all-practical purposes it is write-only code.
Because of this, I typically start refactoring my code while I am still writing it, because my increasing insight into the problem space leads me to improve my code design. Class hierarchies change, interfaces get reworked, variables get renamed, data structures get replaced, and new algorithms get implemented. When I am struggling with knocking out a high quality solution, I realize that this is because I don't understand the problem well enough yet. The only answer to that is just muddling through, because I know that with enough work and thinking, the necessary insights will come.
I continue doing this until the code is beautiful, because only beautiful code is good code.
Often when I look at code reviews, I see code that is not beautiful. Quite often that is a function of both the code that was already there and the code that is being added. The code review needs to merge these two bits together and it is hard to make beautiful code for two bits of code if one of the bits is not beautiful to begin with.
The problem with reviewing non-beautiful code is that non-beautiful code is hard to understand. There are often no comments, names are suboptimal, there is no logical consistency, the API is not thought through, and the formatting is not conducive to easy reading. In code like that it is hard for the casual reader to figure out what is happening, why it is happening, and if the proposed changes are going to improve matters in any way.
Many people will tell me that code reviews should only be done by people who are experts in the code base. These people are wrong. Code reviews that cannot be done by people who are not in the project themselves, are reviews of bad code.
I have had the opportunity to read a lot of my own old code and have consequently often wondered what the hell I was up to when I wrote that. This gets worse the older the code is, as patterns and libraries go out of style and choices that had to be made back then make less sense now. If I, the author of the original code, have difficulties figuring out what the code does and how it works, how does anyone else stand a chance?
Code spends more time being in maintenance mode than it does being developed for the first time. For that reason, the structure and quality of the code needs to be optimized for being maintained and not to afford “optimal” (read: speedy) initial development. I don’t care if it takes longer to write beautiful code; that investment will be earned back with plenty of interest during the maintenance phase.
A lot of code I see is also not very robust.
I was recently in a discussion about how to handle certain status changes and the discussion focused on how to handle a particular illegal status change coming in from an upstream system. One of the engineers proposed having a chat with the team responsible for the upstream system and tell them not to send that particular state change so that we wouldn't have to consider it.
I am abhorred and endeared at the same time by so much innocence.
Of course we need to talk to the upstream team and ensure that they understand what the contract is; hopefully they will do a good job with that knowledge. But we should also make sure that we deal correctly with any invalid status changes when they come in, because they will. Who in their right mind expects input coming in from the outside to conform to anything? You just always check and do the needful, regardless of what people send you.
My experience as an SRE permanently modified my brain. Whenever I read “will” or “should” in a design doc, I automatically translate that to “won’t” and then I parse the sentence again to imagine what would happen in that case. Same for “won’t” and “shouldn’t”, which I internally translate to “will”. In the same vein, “must” becomes “might”, and “never” becomes “sometimes”.
Try this translation table for yourself with the following sentence: “Our code will always do X because Y must never happen.”
Here is something that I am struggling with: How to move a team that is writing mostly working but non-beautiful code to a state where they start producing beautiful code.
I could start commenting excessively on every code review, but I don't have the time for that and I wonder if it would be effective. I could write code and style guidelines, but then if there is no enforcement, I am in the same boat as I am now, but now in the proud possession of code and style guidelines that are widely ignored.
Should I give out a quarterly beautiful code prize? Should I write and talk more about the importance of beautiful code? Whenever I commit code (a rarity, unfortunately) I try to set a high bar so that people can see how it is done, but that has so far also been unsuccessful.
Or is this an unwinnable battle?
> One of the engineers proposed having a chat with the team responsible for the upstream system and tell them not to send that particular state change so that we wouldn't have to consider it.
> I am abhorred and endeared at the same time by so much innocence.
I'm glad to hear you say this. For me, maybe its because I lack trust in anything that I cannot directly control, but forward progress for me is always about something I have direct control of. I cannot guarantee other teams hold up their side of the bargain, so I focus on the aspects of the system I can fix to handle the 'environment' it's working in (sometimes that other team is a massive open source project that has even less control *cough* kubernetes *cough* [as opposed to another team in the same company where leaders may be shared and have influence]). That philosophy has always served me well and ensured that even when the contract is broken, the system continues to function.
> Or is this an unwinnable battle?
I have mostly resigned to this being unwinnable. I am trying again to win this battle. But its slow and painful and amounts to 1on1 convincing a person at a time. Critical mass looks like enough 'leaders' pushing I guess. And/or enough code that 'copying/pasting' any random snippet leads to the same result (as much as I hate copy/paste code).
This is where science/engineering meets art for me. And subjectiveness creeps in which makes it hard to 'win' (because what is 'winning'? is it even correct?). So, the table is already tilted against us. Beyond this, timelines motivate some folks to not care about these aspects (not necessarily their fault, they are at a different spot in life).
I find myself using the phrase that has always captured it so succinctly. "Culture eats strategy for breakfast". Individuals can shine in any culture, but the group culture has significant pressure to avoid change.
How about training an AI model on carefully handpicked changelists, which Jos extensively commented on? Maybe not yet. But maybe soon.