How To Write Clean Code in Python

Tips Refactor Your Legacy Python Code

Why Should I Write Clean Code?

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

Writing clean code is an important skill because it allows you to clearly communicate with the next person who works with what you’ve written. It’s a communication tool between humans and not only for machines.

How to Format Your Python Code?

Clean code is the commitment to coding standards, formatting, linting tools, and other checks regarding the layout of the code

Docstrings are Essential

Docstrings are basically documentation embedded in the source code.

def foo():
"""Do something"""
    return None

print(foo.__doc__)


OUT:
'Do something'

You Must Know PEP 8 (Python Enhancement Proposal)

PEP 8 is a style guide that describes the coding standards for Python. It’s the most popular guide within the Python community. Here are some important highlights.

Naming Conventions:

  • Variable and function names should be Snake case and all lowercase (my_variable) and (my_function())
  • Class names should be Pascal case (MyClass)
  • Constants should be all uppercase (MY_CONSTANT = 1234)
  • Modules should be also snake_case names and all lowercase (my_module)
  • Sticking with single quotes or double quotes, but don’t switch between them in your code

Whitespace:

  • Indent using 4 spaces
  • Avoid extra spaces within brackets or braces
  • Don’t use spaces around the = sign when used to indicate a keyword argument

Lines:

  • Lines should not be longer than 79 characters
  • Avoid multiple statements on the same line
  • Imports should be on separate lines

Imports:

  • Never import with a wildcard from pandas import *
  • Never import many libraries in one line import sys, os, numpy

Dive Deeper and Read Google Python Style Guide

If you looking forward to work for google, it would be a good idea to learn the google coding style.

Use Linters to Find Coding Mistakes

Linters help to identify small coding mistakes, errors, dangerous code patterns and keep your code formatted. There are two types of linters: logical and stylistic.

The most popular Python linters are:

Use Code Formatters to Automate your Code Styling

Using an automatic code formatters will do this styling job for you. Most of them allow you to create a style configuration file. You can install them as an extension in VS Code.

The most popular Python code formatters are:

Comments Aren’t Always a Good Idea!

Bad Comments are a code smell. Writing and then maintaining comments is an expense. Your IDE doesn’t check your comments so there is no way to determine that comments are correct. It could confuse the other developers. Robert C. Martin aka “Uncle Bob” said in his book in chapter 4.

“The proper use of comments is: to compensate for our failure to express ourselves in code. Every use of a comment represents a failure. So don’t comment first. Try everything else, then comment as a last resort.”

1. “Don’t comment bad code — rewrite it.” — Brian Kernighan

Commenting bad code — like # TODO: RE-WRITE THIS TO BE BETTER — only helps you in the short term. Sooner or later one of your colleagues will have to work with your code and they’ll end up rewriting it after spending multiple hours trying to figure out what it does.

2. Readable code doesn’t need comments

Adding useless comments will only make your code less readable. If your code is readable enough you don’t need comments.

Before

avgs = []

# convert to cents
a = x * 100

# avg cents per customer 
avg = a / n

# add avg to list
avgs.append(avg)

After

track_average_list = []

total_cents = total * 100
average_per_customer = total_cents / customer_count

track_average_list.append(average_per_customer)

3. Don’t add noise comments

Don’t add comments that do not add anything of value to the code. This is bad:

numbers = [1, 2, 3, 4, 5]

# This variable stores the sum of list of numbers.
numbers_sum = sum(numbers)
print(my_sum)

Apply SOLID Principles

Building software is an incredibly hard task — the logic of the code is complex, its behavior at runtime is hard to predict, requirements change constantly as well as the environment, and there are multiple things that can go wrong.

The SOLID principles are key guidelines for good object-oriented software design. It consists of the following five principles:

Single Responsibility

“A class should have one, and only one, reason to change.”

Make things (classes, functions, etc.) responsible for fulfilling one type of role. e.g. Refactor code responsibilities into separate classes.

Open–Closed

“Entities should be open for extension, but closed for modification.”

Be able to add new functionality to existing code easily without modifying existing code. e.g. Use abstract classes. These can define what subclasses will require and strengthen Principle 1. by separating code duties.

Liskov substitution

“Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.”

When a class inherits from another class, the program shouldn’t break and you shouldn’t need to hack anything to use the subclass. e.g. Define constructor arguments to keep inheritance flexible.

Interface segregation

“A client should not be forced to implement an interface that it doesn’t use.”

Interface Segregation Make interfaces (parent abstract classes) more specific, rather than generic. e.g. Create more interfaces (classes) if needed and/or provide objects to constructors

Dependency inversion

“Depend upon abstractions, not concretions.”

Dependency Inversion Make classes depend on abstract classes rather than non-abstract classes. e.g. Make classes inherit from abstract classes.

DRY (Don’t repeat yourself)

DRY aimed at reducing repetition of code patterns, replacing it with abstractions or using data normalization to avoid redundancy. Every piece of knowledge must have a single, unambiguous, authoritative representation within a system. The opposing view to DRY is called WET Code.

KISS (Keep it simple, stupid)

KISS is a design principle noted by the U.S. Navy in 1960. The KISS principle states that most systems work best if they are kept simple rather than made complicated; therefore, simplicity should be a key goal in design, and unnecessary complexity should be avoided.

Avoid Code Smells !!!

How can code “smell”?? A code smell is a surface indication that usually corresponds to a deeper problem in the system. Martin Fowler wrote a book code smells in his Refactoring book.

