DEV Community

Cover image for Writing code for your future self
Sunny Singh
Sunny Singh

Posted on • Originally published at sunnysingh.io

Writing code for your future self

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.

Tweet this


WTF is this? 😑
β€” You, looking at your code from 1 year ago.

Tweet this

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.

The two states of every programmer: Left panel with person moving multiple knobs and switches with the following caption, I am a god. Right panel is a clueless dog at a computer with the following caption, I have no idea what I'm doing.

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 or data.
  • Use a prefix like is or has for booleans (even in typed languages).
  • Use a prefix like get or create for functions to denote an action.
  • Use a prefix like min or total for more descriptive numbers.
  • Use proper pluralization when creating arrays like users.
  • Avoid one letter variables like e. Just use event or error.
  • 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();
}
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
molly profile image
Molly Struve (she/her)

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.

Collapse
 
sunnysingh profile image
Sunny Singh

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.

Collapse
 
zeljkobekcic profile image
TheRealZeljko • Edited

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

Thread Thread
 
tdashworth profile image
Tom Ashworth

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.

Collapse
 
timmybird profile image
Bartek Svaberg

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 it isUserNameLengthValid() or something similar. That would allow me to just go

if (!isUserNameLengthValid(userName) return

and skip all the constants.

And if you want to be really idiomatic about things just create a UserName type that has a function called hasValidLength(). That would allow you to write:

if (!userName.hasValidLength()) return
Collapse
 
qm3ster profile image
Mihail Malo
// this is a user object or undefined, should probably use `some` instead of `find`
const isUserPostCreated = users[0] && posts.find(post => post.userId === users[0].id)

if (isUserPostCreated) {
  showUserPost()
}

I'd usually do the following:

const [user] = users
if (!user) return

const { id } = user

// Does the user have any posts?
if (!posts.some(({ userId }) => userId === id)) return

showUserPost()

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).

const [user] = users
if (!user) return

const { id } = user

const userHasPosts = posts.some(({ userId }) => userId === id)
if (!userHasPosts) return

showUserPost()

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.

Collapse
 
sunnysingh profile image
Sunny Singh • Edited

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 πŸ˜…

Collapse
 
qm3ster profile image
Mihail Malo

Variables become stale too :v
Don't tell me you have never seen something like

isValid.forEach(name(true))

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.

Thread Thread
 
sunnysingh profile image
Sunny Singh

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.

Collapse
 
vinceramces profile image
Vince Ramces Oliveros

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!

Collapse
 
sunnysingh profile image
Sunny Singh • Edited

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!

Collapse
 
tylerlwsmith profile image
Tyler Smith

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.

Collapse
 
sunnysingh profile image
Sunny Singh

Super happy to hear this! Glad you were able to grab something actionable.

Collapse
 
djangotricks profile image
Aidas Bendoraitis

Very good article! Thanks.

I just noticed that in the refactored code

return isLengthValid && isAlphanumeric && isUsernameTaken;

should rather be

return isLengthValid && isAlphanumeric && !isUsernameTaken;
Collapse
 
sunnysingh profile image
Sunny Singh

You're right, good catch! Maybe I should write unit tests for my article code blocks haha.

Collapse
 
qm3ster profile image
Mihail Malo

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.)

async function validateUsername(username) {
  if (!validateUsernameLength(username)) return false
  if (!validateAlphanumeric(username)) return false
  if (await checkUsernameExists(username)) return false
  return true
}
Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
timmybird profile image
Bartek Svaberg

Deleted the one without love. =)

Collapse
 
marissab profile image
Marissa B • Edited

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.

Collapse
 
sunnysingh profile image
Sunny Singh

Thanks Marissa! Exactly, being able to understand how your code is functioning definitely helps with future changes.

Collapse
 
nickhiebertdev profile image
Nick Hiebert • Edited

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.

Collapse
 
sunnysingh profile image
Sunny Singh

Hi Nick! I'm happy to hear that. You've covered the key points of the article.

Collapse
 
qm3ster profile image
Mihail Malo

There are only two hard things in Computer Science: cache invalidation and naming things.

I guess off-by-one errors are in the past since everyone switched to iterators/functional methods?

Collapse
 
sunnysingh profile image
Sunny Singh

Haha I was debating on including that but I don't think that's the original quote. Didn't seem relevant anyway.

Collapse
 
qm3ster profile image
Mihail Malo

Well I have two comments on this post where I go on about how hard naming is, so the rest was definitely to the point.

Collapse
 
sebas86 profile image
Sebastian Śledź

Hmm. Keep in mind that you can do more readable code without sacrificing performance. In last example you can still use functions directly in logical condition instead executing them and store result in temporary variable and code will be perfectly readable without querying DB when other checks fail. ;)

Collapse
 
sunnysingh profile image
Sunny Singh

Yep, I definitely agree in cases where the optimized version is just as readable.

Collapse
 
jessekphillips profile image
Jesse Phillips

I would challenge the idea that single responsibility means can't do more than one thing. Several have shown the challenge of refactoring into this approach.

The db access I would argue isn't related to username validation. I mean if it isn't valid how does someone have it.

Collapse
 
djsuszi profile image
pkassin

Difference is that in first case validation will not db.query if username is too long. In second that db.query and other later tests are usuless

Collapse
 
sunnysingh profile image
Sunny Singh • Edited

True. For the same functionality you would want something like this:

function validateUsername(username) {
  const isLengthValid = validateUsernameLength(username);

  if (!isLengthValid) return false;

  const isAlphanumeric = validateAlphanumeric(username);

  if (!isAlphanumeric) return false;

  const isUsernameTaken = checkUsernameExists(username);

  if (isUsernameTaken) return false;

  return true;
}

Or even this:

function validateUsername(username) {
  return validateUsernameLength(username)
    && validateAlphanumeric(username)
    && !checkUsernameExists(username);
}

Which would only run checkUsernameExists if the tests before it pass. But it's somewhat pseudo code anyway. The point of db.query was to show the single responsibility.

Collapse
 
clurquizar profile image
curquiza

The fundamentals, well explained with examples πŸ‘Œ Thank you !!