TLDR Clean Code, Functions

Heshan P.
AMA Technology Blog
7 min readDec 18, 2023
Photo by Sebastian Svenson on Unsplash

In the context of clean code, functions act as Verbs — they communicate actions. These actions must be clear and concise for readability and maintainability. Without clean functions, your code will never be ‘clean’. Uncle Bob outlines some techniques that help with this and they are fairly straightforward to understand and implement.

Small

Functions should be small. Bloated functions take up a lot of your cognitive load and “leak” unnecessary complexity to everything around it.

Small is easy right? Not really.

Trying to solve a problem while keeping functions small is incredibly challenging. It takes a lot of practice, and for a novice developer like myself, it can take a lot of time and effort. One of the easiest ways to keep functions small is to make sure it does one thing. This concept is also known as the Single Responsibility Principle(SRP) and it's a key quality of maintainable code.

Do One Thing

A function should have one reason to exist, and all other responsibilities should be delegated to sub-functions whenever possible.

There are many scenarios where we need a function to orchestrate and direct actions. Let’s say you are working on an e-commerce flow’s order processing functionality. Processing an order can take several actions like checking inventory, saving the order to a database, and sending a confirmation email. Ideally, each of these actions is contained in its own function with a high-level function orchestrating the flow. One thing you shouldn’t do is put all the actions in a single massive function.

When you are naming the function, if you struggle to find a name without using “and” — you are probably doing too much. Try to go back and extract some logic out to a separate function.

Small, SRP-friendly functions also make testing much easier! When working on a code base that has 1000s of tests, complex business logic, and a high cost of failure, you have to be able to test it confidently. Small functions provide confidence because they are self-contained. The risk of breaking something you didn't even touch is manageable.

Top to Bottom

The Stepdown Rule states that code should “read like a top-down narrative. We want every function to be followed by those at the next level of abstraction”. Well, what does that mean? Uncle Bob suggests a simple exercise. Read to function in a series of “To” paragraphs. Let’s practice.

I am going to write the narrative as follows:

To process the order, we check if the order is valid, 
then we save the order to the DB,and then we send a confirmation e-mail.

To validate the order, we verify the inventory, ...

To verify inventory, we ...

Then we work on the function:

function processOrder(order: Order): void {
if (validateOrder(order)) {
saveOrder(order);
sendConfirmationEmail(order);
} else {
throw new OrderProcessError('Invalid order');
}
}

function validateOrder(order: Order): boolean {
return verifyInventory(order.items) && /* Other validation checks */;
}

function verifyInventory(items: Product[]): boolean {
// check inventory with DB
// ...
return true;
}


function saveOrder(order: Order): void {
authenticateDb();
//Save to Db
}

function authenticateDb(): void {
// Authenticate and initialization logic
}

function sendConfirmationEmail(order: Order): void {
// Send an email to the customer confirming the order
}

Side Effects

Side effects are actions that are performed in a function without it being too obvious. Uncle Bob calls them lies. This is because your function said it does “A” but it does “A” and a little bit of “B” or “C” on the side. I snuck a side-effect into the previous example. It's hard to see at first, but side effects are sneaky like that.

Side effects in functions can often go unnoticed, creeping in without our awareness. These side effects refer to actions that are performed within a function, but may not be immediately obvious or aligned with the intended purpose of the function. Uncle Bob calls them lies because they contradict the explicit behavior promised by the function.

function saveOrderToDatabase(order: Order): void {
authenticateDb()
// Save the order to the database
}

In this example, I introduced a side-effect (lies!) by adding authenticateDb()

It can be difficult to identify side effects. You could argue that authenticating is required to access and save to the database; however, this function should be all about saving, and nothing else. Remember — functions should do one thing. This would be much cleaner:

function processOrder(order: Order): void {
if (validateOrder(order)) {
const dbConnection = authenticateDb(); // only concern is to auth Db
saveOrderToDatabase(order, dbConnection); // only concern is to save
//..
} else {
//..
}
}

function saveOrderToDatabase(order: Order, dbConnection: DbConnection): void {
// Save the order to the database using the given dbConnection
}

function authenticateDb(): DbConnection {
// Authenticate and initialize the database connection
// ...
const dbConnection: DbConnection = /* Initialize the database connection */;
return dbConnection;
}

Arguments

Photo by Hal Cooks on Unsplash

