Skip to main content

Clean Code

key-pain-points

core-principles

workflow

Naming

Naming things (= variables, properties, functions, methods, classes) correctly and in an understandable way if an extremely important part of writing clean code.

Indeed - if poor names are chosen - pretty much all other concepts taught throughout the course will not help that much.

Link: Document Naming

Comments & Formatting

You could think that comments help with code readability. In reality, the opposite is often the case though.

Proper code formatting (i.e. keeping lines short, adding blank lines etc.) on the other hand helps a lot with reading and understanding code.

Link: Document Comments & Formatting

Bad Comments

TypeDescription
Dividers & MarkersDividers and markers are redundant. If you code is written in a clean way (i.e. you use proper names etc.), it's obvious what your different code parts are about.
Redundant InformationComments like in this example add nothing. Instead, you stop and spend time reading them - just to learn what you already knew because the code used proper names.
Commented Out CodeCommented out code is dead code. It should be removed. If you want to keep it, use a version control system.
Misleading CommentsMisleading comments are the worst. They make you think that the code does something else than it actually does.

Good Comments

TypeDescription
Legal InformationIn some projects and/ or companies, you could be required to add legal information to your code files.
"Required" ExplanationsIn rare cases, adding extra explanations next to your code does help - even if you named everything properly.
WarningsAlso in rare cases, warnings next to some code could make sense - for example if a unit test may take a long time to complete or if a certain functionality won't work in certain environments.
Todo NotesEven though, you shouldn't over-do it, adding "Todo" notes can also be okay.

Formatting

formatting

Functions (& Methods)

Functions and methods (I don't differentiate here) are the meat of any code we write. All our code is part of some function or method after all. And we use functions to call other functions, build re-usable functionalities and more.

That's why it's extremely important to write clean functions. Functions are made up of three main parts:

  • Their name
  • Their parameters (if any)
  • Their body

The naming of functions and methods is covered in the "Naming" section already. This section cheat sheet focuses on the parameters and body therefore.

TypeDescription
Minimize The Number Of ParametersThe fewer parameters a function has, the easier it is to read and call (and the easier it is to read and understand statements where a function is called).
Keep Functions SmallBesides the number of parameters, the function body should also be kept small.
Do One ThingIn order to be small, functions should just do one thing. Exactly one thing. This ensures that a function doesn't do too much.
Levels of AbstractionLevels of abstraction can be confusing but in the end, it's quite straightforward concept.
Rules Of ThumbThe concept of "levels of abstraction" can be scary and you absolutely should NOT spend hours on your code, just to analyze which levels you migth have there.

Link: Document Functions (Methods)

Control Structures

No matter which kind of application you're building - you will most likely also use control structures in your code: if statements, for loops, maybe also while loops or switch-case statements.

Control structures are extremely important to coordinate code flow and of course you should use them.

But control structures can also lead to bad or suboptimal code and hence play an important role when it comes to writing clean code.

TypeDescription
Prefer Positive ChecksThis is a simple one. It can make sense to use positive wording in your if checks instead of negative wording. Though - in my opinion at least - sometimes a short negative phrase is better than a constructed positive one.
Avoid Deep NestingThis is very important! You should absolutely avoid deeply nested control structures since such code is highly unreadable, hard to maintain and also often error-prone.
Polymorphism & Factory FunctionsSometimes, you end up with duplicated if statements and duplicated checks just because the code inside of these statements differs slightly.
Embrace ErrorsErrors are another nice way of getting rid of redundant if checks. They allow us to utilize mechanisms built into the programming language to handle problems in the place where they should be handled (and cause them in the place where they should be caused...).

Link: Document Control Structures

Classes, Objects & Data Containers

Objects vs Data Containers / Data Structures objects-data-containers

  • Classes are blueprints for objects.
  • Polymorphism is a fancy term but in the end it just means that you re-use code (e.g. call the same method) multiple times but that the code will do different things, depending on the object type.
  • Classes Should Be Small

Link: Document Classes, Objects & Data Containers

SOLID Principles

S: Single-Responsibility Principle (SRP)

A class should only have a single responsibility - it should only change for this one responsibility.

class ReportDocument:
def generate_report(self, data):
# Implement the logic for generating a report using the provided data
pass

def create_pdf(self, report):
# Implement the logic for creating a PDF from the generated report
pass

O: Open-Closed Principle (OCP)

A class should be open for extension but closed for modification.

from abc import ABC, abstractmethod

class Printer(ABC):
@abstractmethod
def print(self, data):
pass

class PrinterImplementation:
def verify_data(self, data):
# Implement the logic for verifying data
pass

class WebPrinter(PrinterImplementation, Printer):
def print(self, data):
# Implement the logic for printing a web document
pass

class PDFPrinter(PrinterImplementation, Printer):
def print(self, data):
# Implement the logic for printing a PDF document
pass

class PagePrinter(PrinterImplementation, Printer):
def print(self, data):
# Implement the logic for printing a real page
pass

L: Liskov Substitution Principle (LSP)

Objects in a program should be replaceable with instances of their subtypes.

class Bird:
pass

class FlyingBird(Bird):
def fly(self):
print('Flying...')

class Eagle(FlyingBird):
def dive(self):
print('Diving...')

# Instantiate and use Eagle
eagle = Eagle()
eagle.fly()
eagle.dive()

class Penguin(Bird):
pass

I: Interface Segregation Principle (ISP)

Many client-specific interfaces are better than one general-purpose interface.

from abc import ABC, abstractmethod

class Database(ABC):
@abstractmethod
def store_data(self, data):
pass

class RemoteDatabase(ABC):
@abstractmethod
def connect(self, uri):
pass

class SQLDatabase(Database, RemoteDatabase):
def connect(self, uri):
# connecting...
pass

def store_data(self, data):
# Storing data...
pass

class InMemoryDatabase(Database):
def store_data(self, data):
# Storing data...
pass

D: Dependency Inversion Principle (DIP)

One should depend upon abstractions, not concretions.

from abc import ABC, abstractmethod

class Database(ABC):
@abstractmethod
def store_data(self, data):
pass

class SQLDatabase(Database):
def connect(self, uri):
# connecting...
print(f"Connected to {uri}")

def store_data(self, data):
# Storing data...
print(f"Stored data: {data}")

class App:
def __init__(self, database):
self.database = database

def save_settings(self):
self.database.store_data('Some data')

# Instantiate SQLDatabase and App
sql_database = SQLDatabase()
sql_database.connect('my-url')
app = App(sql_database)

# Use the App instance
app.save_settings()