Comentarios que mienten y comentarios que ayudan
En resumen: Un buen comentario dice por qué el código es como es; un comentario que cuenta de nuevo el código se queda obsoleto y empieza a mentir, por eso se reemplaza por un buen nombre o una función.
Problema
El comentario en el que todos confían
La tienda da envío gratis a partir de cierto importe. La primavera pasada el umbral era de 50 €, y el comentario sobre DeliveryFee dice justo eso. Luego marketing lo subió a 100 €. El número del código se cambió. El comentario no.
Este otoño un compañero escribe el banner del carrito. Lee el comentario, no el código, y pone 50:
// Envío gratis para pedidos desde 50 €.static decimal DeliveryFee(decimal total){ if (total >= 100) { return 0; } return 4.90m;}
static string Banner(decimal total){ if (total >= 50) { return "¡El envío es gratis!"; } return $"Faltan {50 - total:F2} € para el envío gratis";}decimal total = 60;Console.WriteLine($"Carrito: {total:F2} €");Console.WriteLine(Banner(total));Console.WriteLine($"Envío: {DeliveryFee(total):F2} €");Carrito: 60.00 €¡El envío es gratis!Envío: 4.90 €Un cliente con un carrito de 60 € lee «¡El envío es gratis!» y luego paga 4,90 € de envío. Soporte mira DeliveryFee y no encuentra ningún error: la función hace exactamente lo que dice su código. El error está en un texto que nadie ejecuta.
Míralo
El código siguió, el comentario se quedó
Esto es lo que pasó, paso a paso:
// Envío gratis para pedidos desde 50 €.static decimal DeliveryFee(decimal total){ if (total >= 100) { return 0; } return 4.90m;}
static string Banner(decimal total){ if (total >= 50) { return "¡El envío es gratis!"; } return $"Faltan {50 - total:F2} € para el envío gratis";}
decimal total = 60;Console.WriteLine($"Carrito: {total:F2} €");Console.WriteLine(Banner(total));Console.WriteLine($"Envío: {DeliveryFee(total):F2} €");Paso a paso, en palabras
- La primera versión: el comentario (línea 1) y la comprobación (línea 4) dicen lo mismo: 50 €.
- El umbral pasa a 100 €. Alguien cambia el número de la línea 4 y no toca el comentario. El compilador revisa el código. Los comentarios no los revisa nadie.
- Un compañero escribe el banner. Lee el comentario, no el código, y en la línea 13 pone 50.
- Un carrito de 60 €. El banner pregunta su propio umbral: 60 ≥ 50, así que «¡El envío es gratis!».
- La tarifa pregunta el umbral real: 60 < 100, así que el envío cuesta 4,90 €.
- El cliente lee «gratis» y paga 4,90 €. La mentira está en la línea 1. El error que provocó está en la línea 13.
El compilador revisa cada línea de código. Las pruebas revisan lo que hace el código. Nadie revisa el comentario: es texto junto al código, y cada cambio en el código puede dejarlo atrás. Esto se llama un comentario obsoleto (stale comment). Es peor que no tener comentario, porque suena muy seguro de sí mismo.
La idea
Deja que lo diga el código
El umbral es una regla de la tienda, y la regla vive en dos lugares: en el código de la tarifa y en la cabeza de cada persona que leyó el comentario. Ponle un nombre, y las dos funciones preguntan por el nombre:
const decimal FreeDeliveryFrom = 100;
static decimal DeliveryFee(decimal total){ if (total >= FreeDeliveryFrom) { return 0; } return 4.90m;}
static string Banner(decimal total){ if (total >= FreeDeliveryFrom) { return "¡El envío es gratis!"; } return $"Faltan {FreeDeliveryFrom - total:F2} € para el envío gratis";}decimal total = 60;Console.WriteLine($"Carrito: {total:F2} €");Console.WriteLine(Banner(total));Console.WriteLine($"Envío: {DeliveryFee(total):F2} €");Carrito: 60.00 €Faltan 40.00 € para el envío gratisEnvío: 4.90 €El comentario ya no hace falta: el nombre dice lo mismo. La diferencia es que un nombre no puede quedarse atrás. Si el umbral pasa a 150 €, cambias una línea y Banner recibe el nuevo umbral. Si alguien borra la constante, el programa deja de compilar.
Esta es la regla de la lección sobre nombres (Nombres en el código): antes de escribir un comentario, comprueba si un nombre puede decir lo mismo.
Pruébalo
Predice la salida
Un hotel vende noches en línea. ¿Qué imprime el programa?
// El precio con 20% de IVA.static decimal WithVat(decimal net){ return net * 1.09m;}
Console.WriteLine($"{WithVat(100):F2} €");El comentario dice 20%, y el código multiplica por 1.09. Las noches de hotel en Bulgaria llevan un IVA del 9%, y alguien cambió el número sin tocar el comentario. El programa escucha al código.
Míralo
Comentarios que repiten el código
Cada comentario aquí dice lo mismo que la línea de abajo:
// El precio del producto.decimal price = 25.00m;// La cantidad.int quantity = 3;// Multiplicamos el precio por la cantidad.decimal total = price * quantity;// Imprimimos el total.Console.WriteLine($"Total: {total:F2} €");Total: 75.00 €Lo lees todo dos veces y no aprendes nada nuevo. Peor aún, aprendes a saltarte los comentarios, y te saltas también el que importa. Bórralos. El programa es el mismo, y la salida también.
Míralo
Un comentario en lugar de un nombre, un comentario en lugar de una función
Aquí los comentarios explican cosas que el código esconde:
int n = 7; // número de pedidos del clientedecimal s = 420; // gasto del cliente en euros// El cliente es VIP: al menos 5 pedidos o al menos 500 € gastados.if (n >= 5 || s >= 500){ Console.WriteLine("Descuento VIP: 10%");}Descuento VIP: 10%El comentario junto a n está ahí porque el nombre no dice nada. Dale el nombre del comentario: orders. El comentario sobre el if describe una regla. Una regla con nombre es una función:
static bool IsVip(int orders, decimal spent){ return orders >= 5 || spent >= 500;}int orders = 7;decimal spent = 420;if (IsVip(orders, spent)){ Console.WriteLine("Descuento VIP: 10%");}Descuento VIP: 10%La salida es la misma, y no hay comentarios. La línea del if se lee como una frase. Si la regla pasa a ser «al menos 10 pedidos», cambias el cuerpo de IsVip y cada lugar que la llama recibe la nueva regla. Cómo extraer una función así está en la lección Funciones.
Míralo
Código comentado y un TODO sin responsable
static void Confirm(string orderId){ SendEmail(orderId); // SendSms(orderId); // if (smsEnabled) // { // SendSms(orderId); // } // TODO: arreglar}Correo del pedido #1063¿Se envía un SMS? Nadie sabe si se desactivó a propósito, por un tiempo o por error, y nadie se atreve a borrarlo. Así vive el código comentado durante años. Bórralo: el código viejo está en el historial de git y vuelve con un solo comando.
Un TODO sin nombre y sin fecha es un deseo que nadie posee. Un buen TODO dice quién, qué ticket y cuándo:
static void Confirm(string orderId){ SendEmail(orderId); // TODO(maria, SHOP-412): añadir SMS cuando el nuevo proveedor pase las pruebas.}Correo del pedido #1063Mejor aún, la tarea vive en el ticket, y en el código solo queda el enlace.
La idea
Qué dice un buen comentario
El código dice qué hace. Un buen comentario dice algo que el código no puede decir:
// El mensajero se queda con el 30% de la tarifa, redondeado hacia abajo al céntimo.// Así lo dice el contrato con él (cláusula 4.2), por eso aquí no hay Math.Round.static long CourierShareCents(long feeCents){ return feeCents * 30 / 100;}long fee = 495; // SHOP-388: el mensajero envía la tarifa en céntimos, no en euros.Console.WriteLine($"Mensajero: {CourierShareCents(fee) / 100m:F2} €");Mensajero: 1.48 €Aquí hay tres cosas así:
- por qué: redondear hacia abajo parece un error hasta que lees que el contrato lo dice. Sin el comentario, alguien lo «arreglará»;
- dónde saber más: el ticket
SHOP-388cuenta la historia que no cabe en el código; - la unidad que el tipo no dice: céntimos, no euros. En la lección sobre code smells verás cómo esto pasa al propio tipo.
Una más: una advertencia como «Lento: cada llamada es una petición al banco, unos 300 ms. No la llames en un bucle.» Un comentario así le ahorra un día de búsqueda a la siguiente persona.
Un buen comentario también puede quedarse obsoleto. Pero rara vez lo hace, porque no repite el código: el contrato no cambia cada vez que cambias la función.
Más precisamente, para curiosos
git blame muestra cuándo se escribió cada línea. Si el código bajo un comentario es más nuevo que el propio comentario, ten cuidado: alguien tocó el código y quizá no llegó al comentario.
Míralo
Documentación para quien llama
Quien llama a DeliveryFee desde otro módulo no quiere leer su cuerpo. Quiere saber qué pasar y qué recibe. Para esto cada lenguaje tiene un comentario de documentación, que el editor muestra cuando pasas el ratón sobre el nombre:
const decimal FreeDeliveryFrom = 100;
/// <summary>La tarifa de envío de un pedido.</summary>/// <param name="total">El total de los productos en euros, sin el envío.</param>/// <returns>0 desde FreeDeliveryFrom, si no la tarifa del mensajero.</returns>static decimal DeliveryFee(decimal total){ if (total >= FreeDeliveryFrom) { return 0; } return 4.90m;}Console.WriteLine($"Envío con 60 €: {DeliveryFee(60):F2} €");Console.WriteLine($"Envío con 120 €: {DeliveryFee(120):F2} €");Envío con 60 €: 4.90 €Envío con 120 €: 0.00 €/// sobre el método, con <summary>, <param> para cada parámetro y <returns> para el resultado. Visual Studio y Rider los muestran en cada llamada, y el compilador puede reunirlos en un archivo XML junto al programa.La documentación fija el contrato: qué significa el parámetro y en qué unidades, qué devuelve y cuándo hay un error. No dice cómo funciona por dentro. Los números del código no se repiten en ella: en lugar de «desde 100 €» nombra la constante, así que no puede quedarse atrás.
Más precisamente, para curiosos
<GenerateDocumentationFile>true</GenerateDocumentationFile> en el proyecto, el compilador avisa de un método público sin documentación (CS1591) y de un <param> que nombra un parámetro que no existe (CS1572). Así, al menos los nombres en la documentación no pueden quedar obsoletos en silencio.Completa la parte que falta:
const decimal FreeDeliveryFrom = 100;
/// <summary>La tarifa de envío de un pedido.</summary>
/// < name="total">El total de los productos en euros, sin el envío.</param>
/// <returns>0 desde FreeDeliveryFrom, si no la tarifa del mensajero.</returns>
static decimal DeliveryFee(decimal total)
{
if (total >= FreeDeliveryFrom)
{
return 0;
}
return 4.90m;
}<param name="total"> describe el parámetro total.Pruébalo
Encuentra el error
La tienda envía un correo «¿Qué tal tu pedido?» un día después de la entrega. El último pedido del cliente es del 24/10, y el correo pregunta por el del 12/10:
var orders = new List<string> { "2026-10-19 #1057", "2026-10-24 #1063", "2026-10-12 #1041" };Console.WriteLine(Reminder(orders));¿Qué tal tu pedido 2026-10-12 #1041?¿Qué línea toma el pedido equivocado?
Elige la línea con el error.
Esta línea toma el primer pedido, porque el comentario dice «del más nuevo al más antiguo». Pero el código ordena por fecha de forma ascendente: el primero es el más antiguo. Alguien cambió el orden para el informe mensual y dejó el comentario. La corrección tiene dos pasos: tomar el último pedido y reemplazar el comentario por un nombre que diga el orden:
static List<string> OldestFirst(List<string> orders){ var sorted = new List<string>(orders); sorted.Sort(); return sorted;}
static string Reminder(List<string> orders){ string latest = OldestFirst(orders)[^1]; return "¿Qué tal tu pedido " + latest + "?";}var orders = new List<string> { "2026-10-19 #1057", "2026-10-24 #1063", "2026-10-12 #1041" };Console.WriteLine(Reminder(orders));¿Qué tal tu pedido 2026-10-24 #1063?Ejercicio
Limpia los comentarios
Resuélvelo en tu propio editor.
Descuento para clientes fieles. La función tiene un comentario que miente, un comentario que repite el código, una línea comentada y un TODO sin responsable:
// Descuento para clientes fieles: 5%.static decimal LoyaltyTotal(decimal total, int orders){ // si el cliente tiene más de 10 pedidos if (orders >= 20) { // total = total * 0.95m; total = total * 0.90m; // reducimos total } // TODO: redondeo return total;}Console.WriteLine($"Suma: {LoyaltyTotal(200, 25):F2} €");Console.WriteLine($"Suma: {LoyaltyTotal(200, 12):F2} €");Suma: 180.00 €Suma: 200.00 €Límpiala: quita el ruido y el código muerto, reemplaza los números por nombres, y o haz el TODO o dale un responsable y un ticket. La salida debe seguir igual.
Más tareas
- Documenta el orden. Escribe un comentario de documentación para
OldestFirstde la tarea de los pedidos: qué recibe, qué devuelve y en qué orden. - El banner a 99,99 €. Ejecuta el banner de la tarjeta «Deja que lo diga el código» con un carrito de 99,99 € y de 100 €. Comprueba que las dos funciones coinciden justo en el umbral.
- Tu proyecto. Abre tu último proyecto y busca tres comentarios. Para cada uno decide: ¿repite el código, dice por qué o miente?
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: static: lo que comparten todos los objetos · Listas · Nombres en el código · Funciones: una tarea, pocos parámetros, sin efectos secundarios ocultos
- Continúa en: Code smells: método largo, clase grande, duplicación, primitivos en vez de tipos, cadenas largas de if · Ejercicio: una clase desordenada, limpiada paso a paso
- Ejercicios sobre el tema: Código limpio y refactorización