Software development at times feels like a one-hundred-metre dash. At times it feels like a never-ending endurance race. At yet other times, software development is a whole series of short races, where you are blindfolded, spun around in circles before starting, and then criticised for not having already crossed the finish line yesterday.
Given the vagaries of software engineering, unrealistic and often contradictory expectations from colleagues, customers and (admit it) yourself, how then can you better prepare for those mad dashes? Is there anything you can do to ease your stress at these inevitable times of high pressure and short deadlines? Yes! There is! It is so simple, why haven’t you thought of it before?
Actually, that’s probably not (usually) a good idea. So then, we need a different strategy. A strategy which will enable you to work more effectively, and improve your own (future) happiness. As a starting point, I propose taking some small steps which will set you on a better course. These steps won’t necessarily ease your immediate workload, and some of them will add a little extra to your daily process. However, the theory behind this proposal is that a few little changes in your development process can yield major benefits down the track. This is especially true at times of particular high stress. And the most relevant example of that is; when you really, really need to solve a major production fault last week.
Step 1: Write useful, comprehensive commit messages.
If you aren’t doing this, have a stern word with yourself and don’t ever (not) do it again. There is absolutely no point in writing commit messages like these (which I have seen time and time again);
- “Work in progress” / “WIP” (thanks for nothing, guy)
- “Fixed a bug with the text module” (what bug? Why did you “fix“ this bug in the way you did?)
- “Changed SomeFunction() to return 0 instead of -1” (yes, I can see that from the diff, but why did you change this? What impact did it have? Why not return 123456789 instead?)
Joe Lencioni recently wrote a nice, concise piece on crafting great commit messages.
“I try to be mindful that commit messages are for two audiences: code reviewers and future developers (including myself). I aim at giving these audiences context to understand why I made the decisions I made, which will help them make better decisions about the code I am committing today.” – Joe Lencioni
A commit message does not need to tell me what you did. I can work that out myself from the corresponding diff. I (usually) don’t need to know how you did it either; assuming I can actually read your code and you haven’t made it overly complicated, I can probably also derive the how for myself (There might be exceptions to this where you’ve employed a particularly arcane, obscure or complex algorithm and some explanation there would be nice.)
I’ll re-emphasise the previous quote; the most important thing to focus on when writing a commit message is why. Why was the change required? Why did you use the approach you chose (and not another)? Why should you be allowed to ever make code changes again?
To keep our future selves (and our colleagues) sane, we need to focus on the why, because the what and the how are contained as evidence in the code itself. But I cannot determine from the code that you got some advice from a mentor, or found a relevant answer on StackOverflow. I can’t tell that you considered a more efficient, yet more complex, algorithm, but didn’t have the time to fully understand and test it. I can’t tell that you wrote this code expecting that users never use the letter ‘Q’ in their input. Leaving these sorts of hints for your (future) self can only ever help when you need to understand the nature and reason for some section of code.
If you can’t take a minute to describe your change in detail with a very strong focus on the why, or the commit is too large to encapsulate with a sensible message, then don’t make the commit. Take some extra minutes now, and save yourself hours later.
Step 2: Test your failure paths
Please don’t be one of those developers who test only the ‘happy path’ through their code and then proudly declare it “finished”. Nothing could be further from the truth, or more disrespectful of your colleagues and customers.
I have lost countless man-days integrating or testing someone else’s code only to find that as soon as I step off the tightrope of inputs and state this developer relied on, I have broken the system. Taking some time to test your own code for failures, both saves someone else’s time doing it for you (and having to raise defects against your work), and saves yourself time in the future trying to diagnose faults when they hit production and your inputs and states can no longer be guaranteed.
Save yourself the pain of live-debugging production faults in mission-critical systems , and assume that your code is going to fail. Become a coding pessimist. If you assume your code can fail, you’ll start to see all the ways in which it will.
Believe me, it’s easier and faster to fix now than it is once the customers get hold of it.
Step 3: Assume a fail-early mentality as your default
This is a tricky one, and I’ve had arguments lively debates on this topic many times before. Some folks would advocate taking this approach to the extreme; “If anything goes wrong, crash”. Some folks will take the opposite extreme; “If something goes wrong, just ignore it!” (and maybe throw in a restart or retry). Some systems are explicitly designed around this latter mentality (for example the automatic Application Pool recycling in IIS which caters for the likely scenario of memory leak due to poor code or the .NET large object heap fragmentation, by hiding the problem on your behalf).
“Fail-early” to me means that you will take steps to have your application break, or at least alert somebody, as soon as an error occurs. You will not hide your errors in try-catch-ignore blocks. You don’t rely on a magic gnome to trawl through your production logs and let you know what’s gone wrong in the last 7 days.
There are clearly times when you’ll want to minimise the disruption from a particular error, and don’t want your users to see all your dirty laundry. My advice here is treat “fail early” as your default setting. If you feel that this is not the appropriate way to handle some error, argue your way out of your default position. Start to think about the implications of hidden errors. Debugging downstream production errors due to invalid state caused by a hidden fault is the worst.
Step 4: Write helpful log and exception messages
There are very few scenarios in which “SomeMethod failed” is useful information when trying to diagnose a software fault. Generally speaking, exception messages should be designed for developers. Write an error message with your future self in mind. Ask yourself; “if I see this error message in 6 months time, will I know what it means?”. The answer to this question is surprisingly often “No, I won’t, but my code never fails, so this error message is just for posterity, really.”
Why do exception messages not contain useful details? Good question.
Step 5: Read this book.
Although it’s over 15 years old, The Pragmatic Programmer contains reams of still-relevant advice and guidance on software engineering principles and better practices. I can’t say it any better than the highly-experienced folks who’ve reviewed this book in the past, so I’ll just quote one at random;
“I would buy a copy, read it twice, then tell all my colleagues to run out and grab a copy. This is a book I would never loan because I would worry about it being lost.” – Martin Fowler
Just do it; you’ll learn so much. You’ll even re-learn ideas you didn’t know you’d forgotten.
Hopefully some of these tips will resonate. Try some of these steps once, just once, and when your future self realises the awesome work done by your past self, (s)he’ll smile.