paint-brush
Clean Code for Python – Stop Writing Bad Codeby@proflead

Clean Code for Python – Stop Writing Bad Code

by Vladislav GuzeySeptember 9th, 2024
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

In this article, we'll explore key principles from the book "Clean Code" by Robert C. Martin. These principles will help you write clean, readable, and maintainable Python code. Messy code, often referred to as "spaghetti code," is characterized by its lack of organization and clarity.
featured image - Clean Code for Python – Stop Writing Bad Code
Vladislav Guzey HackerNoon profile picture


Are you tired of writing messy and unorganized code that leads to frustration and bugs? You can transform your code from a confusing mess into something crystal clear with a few simple changes. In this article, we'll explore key principles from the book "Clean Code" by Robert C. Martin, also known as Uncle Bob, and apply them to Python. Whether you're a web developer, software engineer, data analyst, or data scientist, these principles will help you write clean, readable, and maintainable Python code.



What is a Messy Code?

Messy code, often referred to as "spaghetti code," is characterized by its lack of organization and clarity, making it difficult to read, understand, and maintain.


A messy code

Here are some key attributes of messy code:


  • Poor Naming Conventions: Variables, functions, and classes have ambiguous or non-descriptive names, making it hard to discern their purpose or functionality.
  • Lack of Structure: The code lacks a coherent structure, often with functions or classes that are too long, do too many things, or are poorly organized.
  • Inconsistent Formatting: Inconsistent use of indentation, spacing, and line breaks, which makes the code visually unappealing and harder to follow.
  • Excessive Comments: Over-reliance on comments to explain what the code does, often because the code itself is not self-explanatory.
  • Duplication: Repeated code blocks that could be consolidated, leading to redundancy and increased maintenance effort.
  • Poor Error Handling: Inadequate mechanisms for handling exceptions or errors, resulting in code that is fragile and prone to crashing.
  • Side Effects: Functions or methods that alter global states or variables outside their scope, leading to unpredictable behavior.
  • Lack of Modularity: Code that is not broken down into reusable, independent components, making it difficult to test and reuse.


All of these often lead to errors and complicated maintenance. Let's explore some principles from Uncle Bob's "Clean Code" that can help you improve your code.


Python Examples of Bad and Good Code

Meaningful Naming

For example, look at the first function. What's f? What are x and y? We have no idea what this code does just by looking at it. Then, look at the second function. Much better, right? Clear names make it obvious what the function does; no guesswork is needed.


# bad
def f(x, y):
    return x + y

# good
def calculate_sum(first_number, second_number):
    return first_number + second_number


Functions Should Do One Thing

Here’s an example where one function is doing too many things at once:

# bad
def process_numbers(numbers, file_path):
    # Calculate the sum of numbers
    total = sum(numbers)
    
    # Print the sum
    print(f"The sum is: {total}")
    
    # Save the sum to a file
    with open(file_path, 'w') as file:
        file.write(f"Sum: {total}")

    return total

This will be hard to maintain. Each responsibility is better off as its own function:

# good
def calculate_sum(numbers):
    """Calculate the sum of a list of numbers."""
    return sum(numbers)

def print_sum(total):
    """Print the sum of numbers."""
    print(f"The sum is: {total}")

def save_sum_to_file(total, file_path):
    """Save the sum to a file."""
    with open(file_path, 'w') as file:
        file.write(f"Sum: {total}")

Now, each function has one clear responsibility. Simple and easy to manage!


# This function adds two numbers
def calculate_sum(first_number, second_number):
    return first_number + second_number


Do we really need that comment? The function name is clear enough. Let's focus on making the code itself self-explanatory.


Error Handling

Proper error handling is essential for robust code. Instead of letting errors break your program, handle them gracefully.

Here’s an example without proper error handling:

# bad
def divide(a, b):
    return a / b


If `b` is zero, this will cause an error. Let’s fix it:



# good
def divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return "Cannot divide by zero"

Now, instead of crashing, the program returns a helpful message.


Keep Code Formatting Consistent

Formatting matters! Code that’s messy or inconsistent can be harder to read. Use consistent spacing and indentation, and avoid cramming too much into one line.


def multiply(a,b):return a*b


DRY Principle (Don’t Repeat Yourself)

Duplicate code is harder to maintain and prone to errors.

# bad
def print_dog_sound():
    print("Woof")

def print_cat_sound():
    print("Meow")

Instead of repeating similar code, we can refactor it:

# good
def print_animal_sound(animal):
    sounds = {
        'dog': 'Woof',
        'cat': 'Meow'
    }
    print(sounds.get(animal, "Unknown animal"))


Better right? :)


TDD, or Test-Driven Development

Test-Driven Development means writing tests before writing the actual code. It ensures that your code does what it’s supposed to. By writing tests first, you create a safety net that catches issues early on.

def test_add():
    assert add(2, 3) == 5
    assert add(-1, 1) == 0

Avoid Side Effects

Side effects occur when a function modifies some state outside its local environment or has an observable interaction with the outside world beyond its primary purpose.


This function modifies a global variable, which is a side effect. Let's fix it.

# bad

total = 0

def add_to_total(value):
    global total
    total += value
    return total

print(add_to_total(5))  # Output: 5
print(add_to_total(3))  # Output: 8


This function returns a result without modifying any external state.

# good

def add_numbers(a, b):
    return a + b

total = 0
total = add_numbers(total, 5)
print(total)  # Output: 5
total = add_numbers(total, 3)
print(total)  # Output: 8

Command Query Separation

This principle states that every method should either be a command that performs an action or a query that returns data to the caller, but not both.

# bad

class Stack:
    def __init__(self):
        self.items = []

    def pop_and_return_size(self):
        self.items.pop()
        return len(self.items)

stack = Stack()
stack.items = [1, 2, 3]
size = stack.pop_and_return_size()
print(size)  # Output: 2


Look at this example:

The "pop_and_return_size" method modifies the stack (command) and returns a value (query). Let's fix it.

# good 
class Stack:
    def __init__(self):
        self.items = []

    def pop(self):
        return self.items.pop()

    def size(self):
        return len(self.items)

stack = Stack()
stack.items = [1, 2, 3]
stack.pop()
size = stack.size()
print(size)  # Output: 2

Here, pop() is a command, and size() is a query.


Conclusion

By avoiding common mistakes like using vague names, writing long functions, neglecting error handling, and duplicating code, you can make your code cleaner and more maintainable. For more in-depth advice, I highly recommend reading "Clean Code".

Clean Code Writing clean code isn’t just about making your program run; it’s about making it better. It saves you time, reduces bugs, and makes collaboration easier. Plus, when you come back to your code months later, you’ll thank yourself for keeping it clean.


Remember, practice makes perfect. Keep these principles in mind, and your code will be crystal clear.


Thank you for reading, and happy coding! :)