Bill Sourour has a great post on Medium about code he’s ashamed of from an ethical standpoint. But there are also a lot of technical reasons to be ashamed of your software. Here are the 11 adjectives that describe software you won’t be ashamed of.
No matter what language or technology stack you use, if you can describe your code with these adjectives, the rest will probably follow. See how many you can apply to your own code.
1. Debuggable
Most modern runtimes let you attach a debugger of some kind. Even Node.js can be debugged with Visual Studio. You should write your code with the idea that you might one day attached a debugger to figure out what the freaking heck is going on.
It is hard to describe what that means exactly, but when you do this it affects how to structure data sometimes. You may use more temporary variables so that you don’t have to dig through a structure. Fortunately, most of this turns out to be good practice anyhow.
The best way to start is to practice using a debugger when you don’t have a problem. Step through and see what an assignment looks like: What would you do if that had the wrong value?
2. Loggable
Even if you have a modern debugger that you can attach to your runtime and step through your code, the world just does not work that way. Meaning, you code may run somewhere else, it may be serverless, it may be multithreaded or distributed, or it may run on a cloud somewhere. In those environments, it may not behave the same as on your computer.
So, you’re going to need a log. And that means you need a logging framework. You need to write the code and set up the logging in a way that makes the log readable or at least digestible with some sort of log reader. You need to make this part of your software going in.
If you fail to do so, you end up doing production deployments to deploy logging code to debug a production problem. In other words, your hair is on fire and you are walking into an oil refinery.
3. Testable
At long last, write unit tests. This is a red line. Leave any company that traps you in the old “business case for writing unit tests” discussion—chaos and hell is going to follow this discussion.
Testable software is broken down into intelligible functions that do simple verifiable things. Testable software works better and is more resilient.
If you have legacy code that has no tests, write tests as you modify that code. Write them first if you can! If you have to restructure something to make it testable, do it (a little at a time, but do it).
4. Fast-failable
The biggest warning sign for unreliable code is a line of code like if (myvar == null) myvar ="";
. The developer that wrote this line has led you down the path to intermittent behavior. Hell is a better place because it is predictable. Intermittent behavior leaves you in suspense until that special moment (usually 2 a.m. before your most important business day or a release) when everything blows up in your face.
If something “can’t happen” then it should not happen. When it does, the code should exit with a big fat error message. It is ugly, but it means the problem will be fixed and not cause any more problems downstream.
5. Idempotent
That weird word that means “if you do this again it won’t matter.” While not everythingcan be idempotent, anything that can be should be. Meaning, if you refresh a commerce page, you shouldn’t end up with two orders (simply because there is a token from the previous page that is verified and instead you’re given an order status).
Idempotent behavior should pervade the code. This means when you change a line in the debugger and see if that fixes the problem so the debugger returns to the beginning of the routine, it will all work out okay. This makes for predictable software that doesn’t create data messes easily.
6. Immutable
Functional code has largely made this a given. When you have a variable, it gets assigned once and then changes result in a new data structure. Even if you’re not writing functional code, you can achieve immutability.
Immutable code is more resilient and avoids all kinds of thread messes. There are low-level reasons (number of dispatches) that may make immutable (or functional or object-oriented) code undesirable for special cases (such as writing a low-level interpreter), but those reasons don’t exist in the normal business code most of you are writing. (That means that no optimization of dispatches holds a candle to your first I/O call.)
7. Intelligible
When functional programming came into vogue, people went a little nuts with it and forgot that the code still needs to be readable. Same thing with object-oriented code a decade or two before.
As a first principle, the compile makes code the computer can read. Your job as a developer is to make code a person can read.
8. Modifiable
Not long ago, I wanted to make a supersimple change to a piece of code (grab the username from the session context). Because of how this code was written, there was a lot to unravel and modify to grab that and get it to the place it was needed. In other words, the code wasn’t really modifiable except by the guy that wrote it. Whatever design principle he was using wasn’t documented, intelligible, or easily understood by the next developer down the line.
Each class, module, function, etc. should be written with the idea that things may change. You may need one more piece of data (such as security info or context). There is nearly always a Version 2 of anything, so write your code with the idea that this will happen.
In object-oriented code, that means not creating a massive inheritance structure. In functional programming, it may mean designing functions that aren’t quite so fine-grained or with parameters that have context.
9. Documentable
Good code should largely self-document itself. This is about class, function, and variable naming. It is also about JavaDoc (or your language’s equivalent). It is also about the design of the code.
If you can’t document “this thing” and “what it does” without referring to ten other things, maybe you should rethink your design.
10. Modular
Good code is broken up into logical parts that you can run and modify independently, at least with a harness of some sort that supplies the necessary prerequisite data.
11. Buildable
If other developers can’t just grab your code from Git and run the build, you need to work on that build. (Yes, it’s fine if they need a “well known” build tool like Maven or Make.) If it requires a weeklong, multiday, or multihour process then your build needs work.
Nothing taxes a team more than a build that must be fought by every new team member, when developers get new computers, or during an important release or hotfix.