Home
About meEventsArticlesPodcastsContact

Don’t make me think. Just KISS! (Keep It Simple, Stupid)

By Dominika Zając
Published in Articles
May 30, 2021
5 min read

Word ‘Simple’ created from light scrabble tiles. Photo by Pablo Arroyo on Unsplash

Many developers, especially at the beginning of their careers, love to create complicated solutions to prove their skills. It’s nice to invent something so ‘hacky’ that you have to explain it to others — you feel smarter, more intelligent, and creative. Also, you may think it shows your expertise when the rest of the team has to ask you for some tips to understand your solution… But you know what? It doesn’t work in the IT world (even if sometimes we would love to). Great programmers are not artists — they are a craftsman. Team workers who are doing their stuff not only for them but also people who will work with their code later on. People who understand that overcomplicating your code just to prove your skills will take the opposite effect. It’s why the code of the best programmers is different from the good ones. As Martin Fowler said:

“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.”

And I strongly agree with that words. The question is — how to create code for people not only for the compilers? Fortunately, the answer is easy — use one of the most powerful best practices in programming called KISS — Keep It Simple, Stupid.

KISS

The rule was created in U.S. Navy in 1960. Their goal is to keep everything as simple as possible (but no more). It can be applied in almost all life areas — engineering, business, soft-skills — and also in twelve steps programs (mutual aid organizations helping to recovery from substance addictions). Many people in IT summarize it as do not overengineer. And even, in theory, it sounds almost silly it is super important and has a significant impact. There are many reasons to write simple code:

  • Simple, readable code is easier to maintain and extend.
  • Simple solutions are easier to debug.
  • There is a lower likelihood of bugs.
  • KISS-based code is easier to test (also automatically).
  • Entry-level for a project is lower (you need less time to hand over your code to a new developer).

What is most important for me — I don’t have to spend a lot of time trying to understand the code. Doesn’t matter if I woke up in the middle of the night because of alerting, work with the new codebase, or come back to the project after some months — few seconds are enough to get what’s going on. Our brain — an awesome built-in processor — has limited capacity, don’t waste its power when no needed!

Photo by James Harrison on Unsplash

Practical tips

When you know why it’s so important you may start wondering how to apply the KISS rule in your code. Below, you can find some useful tips:

Use meaningful, readable variables

Why do we need labels? Because thanks to that we can express the whole meaning with one word. When I say elephant I don’t have to add it’s a big animal with a long trunk and big ears. So, why you still use variables like tmp, MyClass or helper? Using correct, meaningful, and readable variable names avoids a lot of struggles to understand your code. Just take a look at the code below:

class MyClass {
  helper = (i: number): string => {
    if (i % 3 === 0 && i % 5 === 0) return 'FizzBuzz';
    if (i % 3 === 0) return 'Fizz';
    if (i % 5 === 0) return 'Buzz';
    return i.toString();
  };
  
  main = (j: number): void => {
    for (let i = 1; i <= j; i++ ) {
      let tmp = this.helper(i);
      console.log(tmp);
    }
  };
}
const tmp2 = new MyClass();
tmp2.main(20);

Can you in a few seconds say what that code does? Of course, you can understand the functionality but it’s not the best experience. Let’s change it a little:

class FizzBuzz {
  getWordForNumber = (number: number): string => {
    if (number % 3 === 0 && number % 5 === 0) return 'FizzBuzz';
    if (number % 3 === 0) return 'Fizz';
    if (number % 5 === 0) return 'Buzz';
    return number.toString();
  };
  
  playGame = (numberLimit: number): void => {
    for (let current = 1; current <= numberLimit; current++ ) {
      let word = this.getWordForNumber(current);
      console.log(word);
    }
  };
}
const fizzBuzz = new FizzBuzz();
fizzBuzz.playGame(20);

I changed only names but the impact is really huge — even in a so simple case. Just think how useful it may be in real-life scenarios!

Extract constant values into variables

Probably, you can recall at least one moment when you saw some numbers in a code and had no idea what they are meaning. Is it some business requirements? Or mathematical rule? Maybe some system/law limitations? Even if you remember them during implementation meaning of them might be not so obvious after some time. We all remember Phi number is around 3.14 or day is 24 hours. But can you say what 84600 is? Or number 9 in your code? Saving constant values into variables with descriptive names (like below) can avoid that problem.

