Chances are your code looks like crap, is hard to read, and hard to understand. There… I’ve said it. Sorry if that sounded harsh, but it’s the painful truth. Writing readable code is difficult.
I’ve seen too much code in my career that was written so badly that I had to throw it away and redo the entire project. And the most painful fact is that this code was not written by beginners either!
Writing nice, readable code takes time and effort, sure. But it’s also a matter of learning the right habits. With a few simple tweaks to your coding style, you can improve your code quality ten-fold without much effort.
1. Create short, well-named functions
Extract as much as you possibly can into functions and make these functions do one thing. Name them accordingly.
For example, you might want to create a function that reads a file into a string and name it
read_file_to_str(file_name: str). Without reading your code in detail, people will know what it does. They don’t need to dive into the function to see what it does.
Ideally, your code is a sequence of function calls like this that almost read like human language. Only when needed, the reader can dive in deeper. This type of code documents itself; it’s readable code!
To give you an idea: if your function contains more than 10 lines of code, that’s a smell. Look closely and see if functionality can be split into smaller (well-named) functions. You’ll find that you often can. If you can’t, that’s OK too. Sometimes functions have to be a bit longer. At least you tried.
Go ahead and open some of your recent code and do this right now. Don’t forget to come back here though, we’re only getting started.
2. Booleans should make for good sentences
Let’s continue down this path of writing readable code by looking at booleans. Booleans are often used in conjunction with
if statements. Take the example of a debug mode toggle. Consider these options for a debug mode variable:
Now fill in the blanks in this piece of code:
if <debug mode var name>: // do some stuff
Which option reads best? I would not pick 1, even though it might seem like the obvious choice. Number 4 is not super readable either. However, options 2 and 3 make for a nice, readable code flow. I would pick 3:
if <debug mode var name>: // do some stuff
In the same line, you can store more complex conditions in one properly named boolean. Take for example a complex business application, in which you need to call a couple of functions and do some checks to determine if the user has access to a specific page. You could create a large if statement, but that will probably be hard to read.
Instead, you can store the conditions in a boolean, and branch on that boolean instead:
user_has_access = is_logged_in(user) and (has_update_permissions(user) or user.type == 'admin') if user_has_access: ... ...
In this example, it also helps that we’ve created well-named functions that do one thing, like
3. Think hard about variable names
Perhaps there once was a thin use-case for short variable names. I guess people without a proper editor liked to save time typing names. Or maybe they only had 40 or 80 chars of screen width?
These days, however, we have massive screen estate and editors that auto-complete and auto-format almost anything. So don’t be afraid to write long variable names. It will save a lot of guessing and search-around time for future you. And the added bonus? That’s right; readable code.
Say you need to create a list (or array) with the names of all your Fussball club members. What would you choose?
member_names is an excellent middle ground. It’s super clear that it must be some kind of list with member names. In the right context, that is usually enough.
Perhaps #4 is OK too, but you shouldn’t make your variable names longer than necessary. Only if you have multiple member lists, #4’s length is essential to clearly distinguish between the Fussball club members and the chess club members.
4. Return early
There’s a concept called returning early, and like all of these tips, it’s language agnostic. The fundamental trick is to return from a function as soon as possible. I started using it way back when programming in Java, but I use it in Python, TypeScript, and JavasScript as well.
To illustrate, let’s look at a pattern that you’ll come across often: a need to check a few things at the start of your function before continuing with the actual work:
def do_something(user: User): if not user.logged_in: # print some error return # do something ...
Things to note:
- We don’t need an
elseclause, saving us space and causing less indentation, helping us write readable code
- It’s super clear that for this function, the user must be logged in
- There’s no need for and else-block, reducing the cognitive load on your reader (or future you)
The alternative I often encounter looks somewhat like this:
def do_something(user: User): if user.logged_in: # a large, overly indented blob of code ... ... else: # Reader might even need to scroll back # to see why this else-block is here ... return
This works not just for functions. At the start of a program, you might need to check the command-line arguments or configuration file. You can use the same style and exit early in case of errors. In this case, we load a YAML config file with Python:
config = yaml.safe_load('config.yml') if not config_is_valid(config) ... handle error sys.exit(1)
If you can, return early. It’s easier to understand, and easier to read.
5. Use a code formatter
You need a code formatter that automatically formats your code. It’s one of the easiest steps to take when you want to create more readable code. Don’t rely on your own formatting capabilities. Others have given this way more thought than you have, and there are multiple advantages to forcing yourself and all team members to use the same code formatter:
- A cleaner git history, in which only the actual code changes are present, and not 100 lines of code reformatting
- You all save time otherwise spend on formatting code manually
- No discussions at the coffee machine (single quotes vs double quotes, one vs two lines between functions, etcetera)
If you’re a Python programmer, take a look at Black and don’t look back. It’s is an opinionated formatter with only a couple of tweakable settings. You’ll like most of it, and perhaps you’ll hate some of what it does. But the beauty is that it does so consistently and you can trust it does so for good reasons.
Some of the biggest Python projects (like Django) use black to format their entire codebase. Don’t waste time and energy formatting code, use a formatter!
6. Use linters
Let’s continue down this path of delegating work to our computers. A linter is a tool that analyzes source code to:
- Warn about stylistic errors
- Flag programming errors
- Detect common bugs
- Discovers suspicious constructs
flake8. Make some time and search for a linter you like, that works for your programming language(s) of choice.
Readable code: conclusions
There’s a bottom line to all these tips:
- Make a serious effort to properly name functions and variables
- Reduce the cognitive load by creating small functions that do one thing, and using tricks like returning early
- Use tools to format and check your code automatically
You can learn these readable code tips and tricks in about 10 minutes, but there’s a difference between learning something and mastering it. Apply these tips in your daily coding and you’ll soon have internalized them. Bookmark and come back to this article to remind yourself about writing readable code from time to time.
If you liked these tips, please share the article with your friends and colleagues. Happy coding!