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
- 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.
- 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.
- 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.
- 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 voidfuera de un manejador de eventos: la excepción no se puede capturar y tumba el proceso..Resulto.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. CancellationTokenque 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.