The readability of a function can be enhanced or complicated by its arguments. While it is ideal for functions to have zero arguments, this may not always be feasible. It is generally acceptable to have 1–2 arguments to cater to most situations. Well, there are some scenarios where we can’t get away with that. When we have to pass a lot of information to a function the solution is Objects. They allow for better organization and representation of multiple related arguments. Our common tech stack at AMA Software Development uses TypeScript, where we can use types (similar to interfaces) to clean up arguments.

type Order = {
orderId: string;
items: string[];
customerName: string;
shippingAddress: string;
};

function placeOrder(order: Order) {
// ...
}

const order: Order = {
orderId: "236",
items: ["Jersey", "Hat"],
customerName: "J Lames",
shippingAddress: "123 Main St, Los Angeles"
};

placeOrder(order);

The argument for placeOrder is easy to read and if we were to revisit this in a few weeks, we would have no trouble adding a feature, modifying types, testing, or debugging. We could take it a step further and even do something like :

type Customer = {
name: string;
address: string;
};

type Order = {
orderId: string;
items: string[];
customer: Customer;
};

We separate Customer from Order. This Customer type can be shared anywhere else the Customer is involved. This is useful in large projects where you have to ensure you are consistent within a team or across multiple teams that have something to do with Customer.

TypeScript provides various tools to achieve a balance between flexibility and robustness. Two of these tools are Intersection and Union types. They can go a long way in cleaning up functions.

Descriptive Names

I already wrote about naming in another blog post. So, let's not go in too deep. Naming communicates intent, and it's essential.

I have a few examples below to illustrate this.

//BAD
function calculate(a: number, b: number): number {
//
}
//OK
function calculateSubTotal(a: number, b: number): number {
//...
}
//GOOD
function calculateSubTotal(sales: number, pricePerUnit: number): number {
//...
}

How Do You Write Clean Functions?

Uncle Bob relates it to writing anything. You get the ideas on paper(so to say) and then refine them. Everyone has a different way of doing this.

My approach would be something like this:

  1. (Ideally) Write tests to test the function. Nothing complex, we can add more tests later. They are not going to pass. Look at Test Driven Development. (I’ll be honest, I don’t always do this).
  2. Write a function to achieve your goal. Don’t worry about how clean it is, but keep it as clean as possible. You don't want to waste too much of your energy on cleanliness at this point, instead focus on functionality, but your future self would appreciate a bit of tidiness.
  3. Tests should still pass, but you still have a messy function.
  4. Start cleaning up (refactor). Extract functions out of the main function. Extract more. Extract functions from the functions you just created. They should do one thing! Follow all the techniques in Clean Code, and you should come up with clean functions after a while.
  5. Test again, they should pass. You may need to add more tests depending on refactoring.

Step 4 takes time and practice. Sometimes it feels like a waste of time, but keep in mind that Clean Code saves time in the long run. On the other hand, don’t let this get in the way of progression — there will always be improvements. Appreciate feedback on PR reviews. Another set of eyes is always nice.

Clean Functions cannot be summarized in a “TLDR” format to give the deserved attention. I struggle to summarize it, as you can see — it's not a short post. I would highly encourage anyone interested to read Clean Code, especially the chapter on Functions.

Agile Alliance. “What is TDD?” accessed Oct 19, 2023. https://www.agilealliance.org

DigitalOcean. “S.O.L.I.D: The First Five Principles of Object-Oriented Design.” accessed May 25, 2023.
https://www.digitalocean.com/community/conceptual-articles/s-o-l-i-d-the-first-five-principles-of-object-oriented-design.

Martin, Robert C. Clean Code: A Handbook of Agile Software Craftsmanship. Prentice Hall, 2009.

Microsoft. Unions and intersection types” accessed Oct 19, 2023.
https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html

Disclaimer

“The posts on this blog are provided ‘as is’ with no warranties and confer no rights. The opinions expressed on this site are my own and do not necessarily represent those of my employer.”

Who Are You and Why Are You Doing This?

I am a junior dev at AMA and I have had the opportunity to learn from a lot of different perspectives. Clean Code is a common term, and it can be applied to any codebase, business, tech stack, or life in general.

You will probably be able to tell that I am very much inexperienced in software development and blogging. This is my attempt to condense some information that can help fellow noobies, and maybe refresh the perspectives of some experienced devs.

--

--