How to write more readable code

Subscribe to my newsletter and never miss my upcoming articles

Today I am going to share with you 5 tips on how to write more readable code. I am using these tips on a daily level and they are my must-haves. So, let's dig in:

Name it right

Always follow the naming convention! Have in mind that naming convention isn't always the same for all languages so you will probably have to look up and explore, but that time isn't wasted, it is invested.

Name your project, sub-projects, layers, packages, modules, and everything else descriptively with the following thought in mind: "Someone else will read my code and fix my bugs". Don't try to make bugfixer's life harder, he is your friend!

The same is with your variables, objects, classes, and all parts of codes. I have my personal rules which I follow:

  • don't use shorthands,
  • meaningful means better,
  • variables are nouns, methods are verbs,
  • bool variables should come with the "is" or "has" prefix.

Example:

# Bad examples

a: int = 3
wtxt: str = "Hi, there!"
successful: bool = True


def translation() -> str:
    return "English translation"


# Good examples

number_of_rounds: int = 3
welcome_text: str = "Hi, there!"
is_successful: bool = True


def get_translation() -> str:
    return "English translation"

Respect immutability

If you ask me immutability is one of the core principles. You should respect it. Besides making your code cleaner, it guards you against dumb mistakes where you accidentally mutate variables. Basically, I prefer defining a new variable whenever I should update the old one, which should stay the same.

from typing import List


# Bad example

def get_emails() -> List[str]:
    emails: List[str] = []

    try:
        with open('users.txt', 'r') as file:
            # Now imagine I did something fancy and I changed the emails variable
            emails = ['me@kostakuu.rs']  # This is just mock data
    except Exception:
        # Imagine I logged exception
        pass

    return emails


# Good example

def get_emails_better() -> List[str]:
    try:
        with open('users.txt', 'r') as file:
            # Now imagine I did something fancy and I changed the emails variable
            return ['me@kostakuu.rs']  # This is just mock data
    except Exception:
        # Imagine I logged exception
        pass

    return []

One level of nesting is okay, two are a call for refactoring

With every level of nesting, our code becomes less readable and more complex. So, I prefer to have only one level of nesting if I need nesting, if I have two then it is a refactor time.

By doing this, we are not only getting more readable code, but it will be easier to maintain and debug. Let's take a look at the previous example:

from typing import List


# Bad example

def get_emails() -> List[str]:
    try:
        with open('users.txt', 'r') as file:
            # Now imagine I did something fancy and I changed the emails variable
            return ['me@kostakuu.rs']  # This is just mock data
    except Exception:
        # Imagine I logged exception
        pass

    return []


# Good example

def try_to_read_emails() -> List[str]:
    try:
        return get_emails_better()
    except Exception:
        # Imagine I logged exception
        pass

    return []


def get_emails_better() -> List[str]:
    with open('users.txt', 'r') as file:
        # Now imagine I did something fancy and I changed the emails variable
        return ['me@kostakuu.rs']  # This is just mock data

Avoid bool parameters

In most of the cases if you have a bool parameter you will perform some checks and have if statement(s). I don't say you will always have this behavior, but I am sure that you can remember that you used the bool parameter at least once to violate the single responsibility principle. Those methods often contain "or" in the name, but that is not a strong rule.

# Bad example

def get_logged_in_user_email_or_mock(mock_email: bool = False) -> str:
    if mock_email:
        return 'mock@kostakuu.rs'
    else:
        # Imaging I did something nice and read real email
        return 'me@kostakuu.rs'


# Good example

def get_logged_in_user_email() -> str:
    # Imaging I did something nice and read real email
    return 'me@kostakuu.rs'


def get_mocked_logged_in_user_email() -> str:
    return 'mock@kostakuu.rs'

Polymorphism over IFs/enums

How many times did you see that some "type" variable is used to determine the execution path of the method? I saw that more times that I would like to admit.

And, again if we have that "type" that means that we will have nesting. Great... In order to keep workflow the same, but code much better, we can use polymorphism. With it, we can specify some default template (base class/interface) and all cases (derived classes).

from abc import ABC


def send_email(address: str) -> None:
    # Imagine I sent the email
    pass


def send_push_notification(address: str) -> None:
    # Imagine I sent the push notification
    pass


# Bad example

class Subscription:
    def __init__(self, channel: str, where: str):
        self.channel = channel
        self.where = where

    def notify(self) -> None:
        if self.channel == 'email':
            send_email(self.where)
        elif self.channel == 'push':
            send_push_notification(self.where)

        raise Exception("Channel not implemented yet...")


# Good example

class SubscriptionBase(ABC):
    def __init__(self, channel: str, where: str):
        self.channel = channel
        self.where = where

    def notify(self) -> None:
        pass


class EmailSubscription(SubscriptionBase):
    def notify(self) -> None:
        send_email(self.where)


class PushSubscription(SubscriptionBase):
    def notify(self) -> None:
        send_push_notification(self.where)

I hope you liked today's article. All examples are available at github.com/kostakuu/how-to-write-more-reada..

See you soon!

Comments (2)

sai harish kumar's photo

That's a cool post Let me state the conventions for JAVA:

  1. Class name must be a Noun that start with Upper case letter
  2. Interface name must be Adjective with Upper case letter at start
  3. Method name must be Verb with lower case letter at start
  4. Variable name can be noun/verb with lower case letter at start
  5. Package name should start with lower case letter, should represent the role and words should be seperated by (.) Dot character
  6. Constants should contain all Upper case letters and words are seperated by underscore

In general words are seperated by Camel Casing

Kosta Kupresak's photo

I am glad that you enjoyed reading and thank you for sharing conventions for Java