We've all been there. You write a piece of code, read through it, and think it's perfect because it makes sense to you at the time. Return to that same code a year later and it's unrecognizable π
This code I wrote is perfect! π
β You, 1 year ago.
WTF is this? π‘
β You, looking at your code from 1 year ago.
The problem is that you're writing code for your current self. Instead, you need to write for your future self. Essentially just ask yourself this question: "Will the future me understand the intention of this block of code?"
Here's a few tips that I learned over many years of writing unreadable code.
Do not try to impress yourself
I like writing clever code. It makes me feel smart. That is, until I look back at my clever code a year later and try to figure what it's doing and why I didn't just do it in a simpler and more standard way.
So if you want to do something impressive, write readable code. After all, you can go from feeling like a god to having no idea what you're doing in the same day.
Use meaningful names
I have a hard time coming up with names for my variables, functions, modules, etc. There is even this popular quote:
There are only two hard things in Computer Science: cache invalidation and naming things.
β Phil Karlton
While naming is a skill to develop, I find that most just tend to either underthink or overthink it. Here are a few helpful suggestions I follow:
- Stay away from generic names like
container
ordata
. - Use a prefix like
is
orhas
for booleans (even in typed languages). - Use a prefix like
get
orcreate
for functions to denote an action. - Use a prefix like
min
ortotal
for more descriptive numbers. - Use proper pluralization when creating arrays like
users
. - Avoid one letter variables like
e
. Just useevent
orerror
. - Don't be afraid of long names with multiple words like
getTotalDaysSinceLastLogin
.
Most important of all: reduce as much potential confusion as possible.
Separate your conditions
The core of many applications is the logic, which really just translates down to your if
statements. The conditions for those statements can get pretty complex.
In this example, how long does it take you to understand the logic?
if (users[0] && posts.find(post => post.userId === users[0].id)) {
showUserPost();
}
Time is an important aspect here. Sure, I may be able to figure out this code snippet eventually, but if the entire codebase is written in this way then any future maintainer (including yourself) will be ripping their hair out trying to understand it.
You may be rushing to create a comment here, but instead let's just improve the code itself by moving the condition out to a meaningful variable.
const isUserPostCreated = users[0] && posts.find(post => post.userId === users[0].id);
if (isUserPostCreated) {
showUserPost();
}
And if we added another condition? Create another variable.
const isUserPostCreated = users[0] && posts.find(post => post.userId === users[0].id)
const isReaderLoggedIn = getReaderFromDatabase().isLoggedIn();
if (isUserPostCreated && isReaderLoggedIn) {
showUserPost();
}
Now when the future you looks at this code, you will be able to read the entire statement out loud and understand exactly what is going on.
Create functions that have a single responsibility
I am guilty of creating init()
functions that have hundreds of lines of code that do multiple things. It's easy to do, but unfortunately creates immovable code later.
A simple suggestion for this is to follow what is known as the single responsibility principle. This means that a function should only be responsible for one small piece of functionality.
Let's take an example of validating a username.
function validateUsername(username) {
// Invalid if username is over 20 characters.
if (username.length > 20) {
return false;
}
// Invalid if username has non-alphanumeric characters.
if (/[^a-z0-9]/gi.test(username)) {
return false;
}
// Invalid if user already exists in database.
if (db.query('SELECT id FROM users WHERE username = ', username)) {
return false;
}
// Otherwise valid!
return true;
}
In a sense, this does follow the single responsibility principle because it is only validating a username. However, we are running multiple validations here including querying the database. We also can't be fully sure that it's working.
What we can do here is break up this function into other smaller functions.
function validateUsernameLength(username) {
return username.length <= 20;
}
function validateAlphanumeric(string) {
return !/[^a-z0-9]/gi.test(string);
}
function checkUsernameExists(username) {
return db.query('SELECT id FROM users WHERE username = ', username);
}
function validateUsername(username) {
const isLengthValid = validateUsernameLength(username);
const isAlphanumeric = validateAlphanumeric(username);
const isUsernameTaken = checkUsernameExists(username);
return isLengthValid && isAlphanumeric && !isUsernameTaken;
}
Now these smaller functions are more easily changeable, movable, and testable.
Your future you will thank you
And anyone else that may work on the code that you've written.
Did this article relate to your experiences? Do you have any other suggestions for readable code? I am constantly looking to improve myself, so please feel free to tweet me or drop a comment below.
Note: This article was originally written for my personal blog. I am republishing it here for the amazing DEV community.
Top comments (32)
I would say, don't be afraid to leave a comment. Last week I came across some old code I wrote that I didn't think was needed. I yanked it out and was ready to issue a PR but luckily a spec caught an instance where we needed it. I added the code back and put a little comment to where the code was (not in an obvious way)referenced. If I had done that from the start I could have saved my current self a little bit of time and hassle.
That's a perfect use case for a comment! I prefer code that is self descriptive but sometimes a comment is needed to explain why some code exists or was written a certain way.
I would rather put that in the documentation of that function/method/class/whatever. I do not use comments in my code be it Python, Haskell, Java because I think that comments lie to you most of the time (I am a little biased towards this position after reading "Clean Code" from Robert Martin).
//EDIT
If I do not know what this code does after a month I need to rewrite it(assign better names for the values) or write better documentation
I am a junior developer. I agree in the principals of clean code and this is a great use case for a comment. With regards to moving it into documentation, I'd prefer to have a single source of information. It's easier to update a comment (yes, I'm aware they can become outdated) than documentation which has the same issue of becoming out dated.
Why all the constants? I would rather treat a boolean and a function returning a boolean the same way when it comes to naming. In other words, instead of naming the function
validateUserNameLength()
I would name the itisUserNameLengthValid()
or something similar. That would allow me to just goand skip all the constants.
And if you want to be really idiomatic about things just create a
UserName
type that has a function calledhasValidLength()
. That would allow you to write:I'd usually do the following:
I guess I could name that boolean instead of writing the comment (or doing nothing at all) but I'm just being honest, and this best represents what I usually write at this time in my life.
And indeed, naming is hard (I didn't find your name descriptive enough).
Not sure which is better. Comments go stale easier than names, but this pollutes the scope.
Maybe I should wrap the
const
+if
into a block for lexical scoping.That would be wild. Ha. Ha-ha. Ha.
I see no issues with reading your code, plus what I provided in the article are only meant to serve as suggestions. My own examples could definitely be improved as you showed.
I do prefer adding a variable over a comment though, because as you said: comments become stale.
But yeah, this stuff is hard sometimes π
Variables become stale too :v
Don't tell me you have never seen something like
They are easier to update though, since once someone finally realizes what that damn thing represents, they can apply a "rename identifier everywhere" refactoring tool.
Haha, yeah good point. I agree that they are easier to update.
I like to use comments as a last resort to explain why a piece of code is written in a certain way or exists in the first place.
I would enlighten myself using function that returns a boolean keyword
should
followed by a camelCase in naming convention. I would like to thank Dart's linting that separates class, functions,private and public variables and collections(known for arrays in some languages).Leave comments explaing "why" feature. I watched John Papa's video about "Writing Readable Code" and how it helped me learned cleaner and concise code.
Great post! I had the same experience as you did. Thanks!
It's great when languages have built-in constructs that help with writing more readable code.
Definitely agree on having comments that explain "why". I'll take a look at that video, thanks for sharing Vince!
I LOVE the separate your conditions bit here. I never thought about doing that before but I'm gonna start doing this in all of my code.
Super happy to hear this! Glad you were able to grab something actionable.
Very good article! Thanks.
I just noticed that in the refactored code
should rather be
You're right, good catch! Maybe I should write unit tests for my article code blocks haha.
And like in my other comment, I would suggest the early return pattern that has been refactored out of
validateUsername
be reintroduced.Not just for the sake of short circuiting, which is important for performance if some of the later checks make additional network calls, but also because this pattern is always helpful as it doesn't force the developer to keep as many concepts in their head. (No more references to binding? Can forget concept.)
Deleted the one without love. =)
Great points about the naming! I find it easier to make improvements later when I can see not only what I did ages ago, but how I did it just as easily.
Thanks Marissa! Exactly, being able to understand how your code is functioning definitely helps with future changes.
It looks like our experiences are very similar. Making a function do one thing, making meaningful variable/function names. Simplicity is key for readability, I get really confused and I tend to have trouble understanding what's going on if the code is written poorly. Either from my past self or modifying a website that was built by someone else or a framework's code.
Hi Nick! I'm happy to hear that. You've covered the key points of the article.