一键导入
clean-code
Clean code principles including DRY, KISS, and YAGNI for .NET. Use when writing or reviewing code to ensure maintainability and simplicity.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Clean code principles including DRY, KISS, and YAGNI for .NET. Use when writing or reviewing code to ensure maintainability and simplicity.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Interactively elicit, capture, and maintain software/system requirements as a living, traceable repository. Use whenever the user wants to gather, write, refine, or organize requirements — including producing or updating an SRS (software/system requirements specification), writing ADRs / architecture decision records, building a requirements traceability graph (DAG), or keeping a decision ledger. Trigger proactively when the working folder already holds requirements artifacts (a `requirements/` folder of YAML, `decisions/` ADRs, an `srs.md`, or `ledger.md`), or when the user shares documents that are clearly specs, RFCs, or feature briefs and wants them structured. Also use for phrases like "let's spec this out", "interview me about what we're building", "capture these requirements", "write a decision record", or "turn these notes into a spec". Not for project management, code review, application code, dependency manifests like requirements.txt, or hardware "system requirements" (RAM/CPU).
Use this skill whenever the user wants to create a bash script that drives an autonomous coding loop — scripts that run Claude Code on a schedule (cron, GitHub Actions, systemd timer) or on-demand to make repository improvements, execute plans, triage issues, or otherwise do agentic work with minimal human intervention. Triggers include phrases like "scout script", "autonomous script", "agent harness", "script that runs on a schedule", "script that implements a plan", ".scripts/*.sh", or any request for a bash script whose body is "run Claude Code with a prompt and commit the results". Always use this skill before writing such a script — it encodes the interview flow, the adversarial-reviewer pattern, and the locking/ledger conventions.
Task structure and atomic commit patterns for granular, verifiable work units
Conflict identification and resolution patterns for requirements, decisions, and plans
Context window management techniques for maintaining efficiency and preventing context bloat
Exploration map management for tracking discussed areas and uncharted territory during DISCUSS phase
| name | clean-code |
| description | Clean code principles including DRY, KISS, and YAGNI for .NET. Use when writing or reviewing code to ensure maintainability and simplicity. |
| allowed-tools | Read, Grep, Glob, Edit, Write |
Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.
// BAD: Duplicated validation logic
public class UserService
{
public void CreateUser(string email, string name)
{
if (string.IsNullOrWhiteSpace(email) || !email.Contains("@"))
throw new ArgumentException("Invalid email");
// ...
}
public void UpdateEmail(int userId, string newEmail)
{
if (string.IsNullOrWhiteSpace(newEmail) || !newEmail.Contains("@"))
throw new ArgumentException("Invalid email");
// ...
}
}
// GOOD: Single source of truth
public class UserService
{
private readonly IEmailValidator _emailValidator;
public void CreateUser(string email, string name)
{
_emailValidator.ValidateOrThrow(email);
// ...
}
public void UpdateEmail(int userId, string newEmail)
{
_emailValidator.ValidateOrThrow(newEmail);
// ...
}
}
public class EmailValidator : IEmailValidator
{
public bool IsValid(string email) =>
!string.IsNullOrWhiteSpace(email) && email.Contains("@");
public void ValidateOrThrow(string email)
{
if (!IsValid(email))
throw new ArgumentException("Invalid email", nameof(email));
}
}
// BAD: Magic values scattered throughout code
public decimal CalculateDiscount(decimal total)
{
if (total > 100)
return total * 0.1m; // What is 100? What is 0.1?
return 0;
}
public bool IsEligibleForFreeShipping(decimal total)
{
return total > 100; // Duplicated magic number!
}
// GOOD: Named constants
public static class OrderThresholds
{
public const decimal FreeShippingMinimum = 100m;
public const decimal StandardDiscountRate = 0.10m;
}
public decimal CalculateDiscount(decimal total)
{
if (total > OrderThresholds.FreeShippingMinimum)
return total * OrderThresholds.StandardDiscountRate;
return 0;
}
public bool IsEligibleForFreeShipping(decimal total)
{
return total > OrderThresholds.FreeShippingMinimum;
}
// BAD: Connection strings in multiple places
public class OrderRepository
{
private readonly string _conn = "Server=prod;Database=Orders;";
}
public class CustomerRepository
{
private readonly string _conn = "Server=prod;Database=Orders;";
}
// GOOD: Centralized configuration
public class DatabaseOptions
{
public string ConnectionString { get; set; } = string.Empty;
}
// In startup
services.Configure<DatabaseOptions>(configuration.GetSection("Database"));
// In repositories
public class OrderRepository
{
private readonly string _connectionString;
public OrderRepository(IOptions<DatabaseOptions> options)
{
_connectionString = options.Value.ConnectionString;
}
}
// Over-DRY: Forced abstraction hurts readability
public T ProcessEntity<T>(T entity, Func<T, bool> validator, Action<T> processor)
where T : class
{
if (!validator(entity))
throw new ValidationException();
processor(entity);
return entity;
}
// Better: Some duplication is acceptable for clarity
public Order ProcessOrder(Order order)
{
ValidateOrder(order);
SaveOrder(order);
return order;
}
public Customer ProcessCustomer(Customer customer)
{
ValidateCustomer(customer);
SaveCustomer(customer);
return customer;
}
Only extract duplication after you've seen it THREE times:
The simplest solution is usually the best solution.
// BAD: Over-engineered for simple use case
public interface IUserNameFormatter
{
string Format(User user);
}
public abstract class UserNameFormatterBase : IUserNameFormatter
{
protected abstract string GetFirstNamePart(User user);
protected abstract string GetLastNamePart(User user);
public string Format(User user) =>
$"{GetFirstNamePart(user)} {GetLastNamePart(user)}";
}
public class StandardUserNameFormatter : UserNameFormatterBase
{
protected override string GetFirstNamePart(User user) => user.FirstName;
protected override string GetLastNamePart(User user) => user.LastName;
}
public class UserNameFormatterFactory
{
public IUserNameFormatter Create(string type) => type switch
{
"standard" => new StandardUserNameFormatter(),
_ => throw new NotSupportedException()
};
}
// GOOD: Simple and direct
public static class UserExtensions
{
public static string GetFullName(this User user) =>
$"{user.FirstName} {user.LastName}";
}
// BAD: Abstraction for one implementation
public interface IOrderIdGenerator
{
string Generate();
}
public class GuidOrderIdGenerator : IOrderIdGenerator
{
public string Generate() => Guid.NewGuid().ToString();
}
// Registration
services.AddSingleton<IOrderIdGenerator, GuidOrderIdGenerator>();
// GOOD: Direct until you need flexibility
public class Order
{
public string Id { get; } = Guid.NewGuid().ToString();
}
// Add abstraction ONLY when you need a second implementation
// BAD: Hard to understand nested LINQ
var result = orders
.Where(o => o.Status == OrderStatus.Completed)
.GroupBy(o => o.CustomerId)
.Select(g => new
{
CustomerId = g.Key,
TotalSpent = g.Sum(o => o.Total),
OrderCount = g.Count(),
AverageOrder = g.Average(o => o.Total)
})
.Where(x => x.TotalSpent > 1000)
.OrderByDescending(x => x.TotalSpent)
.Take(10)
.SelectMany(x => customers.Where(c => c.Id == x.CustomerId)
.Select(c => new CustomerReport
{
Name = c.Name,
Email = c.Email,
TotalSpent = x.TotalSpent,
OrderCount = x.OrderCount
}))
.ToList();
// GOOD: Break into readable steps
var completedOrders = orders.Where(o => o.Status == OrderStatus.Completed);
var customerOrderSummaries = completedOrders
.GroupBy(o => o.CustomerId)
.Select(g => new CustomerOrderSummary(
CustomerId: g.Key,
TotalSpent: g.Sum(o => o.Total),
OrderCount: g.Count()))
.Where(s => s.TotalSpent > 1000)
.OrderByDescending(s => s.TotalSpent)
.Take(10)
.ToList();
var customerLookup = customers.ToDictionary(c => c.Id);
var reports = customerOrderSummaries
.Select(s => CreateReport(s, customerLookup[s.CustomerId]))
.ToList();
// BAD: What does 'true' mean?
SendEmail(user, "Welcome!", true, false, true);
// GOOD: Named parameters or dedicated methods
SendEmail(user, "Welcome!",
isHtml: true,
includeAttachments: false,
trackOpens: true);
// Even better: Specific methods
SendWelcomeEmail(user);
SendPasswordResetEmail(user);
// BAD: Deep inheritance hierarchy
public abstract class Entity { }
public abstract class AuditableEntity : Entity { }
public abstract class SoftDeletableAuditableEntity : AuditableEntity { }
public class Order : SoftDeletableAuditableEntity { }
// GOOD: Composition and interfaces
public interface IAuditable
{
DateTime CreatedAt { get; }
DateTime? ModifiedAt { get; }
}
public interface ISoftDeletable
{
bool IsDeleted { get; }
DateTime? DeletedAt { get; }
}
public class Order : IAuditable, ISoftDeletable
{
public DateTime CreatedAt { get; init; }
public DateTime? ModifiedAt { get; private set; }
public bool IsDeleted { get; private set; }
public DateTime? DeletedAt { get; private set; }
}
Don't implement something until it is necessary.
// BAD: Building for hypothetical future requirements
public class UserService
{
public User CreateUser(
string email,
string name,
string? middleName = null, // No requirement for this
string? suffix = null, // No requirement for this
string? preferredName = null, // No requirement for this
bool enableTwoFactor = false, // No requirement for this
string? backupEmail = null, // No requirement for this
Dictionary<string, string>? metadata = null) // "Might need it later"
{
// ...
}
}
// GOOD: Only what's needed now
public class UserService
{
public User CreateUser(string email, string name)
{
return new User
{
Id = Guid.NewGuid(),
Email = email,
Name = name,
CreatedAt = DateTime.UtcNow
};
}
}
// BAD: Configurable everything (but we only use JSON)
public interface ISerializer
{
string Serialize<T>(T obj);
T Deserialize<T>(string data);
}
public class JsonSerializer : ISerializer { }
public class XmlSerializer : ISerializer { }
public class YamlSerializer : ISerializer { }
public class BinarySerializer : ISerializer { }
public class MessagePackSerializer : ISerializer { }
public class SerializerFactory
{
public ISerializer Create(string format) => // ...
}
// GOOD: Use what you need
public static class JsonHelper
{
private static readonly JsonSerializerOptions Options = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
public static string Serialize<T>(T obj) =>
System.Text.Json.JsonSerializer.Serialize(obj, Options);
public static T? Deserialize<T>(string json) =>
System.Text.Json.JsonSerializer.Deserialize<T>(json, Options);
}
// BAD: Interface with single implementation, no plans for others
public interface IEmailSender
{
Task SendAsync(string to, string subject, string body);
}
public class SmtpEmailSender : IEmailSender
{
public Task SendAsync(string to, string subject, string body)
{
// Only implementation we'll ever have
}
}
// GOOD: Just use the class directly
public class EmailSender
{
public async Task SendAsync(string to, string subject, string body)
{
// ...
}
}
// Add interface WHEN you actually need a second implementation
// BAD: Caching before measuring
public class ProductService
{
private readonly IMemoryCache _cache;
private readonly IDistributedCache _distributedCache;
private readonly IProductRepository _repository;
public async Task<Product?> GetByIdAsync(int id)
{
var cacheKey = $"product_{id}";
// Check L1 cache
if (_cache.TryGetValue(cacheKey, out Product? product))
return product;
// Check L2 cache
var cached = await _distributedCache.GetStringAsync(cacheKey);
if (cached != null)
{
product = JsonSerializer.Deserialize<Product>(cached);
_cache.Set(cacheKey, product, TimeSpan.FromMinutes(5));
return product;
}
// Database fallback
product = await _repository.GetByIdAsync(id);
if (product != null)
{
var serialized = JsonSerializer.Serialize(product);
await _distributedCache.SetStringAsync(cacheKey, serialized);
_cache.Set(cacheKey, product, TimeSpan.FromMinutes(5));
}
return product;
}
}
// GOOD: Start simple, optimize when needed
public class ProductService
{
private readonly IProductRepository _repository;
public Task<Product?> GetByIdAsync(int id) =>
_repository.GetByIdAsync(id);
}
// Add caching AFTER you've identified it as a bottleneck
See reference.md for more examples.