Clean Code for .NET

repository·master·Indexed 27 days ago

https://github.com/thangchung/clean-code-dotnet

A collection of Clean Code principles adapted for the .NET and .NET Core ecosystem. This guide provides software engineering principles for producing readable, reusable, and refactorable code, covering topics such as consistent capitalization, avoiding deep nesting, meaningful naming, polymorphism over conditionals, and proper encapsulation.

Tokens
15K
Snippets
55
Records
77
Agent score
43%

What's inside clean-code-dotnet

  1. Overview of Clean Code concepts for .NET

    master
    This project provides guidelines for producing readable, reusable, and refactorable software in .NET and .NET Core, adapted from Robert C. Martin's Clean Code. It is intended as a guide for software engineering principles rather than a strict style guide.
  2. Overview of Clean Code for .NET/.NET Core

    master
    The clean-code-dotnet project provides guidance for writing readable, reusable, and refactorable .NET/.NET Core programs based on the principles from Robert C. Martin's book Clean Code. It is not a strict coding style guide, but rather a set of professional wisdom and guidelines to help developers improve software quality.
  3. Avoid flags in method parameters

    master

    Boolean flags in method parameters often indicate that a method has more than one responsibility. Instead of using a flag to toggle behavior, split the method into two distinct, single-purpose methods.

    // Good: Split into two specific methods
    public void CreateFile(string name)
    {
        Touch(name);
    }
    
    public void CreateTempFile(string name)
    {
        Touch("./temp/" + name);
    }
  4. Apply the Dependency Inversion Principle (DIP)

    master

    The Dependency Inversion Principle states that high-level modules should not depend on low-level modules; both should depend on abstractions. Additionally, abstractions should not depend on details; details should depend on abstractions. In .NET, this is often implemented via Dependency Injection (DI) to reduce coupling between modules, making code easier to refactor.

    public interface IEmployee
    {
        void Work();
    }
    
    public class Human : IEmployee
    {
        public void Work() { /* ... */ }
    }
    
    public class Robot : IEmployee
    {
        public void Work() { /* ... */ }
    }
    
    public class Manager(IEnumerable<IEmployee> employees)
    {
        public void Manage()
        {
            foreach (var employee in employees)
            {
                employee.Work();
            }
        }
    }
  5. Use meaningful function names

    master

    Function names should clearly communicate their intent. Avoid vague names like Handle() when a more descriptive name like Send() or Process() would clarify what the function actually does.

    // Good: Clear and obvious
    public class Email
    {
        public void Send()
        {
            SendMail(this._to, this._subject, this._body);
        }
    }
    
    var message = new Email(...);
    message.Send();
  6. Replace conditionals with polymorphism

    master

    Instead of using large switch statements or multiple if blocks to handle different types within a single function, use polymorphism. Define an interface and implement specific logic in separate classes for each type. This ensures each function does only one thing and adheres to the Single Responsibility Principle.

    interface IAirplane
    {
        double GetCruisingAltitude();
    }
    
    class Boeing777 : IAirplane
    {
        public double GetCruisingAltitude() => GetMaxAltitude() - GetPassengerCount();
    }
    
    class AirForceOne : IAirplane
    {
        public double GetCruisingAltitude() => GetMaxAltitude();
    }
  7. Use descriptive function names

    master

    Function names should clearly state their purpose. Avoid vague names like Handle() when a more descriptive name like Send() or Process() better communicates the action being performed.

    // Good: Clear and obvious
    public class Email
    {
        public void Send()
        {
            SendMail(this._to, this._subject, this._body);
        }
    }
    
    var message = new Email(...);
    message.Send();
  8. Avoid flag arguments in method parameters

    master

    Boolean flags passed as parameters indicate that a function has more than one responsibility. Instead of using a flag to toggle behavior, split the function into two distinct, single-responsibility functions.

    // Good: Split into two specific functions
    public void CreateFile(string name)
    {
        Touch(name);
    }
    
    public void CreateTempFile(string name)
    {
        Touch("./temp/" + name);
    }
  9. Handle CPU-bound tasks with Task.Factory.CreateNew

    master
    The async/await pattern is optimized for I/O-bound tasks (network, database, etc.). For heavy CPU-bound/computational tasks (e.g., processing large lists or image rendering), avoid using async/await directly as it may not effectively utilize the thread pool for the computation. Instead, use Task.Factory.CreateNew with TaskCreationOptions.LongRunning to start a dedicated background thread.
  10. Apply the Open/Closed Principle (OCP)

    master

    The Open/Closed Principle states that software entities should be open for extension but closed for modification. You should be able to add new functionality without changing existing code. This is typically achieved by using interfaces or abstract classes instead of checking types or names (e.g., using if (adapterName == "...")) within a consumer class.

    interface IAdapter
    {
        bool Request(string url);
    }
    
    class AjaxAdapter : IAdapter
    {
        public bool Request(string url)
        {
            // request and return promise
        }
    }
    
    class HttpRequester
    {
        private readonly IAdapter Adapter;
    
        public HttpRequester(IAdapter adapter)
        {
            Adapter = adapter;
        }
    
        public bool Fetch(string url)
        {
            return Adapter.Request(url);
        }
    }
  11. Use default parameters instead of conditional logic

    master

    Instead of manually checking if a parameter is null or empty to assign a default value inside the method body, use C# default parameter values in the method signature.

    // Good
    public void CreateMicrobrewery(string breweryName = "Hipster Brew Co.")
    {
        // ...
    }