Clean Code in C#: How to Write Code You Want to Read
Every .NET developer has encountered a situation where their own code becomes unreadable after a month or two. To avoid this, there is the philosophy of Clean Code, popularized by Robert Martin. In this article, we will break down the main principles of clean code in C# with concrete examples that will help you write maintainable and scalable applications.
Why Clean Code is Important for a C# Developer?
Clean code is not just about pretty formatting. It is a set of practices that make code:
- Readable — any developer can quickly understand the logic;
- Maintainable — easy to make changes without risking breaking the system;
- Testable — code is easily covered by unit tests;
- Efficient — no unnecessary abstractions or duplication.
In C#, with its strong typing and powerful .NET capabilities (LINQ, async/await, Records), following Clean Code principles is especially important. Bad code in C# can lead to non-obvious bugs and performance issues.
Principle 1: Naming Variables and Methods
A variable or method name should answer the question "what is this?" or "what does it do?". Avoid single-letter abbreviations (except for loop indices).
Bad Example
public class Data{ public int a; // what is this? public string b; // unclear
public void Do(int x, int y) // what does the method do? { var r = x + y; Console.WriteLine(r); }}Good Example (Clean Code)
public class Customer{ public int Id { get; set; } public string FullName { get; set; }
public void PrintCustomerSummary(int customerId, int orderCount) { var totalOrders = customerId + orderCount; Console.WriteLine($"Customer {customerId} has {totalOrders} orders"); }}Rule: Names should be expressive. If a variable is called data, that is almost always bad. Use customerData, orderList, invoiceTotal.
Principle 2: Small Methods with a Single Responsibility
Each method should do exactly one thing. If a method contains more than 10-15 lines, it most likely needs to be broken down. This is the basis of the Single Responsibility principle (from SOLID).
Bad Example (method does everything)
public void ProcessOrder(int orderId){ // 1. Validation if (orderId <= 0) throw new ArgumentException("Invalid ID"); // 2. Getting data from DB var order = _db.Orders.Find(orderId); // 3. Calculating discount decimal discount = 0; if (order.Total > 1000) discount = order.Total * 0.1m; // 4. Sending email var email = new MailMessage(); email.To.Add(order.CustomerEmail); email.Subject = "Order processed"; // 5. Logging _logger.LogInformation("Order {Id} processed", orderId);}Good Example (refactoring)
public void ProcessOrder(int orderId){ ValidateOrderId(orderId); var order = GetOrderFromDatabase(orderId); var discount = CalculateDiscount(order); SendConfirmationEmail(order, discount); LogOrderProcessing(orderId);}
private void ValidateOrderId(int orderId){ if (orderId <= 0) throw new ArgumentException("Order ID must be positive", nameof(orderId));}
private Order GetOrderFromDatabase(int orderId){ return _db.Orders.Find(orderId) ?? throw new InvalidOperationException("Order not found");}
private decimal CalculateDiscount(Order order){ return order.Total > 1000 ? order.Total * 0.1m : 0;}
private void SendConfir