DRY, KISS, YAGNI
Ba nguyên tắc này là nền tảng trong việc viết code clean và maintainable.
DRY - Don’t Repeat Yourself
Concept
Mỗi piece of knowledge trong hệ thống nên có một single, unambiguous representation. Không nên duplicate code hay logic.
Examples
Bad - Code Duplication:
// Duplicate validation logic
public class UserController : ControllerBase
{
public IActionResult CreateUser(CreateUserRequest request)
{
if (string.IsNullOrEmpty(request.Name))
return BadRequest("Name is required");
if (request.Name.Length < 2)
return BadRequest("Name must be at least 2 characters");
if (request.Name.Length > 100)
return BadRequest("Name must not exceed 100 characters");
// Save user
}
public IActionResult UpdateUser(UpdateUserRequest request)
{
// Same validation repeated!
if (string.IsNullOrEmpty(request.Name))
return BadRequest("Name is required");
if (request.Name.Length < 2)
return BadRequest("Name must be at least 2 characters");
if (request.Name.Length > 100)
return BadRequest("Name must not exceed 100 characters");
// Update user
}
}
Good - Extract to Shared Method:
public static class ValidationHelper
{
public static (bool IsValid, string Error) ValidateName(string name)
{
if (string.IsNullOrEmpty(name))
return (false, "Name is required");
if (name.Length < 2)
return (false, "Name must be at least 2 characters");
if (name.Length > 100)
return (false, "Name must not exceed 100 characters");
return (true, null);
}
}
public class UserController : ControllerBase
{
public IActionResult CreateUser(CreateUserRequest request)
{
var (isValid, error) = ValidationHelper.ValidateName(request.Name);
if (!isValid)
return BadRequest(error);
// Save user
}
public IActionResult UpdateUser(UpdateUserRequest request)
{
var (isValid, error) = ValidationHelper.ValidateName(request.Name);
if (!isValid)
return BadRequest(error);
// Update user
}
}
Good - Use Inheritance or Composition:
// Base class for shared behavior
public abstract class BaseController : ControllerBase
{
protected IActionResult ValidateRequest<T>(T request)
{
// Common validation
}
}
public class UserController : BaseController
{
// Inherits validation
}
Benefits
- Maintainability: Thay đổi một nơi
- Reduced bugs: Logic tập trung
- Better testing: Test một lần
- Readability: Clearer code
KISS - Keep It Simple, Stupid
Concept
Giải pháp đơn giản nhất thường là tốt nhất. Tránh over-engineering và unnecessary complexity.
Examples
Bad - Over-complicated:
// Too many abstractions
public interface IUserProcessor
{
Task<IResult<UserProcessingResult>> ProcessAsync(UserProcessingRequest request);
}
public class UserProcessingContext
{
private readonly IUserProcessor _processor;
private readonly IUserRepository _repository;
private readonly ICacheService _cache;
public async Task<IResult<UserProcessingResult>> ProcessUser(
UserProcessingRequest request,
ProcessingContext context)
{
var strategy = GetProcessingStrategy(context);
return await strategy.ProcessAsync(request);
}
}
Good - Simple and Direct:
// Direct and simple
public class UserService
{
public async Task<User> CreateUser(CreateUserRequest request)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
var user = new User
{
Name = request.Name,
Email = request.Email
};
await _userRepository.AddAsync(user);
return user;
}
}
When to Avoid Over-Simplification
// Don't oversimplify complex domain logic
// Still maintain proper structure for complex scenarios
public class OrderProcessor
{
private readonly IEnumerable<IOrderValidationStrategy> _strategies;
public OrderProcessor(IEnumerable<IOrderValidationStrategy> strategies)
{
_strategies = strategies;
}
public ValidationResult Validate(Order order)
{
var result = new ValidationResult();
foreach (var strategy in _strategies)
{
var strategyResult = strategy.Validate(order);
result.Merge(strategyResult);
}
return result;
}
}
Benefits
- Easier to understand
- Faster to develop
- Less bugs
- Easier to maintain
YAGNI - You Aren’t Gonna Need It
Concept
Không implement tính năng cho đến khi nó thực sự cần thiết. Tránh speculative design.
Examples
Bad - Over-Engineering:
// Building infrastructure for future needs that may never come
public interface IUserRepository
{
Task<User> GetByIdAsync(Guid id);
Task<User> GetByEmailAsync(string email); // Not used yet!
Task<List<User>> GetByStatusAsync(UserStatus status); // Not used yet!
Task<User> GetByPhoneAsync(string phone); // Not used yet!
Task<List<User>> SearchAsync(string query); // Not used yet!
}
public class UserRepository : IUserRepository
{
// Implementing all these methods before they're needed
}
Good - Just Enough:
public interface IUserRepository
{
Task<User> GetByIdAsync(Guid id);
// Add more as needed
}
public class UserRepository : IUserRepository
{
public async Task<User> GetByIdAsync(Guid id)
{
return await _context.Users.FindAsync(id);
}
}
When You Actually Need Extensibility
// If you genuinely anticipate change, use interface properly
public interface IUserRepository
{
Task<User> GetByIdAsync(Guid id);
}
// Extensibility built in through interface, not through extra methods
public interface IUserRepository
{
// Base operations
}
// Future: Easy to extend with decorator pattern
public class CachedUserRepository : IUserRepository
{
private readonly IUserRepository _inner;
private readonly ICache _cache;
public async Task<User> GetByIdAsync(Guid id)
{
return await _cache.GetOrSetAsync(
$"user:{id}",
() => _inner.GetByIdAsync(id));
}
}
Don’t Confuse YAGNI with Bad Design
// YAGNI doesn't mean "write bad code"
public class PaymentService
{
// Good: Proper abstraction exists
private readonly IPaymentGateway _paymentGateway;
// Bad: Hardcoded, not testable
private readonly SqlConnection _connection;
}
Benefits
- Faster delivery: Focus on what matters now
- Less waste: No unused code
- Simpler codebase: Easier to navigate
- Adaptability: Change direction easily
How They Work Together
DRY → Avoid duplication
KISS → Keep solution simple
YAGNI → Only build what's needed
Together: Build simple, non-repeated solutions for current needs
Anti-Patterns to Avoid
| Anti-Pattern | Description |
|---|---|
| Premature Abstraction | Creating abstractions before needed |
| Speculative Generality | Building for “future” features |
| Golden Hammer | Using one solution for everything |
| Not Invented Here | Avoiding existing solutions |
Real-World Application
// Before: DRY violation
public class OrderConfirmationEmail
{
public void SendConfirmation(Order order)
{
var body = $"Order #{order.Id}\n";
body += $"Customer: {order.CustomerName}\n";
// Duplicate email formatting logic
}
}
public class ShippingNotificationEmail
{
public void SendNotification(Order order)
{
var body = $"Order #{order.Id}\n";
body += $"Customer: {order.CustomerName}\n";
// Same logic again!
}
}
// After: DRY + KISS + YAGNI
public class EmailService
{
// Shared, simple, just what's needed
public void Send(string template, object data)
{
var body = RenderTemplate(template, data);
_emailGateway.Send(body);
}
}
Summary
| Principle | Focus | Action |
|---|---|---|
| DRY | Duplication | Extract shared code |
| KISS | Complexity | Simplify solution |
| YAGNI | Future | Build only what’s needed |