Skip to content

Instantly share code, notes, and snippets.

@wojteklu
Last active July 23, 2024 07:47
Show Gist options
  • Save wojteklu/73c6914cc446146b8b533c0988cf8d29 to your computer and use it in GitHub Desktop.
Save wojteklu/73c6914cc446146b8b533c0988cf8d29 to your computer and use it in GitHub Desktop.
Summary of 'Clean code' by Robert C. Martin

Code is clean if it can be understood easily – by everyone on the team. Clean code can be read and enhanced by a developer other than its original author. With understandability comes readability, changeability, extensibility and maintainability.


General rules

  1. Follow standard conventions.
  2. Keep it simple stupid. Simpler is always better. Reduce complexity as much as possible.
  3. Boy scout rule. Leave the campground cleaner than you found it.
  4. Always find root cause. Always look for the root cause of a problem.

Design rules

  1. Keep configurable data at high levels.
  2. Prefer polymorphism to if/else or switch/case.
  3. Separate multi-threading code.
  4. Prevent over-configurability.
  5. Use dependency injection.
  6. Follow Law of Demeter. A class should know only its direct dependencies.

Understandability tips

  1. Be consistent. If you do something a certain way, do all similar things in the same way.
  2. Use explanatory variables.
  3. Encapsulate boundary conditions. Boundary conditions are hard to keep track of. Put the processing for them in one place.
  4. Prefer dedicated value objects to primitive type.
  5. Avoid logical dependency. Don't write methods which works correctly depending on something else in the same class.
  6. Avoid negative conditionals.

Names rules

  1. Choose descriptive and unambiguous names.
  2. Make meaningful distinction.
  3. Use pronounceable names.
  4. Use searchable names.
  5. Replace magic numbers with named constants.
  6. Avoid encodings. Don't append prefixes or type information.

Functions rules

  1. Small.
  2. Do one thing.
  3. Use descriptive names.
  4. Prefer fewer arguments.
  5. Have no side effects.
  6. Don't use flag arguments. Split method into several independent methods that can be called from the client without the flag.

Comments rules

  1. Always try to explain yourself in code.
  2. Don't be redundant.
  3. Don't add obvious noise.
  4. Don't use closing brace comments.
  5. Don't comment out code. Just remove.
  6. Use as explanation of intent.
  7. Use as clarification of code.
  8. Use as warning of consequences.

Source code structure

  1. Separate concepts vertically.
  2. Related code should appear vertically dense.
  3. Declare variables close to their usage.
  4. Dependent functions should be close.
  5. Similar functions should be close.
  6. Place functions in the downward direction.
  7. Keep lines short.
  8. Don't use horizontal alignment.
  9. Use white space to associate related things and disassociate weakly related.
  10. Don't break indentation.

Objects and data structures

  1. Hide internal structure.
  2. Prefer data structures.
  3. Avoid hybrids structures (half object and half data).
  4. Should be small.
  5. Do one thing.
  6. Small number of instance variables.
  7. Base class should know nothing about their derivatives.
  8. Better to have many functions than to pass some code into a function to select a behavior.
  9. Prefer non-static methods to static methods.

Tests

  1. One assert per test.
  2. Readable.
  3. Fast.
  4. Independent.
  5. Repeatable.

Code smells

  1. Rigidity. The software is difficult to change. A small change causes a cascade of subsequent changes.
  2. Fragility. The software breaks in many places due to a single change.
  3. Immobility. You cannot reuse parts of the code in other projects because of involved risks and high effort.
  4. Needless Complexity.
  5. Needless Repetition.
  6. Opacity. The code is hard to understand.
@fosterushka
Copy link

Would be helpful if someone will add code example refactored as above one.

@realgio95
Copy link

Adding Some Code examples in C# with ChatGPT

Code Examples with GPT4o

Certainly! Here are C# code examples illustrating the principles and rules described:

General rules

  1. Follow standard conventions
// Correct
public class OrderService
{
    private readonly IOrderRepository _orderRepository;

    public OrderService(IOrderRepository orderRepository)
    {
        _orderRepository = orderRepository;
    }

    public void PlaceOrder(Order order)
    {
        _orderRepository.Add(order);
    }
}

// Incorrect
public class orderservice
{
    private IOrderRepository orderrepository;

    public orderservice(IOrderRepository orderrepository)
    {
        this.orderrepository = orderrepository;
    }

    public void placeorder(Order order)
    {
        orderrepository.add(order);
    }
}
  1. Keep it simple stupid. Simpler is always better. Reduce complexity as much as possible
// Simple and clear
public double CalculateArea(double radius)
{
    return Math.PI * radius * radius;
}

// Overly complex
public double CalculateArea(double radius)
{
    double area = 0;
    for (int i = 0; i < 360; i++)
    {
        area += (Math.PI / 180) * radius * radius;
    }
    return area;
}
  1. Boy scout rule. Leave the campground cleaner than you found it
