Reducing Function Size
I really like to make the size of my functions as small as possible. For example, let’s say we have a function like this:
from somewhere import Order, get_item
def make_order(data):
if not data["sku"]:
raise Exception("'sku' must be present")
if not data["amount"]:
raise Exception("'amount' must be present")
if data["amount"] <= 0:
raise Exception("'amount' must be greater than 0")
item = get_item(data["sku"])
if not item:
raise Exception("item does not exist")
if data["amount"] > item.stock:
raise Exception("item stock is insufficient")
order = Order(data["sku"], data["amount"])
order.create_invoice()
order.send_invoice()
return order
That’s a lot of things happening inside one function.
This function is bad because it tried to do everything at once. If we want to test a single functionality, we can’t isolate the logic, so these problems may arise:
- I just want to test the payload validation, but I have to create invoice as well.
- I just want to try the stock availability checking part, but I have to run the validation first.
- etc.
Let’s fix it.
Ideally, we would break it into multiple, smaller functions:
from somewhere import Order, get_item
def validate_payload(data):
if not data["sku"]:
raise Exception("'sku' must be present")
if not data["amount"]:
raise Exception("'amount' must be present")
if data["amount"] <= 0:
raise Exception("'amount' must be greater than 0")
def check_stock_availability(data):
item = get_item(data["sku"])
if not item:
raise Exception("item does not exist")
if data["amount"] > item.stock:
raise Exception("item stock is insufficient")
def send_order_invoice(order):
order.create_invoice()
order.send_invoice()
def make_order(data):
validate_payload(data)
check_stock_availability(data)
order = Order(data["sku"], data["amount"])
send_order_invoice(order)
return order
Much better!
Now we can do something like this:
if __name__ == "__main__":
# We can check the validation logic without having to
# run other parts as well:
validate_payload({
"sku": "item-123-abc",
"amount": 5,
})
# We can test the invoice sending logic by creating a
# fake Order:
dummy_order = Order("dummy-sku", 5)
send_order_invoice(dummy_order)
# Etc.
Smaller functions have the following advantages:
- Obvious logic, so the program should be easier to understand.
- Less bug.
- Increasing testability, as shown in the above example.
p.s. the above program can also be refactored further into some dedicated classes, such as OrderCreationValidator
, OrderInvoiceSender
, etc.