Clean Code for .NET
repository·master·Indexed 27 days ago
https://github.com/thangchung/clean-code-dotnetA 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.
What's inside clean-code-dotnet
- 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.
Overview of Clean Code for .NET/.NET Core
masterTheclean-code-dotnetproject 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.Encapsulate conditionals
masterInstead of checking raw object properties directly in anifstatement, encapsulate the logic within a descriptive method on the object itself.Avoid flags in method parameters
masterBoolean 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); }Apply the Dependency Inversion Principle (DIP)
masterThe 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(); } } }Use meaningful function names
masterFunction names should clearly communicate their intent. Avoid vague names like
Handle()when a more descriptive name likeSend()orProcess()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();Replace conditionals with polymorphism
masterInstead of using large
switchstatements or multipleifblocks 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(); }Use descriptive function names
masterFunction names should clearly state their purpose. Avoid vague names like
Handle()when a more descriptive name likeSend()orProcess()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();Avoid flag arguments in method parameters
masterBoolean 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); }Handle CPU-bound tasks with Task.Factory.CreateNew
masterTheasync/awaitpattern 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 usingasync/awaitdirectly as it may not effectively utilize the thread pool for the computation. Instead, useTask.Factory.CreateNewwithTaskCreationOptions.LongRunningto start a dedicated background thread.Apply the Open/Closed Principle (OCP)
masterThe 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); } }Use default parameters instead of conditional logic
masterInstead 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.") { // ... }