// Clean up resources
public void ProcessData()
{
    using (var connection = new SqlConnection(connectionString))
    {
        connection.Open();
        // Perform data operations
    } // Connection is automatically closed here
}
  1. Always find root cause. Always look for the root cause of a problem
// Logging the exception and identifying the root cause
public void ProcessOrder(Order order)
{
    try
    {
        // Process the order
    }
    catch (Exception ex)
    {
        Log.Error("Error processing order: " + ex.Message);
        throw;
    }
}

Design rules

  1. Keep configurable data at high levels
// Configurable data at high level
public class Config
{
    public string DatabaseConnectionString { get; set; }
}

public class DataService
{
    private readonly string _connectionString;

    public DataService(Config config)
    {
        _connectionString = config.DatabaseConnectionString;
    }

    // ...
}
  1. Prefer polymorphism to if/else or switch/case
// Using polymorphism
public abstract class Notification
{
    public abstract void Send();
}

public class EmailNotification : Notification
{
    public override void Send()
    {
        // Send email
    }
}

public class SmsNotification : Notification
{
    public override void Send()
    {
        // Send SMS
    }
}

// Instead of
public void SendNotification(string type)
{
    if (type == "Email")
    {
        // Send email
    }
    else if (type == "SMS")
    {
        // Send SMS
    }
}
  1. Separate multi-threading code
// Using Task for multi-threading
public async Task ProcessDataAsync()
{
    await Task.Run(() => 
    {
        // Perform CPU-bound operations
    });
}
  1. Prevent over-configurability
// Simple configuration
public class AppSettings
{
    public string DatabaseConnectionString { get; set; }
}
  1. Use dependency injection
// Using dependency injection
public class OrderService
{
    private readonly IOrderRepository _orderRepository;

    public OrderService(IOrderRepository orderRepository)
    {
        _orderRepository = orderRepository;
    }

    // ...
}
  1. Follow Law of Demeter. A class should know only its direct dependencies
// Following Law of Demeter
public class OrderProcessor
{
    private readonly OrderService _orderService;

    public OrderProcessor(OrderService orderService)
    {
        _orderService = orderService;
    }

    public void Process(Order order)
    {
        _orderService.PlaceOrder(order);
    }
}

Understandability tips

  1. Be consistent
// Consistent naming
public class UserService
{
    public User GetUserById(int id)
    {
        // ...
    }

    public User GetUserByEmail(string email)
    {
        // ...
    }
}
  1. Use explanatory variables
// Using explanatory variables
var fileName = "data.txt";
var fileContent = File.ReadAllText(fileName);
  1. Encapsulate boundary conditions
// Encapsulating boundary conditions
public class DateRange
{
    public DateTime Start { get; }
    public DateTime End { get; }

    public DateRange(DateTime start, DateTime end)
    {
        if (start > end)
            throw new ArgumentException("Start date must be earlier than end date");

        Start = start;
        End = end;
    }
}
  1. Prefer dedicated value objects to primitive type
// Using value objects
public class Email
{
    public string Address { get; }

    public Email(string address)
    {
        if (string.IsNullOrEmpty(address))
            throw new ArgumentException("Email address cannot be empty");

        Address = address;
    }
}
  1. Avoid logical dependency
// Avoiding logical dependency
public class User
{
    public string Name { get; set; }
    public bool IsActive { get; set; }

    public void Activate()
    {
        IsActive = true;
    }
}
  1. Avoid negative conditionals
// Avoiding negative conditionals
if (IsUserActive)
{
    // Do something
}

// Instead of
if (!IsUserInactive)
{
    // Do something
}

Names rules

  1. Choose descriptive and unambiguous names
// Descriptive names
public class InvoiceService
{
    public void CreateInvoice(Order order)
    {
        // ...
    }
}
  1. Make meaningful distinction
// Meaningful distinction
public class Order
{
    public DateTime OrderDate { get; set; }
}

public class Shipment
{
    public DateTime ShipmentDate { get; set; }
}
  1. Use pronounceable names
// Pronounceable names
public class CustomerService
{
    public void AddCustomer(Customer customer)
    {
        // ...
    }
}
  1. Use searchable names
// Searchable names
public const int MaxUsers = 100;
  1. Replace magic numbers with named constants
// Using named constants
public const int DefaultTimeout = 30;

public void Connect()
{
    // Using named constant
    var timeout = DefaultTimeout;
}
  1. Avoid encodings
// Avoiding encodings
public class UserService
{
    public void CreateUser(string name)
    {
        // ...
    }
}

Functions rules

  1. Small
// Small function
public void SaveUser(User user)
{
    ValidateUser(user);
    _userRepository.Save(user);
}
  1. Do one thing
