Skip to content
jesusprodriguez.com

bug-hunt

Cazar bugs, no oler mal el código

A targeted hunt for correctness bugs in C# and TypeScript: concurrency, nulls, dates, half-finished transactions and swallowed errors.

plugin:
.NET
stack:
.NET Core
task:
Review
version:
v1.0.0
updated:
size:
4.4 KB
read:
3 min
license:
CC-BY-4.0

When it fires

When hunting bugs, reviewing a suspicious module or making sense of an intermittent failure.

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.

  • Follow the data
  • Async and concurrency
  • Dates and decimals
  • Reports with a scenario

How you ask for it

> Hunt for correctness bugs in the billing module: an amount comes out wrong every now and then.

Say this to the agent as it is: the skill loads itself from its description, you do not have to name it.

How to install one

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

The native route, and the only one that updates itself: add the marketplace once and `/plugin marketplace update` brings in new versions. Skills get their own namespace (`azure-devops:azure-pr-review`).

The whole file

This is exactly what you download: no summaries, nothing trimmed.

Heads-up: the skill file itself is written in Spanish. Agents read it fine and answer in your language, but the prose below is not translated.

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.