Back to Articles
Books Jun 11, 2026 5 min read

Clean Code Chapter 3: Functions Should Do One Thing, and Do It Well

My key learnings from Chapter 3 of Clean Code by Robert C. Martin. This chapter taught me that small, focused functions are easier to read, test, maintain, and understand than large functions that try to do everything.

Clean Code Chapter 3: Functions Should Do One Thing, and Do It Well

Clean Code Chapter 3: Functions Should Do One Thing, and Do It Well

After reading Chapter 2 about naming, I moved on to Chapter 3 of Clean Code, which focuses on one of the most fundamental building blocks of software:

Functions.

At first, writing functions seems simple.

You create a method, put your logic inside it, and move on.

But after reading this chapter, I realized that many of the problems we face in codebases come from poorly designed functions.

The central message of this chapter is surprisingly simple:

Functions should do one thing. They should do it well. They should do it only.

It sounds easy.

In reality, it's one of the hardest principles to follow consistently.

Why Large Functions Become a Problem

Most large functions don't start large.

They grow over time.

A new requirement comes in.

A validation gets added.

A logging statement appears.

An email notification gets added.

Before long, a simple function becomes hundreds of lines long.

For example:

public async Task CreateOrder(CreateOrderRequest request)
{
    ValidateRequest(request);

    var customer = await GetCustomer(request.CustomerId);

    CalculateDiscount(customer);

    SaveOrder();

    SendConfirmationEmail();

    WriteAuditLog();

    UpdateAnalytics();

    NotifySalesTeam();
}

At first glance, this looks fine.

But if we look closely, the function is doing many different jobs:

  • Validation
  • Business logic
  • Persistence
  • Email notifications
  • Logging
  • Analytics
  • Team notifications

That's multiple responsibilities in a single method.

Functions Should Do One Thing

Instead of one large function, we can break the work into smaller, meaningful functions.

public async Task CreateOrder(CreateOrderRequest request)
{
    ValidateRequest(request);

    var order = await BuildOrder(request);

    await SaveOrder(order);

    await ProcessOrderCreated(order);
}

Now the high-level workflow is easy to understand.

The details are hidden behind well-named methods.

The reader understands the process without being distracted by implementation details.

Small Functions Are Easier to Read

One lesson that stood out to me is that function size matters.

Robert C. Martin argues that functions should be small.

Very small.

Many developers are comfortable with functions that are 50 or 100 lines long.

The book challenges that idea.

A small function is:

  • Easier to understand
  • Easier to debug
  • Easier to test
  • Easier to reuse

When a function fits on a screen, you can often understand its purpose immediately.

One Level of Abstraction Per Function

This was one of the most interesting concepts in the chapter.

Consider this example:

public void ProcessOrder()
{
    ValidateCustomer();

    if (customer.IsPremium)
    {
        ApplyPremiumDiscount();
    }

    sqlConnection.Execute(sqlQuery);

    SendEmail();
}

Notice how the function jumps between different levels of detail.

Some lines are high-level business operations.

Others are low-level implementation details.

The chapter recommends keeping functions at a single level of abstraction.

For example:

public void ProcessOrder()
{
    ValidateCustomer();

    ApplyDiscounts();

    SaveOrder();

    NotifyCustomer();
}

Now the function reads almost like a story.

Avoid Boolean Parameters

This lesson immediately made me think about code I've written before.

Consider:

GenerateReport(true);

What does true mean?

Nobody knows without inspecting the method.

Now imagine:

GenerateDetailedReport();

or

GenerateSummaryReport();

The intent is immediately clear.

Boolean parameters often indicate that a function is doing more than one thing.

Reduce the Number of Arguments

Functions become harder to understand as parameters increase.

For example:

CreateUser(
    firstName,
    lastName,
    email,
    phone,
    address,
    role,
    department
);

This is difficult to read and easy to misuse.

A better approach is often:

CreateUser(user);

Using meaningful objects can simplify method signatures and improve readability.

Avoid Side Effects

A function should do what its name promises.

Nothing more.

Imagine this method:

CheckPassword();

You expect it to validate a password.

But what if it also:

  • Updates the database
  • Writes logs
  • Sends notifications

Now the function has hidden behavior.

These unexpected actions are called side effects.

Side effects make code harder to understand and maintain.

Command Query Separation

One concept I particularly liked was:

A function should either do something or answer something. Not both.

For example:

Bad:

if (user.Save())
{
}

The method both performs an action and returns information.

Better:

SaveUser(user);

bool exists = UserExists(id);

Commands change state.

Queries return information.

Keeping them separate improves clarity.

My Biggest Takeaway

Before reading this chapter, I often thought about whether a function worked.

Now I think more about whether the function has a clear responsibility.

When a function becomes difficult to name, that's often a sign it's doing too much.

When a function requires extensive comments, that's often a sign it's doing too much.

When a function has many parameters, that's often a sign it's doing too much.

Most function-related problems can be traced back to one issue:

The function has too many responsibilities.

Final Thoughts

Chapter 3 taught me that great software isn't built from clever functions.

It's built from simple functions that are easy to understand.

Small functions.

Focused functions.

Functions that do one thing and do it well.

Going forward, I'll spend more time breaking large methods into smaller, meaningful pieces.

Not because the compiler needs it.

Because future developers do.

Including my future self.

What About You?

What's the largest function you've ever encountered in a production codebase?

And what techniques do you use to keep your functions clean and maintainable?

Share this article

Dabananda Mitra

Dabananda Mitra

Software Engineer specializing in scalable backend systems and minimal, effective API design.