Clean Code - Function

Functions should do one thing

Bad:

from typing import List

class Client:
    active: bool

def email(client: Client) -> None:
    pass

def email_clients(clients: List[Client]) -> None:
    """Filter active clients and send them an email."""
    for client in clients:
        if client.active:
            email(client)

Good:

from typing import List

class Client:
    active: bool

def email(client: Client) -> None:
    pass

def get_active_clients(clients: List[Client]) -> List[Client]:
    """Filter active clients."""
    return [client for client in clients if client.active]

def email_clients(clients: List[Client]) -> None:
    """Send an email to a given list of clients."""
    for client in get_active_clients(clients):
        email(client)

Even better

from typing import Generator, Iterator

class Client:
    active: bool

def email(client: Client):
    pass

def active_clients(clients: Iterator[Client]) -> Generator[Client, None, None]:
    """Only active clients"""
    return (client for client in clients if client.active)

def email_client(clients: Iterator[Client]) -> None:
    """Send an email to a given list of clients."""
    for client in active_clients(clients):
        email(client)

Function arguments (2 or fewer ideally)

Bad:

def create_menu(title, body, button_text, cancellable):
    pass

Good:

from typing import TypedDict

class MenuConfig(TypedDict):
    """A configuration for the Menu.

    Attributes:
        title: The title of the Menu.
        body: The body of the Menu.
        button_text: The text for the button label.
        cancellable: Can it be cancelled?
    """
    title: str
    body: str
    button_text: str
    cancellable: bool

def create_menu(config: MenuConfig):
    title = config["title"]
    # ...

create_menu(
    # You need to supply all the parameters
    MenuConfig(
        title="My delicious menu",
        body="A description of the various items on the menu",
        button_text="Order now!",
        cancellable=True
    )
)

Function names should say what they do

Bad:

class Email:
    def handle(self) -> None:
        pass

message = Email()
# What is this supposed to do again?
message.handle()

Good:

class Email:
    def send(self) -> None:
        """Send this message"""

message = Email()
message.send()

Functions should only be one level of abstraction

Bad:

# type: ignore

def parse_better_js_alternative(code: str) -> None:
    regexes = [
        # ...
    ]

    statements = code.split('\\n')
    tokens = []
    for regex in regexes:
        for statement in statements:
            pass

    ast = []
    for token in tokens:
        pass

    for node in ast:
        pass

Good:

from typing import Tuple, List, Dict

REGEXES: Tuple = (
    # ...
)

def parse_better_js_alternative(code: str) -> None:
    tokens: List = tokenize(code)
    syntax_tree: List = parse(tokens)

    for node in syntax_tree:
        pass

def tokenize(code: str) -> List:
    statements = code.split()
    tokens: List[Dict] = []
    for regex in REGEXES:
        for statement in statements:
            pass

    return tokens

def parse(tokens: List) -> List:
    syntax_tree: List[Dict] = []
    for token in tokens:
        pass

    return syntax_tree

Don’t use flags as function parameters

Bad:

from tempfile import gettempdir
from pathlib import Path

def create_file(name: str, temp: bool) -> None:
    if temp:
        (Path(gettempdir()) / name).touch()
    else:
        Path(name).touch()

Good:

from tempfile import gettempdir
from pathlib import Path

def create_file(name: str) -> None:
    Path(name).touch()

def create_temp_file(name: str) -> None:
    (Path(gettempdir()) / name).touch()


Click here to share this article with your friends on X if you liked it.