Principios de Clean Code en C#
Aprende principios de Clean Code en C# para escribir código legible, mantenible y escalable con ejemplos prácticos.
Clean Code (Código Limpio) es una filosofía de programación que busca escribir código no solo funcional, sino también legible, mantenible y extensible. Basado en el principio “El código se escribe una vez, pero se lee mil veces.”, el objetivo del código limpio no es solo crear un programa sin errores, sino también garantizar que cualquiera (incluido tú mismo) pueda entenderlo fácilmente en el futuro. En este artículo, exploraremos los principios fundamentales y ejemplos prácticos para escribir código limpio en C#.
1. Nombres Significativos (Meaningful Naming)
Los nombres de variables, métodos y clases influyen directamente en la legibilidad del código. Utiliza nombres cortos pero claros y orientados al propósito, en lugar de abreviaturas confusas.
// Mal ejemplo
int x = 5;
var lst = new List<string>();
void DoIt() { ... }
// Buen ejemplo
int retryCount = 5;
List<string> customerNames = new();
void ProcessOrder() { ... }
- Variables: Deja claro qué representan (
count,totalPrice, etc.). - Métodos: Usa nombres basados en acciones (
SaveFile(),CalculateTax()). - Clases: Deben representar un concepto o una responsabilidad (
UserRepository,PaymentService).
2. Principio de Responsabilidad Única (Single Responsibility Principle)
Cada clase o método debe tener una sola responsabilidad. Si un método guarda datos en la base de datos y también envía un correo, probablemente está haciendo dos cosas diferentes.
// Mal ejemplo: múltiples responsabilidades
public class OrderService
{
public void CompleteOrder(Order order)
{
SaveToDatabase(order);
SendEmail(order);
}
}
// Buen ejemplo: responsabilidades separadas
public class OrderRepository
{
public void Save(Order order) { ... }
}
public class EmailService
{
public void SendOrderConfirmation(Order order) { ... }
}
Cada clase debe cambiar solo por una razón. Esto mejora el mantenimiento y facilita las pruebas.
3. Métodos Cortos y Enfocados
Los métodos largos aumentan la complejidad y la posibilidad de errores. Cada método debe ser una unidad pequeña y coherente que realice una sola tarea.
// Mal ejemplo
public void Process()
{
// Más de 100 líneas de lógica...
}
// Buen ejemplo
public void Process()
{
Validate();
CalculateTotal();
SaveChanges();
NotifyCustomer();
}
Los métodos cortos y descriptivos son más fáciles de probar, reutilizar y depurar.
4. Evita la Repetición de Código (DRY – Don’t Repeat Yourself)
Repetir el mismo código dificulta el mantenimiento y aumenta el riesgo de errores. Mueve la lógica común a métodos auxiliares, clases de utilidad o extensiones.
// Mal ejemplo
if (age >= 18) Console.WriteLine("Adulto");
if (age >= 18) SendMail();
// Buen ejemplo
bool IsAdult(int age) => age >= 18;
if (IsAdult(age)) { Console.WriteLine("Adulto"); SendMail(); }
Cuanta menos repetición haya, más fácil será modificar el código sin introducir errores.
5. Reduce los Comentarios — El Código Debe Explicarse por Sí Mismo
Los comentarios excesivos se vuelven obsoletos rápidamente. Idealmente, el código debería explicar por sí mismo lo que hace.
// Mal ejemplo
// Imprimir el nombre del usuario
Console.WriteLine(user.Name);
// Buen ejemplo
Console.WriteLine(user.FullName);
Los comentarios deben explicar por qué se hace algo, no qué se hace.
6. Usa Constantes en Lugar de “Números Mágicos”
Usar directamente números o cadenas (“números mágicos”) reduce la legibilidad. Defínelos como constantes o variables con nombre.
// Mal ejemplo
if (speed > 120) Console.WriteLine("¡Exceso de velocidad!");
// Buen ejemplo
const int MaxSpeed = 120;
if (speed > MaxSpeed) Console.WriteLine("¡Exceso de velocidad!");
7. Usa Guard Clauses (Cláusulas de Protección)
En lugar de usar bloques if anidados, utiliza retornos tempranos para mejorar la legibilidad.
// Mal ejemplo
if (user != null)
{
if (user.IsActive)
{
Process(user);
}
}
// Buen ejemplo
if (user is null) return;
if (!user.IsActive) return;
Process(user);
Las guard clauses simplifican el flujo del código mediante el patrón de “retorno temprano”.
8. Inyección de Dependencias (Dependency Injection)
Evita crear objetos directamente dentro del código. En su lugar, recibe las dependencias desde el exterior; esto mejora la capacidad de prueba y la flexibilidad.
// Ejemplo malo
public class NotificationService
{
private EmailSender sender = new EmailSender();
public void Notify() => sender.Send();
}
// Ejemplo bueno
public class NotificationService
{
private readonly IEmailSender _sender;
public NotificationService(IEmailSender sender) => _sender = sender;
public void Notify() => _sender.Send();
}
Este enfoque es la base de los principios de Inversión de Control (IoC) e Inyección de Dependencias (DI).
9. Manejo de Excepciones
No captures excepciones a menos que sea necesario; si lo haces, manéjalas de manera significativa.
// Ejemplo malo
try
{
Save();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
// Ejemplo bueno
try
{
Save();
}
catch (SqlException ex)
{
_logger.LogError(ex, "Se produjo un error en la base de datos.");
throw; // Relanzar al nivel superior
}
Los bloques amplios de catch (Exception) dificultan el manejo de errores y suelen ocultar problemas reales.
10. Principios SOLID
El código limpio está alineado con los principios SOLID:
- S – Single Responsibility: cada clase debe tener una sola responsabilidad.
- O – Open/Closed: abierto a la extensión, cerrado a la modificación.
- L – Liskov Substitution: las clases derivadas deben poder sustituir a sus clases base.
- I – Interface Segregation: interfaces pequeñas y específicas, sin métodos innecesarios.
- D – Dependency Inversion: depender de abstracciones, no de implementaciones concretas.
Estos principios son los pilares de un código limpio y mantenible.
11. Evita la Complejidad Innecesaria (KISS & YAGNI)
- KISS (Keep It Simple, Stupid): no agregues abstracciones ni optimizaciones innecesarias.
- YAGNI (You Ain’t Gonna Need It): no implementes características que aún no necesitas.
// Ejemplo malo
public interface IAnimal { void Bark(); void Fly(); void Swim(); }
// Ejemplo bueno
public interface IDog { void Bark(); }
public interface IFish { void Swim(); }
El código debe ser simple, enfocado y no excesivamente generalizado.
12. Formato y Consistencia
- Mantén líneas de código razonables (máx. 120 caracteres).
- Evita espacios, líneas o tabulaciones innecesarias.
- Usa Auto-format (Ctrl + K + D) o EditorConfig para mantener la consistencia.
// Ejemplo .editorconfig
[*.cs]
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true
Ejemplo: Limpieza de un Servicio de Pedidos
En el siguiente ejemplo, un bloque de código complejo y difícil de mantener se refactoriza siguiendo los principios de Clean Code.
// Ejemplo malo
public class OrderManager
{
public void Process(Order order)
{
if (order.Total <= 0) throw new Exception("Monto inválido");
Console.WriteLine("Pedido procesado: " + order.Id);
File.AppendAllText("log.txt", DateTime.Now + " - " + order.Id);
}
}
// Ejemplo bueno
public interface ILogger { void Log(string msg); }
public class FileLogger : ILogger
{
public void Log(string msg) => File.AppendAllText("log.txt", $"{DateTime.Now} - {msg}\n");
}
public class OrderValidator
{
public void Validate(Order order)
{
if (order.Total <= 0) throw new ArgumentException("El monto debe ser mayor que cero");
}
}
public class OrderService
{
private readonly ILogger _logger;
private readonly OrderValidator _validator;
public OrderService(ILogger logger, OrderValidator validator)
{
_logger = logger;
_validator = validator;
}
public void Process(Order order)
{
_validator.Validate(order);
Console.WriteLine($"Pedido procesado: {order.Id}");
_logger.Log($"Pedido {order.Id} procesado.");
}
}
Ahora cada clase tiene una responsabilidad clara: el código es más legible, mantenible y fácil de probar.
TL;DR
- El código limpio significa legibilidad y mantenibilidad.
- Nombres significativos, métodos cortos, una sola responsabilidad y sin duplicaciones son esenciales.
- Prefiere buenos nombres, guard clauses e inyección de dependencias antes que comentarios excesivos.
- Reduce la complejidad (KISS, YAGNI) y sigue los principios SOLID.
- Escribe código tanto para humanos como para máquinas.
Artículos relacionados
Arquitectura en capas y Clean Architecture en C#
Aprende arquitectura en capas y Clean Architecture en C# para crear aplicaciones mantenibles y bien estructuradas.
Escribir pruebas unitarias en C# (xUnit, NUnit, MSTest)
Aprende a escribir pruebas unitarias en C# con xUnit, NUnit y MSTest para crear aplicaciones más confiables.
Métodos y uso de parámetros en C#
Aprende a definir métodos y usar parámetros en C#, incluyendo parámetros por valor y referencia, parámetros opcionales y ejemplos.
Principios SOLID en C#
Aplicación de los principios SOLID en C# con ejemplos: código más flexible, mantenible y comprobable.
Uso de FluentValidation en C#
Aprende FluentValidation en C# para definir reglas de validación claras y mantenibles.