DEV Community

mohamed Tayel
mohamed Tayel

Posted on

c# Clean Code: Writing Self-Documenting Code

Introduction:
One of the best practices in software development is writing self-documenting code. But what does that really mean? Simply put, your code should be easy to understand, and its purpose should be clear just by looking at it. This often involves using human-readable names for variables, functions, and methods, as well as ensuring that each function does only one thing.

While this seems like straightforward advice, it's subjective. For one developer, a piece of code might seem crystal clear; for another, it might not. In this article, we’ll explore what makes code truly self-documenting and how you can apply this principle in your work. We'll also discuss where to draw the line between clean and cluttered code, and I’ll provide practical examples to clarify these concepts.

1. What is Self-Documenting Code?

Self-documenting code refers to code that expresses its intention clearly without needing additional comments or documentation. It’s about writing code that can be easily understood by others (and your future self) without the need for explanation.

Key aspects of self-documenting code include:

  • Descriptive Naming: Variables, functions, and classes should have names that describe their purpose and behavior.
  • Single Responsibility: Each function or method should do one thing and one thing only.
  • Clarity Over Cleverness: Don’t try to write overly clever code; simplicity and readability are more important.

2. Descriptive Naming: The Foundation of Clarity

Choosing the right names for your variables, functions, and classes is the first step in making your code self-documenting. Let's take a look at a common example of unclear vs. clear naming.

Before:

public class PService
{
    public P CreateP(int i)
    {
        var p = new P();
        p.Id = i;
        return p;
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, it's difficult to understand the purpose of the class or method at a glance. The variable names are too vague, and it’s unclear what the class or function does.

After:

public class ProductService
{
    public Product CreateProduct(int productId)
    {
        var product = new Product();
        product.Id = productId;
        return product;
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, the class name (ProductService), method name (CreateProduct), and variable names (product, productId) are much more descriptive, making it easier to understand the intent of the code. Without any comments, a fellow developer can look at the code and immediately know that this method creates a product with a given ID.

3. Single Responsibility: Doing One Thing Well

Another key aspect of self-documenting code is that each function or method should have a single, well-defined responsibility. If a method tries to do too much, it becomes harder to understand and maintain.

Before:

public class ProductService
{
    public Product CreateAndSaveProduct(int productId)
    {
        var product = new Product();
        product.Id = productId;
        SaveProductToDatabase(product);
        SendProductNotification(product);
        return product;
    }
}
Enter fullscreen mode Exit fullscreen mode

Here, the method CreateAndSaveProduct is doing multiple things: creating a product, saving it to the database, and sending a notification. This violates the Single Responsibility Principle, making the method harder to maintain and test.

After:

public class ProductService
{
    public Product CreateProduct(int productId)
    {
        var product = new Product();
        product.Id = productId;
        return product;
    }

    public void SaveProductToDatabase(Product product)
    {
        // Logic to save product to the database
    }

    public void SendProductNotification(Product product)
    {
        // Logic to send a notification
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, each method has a single responsibility: CreateProduct handles product creation, SaveProductToDatabase saves it, and SendProductNotification sends a notification. This separation makes the code cleaner and easier to extend or modify.

4. Clarity Over Cleverness

As developers, we sometimes get tempted to write clever code—using short variable names or complicated logic in a single line—but this often backfires. The goal is to write code that others (and future you) can understand immediately.

Before:

public int CaclP(P p)
{
    return p?.Items?.Sum(i => i.Qty * i.Price) ?? 0;
}
Enter fullscreen mode Exit fullscreen mode

This code uses short names and a concise one-liner, but it’s difficult to quickly understand what’s happening.

After:

public int CalculateTotalPrice(Product product)
{
    if (product == null || product.Items == null)
        return 0;

    return product.Items.Sum(item => item.Quantity * item.Price);
}
Enter fullscreen mode Exit fullscreen mode

In the refactored version, we use more descriptive names (CalculateTotalPrice, Product, Quantity), and while the logic is slightly more verbose, it’s much easier to read and understand.

5. The Subjective Nature of Clean Code

One challenge with self-documenting code is that “clean” is often subjective. What one developer considers easy to understand might be unclear to someone else. A good rule of thumb is to ask yourself: “Would I understand this code if I came back to it in six months?” If the answer is no, it’s time to refactor.

As a humorous take, you can gauge the clarity of your code by imagining how many expletives would be said during a code review. The fewer, the cleaner the code.

6. Conclusion: Aiming for Readability and Maintainability

Self-documenting code doesn’t require external documentation to explain what it does. It’s easy to read, maintain, and extend. By using descriptive names, keeping methods focused on a single responsibility, and prioritizing clarity over cleverness, you’ll make your code more understandable for others—and for yourself in the future.

Start small: the next time you write a piece of code, take a moment to review your variable and method names. Are they descriptive enough? Is each function doing one thing? Writing clean, self-documenting code is an ongoing process, but it’s one that pays off in the long run.

Top comments (0)