According to https://refactoring.guru/refactoring/smells there are many code smells, that we should avoid.

  • Long Method: A method contains too many lines of code. Generally, any method longer than ten lines should make you start asking questions.
  • Large Class: A class contains many fields/methods/lines of code.
  • Primitive Obsession: Use of primitives instead of small objects for simple tasks (such as currency, ranges, special strings for phone numbers, etc.)
  • Use of constants: for coding information (such as a constant USER_ADMIN_ROLE = 1 for referring to users with administrator rights.)
  • Long Parameter List: More than three or four parameters for a method.
  • Data Clumps: Sometimes different parts of the code contain identical groups of variables (such as parameters for connecting to a database). These clumps should be turned into their own classes.
  • Temporary Field: Temporary fields get their values (and thus are needed by objects) only under certain circumstances. Outside of these circumstances, they’re empty.
  • Refused Bequest: If a subclass uses only some of the methods and properties inherited from its parents, the hierarchy is off-kilter. The unneeded methods may simply go unused or be redefined and give off exceptions.
  • Alternative Classes with Different Interfaces: Two classes perform identical functions but have different method names.
  • Divergent Change: You find yourself having to change many unrelated methods when you make changes to a class. For example, when adding a new product type you have to change the methods for finding, displaying, and ordering products.
  • Shotgun Surgery: Making any modifications requires that you make many small changes to many different classes.
  • Parallel Inheritance Hierarchies: Whenever you create a subclass for a class, you find yourself needing to create a subclass for another class.
  • Comments: A method is filled with explanatory comments.
  • Duplicate Code: Two code fragments look almost identical.
  • Lazy Class: Understanding and maintaining classes always costs time and money. So if a class doesn’t do enough to earn your attention, it should be deleted.
  • Dead Code: A variable, parameter, field, method or class is no longer used (usually because it’s obsolete).
  • Speculative Generality: There’s an unused class, method, field or parameter.
  • Feature Envy: A method accesses the data of another object more than its own data.
  • Inappropriate Intimacy: One class uses the internal fields and methods of another class.
  • Middle Man: If a class performs only one action, delegating work to another class, why does it exist at all?

Dependency Injection

The principle of dependency Injection aids in reducing coupling and boosting cohesion. The strength of the relationship between the components determines coupling and cohesion:

  • High Coupling: is Like using superglue. Not easily disassembled.
  • High Cohesion: Is Like using screws. Very simple to disassemble and reassemble in a new way.

Cohesion often correlates with coupling. Higher cohesion usually leads to lower coupling and vice versa. Low coupling brings flexibility. Your code becomes easier to change and test.

Objects do not create each other anymore. They provide a way to inject the dependencies instead. Here is an Example:

Before

import os


class ApiClient:

    def __init__(self) -> None:
        self.api_key = os.getenv("API_KEY")  # <-- dependency
        self.timeout = int(os.getenv("TIMEOUT"))  # <-- dependency


class Service:

    def __init__(self) -> None:
        self.api_client = ApiClient()  # <-- dependency


def main() -> None:
    service = Service()  # <-- dependency
    ...


if __name__ == "__main__":
    main()

After

import os


class ApiClient:

    def __init__(self, api_key: str, timeout: int) -> None:
        self.api_key = api_key  # <-- dependency is injected
        self.timeout = timeout  # <-- dependency is injected


class Service:

    def __init__(self, api_client: ApiClient) -> None:
        self.api_client = api_client  # <-- dependency is injected


def main(service: Service) -> None:  # <-- dependency is injected
    ...


if __name__ == "__main__":
    main(
        service=Service(
            api_client=ApiClient(
                api_key=os.getenv("API_KEY"),
                timeout=int(os.getenv("TIMEOUT")),
            ),
        ),
    )

Seehttps://python-dependency-injector.ets-labs.org/introduction/di_in_python.html

Write Test Cases Before Your Feature (TDD)

Quality software doesn’t come without tests. The earlier you find out about problems in your code, the less impact they have. It also costs less to deal with them.

Test-Driven Development (TDD)

TDD is a methodology in software development that focuses on an iterative development cycle where the emphasis is placed on writing test cases before the actual feature or function is written.

The Shift Left Testing Methodology

The movement to “shift-left” is about moving critical testing practices to earlier in the development lifecycle. This term is found especially in Agile, Continuous, and DevOps initiatives.

While the Python standard library comes with a unit testing framework called unittest, pytest is the go-to testing framework for testing Python code.

The pytest framework makes it easy to write small, readable tests, and can scale to support complex functional testing for applications and libraries. Learn more here: https://docs.pytest.org

Don’t Reinvent the Wheel, Learn Design Patterns

The design pattern is a technique which used by the developer to solve the commonly occurring software design. In simple word, it is a predefine pattern to solve a recurring problem in the code. These patterns are mainly designed based on requirements analysis.

I found this useful website (https://refactoring.guru/design-patterns/python) to learn design patterns in python with code examples and amazing illustrations.

Conclusion

Clean code coding is challenging. You cannot write good, clean code by following a single recipe. To master, it takes practice and time. I’ve looked at a few coding standards and general principles that can make your code stronger. The best advice I can give you is to be consistent and make an effort to write straightforward, testable code. You should assume that your code is difficult to use if it is difficult to test.

References

Amr Khalil
Amr Khalil

https://gravatar.com/amrmaro

Articles: 2

Leave a Reply

Your email address will not be published. Required fields are marked *