// Single responsibility
public void ValidateUser(User user)
{
    if (string.IsNullOrEmpty(user.Name))
        throw new ArgumentException("User name cannot be empty");
}
  1. Use descriptive names
// Descriptive names
public void SendEmailConfirmation(User user)
{
    // ...
}
  1. Prefer fewer arguments
// Fewer arguments
public void CreateOrder(int userId, int productId)
{
    // ...
}
  1. Have no side effects
// No side effects
public int CalculateSum(int a, int b)
{
    return a + b;
}
  1. Don't use flag arguments
// Separate methods
public void EnableFeature()
{
    // ...
}

public void DisableFeature()
{
    // ...
}

Comments rules

  1. Always try to explain yourself in code
// Self-explanatory code
public void CalculateTotalPrice()
{
    var totalPrice = items.Sum(item => item.Price * item.Quantity);
}
  1. Don't be redundant
// Redundant comment
// Adds two numbers
public int Add(int a, int b)
{
    return a + b;
}
  1. Don't add obvious noise
// Obvious noise
public int Multiply(int a, int b)
{
    return a * b; // Multiply a and b
}
  1. Don't use closing brace comments
// No closing brace comments
public void ProcessOrder()
{
    // ...
}
  1. Don't comment out code. Just remove
// Remove commented code
// public void OldMethod()
// {
//     // ...
// }
  1. Use as explanation of intent
// Explanation of intent
public void SaveData()
{
    // Save data to ensure it is not lost
    _dataRepository.Save();
}
  1. Use as clarification of code
// Clarification of code
public void CalculateDiscount()
{
    // Apply 10% discount to all items
    foreach (var item in items)
    {
        item.Price *= 0.9;
    }
}
  1. Use as warning of consequences