const SECONDS_PER_DAY = 84600;
const DAILY_HOURS_DRIVER_LIMIT = 9;

Museum Koening metro station name A good label helps us understand what a given thing is (or where we are). Photo by Tim Rüßmann on Unsplash

Extract logic to separate, well-named functions

Do you know how to eat an elephant? A bite at a time. It works with code too. Many people split code into smaller parts only when it is too long. But sometimes it may be useful to split a short code. Just look at the code below:

const isFormValid = {
  email: string,
  phone: string,
  address: string,
  validator: Validator
} => {
  return (email !== null && validator.isEmail(email)) || (phone !== null 
  && validator.isPhone(phone)) || ( address !== null 
  && validator.isAddress(address));
};

and its improved version:

const isFormValid = {
  email: string,
  phone: string,
  address: string,
  validator: Validator
} => {
  const isMailValid = email !== null && validator.isEmail(email);
  const isPhoneValid = phone !== null && validator.isPhone(phone);
  const isAddressValid = address !== null && validator.isAddress(address);
  return isMailValid || isPhoneValid || isAddressValid;
};

Which one is better in your opinion?

Avoid multiple nesting

Take a look at the code below:

class FizzBuzz {
  getWordForNumber = (number: number): string => {
    if (number % 3 === 0 || number % 5 === 0) {
      if (number % 3 === 0 ) {
        if (number % 5 === 0 ) {
          return 'FizzBuzz';
        }
        return 'Fizz';
      } else {
        return 'Buzz';
      }
    } else {
      return number.toString();
    }
  }
};

A little scary — don’t you think? Analyzing that structure is not easy — you have to focus to understand all conditions. But what if you change the logic a little and use the fast returning method?

class FizzBuzz {
  getWordForNumber = (number: number): string => {
    if (number % 3 === 0 && number % 5 === 0) return 'Fizzbuzz';
    if (number % 3 === 0) return 'Fizz';
    if (number % 5 === 0) return 'Buzz';
    return number.toString();
  };
}

Much better, don’t you think? Code is shorter and easier to understand. Also, creating test scenarios is much easier. That simple method can be super useful in your daily coding — when you see multiple nested structures spend some minutes to figure out if you can simplify it using the fast return method.

Photo by Fotis Fotopoulos on Unsplash

Use built-in methods and structures

You don’t have to build everything from the scratch. If the programming language you use offers implementation of more complicated data structures/patterns just use them. They are well-tested by all people using a given language and wide-known by all developers. Don’t reinvent the wheel — just use what is given for you.

Remove “hacks”

The snippet below presents one of the most creative codes I’ve ever seen:

const isEven = (number: number): boolean => {
  const regex = /^-?\d*[02468]$/;
  return regex.test(number.toString());
};

Yes, it works. But — is it really the best way to check if a number is even? I don’t think so. Regexes are super sensitive for mistakes — and not natural for humans to understand. Don’t overengineer it — the easiest solutions are the best!

const isEven = (number: number): boolan => {
  return number % 2 === 0;
};

Of course, that example is simplified but the tip works in real-life code too. Think about your code — do you have any hacks or tricks in them? It’s time to say goodbye to them

Photo by Garry Chan on Unsplash

Remove unneeded code

Nothing is cleaner than an empty card, isn’t it? No code means no bugs, no problems with legacy, no security vulnerabilities. So, why you still keep your unused code in the repository? Even if it’s commented it makes it harder to read and understand code. The next developer may be afraid to remove it as they don’t know how important it is. Just get rid of it —the entries will still exist in your git files so you can revert them easily when needed.

And the last tip — probably best remembered:

“Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live” ~John Woods

Final appeal

In a summary, I have some appeal for you. A dear programmer who wrote legacy code I’m currently struggling with. Dear co-worker or open-source code contributor. Dear each person who is involved in software engineering processes at any level. I have a huge ask for you — please, respect my and your time. Let us focus on important tasks, not trying to understand your over-engineered code. Don’t make me think — just Keep It Simple, Stupid!


Tags

articlebestPracticesprogrammingwebDevelopmentdevelopment
Previous Article
How to make the Internet available for everyone — accessibility in the web development
Dominika Zając

Dominika Zając

Full-stack developer

Related Posts

How to make the Internet available for everyone — accessibility in the web development
April 30, 2021
11 min
Icons made by Freepik
from www.flaticon.com

© 2021, All Rights Reserved.

Quick Links

About meContact

Social Media