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.