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()