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.
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.
Top comments (0)