Claude-skill-registry clean-code-dotnet
Clean Code principles adapted for C#/.NET including naming, variables, functions, SOLID, error handling, and async patterns. Use when: (1) reviewing C# code, (2) refactoring for clarity, (3) writing new code, (4) code review feedback.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/clean-code-dotnet" ~/.claude/skills/majiayu000-claude-skill-registry-clean-code-dotnet && rm -rf "$T"
manifest:
skills/data/clean-code-dotnet/SKILL.mdsource content
Clean Code .NET
Clean Code principles from Robert C. Martin, adapted for C#/.NET. Use as checklist during code reviews and refactoring.
Naming
Use Meaningful Names
// ❌ Bad int d; var dataFromDb = db.GetFromService().ToList(); // ✅ Good int daySinceModification; var employees = _employeeService.GetEmployees().ToList();
Avoid Hungarian Notation
// ❌ Bad int iCounter; string strFullName; public bool IsShopOpen(string pDay, int pAmount) { } // ✅ Good int counter; string fullName; public bool IsShopOpen(string day, int amount) { }
Use Pronounceable Names
// ❌ Bad public class Employee { public DateTime sWorkDate { get; set; } public DateTime modTime { get; set; } } // ✅ Good public class Employee { public DateTime StartWorkingDate { get; set; } public DateTime ModificationTime { get; set; } }
Use Domain Names
// ✅ Good - Use patterns developers know var singletonObject = SingleObject.GetInstance(); var factory = new PatientFactory(); var repository = new PatientRepository();
Variables
Return Early, Avoid Deep Nesting
// ❌ Bad - Deep nesting public bool IsShopOpen(string day) { if (!string.IsNullOrEmpty(day)) { day = day.ToLower(); if (day == "friday") { return true; } else if (day == "saturday") { return true; } // ... more nesting } return false; } // ✅ Good - Guard clauses + early return public bool IsShopOpen(string day) { if (string.IsNullOrEmpty(day)) return false; var openingDays = new[] { "friday", "saturday", "sunday" }; return openingDays.Contains(day.ToLower()); }
Avoid Magic Strings
// ❌ Bad if (userRole == "Admin") { } // ✅ Good const string AdminRole = "Admin"; if (userRole == AdminRole) { } // ✅ Better - Use enum public enum UserRole { Admin, User, Guest } if (userRole == UserRole.Admin) { }
Don't Add Unneeded Context
// ❌ Bad - Redundant prefix public class Car { public string CarMake { get; set; } public string CarModel { get; set; } public string CarColor { get; set; } } // ✅ Good public class Car { public string Make { get; set; } public string Model { get; set; } public string Color { get; set; } }
Use Default Arguments
// ❌ Bad public void CreateMicrobrewery(string name = null) { var breweryName = !string.IsNullOrEmpty(name) ? name : "Hipster Brew Co."; } // ✅ Good public void CreateMicrobrewery(string breweryName = "Hipster Brew Co.") { // breweryName is always valid }
Functions
Functions Should Do One Thing
// ❌ Bad - Multiple responsibilities public void SendEmailToListOfClients(string[] clients) { foreach (var client in clients) { var clientRecord = db.Find(client); if (clientRecord.IsActive()) { Email(client); } } } // ✅ Good - Single responsibility public void SendEmailToActiveClients(string[] clients) { var activeClients = GetActiveClients(clients); activeClients.ForEach(client => Email(client)); } public List<Client> GetActiveClients(string[] clients) { return db.Find(clients).Where(c => c.IsActive).ToList(); }
Avoid Side Effects
// ❌ Bad - Modifies global state var name = "Ryan McDermott"; public void SplitAndEnrichFullName() { var temp = name.Split(" "); name = $"First: {temp[0]}, Last: {temp[1]}"; // Side effect! } // ✅ Good - Pure function public string SplitAndEnrichFullName(string name) { var temp = name.Split(" "); return $"First: {temp[0]}, Last: {temp[1]}"; }
Avoid Negative Conditionals
// ❌ Bad public bool IsDOMNodeNotPresent(string node) { } if (!IsDOMNodeNotPresent(node)) { } // Double negative! // ✅ Good public bool IsDOMNodePresent(string node) { } if (IsDOMNodePresent(node)) { }
Avoid Flag Parameters
// ❌ Bad - Flag indicates multiple responsibilities public void CreateFile(string name, bool temp = false) { if (temp) Touch("./temp/" + name); else Touch(name); } // ✅ Good - Separate methods public void CreateFile(string name) => Touch(name); public void CreateTempFile(string name) => Touch("./temp/" + name);
Limit Function Arguments (2 or fewer)
// ❌ Bad public void CreateMenu(string title, string body, string buttonText, bool cancellable) { } // ✅ Good - Use object public class MenuConfig { public string Title { get; set; } public string Body { get; set; } public string ButtonText { get; set; } public bool Cancellable { get; set; } } public void CreateMenu(MenuConfig config) { }
Encapsulate Conditionals
// ❌ Bad if (article.state == "published") { } // ✅ Good if (article.IsPublished()) { }
Remove Dead Code
// ❌ Bad public void OldRequestModule(string url) { } // Unused! public void NewRequestModule(string url) { } var request = NewRequestModule(requestUrl); // ✅ Good - Delete unused code public void RequestModule(string url) { } var request = RequestModule(requestUrl);
SOLID Principles
Single Responsibility (SRP)
// ❌ Bad - Two responsibilities class UserSettings { public void ChangeSettings(Settings settings) { if (VerifyCredentials()) { /* ... */ } } private bool VerifyCredentials() { /* ... */ } // Auth responsibility } // ✅ Good - Separated class UserAuth { public bool VerifyCredentials() { /* ... */ } } class UserSettings { private readonly UserAuth _auth; public void ChangeSettings(Settings settings) { if (_auth.VerifyCredentials()) { /* ... */ } } }
Open/Closed (OCP)
// ❌ Bad - Must modify to extend class HttpRequester { public bool Fetch(string url) { if (adapterName == "ajaxAdapter") return MakeAjaxCall(url); else if (adapterName == "httpNodeAdapter") return MakeHttpCall(url); // Must add more else-if for new adapters! } } // ✅ Good - Open for extension, closed for modification interface IAdapter { bool Request(string url); } class AjaxAdapter : IAdapter { public bool Request(string url) { /* ... */ } } class HttpRequester { private readonly IAdapter _adapter; public bool Fetch(string url) => _adapter.Request(url); }
Liskov Substitution (LSP)
// ❌ Bad - Square breaks Rectangle behavior class Square : Rectangle { public override void SetWidth(double width) { Width = Height = width; } } // ✅ Good - Use abstraction abstract class Shape { public abstract double GetArea(); } class Rectangle : Shape { /* ... */ } class Square : Shape { /* ... */ }
Interface Segregation (ISP)
// ❌ Bad - Robot can't eat but must implement interface IEmployee { void Work(); void Eat(); } class Robot : IEmployee { public void Work() { /* ... */ } public void Eat() { /* Robot can't eat! */ } } // ✅ Good - Segregated interfaces interface IWorkable { void Work(); } interface IFeedable { void Eat(); } class Human : IWorkable, IFeedable { /* ... */ } class Robot : IWorkable { /* ... */ }
Dependency Inversion (DIP)
// ❌ Bad - Depends on concrete types class Manager { private readonly Robot _robot; private readonly Human _human; } // ✅ Good - Depends on abstractions class Manager { private readonly IEnumerable<IEmployee> _employees; public Manager(IEnumerable<IEmployee> employees) { _employees = employees; } }
Constructor Dependency Smell (SRP Indicator)
Too many constructor dependencies indicate SRP violation:
// ❌ Code Smell: 15 dependencies = too many responsibilities! public class LicensePlateAppService : ApplicationService { public LicensePlateAppService( IRepository<LicensePlate, Guid> licensePlateRepository, IRepository<LicensePlateWithoutTag, Guid> licensePlateWithoutTagRepository, IRepository<ASN, Guid> asnRepository, IRepository<Project, Guid> projectRepository, IRepository<Tag, Guid> tagRepository, IRepository<SKU, Guid> skuRepository, IRepository<Customer, Guid> customerRepository, IRepository<LicensePlateHold, Guid> licensePlateHoldRepository, IRepository<LicensePlateLocation, Guid> licensePlateLocationRepository, IRepository<Location, Guid> locationRepository, IWarehouseAppService warehouseAppService, IWarehouseOwnerAppService warehouseOwnerAppService, IBlobContainer<BulkUpdateLPExcelFileContainer> fileContainer, LicensePlateService.LicensePlateServiceClient licensePlateServiceClient, CommonDependencies<LicensePlateAppService> commonDependencies) { } } // ✅ Good: Split by responsibility public class LicensePlateAppService { } // CRUD only (~5 deps) public class LicensePlateBulkService { } // Bulk imports (~4 deps) public class LicensePlateEventPublisher { } // Events (~3 deps)
Dependency Count Guidelines:
| Dependencies | Status | Action |
|---|---|---|
| 1-5 | ✅ Normal | Acceptable |
| 6-8 | ⚠️ Warning | Review for splitting opportunities |
| 9+ | ❌ Smell | Refactor required - class has too many responsibilities |
Refactoring Strategies:
- Extract Service - Move related operations to a dedicated service
- Facade Pattern - Group related dependencies behind a facade
- Domain Events - Decouple via publish/subscribe instead of direct calls
- Mediator Pattern - Use MediatR to reduce direct dependencies
Error Handling
Don't Use throw ex
throw ex// ❌ Bad - Loses stack trace catch (Exception ex) { logger.LogError(ex); throw ex; // Stack trace lost! } // ✅ Good - Preserves stack trace catch (Exception ex) { logger.LogError(ex); throw; // Rethrows with original stack } // ✅ Also Good - Wrap with inner exception catch (Exception ex) { throw new BusinessException("Operation failed", ex); }
Don't Ignore Caught Errors
// ❌ Bad - Silent swallow catch (Exception ex) { } // Never do this! // ✅ Good - Handle or propagate catch (Exception ex) { _logger.LogError(ex, "Operation failed"); throw; // Or handle appropriately }
Use Multiple Catch Blocks
// ❌ Bad - Type checking in catch catch (Exception ex) { if (ex is TaskCanceledException) { /* ... */ } else if (ex is TaskSchedulerException) { /* ... */ } } // ✅ Good - Separate catch blocks catch (TaskCanceledException ex) { // Handle cancellation } catch (TaskSchedulerException ex) { // Handle scheduler error }
Comments
Avoid Positional Markers and Regions
// ❌ Bad #region Scope Model Instantiation var model = new Model(); #endregion #region Action setup void Actions() { } #endregion // ✅ Good - Let code speak var model = new Model(); void Actions() { }
Don't Leave Commented Code
// ❌ Bad DoStuff(); // DoOtherStuff(); // DoSomeMoreStuff(); // ✅ Good - Use version control DoStuff();
Only Comment Business Logic Complexity
// ❌ Bad - Obvious comments var hash = 0; // The hash var length = data.Length; // Length of string // ✅ Good - Explains WHY, not WHAT // Using djb2 hash for good speed/collision tradeoff hash = ((hash << 5) - hash) + character;
Quick Reference Checklist
Code Review Checklist
- Naming: Meaningful, pronounceable, no Hungarian
- Functions: Single responsibility, <3 args, no flags
- Variables: No magic strings, early returns, no nesting >2
- SOLID: Interfaces over concrete, small focused classes
- Dependencies: Constructor has <8 dependencies (SRP indicator)
- Error Handling: No
, no silent catch, specific exception typesthrow ex - Comments: No regions, no dead code, explains WHY
References
- references/solid-principles.md: Full SOLID examples
- references/async-patterns.md: Async/await guidelines
- references/editorconfig-template.md: .editorconfig template
Source: clean-code-dotnet