One of the chief concerns in software design and development is to create an intuitive user experience. However, developers often forget that they actually have two sets of users to consider: the end-user consuming the product, and the other developers using and working on the code itself.
Not upholding good “Code UX” affects the maintainability of applications, decreases team productivity, and ultimately, slows down the speed of development. It’s therefore critical to understand why we should care about the experience of our fellow developers — the “users” of our code.
In his book on usability, “Don’t Make Me Think,” Steve Krug presents three compelling principles for building and presenting websites:
Don’t make me think.
It doesn’t matter how many times I have to click, as long as each click is a mindless, unambiguous choice.
Get rid of half the words on each page, then get rid of half of what is left.
With simple tweaks, Krug’s laws can be applied to other publication modalities and programming best practices — including source code:
Don’t make me think.
Finding my way around code should be trivial and unambiguous.
Always look for ways to reduce the complexity of the code base.
Let’s take a look at each of these and how they should inform our development approach.
Don’t make me think.
Krug writes, always make the intent and usage of your code “self-evident, obvious, and self-explanatory.” Even a junior developer without any experience with your application should be able to skim through and have a pretty good idea of what it does.
Remember that every question a developer has to ask while reading your code adds to his or her cognitive workload, thereby distracting attention from the more important task at hand. Methods like the SOLID principles and choosing meaningful names for namespaces, classes, methods, and variables can go a long way towards making your code more expressive.
Keeping classes and methods concise also helps with readability. Imagine, for instance, reading an article that consists of a single massive paragraph, without adequate separation – or what Krug would call “whitespace.” It’s the same with code that requires double and triple readings to understand.
In-code documentation should be used consistently. Modern IDEs display the header comment of classes and methods as we use them in different parts of software, and when they’re well-written, they help others avoid sinking time into navigating to different parts of the application to figure out what happens.
Finding your way should be trivial and unambiguous.
Carefully chosen naming conventions and in-code comments not only help other developers understand the intent of your code, but act as valuable signposts. Developers should be able to pinpoint specific namespaces, classes, and method names, in the same way we’d be able to locate a specific topic from a book’s table of contents.
Removing repetitions will also make your code more navigable. Specifically, you can apply the SPOT rule for a single, unambiguous, and authoritative representation of the different pieces of knowledge within your application. This eliminates questions about when and where different representations are used, and reduces the chances that bugs will be introduced because you’ve forgotten to keep all representations in sync.
Following the SPOT rule and the SOLID principles together helps eliminate side effects between different system components; they make the code more predictable and isolate the logic between tasks. Good separation of concerns should prevent the need to make changes across a whole system when making changes to a single module.
Always look for ways to reduce complexity.
Complexity increases with each line of code we add, which in turn also adds to our cognitive workload.
In his article on YAGNI, Martin Fowler warns against implementing functionalities too early in a system. It can be tempting to do so in anticipation of a feature that’s on the roadmap. However, the cost is adding unnecessary complexity to your systems – complexity that you’re then forced to maintain until the feature is fully developed and released.
With ever-changing software requirements, it’s highly probable that your future feature will be abandoned or change dramatically in scope such that it will require substantial modifications. Consider carefully which functionalities need to be added now, and which can be saved for a later, more prudent date.
And finally, putting it all together…
If we assume that the effort to understand and use disorganized code is about the same as writing code that’s easy to understand, then I’d argue the latter is more valuable because the time spent writing good code is done once, whereas the time required to decipher messy code is expended each and every time a programmer works on that software. That sounds like an expensive and frustrating way to develop software.