5 essential tips to writing readable code

5 essential tips to writing readable code

Code is often read than written, making code readability a crucial aspect of software development. Readable code is easy to understand, allowing developers to follow the logic effortlessly through the lines. In this article, drawing from my experience as a tutor for Rhodes University's Computer Science students, I'll share five essential tips for writing code that is not just functional but also a pleasure to read.

We will begin by looking at the following Python program

Before 🙃

def cc(s, k):
    r = ""
    for i in s:
        if i.isalpha(): 
            r += chr((ord(i) + k - 97) % 26 + 97) if i.islower() else chr((ord(i) + k - 65) % 26 + 65)
        else:
            r += i
    return r
t = "readable code?"
ct = cc(t,3)
print(f'{t} encrypted => {ct}')

At first glance, can you tell what it does?

💡 ord(char) Given a string representing one Unicode character, return an integer representing the Unicode code point of that character.

chr(i) Return the string representing a character whose Unicode code point is the integer i

Let's go! 🚀

Whitespace: The visual guide

Whitespace is not just a formatting choice; it's a visual guide for developers. Avoid one-liner tricks that may save space but sacrifice readability. Use indentation consistently to indicate code blocks and create a visual hierarchy within your code.

def cc(s, k):
    r = ""
    for i in s:
        if i.isalpha():
            if i.islower(): # good ol if statement instead of tenary
                r += chr((ord(i) + k - 97) % 26 + 97)
            else:
                r += chr((ord(i) + k - 65) % 26 + 65)
        else:
            r += i
    return r
# add line breaks between sections
t = "readable code?"
ct = cc(t,3)

print(f'{t} encrypted => {ct}')

Variable Names: The art of clarity

Picking descriptive variable names is like giving your code a friendly tour guide. When names are straightforward and welcoming, your code becomes a pleasant journey for anyone reading it.

It would help if you embraced a consistent naming convention across your codebase. Whether it's CamelCase or snake_case, be consistent to enhance readability.

Don't shy away from lengthy variable names if they accurately describe the content. A variable named user_input is far more informative than u_i. Descriptive names reduce the need for excessive comments, making your code self-explanatory.

For variables that represent a boolean value indicating true or false, it is advisable to use a naming convention that includes the "is" prefix.

ALPHABET_SIZE = 26
LOWER_A_ASCII = ord('a')
UPPER_A_ASCII = ord('A')

def caesar_cipher(text, shift_key):
    result = ""
    for char in text:
        if char.isalpha():
                if char.islower():
                    result += chr((ord(char) + shift_key - LOWER_A_ASCII) % ALPHABET_SIZE + LOWER_A_ASCII) 
                else :
                    result += chr((ord(char) + shift_key - UPPER_A_ASCII) % ALPHABET_SIZE + UPPER_A_ASCII)
        else:
            result += char
    return result

text = "readable code?"
cipher_text = caesar_cipher(text,3)

print(f'{text} encrypted => {cipher_text}')

We also replaced our code's "magic numbers" with constants ALPHABET_NUMBER, LOWER_A_ASCII, and UPPER_A_ASCII.

Functions: The power of modularity

Functions in your code should have a single responsibility, encapsulating a specific task. The name of a function should precisely convey what it does. Break down your code into smaller, reusable functions. This not only enhances readability but also promotes maintainability and reusability.

ALPHABET_SIZE = 26
LOWER_A_ASCII = ord('a')
UPPER_A_ASCII = ord('A')

def shift_letter(char, shift_key):
    if char.islower():
        return chr((ord(char) + shift_key - LOWER_A_ASCII) % ALPHABET_SIZE + LOWER_A_ASCII)
    elif char.isupper():
        return chr((ord(char) + shift_key - UPPER_A_ASCII) % ALPHABET_SIZE + UPPER_A_ASCII)
    else:
        return char

def caesar_cipher(text, shift_key):
    result = ""
    for char in text:
            result += shift_letter(char, shift_key)
    return result

text = "readable code?"
cipher_text = caesar_cipher(text,3)

print(f'{text} encrypted => {cipher_text}')

To comment or not to comment?

Comments are a double-edged sword. While they can be helpful, they are often overlooked and must be updated, leading to confusion. Prioritize writing self-explanatory code, using comments sparingly for complex algorithms or unconventional solutions.

ALPHABET_SIZE = 26
LOWER_A_ASCII = ord('a')
UPPER_A_ASCII = ord('A')

def shift_letter(char, shift_key):
    '''
    "The normalization step (using LOWER_A_ASCII and UPPER_A_ASCII)
    is employed to adjust the ASCII values of letters, 
    ensuring that they are shifted within the
    range of 0 to 25, corresponding to the positions in the alphabet.
    ''' 
    # Check if the character is a lowercase letter 
    if char.islower():
        return chr((ord(char) + shift_key - LOWER_A_ASCII) % ALPHABET_SIZE + LOWER_A_ASCII)
    # Check if the character is an uppercase letter 
    elif char.isupper():
        return chr((ord(char) + shift_key - UPPER_A_ASCII) % ALPHABET_SIZE + UPPER_A_ASCII)
    else:
        # If the character is not a letter, return it unchanged 
        return char

def caesar_cipher(text, shift_key):
    result = ""
    for char in text:
        # Apply the shift to each character in the text 
        result += shift_letter(char, shift_key)
    return result

# Example usage 
text = "readable code?"
cipher_text = caesar_cipher(text,3)

print(f'{text} encrypted => {cipher_text}')

Our code looks cluttered because of the unnecessary comments. But, since we have used descriptive names, we should be able to trim out some comments.

After 🙂

ALPHABET_SIZE = 26
LOWER_A_ASCII = ord('a')
UPPER_A_ASCII = ord('A')

def shift_letter(char, shift_key):
    '''
    "The normalization step (using LOWER_A_ASCII and UPPER_A_ASCII)
    is employed to adjust the ASCII values of letters, 
    ensuring that they are shifted within the
    range of 0 to 25, corresponding to the positions in the alphabet.
    ''' 
    if char.islower():
        return chr((ord(char) + shift_key - LOWER_A_ASCII) % ALPHABET_SIZE + LOWER_A_ASCII)
    elif char.isupper():
        return chr((ord(char) + shift_key - UPPER_A_ASCII) % ALPHABET_SIZE + UPPER_A_ASCII)
    else:
        # If the character is not a letter, return it unchanged
        return char

def caesar_cipher(text, shift_key):
    result = ""
    for char in text:
        # Apply the shift to each character in the input string
        result += shift_letter(char, shift_key)
    return result

# Example usage 
text = "readable code?"
cipher_text = caesar_cipher(text,3)

print(f'{text} encrypted => {cipher_text}')

💡 In cryptography, a Caesar cipher ... is one of the simplest and most widely known encryption techniques. It is a type of substitution cipher in which each letter in the plaintext is replaced by a letter some fixed number of positions down the alphabet. For example, with a left shift of 3, D would be replaced by A, E would become B, and so on.

-Wikipedia

Consistency is key

Consistency ties all these tips together. Adopting a consistent coding style, variable naming convention, and commenting approach across your projects promotes a seamless reading experience for anyone who encounters your code.

Some tools help you with this. Use them. When writing Python code, I use autopep8, a tool that automatically formats Python code to conform to the PEP 8 style guide. A supporting VS code extension is available.

When I'm writing JavaScript  😅 Typescript, I use eslint and Prettier.

In conclusion, writing readable code is not just a best practice, it's a professional courtesy to your future self and collaborators. By implementing these tips, you'll produce code that works and is a pleasure to read and maintain.