Saltar al contenido
jesusprodriguez.com

bug-hunt

Cazar bugs, no oler mal el código

Búsqueda dirigida de bugs de correctitud en C# y TypeScript: concurrencia, nulos, fechas, transacciones a medias y errores tragados.

plugin:
.NET
stack:
.NET Core
tarea:
Revisar
versión:
v1.0.0
actualizada:
tamaño:
4.4 KB
lectura:
3 min
licencia:
CC-BY-4.0

Cuándo se activa

Al buscar bugs, revisar un módulo sospechoso o entender un fallo intermitente.

description: Búsqueda dirigida de bugs de correctitud en código C#/.NET y TypeScript - concurrencia, nulos, fechas, transacciones a medias y errores tragados. Úsala cuando pidan encontrar bugs, revisar un módulo sospechoso o entender por qué algo falla de forma intermitente.

  • Seguir el dato
  • Async y concurrencia
  • Fechas y decimales
  • Reporte con escenario

Cómo se le pide

> Busca bugs de correctitud en el módulo de facturación: hay un importe que sale mal de vez en cuando.

Escríbeselo tal cual al agente: la skill se carga sola por la descripción, no hay que nombrarla.

Cómo se instala

/plugin marketplace add https://jesusprodriguez.com/skills/marketplace.json
/plugin install dotnet@jprodriguez-toolkit

La vía nativa, y la única que se actualiza sola: el marketplace se añade una vez y `/plugin marketplace update` trae las versiones nuevas. Las skills quedan con espacio de nombres propio (`azure-devops:azure-pr-review`).

El fichero, entero

Esto es exactamente lo que descargas: sin resúmenes ni recortes.

Cazar bugs, no oler mal el código

Esto no es una revisión de estilo. El único objetivo es encontrar código que produce un resultado incorrecto o que se rompe en producción. Un nombre feo no es un bug.

Método

  1. Empieza por el síntoma si lo hay. Un stack trace, un log o un dato incorrecto acotan la búsqueda mil veces mejor que leer el módulo entero.
  2. Sin síntoma, empieza por los bordes: entrada del usuario, llamadas a sistemas externos, escritura en base de datos, código concurrente y cualquier cosa con fechas. Ahí vive casi todo.
  3. Sigue el dato, no el fichero. Toma un valor de entrada y recórrelo hasta donde se persiste o se muestra. Los bugs aparecen en las conversiones y en los límites entre capas.
  4. Cada hallazgo necesita un escenario concreto: entrada + estado → salida incorrecta. Si no sabes escribir el escenario, no has encontrado un bug: has encontrado una sospecha, y así hay que presentarla.

Patrones que producen bugs de verdad

Nulos y colecciones vacías First(), Single(), [0], .Value sobre un Nullable, desestructuración de una respuesta que puede venir vacía. El caso “no hay resultados” casi nunca se prueba.

Async mal hecho (C#)

  • async void fuera de un manejador de eventos: la excepción no se puede capturar y tumba el proceso.
  • .Result o .Wait() sobre una tarea → deadlock en cuanto haya contexto de sincronización.
  • Tareas lanzadas sin await (fire and forget): si el proceso termina antes, el trabajo se pierde en silencio.
  • CancellationToken que se recibe y no se pasa hacia abajo.

Concurrencia Estado estático mutable, singletons con campos de instancia, DbContext compartido entre hilos (no es thread-safe), lectura-modificación-escritura sin concurrencia optimista: dos peticiones simultáneas y la última pisa a la primera sin que nadie se entere.

Fechas y zonas horarias DateTime.Now en lugar de UtcNow, guardar sin Kind, comparar un instante UTC con uno local, aritmética de días sobre cambios de hora, y el clásico “funciona salvo el último día del mes”.

Números double para dinero (usa decimal), división entera donde se esperaba decimal, redondeo aplicado dos veces, desbordamiento en un int que acumula.

Transacciones a medias Varias escrituras sin transacción, o una llamada a un sistema externo dentro de la transacción: si el commit falla después de cobrar, el cliente pagó y el pedido no existe. Pregunta siempre: ¿qué queda si esto falla en el paso 3?

Errores tragados catch { }, catch (Exception) { return null; }, catch que loguea y sigue como si nada. Convierten un fallo ruidoso en datos corruptos silenciosos, que es infinitamente peor.

Cadenas y cultura ToLower() sin cultura invariante, comparaciones dependientes de la cultura en lógica de negocio, Parse de decimales con coma o punto según el servidor.

TypeScript == con null/undefined mezclado con 0 y '', as que miente sobre el tipo real de una respuesta HTTP, Promise sin await dentro de un forEach, mutación de un objeto que también está en el estado de la interfaz.

Cómo se reporta

Uno por hallazgo, ordenados por impacto:

ALTO · src/Application/Orders/OrderService.cs:88
Dos confirmaciones simultáneas del mismo pedido lo cobran dos veces.
Se lee Status, se comprueba y se escribe sin control de concurrencia; entre
la lectura y el guardado cabe otra petición.
Escenario: dos clics en "Pagar" con 200 ms de diferencia.
Arreglo: token de concurrencia (rowversion) en Order, o filtro en el UPDATE
por el estado esperado y comprobar filas afectadas.
  • Impacto primero: alto (dato incorrecto o pérdida de dinero), medio (fallo recuperable), bajo (caso raro).
  • Un arreglo propuesto por hallazgo, no tres alternativas.
  • Separa lo confirmado de lo sospechado. Mezclar diez sospechas con dos bugs reales hace que no se arregle ninguno.
  • Si el bug es reproducible, el arreglo empieza por un test que falle antes de tocar nada.