# 8 Tips To Write Clean Code

> Clean code is code that's easy to read, maintain, and understand. It's a skill you can learn with deliberate practice. My favorite way to practice is a refactoring exercise, so I prepared one for you: we'll improve one badly written method step by step.

Published: 2023-07-15. Author: Milan Jovanović.

Canonical: https://milanjovanovic.tech/blog/8-tips-to-write-clean-code

Clean code is easy to read and maintain, and refactoring exercises are how you practice writing it.
This issue takes one badly written `Process` method through eight steps: early returns, merged conditions, LINQ, a descriptive method name, custom exceptions, constants for magic numbers, enums for magic strings, and the result object pattern.

**Clean code** is code that's easy to read, maintain, and understand.

I consider writing **clean code** a skill.

And it's a **skill** that **you can learn** and improve with deliberate practice.

My favorite approach for practicing **clean coding** is doing [**refactoring exercises**](https://milanjovanovic.tech/blog/5-awesome-csharp-refactoring-tips).

So I prepared one for you today, and we're going to improve it one step at a time by applying **clean code principles**.

Let's dive in!

## Starting Point

I like starting with a problem when trying to learn new concepts.

And the more illustrative the problem, the better.

So we'll use some poorly written code as the starting point for our refactoring.

And in each step, I will highlight what the current issue is and how we will fix it.

Here's what I see when I look at the `Process` method:

- Deep nesting of code - 4 levels, to be precise
- Precondition checks are applied one after the other
- Throwing exceptions to represent a failure

How can we turn this into **clean code**?

```csharp
public void Process(Order? order)
{
    if (order != null)
    {
        if (order.IsVerified)
        {
            if (order.Items.Count > 0)
            {
                if (order.Items.Count > 15)
                {
                    throw new Exception(
                        "The order " + order.Id + " has too many items");
                }

                if (order.Status != "ReadyToProcess")
                {
                    throw new Exception(
                        "The order " + order.Id + " isn't ready to process");
                }

                order.IsProcessed = true;
            }
        }
    }
}
```

## #1: Early Return Principle

It should be painfully obvious by now that the initial version is deeply nested because of the `if` statements applying precondition checks.

We'll solve this using the **early return principle**, which states that we should return from a method as soon as the conditions for that have been met.

In the case of the `Process` method, this means moving from a deeply nested structure to a set of **guard clauses**.

```csharp {3-6,8-11,13-16}
public void Process(Order? order)
{
    if (order is null)
    {
        return;
    }

    if (!order.IsVerified)
    {
        return;
    }

    if (order.Items.Count == 0)
    {
        return;
    }

    if (order.Items.Count > 15)
    {
        throw new Exception(
            "The order " + order.Id + " has too many items");
    }

    if (order.Status != "ReadyToProcess")
    {
        throw new Exception(
            "The order " + order.Id + " isn't ready to process");
    }

    order.IsProcessed = true;
}
```

## #2: Merge If Statements To Improve Readability

The **early return principle** makes the `Process` method more readable.

But there's no need to have one **guard clause** after another.

So we can merge all of them into one `if` statement.

The behavior of the `Process` method remains unchanged, but we remove a lot of excess code.

```csharp {3-8}
public void Process(Order? order)
{
    if (order is null ||
        !order.IsVerified ||
        order.Items.Count == 0)
    {
        return;
    }

    if (order.Items.Count > 15)
    {
        throw new Exception(
            "The order " + order.Id + " has too many items");
    }

    if (order.Status != "ReadyToProcess")
    {
        throw new Exception(
            "The order " + order.Id + " isn't ready to process");
    }

    order.IsProcessed = true;
}
```

## #3: Use LINQ For More Concise Code

A quick improvement can be using **LINQ** to make the code more concise and expressive.

Instead of checking for `Items.Count == 0`, I prefer using the LINQ `Any` method.

You could argue that LINQ has worse performance, but I always optimize for readability.

There are far more expensive operations in an application than a simple method call.

```csharp {5}
public void Process(Order? order)
{
    if (order is null ||
        !order.IsVerified ||
        !order.Items.Any())
    {
        return;
    }

    if (order.Items.Count > 15)
    {
        throw new Exception(
            "The order " + order.Id + " has too many items");
    }

    if (order.Status != "ReadyToProcess")
    {
        throw new Exception(
            "The order " + order.Id + " isn't ready to process");
    }

    order.IsProcessed = true;
}
```

## #4: Replace Boolean Expression With Descriptive Method

Merging multiple conditions into one `if` statement means writing less code, but it can **decrease readability** with **complex conditions**.

However, you can fix this and improve readability by using a variable or method with a **descriptive name**.

I prefer using methods, so I will introduce the `IsProcessable` method to represent the precondition check.

```csharp {3, 23-28}
public void Process(Order? order)
{
    if (!IsProcessable(order))
    {
        return;
    }

    if (order.Items.Count > 15)
    {
        throw new Exception(
            "The order " + order.Id + " has too many items");
    }

    if (order.Status != "ReadyToProcess")
    {
        throw new Exception(
            "The order " + order.Id + " isn't ready to process");
    }

    order.IsProcessed = true;
}

static bool IsProcessable(Order? order)
{
    return order is not null &&
           order.IsVerified &&
           order.Items.Any();
}
```

## #5: Prefer Throwing Custom Exceptions

Now let's talk about throwing exceptions.
I like to use exceptions for _"exceptional"_ situations only, and I don't use them for flow control in my code.

Having said that, if you _do_ want to use exceptions for flow control, it's better to use **custom exceptions**.

You can introduce valuable contextual information and better describe the reason for throwing the exception.

And if you want to [**handle these exceptions globally**](https://milanjovanovic.tech/blog/global-error-handling-in-aspnetcore-8), you can create a base class to be able to catch specific exceptions.

```csharp {10,15}
public void Process(Order? order)
{
    if (!IsProcessable(order))
    {
        return;
    }

    if (order.Items.Count > 15)
    {
        throw new TooManyLineItemsException(order.Id);
    }

    if (order.Status != "ReadyToProcess")
    {
        throw new NotReadyForProcessingException(order.Id);
    }

    order.IsProcessed = true;
}

static bool IsProcessable(Order? order)
{
    return order is not null &&
           order.IsVerified &&
           order.Items.Any();
}
```

## #6: Fix Magic Numbers With Constants

A common **code smell** I see is the use of **magic numbers**.

They are usually easy to spot because they're used to check if numeric some condition applies.

The problem with **magic numbers** is that they **carry no meaning**.

The code is harder to reason about, and more error-prone.

Fixing **magic numbers** should be straightforward, and one solution is introducing a constant.

```csharp {1,10}
const int MaxNumberOfLineItems = 15;

public void Process(Order? order)
{
    if (!IsProcessable(order))
    {
        return;
    }

    if (order.Items.Count > MaxNumberOfLineItems)
    {
        throw new TooManyLineItemsException(order.Id);
    }

    if (order.Status != "ReadyToProcess")
    {
        throw new NotReadyForProcessingException(order.Id);
    }

    order.IsProcessed = true;
}

static bool IsProcessable(Order? order)
{
    return order is not null &&
           order.IsVerified &&
           order.Items.Any();
}
```

## #7: Fix Magic Strings With Enums

Similar to **magic numbers**, we have the **magic strings** **code smell**.

A typical use case for **magic strings** is to represent some sort of state.

You'll notice that we're comparing the `Order.Status` value to a **magic string** to check if the order is ready to process.

A few **problems** with **magic strings**:

- Easy to make mistakes (typo)
- Lack of strong typing
- Not refactoring proof

Let's create an `OrderStatus` `enum` to represent the possible states:

```csharp
enum OrderStatus
{
    Pending = 0,
    ReadyToProcess = 1,
    Processed = 2
}
```

And now we have to use the appropriate `OrderStatus` in the check:

```csharp {15,21}
const int MaxNumberOfLineItems = 15;

public void Process(Order? order)
{
    if (!IsProcessable(order))
    {
        return;
    }

    if (order.Items.Count > MaxNumberOfLineItems)
    {
        throw new TooManyLineItemsException(order.Id);
    }

    if (order.Status != OrderStatus.ReadyToProcess)
    {
        throw new NotReadyForProcessingException(order.Id);
    }

    order.IsProcessed = true;
    order.Status = OrderStatus.Processed;
}

static bool IsProcessable(Order? order)
{
    return order is not null &&
           order.IsVerified &&
           order.Items.Any();
}
```

## #8: Use The Result Object Pattern

I said I don't prefer using exceptions for flow control. But how can we fix this?

One solution is using the [**result object pattern**](https://milanjovanovic.tech/blog/functional-error-handling-in-dotnet-with-the-result-pattern).

You can use a generic `Result` class to represent all types of results or a specific one like `ProcessOrderResult`.

To make your result objects encapsulated, expose a set of factory methods to create the concrete result type.

```csharp
public class ProcessOrderResult
{
    private ProcessOrderResult(
        ProcessOrderResultType type,
        long orderId,
        string message)
    {
        Type = type;
        OrderId = orderId;
        Message = message;
    }

    public ProcessOrderResultType Type { get; }

    public long OrderId { get; }

    public string? Message { get; }

    public static ProcessOrderResult NotProcessable() =>
      new(ProcessOrderResultType.NotProcessable, default, "Not processable");

    public static ProcessOrderResult TooManyLineItems(long oderId) =>
      new(ProcessOrderResultType.TooManyLineItems, orderId, "Too many items");

    public static ProcessOrderResult NotReadyForProcessing(long oderId) =>
      new(ProcessOrderResultType.NotReadyForProcessing, oderId, "Not ready");

    public static ProcessOrderResult Success(long oderId) =>
      new(ProcessOrderResultType.Success, oderId, "Success");
}
```

Using an `enum` like `ProcessOrderResultType` will make consuming the result object easier with switch expressions.
Here's the `enum` to represent the `ProcessOrderResult.Type`:

```csharp
public enum ProcessOrderResultType
{
    NotProcessable = 0,
    TooManyLineItems = 1,
    NotReadyForProcessing = 2,
    Success = 3
}
```

And now the `Process` method becomes:

```csharp {7,12,17,23}
const int MaxNumberOfLineItems = 15;

public ProcessOrderResult Process(Order? order)
{
    if (!IsProcessable(order))
    {
        return ProcessOrderResult.NotProcessable();
    }

    if (order.Items.Count > MaxNumberOfLineItems)
    {
        return ProcessOrderResult.TooManyLineItems(order);
    }

    if (order.Status != OrderStatus.ReadyToProcess)
    {
        return ProcessOrderResult.NotReadyForProcessing(order);
    }

    order.IsProcessed = true;
    order.Status = OrderStatus.Processed;

    return ProcessOrderResult.Success(order);
}

static bool IsProcessable(Order? order)
{
    return order is not null &&
           order.IsVerified &&
           order.Items.Any();
}
```

Here's how using an `enum` for the `ProcessOrderResult.Type` allows you to write a switch expression:

```csharp
var result = Process(order);

result.Type switch
{
    ProcessOrderResultType.TooManyLineItems =>
        Console.WriteLine($"Too many line items: {result.OrderId}"),

    ProcessOrderResultType.NotReadyForProcessing =>
        Console.WriteLine($"Not ready for processing {result.OrderId}"),

    ProcessOrderResultType.Success =>
        Console.WriteLine($"Processed successfully {result.OrderId}"),

    _ => Console.WriteLine("Failed to process: {OrderId}", result.OrderId),
};
```

## Takeaway

That's it, 8 tips to write **clean code**:

- [Early return principle](#1-early-return-principle)
- [Merge multiple if statements](#2-merge-if-statements-to-improve-readability)
- [Use LINQ for conciseness](#3-use-linq-for-more-concise-code)
- [Replace boolean expression with method](#4-replace-boolean-expression-with-descriptive-method)
- [Prefer throwing custom exceptions](#5-prefer-throwing-custom-exceptions)
- [Replace magic numbers with constants](#6-fix-magic-numbers-with-constants)
- [Replace magic string with enums](#7-fix-magic-strings-with-enums)
- [Use the result object pattern](#8-use-the-result-object-pattern)

Writing **clean code** is a matter of deliberate practice and experience.

Most people will read about **clean coding principles**, but few will strive to apply them daily.

This is where you can set yourself apart.

I also made a video about these **clean code** tips, you can watch it [here.](https://youtu.be/McDvyFglkvU)

Hope this was helpful.

See you next week!

**Today's action step:**
Take a look at your project and see if you're making some of the mistakes I highlighted here.
And then fix them using the clean code tips I shared with you.

---

## Frequently asked questions

### What is clean code?

Clean code is code that is easy to read, maintain, and understand. Writing it is a skill you can learn and improve with deliberate practice, and refactoring exercises on poorly written code are one of the best ways to practice it.

### What is the early return principle?

The early return principle says you should return from a method as soon as the conditions for returning are met. Applied to deeply nested if statements, it turns the nesting into a flat set of guard clauses at the top of the method, which is much easier to read.

### Why are magic numbers and magic strings a code smell?

They carry no meaning, which makes code harder to reason about and more error-prone. Replace magic numbers with named constants. Replace magic strings that represent state with enums, which give you strong typing, protect against typos, and survive refactoring.

### Should you use exceptions for flow control in C#?

Prefer not to. Reserve exceptions for truly exceptional situations. If you do use them for flow control, throw custom exceptions that carry contextual information rather than generic ones. A better alternative is the result object pattern, which represents failures as return values.

### What is the result object pattern?

Instead of throwing exceptions, a method returns a result class that describes the outcome. The class exposes private constructors plus factory methods for each result type, and an enum on the result makes it easy for callers to handle outcomes with a switch expression.

### Is LINQ bad for performance compared to manual checks?

LINQ methods like Any can be marginally slower than direct checks like Count == 0, but the difference rarely matters. Optimize for readability first, since most applications have far more expensive operations than a simple method call.