```csharp
// Warning of consequences
public void DeleteUser(int userId)
{
    // Warning: This action is irreversible
    _userRepository.Delete(userId);
}

Source code structure

  1. Separate concepts vertically
// Vertical separation
public class UserService
{
    // Constructor
    public UserService(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    // Public methods
    public void CreateUser(User user)
    {
        // ...
    }

    // Private methods
    private void ValidateUser(User user)
    {
        // ...
    }
}
  1. Related code should appear vertically dense
// Related code close together
public class OrderService
{
    public void CreateOrder(Order order)
    {
        ValidateOrder(order);
        SaveOrder(order);
    }

    private void ValidateOrder(Order order)
    {
        // ...
    }

    private void SaveOrder(Order order)
    {
        // ...
    }
}
  1. Declare variables close to their usage
public void ProcessOrder(Order order)
{
    var orderTotal = CalculateTotal(order); // Variable declared close to usage
    // ...
}
  1. Dependent functions should be close
public class OrderService
{
    public void CreateOrder(Order order)
    {
        var total = CalculateTotal(order);
        SaveOrder(order, total);
    }

    private decimal CalculateTotal(Order order)
    {
        // ...
    }

    private void SaveOrder(Order order, decimal total)
    {
        // ...
    }
}
  1. Similar functions should be close
public class UserService
{
    public User GetUserById(int id)
    {
        // ...
    }

    public User GetUserByEmail(string email)
    {
        // ...
    }
}
  1. Place functions in the downward direction
public class OrderService
{
    public void ProcessOrder(Order order)
    {
        ValidateOrder(order);
        SaveOrder(order);
    }

    private void ValidateOrder(Order order)
    {
        // ...
    }

    private void SaveOrder(Order order)
    {
        // ...
    }
}
  1. Keep lines short
// Short lines
public void DisplayUserInfo(User user)
{
    Console.WriteLine($"Name: {user.Name}, Email: {user.Email}");
}
  1. Don't use horizontal alignment
// No horizontal alignment
public int MaxUsers    { get; set; }
public string Database { get; set; }
  1. Use white space to associate related things and disassociate weakly related
// Using white space
public class UserService
{
    public void CreateUser(User user)
    {
        ValidateUser(user);
        _userRepository.Save(user);
    }

    private void ValidateUser(User user)
    {
        if (string.IsNullOrEmpty(user.Name))
            throw new ArgumentException("Name is required");
    }
}
  1. Don't break indentation
// Correct indentation
public void ProcessData()
{
    if (data.IsValid)
    {
        SaveData(data);
    }
}

Objects and data structures

  1. Hide internal structure
// Encapsulation
public class Order
{
    private List<OrderItem> _items;

    public void AddItem(OrderItem item)
    {
        _items.Add(item);
    }

    public decimal GetTotal()
    {
        return _items.Sum(i => i.Price * i.Quantity);
    }
}
  1. Prefer data structures
// Data structure
public class Point
{
    public int X { get; set; }
    public int Y { get; set; }
}
  1. Avoid hybrids structures (half object and half data)
// Avoid hybrids
public class User
{
    private string passwordHash;

    public string Name { get; set; }

    public void SetPassword(string password)
    {
        passwordHash = Hash(password);
    }

    private string Hash(string input)
    {
        // Hashing logic
    }
}
  1. Should be small
// Small class
public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
    public string ZipCode { get; set; }
}
  1. Do one thing
// Single responsibility
public class EmailValidator
{
    public bool IsValid(string email)
    {
        // Validation logic
    }
}
  1. Small number of instance variables
// Few instance variables
public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}
  1. Base class should know nothing about their derivatives
// Proper inheritance
public abstract class Animal
{
    public abstract void MakeSound();
}

public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Bark");
    }
}
  1. Better to have many functions than to pass some code into a function to select a behavior
// Many functions
public class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }

    public int Subtract(int a, int b)
    {
        return a - b;
    }
}
  1. Prefer non-static methods to static methods
// Non-static method
public class Logger
{
    public void Log(string message)
    {
        // Log message
    }
}

Tests

  1. One assert per test
[Test]
public void TestAddition()
{
    var calculator = new Calculator();
    var result = calculator.Add(2, 3);
    Assert.AreEqual(5, result);
}
  1. Readable
// Readable test
[Test]
public void ShouldReturnTrueForValidEmail()
{
    var emailValidator = new EmailValidator();
    var isValid = emailValidator.IsValid("test@example.com");
    Assert.IsTrue(isValid);
}
  1. Fast
// Fast test
[Test]
public void ShouldProcessOrderQuickly()
{
    var orderProcessor = new OrderProcessor();
    var stopwatch = Stopwatch.StartNew();
    orderProcessor.Process(order);
    stopwatch.Stop();
    Assert.Less(stopwatch.ElapsedMilliseconds, 1000);
}
  1. Independent
// Independent test
[Test]
public void ShouldSaveUser()
{
    var userRepository = new UserRepository();
    var userService = new UserService(userRepository);
    var user = new User { Name = "John Doe" };
    
    userService.SaveUser(user);

    var savedUser = userRepository.GetUserByName("John Doe");
    Assert.AreEqual(user.Name, savedUser.Name);
}
  1. Repeatable
// Repeatable test
[Test]
public void ShouldCalculateTotalPrice()
{
    var cart = new ShoppingCart();
    cart.AddItem(new Product { Price = 10, Quantity = 2 });
    cart.AddItem(new Product { Price = 5, Quantity = 3 });

    var totalPrice = cart.CalculateTotalPrice();

    Assert.AreEqual(35, totalPrice);
}

Code smells

  1. Rigidity
// Rigid code example
public class OrderProcessor
{
    public void ProcessOnlineOrder(OnlineOrder order)
    {
        // ...
    }

    public void ProcessInStoreOrder(InStoreOrder order)
    {
        // ...
    }
}
  1. Fragility
// Fragile code example
public class User
{
    public string Name { get; set; }
    public string Email { get; set; }
}

public class UserService
{
    private readonly List<User> _users;

    public void UpdateUserEmail(string name, string newEmail)
    {
        var user = _users.FirstOrDefault(u => u.Name == name);
        if (user != null)
        {
            user.Email = newEmail;
        }
    }
}
  1. Immobility
// Immobile code example
public class ReportGenerator
{
    private readonly Database _database;

    public ReportGenerator(Database database)
    {
        _database = database;
    }

    public void GenerateReport()
    {
        var data = _database.GetData();
        // Generate report using data
    }
}
  1. Needless Complexity
// Needless complexity
public class ComplexCalculator
{
    public int Calculate(int a, int b, string operation)
    {
        if (operation == "add")
        {
            return a + b;
        }
        else if (operation == "subtract")
        {
            return a - b;
        }
        else if (operation == "multiply")
        {
            return a * b;
        }
        else if (operation == "divide")
        {
            return a / b;
        }
        else
        {
            throw new InvalidOperationException("Unknown operation");
        }
    }
}
  1. Needless Repetition
// Needless repetition
public class UserService
{
    private readonly IUserRepository _userRepository;

    public UserService(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    public void CreateUser(User user)
    {
        if (string.IsNullOrEmpty(user.Name))
            throw new ArgumentException("Name ```csharp
            throw new ArgumentException("Name is required");

        if (string.IsNullOrEmpty(user.Email))
            throw new ArgumentException("Email is required");

        _userRepository.Save(user);
    }
}
  1. Opacity
// Opaque code example
public class InvoiceService
{
    public void ProcessInvoice(Invoice invoice)
    {
        if (invoice != null)
        {
            if (invoice.Amount > 0)
            {
                if (invoice.Status == "Pending")
                {
                    // Process the invoice
                }
            }
        }
    }
}

By applying these principles and rules in your C# code, you can enhance readability, maintainability, and overall code quality.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment