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 delivery_fee 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 €.def delivery_fee(total): if total >= 100: return 0 return 4.90
def banner(total): if total >= 50: return "¡El envío es gratis!" return f"Faltan {50 - total:.2f} € para el envío gratis"total = 60print(f"Carrito: {total:.2f} €")print(banner(total))print(f"Envío: {delivery_fee(total):.2f} €")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 delivery_fee 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 €.def delivery_fee(total): if total >= 100: return 0 return 4.90
def banner(total): if total >= 50: return "¡El envío es gratis!" return f"Faltan {50 - total:.2f} € para el envío gratis"
total = 60print(f"Carrito: {total:.2f} €")print(banner(total))print(f"Envío: {delivery_fee(total):.2f} €")Paso a paso, en palabras
- La primera versión: el comentario (línea 1) y la comprobación (línea 3) dicen lo mismo: 50 €.
- El umbral pasa a 100 €. Alguien cambia el número de la línea 3 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 9 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 9.
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:
FREE_DELIVERY_FROM = 100
def delivery_fee(total): if total >= FREE_DELIVERY_FROM: return 0 return 4.90
def banner(total): if total >= FREE_DELIVERY_FROM: return "¡El envío es gratis!" return f"Faltan {FREE_DELIVERY_FROM - total:.2f} € para el envío gratis"total = 60print(f"Carrito: {total:.2f} €")print(banner(total))print(f"Envío: {delivery_fee(total):.2f} €")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 (en Python se detiene en la primera llamada, con un error que dice que el nombre no existe).
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.def with_vat(net): return net * 1.09
print(f"{with_vat(100):.2f} €")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.price = 25.00# La cantidad.quantity = 3# Multiplicamos el precio por la cantidad.total = price * quantity# Imprimimos el total.print(f"Total: {total:.2f} €")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:
n = 7 # número de pedidos del clientes = 420 # gasto del cliente en euros# El cliente es VIP: al menos 5 pedidos o al menos 500 € gastados.if n >= 5 or s >= 500: print("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:
def is_vip(orders, spent): return orders >= 5 or spent >= 500orders = 7spent = 420if is_vip(orders, spent): print("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 is_vip 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
def confirm(order_id): send_email(order_id) # send_sms(order_id) # if sms_enabled: # send_sms(order_id) # TODO: arreglarCorreo 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:
def confirm(order_id): send_email(order_id) # 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 round().def courier_share_cents(fee_cents): return fee_cents * 30 // 100fee = 495 # SHOP-388: el mensajero envía la tarifa en céntimos, no en euros.print(f"Mensajero: {courier_share_cents(fee) / 100:.2f} €")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 delivery_fee 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:
FREE_DELIVERY_FROM = 100
def delivery_fee(total: float) -> float: """La tarifa de envío de un pedido.
total es el total de los productos en euros, sin el envío. Devuelve 0 desde FREE_DELIVERY_FROM, si no la tarifa del mensajero. """ if total >= FREE_DELIVERY_FROM: return 0 return 4.90print(delivery_fee.__doc__.splitlines()[0])print(f"Envío con 60 €: {delivery_fee(60):.2f} €")print(f"Envío con 120 €: {delivery_fee(120):.2f} €")La tarifa de envío de un pedido.Envío con 60 €: 4.90 €Envío con 120 €: 0.00 €delivery_fee.__doc__, y help(delivery_fee) la muestra en la consola. total: float y -> float son anotaciones de tipo (type hints): Python no las comprueba al ejecutar, pero el editor y herramientas como mypy sí.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
"60" a delivery_fee. PEP 257 describe cómo escribir un docstring: la primera línea es una frase, luego una línea vacía y los detalles.Completa la parte que falta:
FREE_DELIVERY_FROM = 100
def delivery_fee(total: ) -> float:
"""La tarifa de envío de un pedido.
total es el total de los productos en euros, sin el envío. Devuelve 0
desde FREE_DELIVERY_FROM, si no la tarifa del mensajero.
"""
if total >= FREE_DELIVERY_FROM:
return 0
return 4.90total: float.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:
orders = ["2026-10-19 #1057", "2026-10-24 #1063", "2026-10-12 #1041"]print(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:
def oldest_first(orders): return sorted(orders)
def reminder(orders): latest = oldest_first(orders)[-1] return f"¿Qué tal tu pedido {latest}?"orders = ["2026-10-19 #1057", "2026-10-24 #1063", "2026-10-12 #1041"]print(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%.def loyalty_total(total, orders): # si el cliente tiene más de 10 pedidos if orders >= 20: # total = total * 0.95 total = total * 0.90 # reducimos total # TODO: redondeo return totalprint(f"Suma: {loyalty_total(200, 25):.2f} €")print(f"Suma: {loyalty_total(200, 12):.2f} €")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
oldest_firstde 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: Lo que comparten todos los objetos: atributos de clase · Listas: append, remove y sort · 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