Code Tip: Single Level of Abstraction

· 2 min read · 267 words
#programming #best-practices

Functions should have a single level of abstraction

I discovered this tip in Uncle Bob’s book Clean Code.

It is my favourite tip because it is:

  • simple to reason about
  • easy to apply to your work

Example

(Not a real world example — I have changed things for brevity)

/**
 * Multiple levels of abstraction; we have:
 * (a) function calls
 * (b) property assignments
 * (c) a conditional statement
 */
async function createUser(userDto: UserDto) {
  const user = new User();
  user.name = userDto.name;
  user.email = userDto.email;
  user.createdAt = Date.now();

  if (userDto.country === "UK") {
    logger.logActivity(`User created in the UK: ${user.id}`);
  } else {
    logger.logActivity(`User created somewhere in the world: ${user.id}`);
  }

  db.add(user);
  await db.save();
}
/**
 * Single level of abstraction: we only have function calls
 */
async function createUser(userDto: UserDto) {
  const user = User.createFromDto(userDto);
  logUserCreated(user);
  await createUserInDb(user);
}

This results in code that is much easier to read and maintain.

Why it works

When a function operates at a single level of abstraction, a reader can understand the high-level flow at a glance, then dive into any sub-function if they need the details. You’re not forced to hold multiple layers in your head simultaneously.

Think of it like a good table of contents: it tells you what the chapters are, not their entire contents.

Applying it

Next time you write a function, ask yourself: are all the operations in this function at the same altitude? If you have raw property assignments sitting next to high-level service calls, it’s a signal to extract.