El patrón más peligroso que veo en código de producción no es un bug: es un catch vacío. Un error silenciado no desaparece, solo reaparece más tarde y más lejos del sitio donde ocurrió.

Falla rápido y ruidoso

Si algo no debería pasar, no lo escondas. Un error visible en desarrollo es un bug que arreglas hoy; un error tragado es un ticket de soporte la semana que viene.

// ❌ el error se evapora
try {
  await saveUser(user);
} catch {}

// ✅ al menos sabes qué pasó
try {
  await saveUser(user);
} catch (err) {
  logger.error('saveUser failed', { userId: user.id, err });
  throw err;
}

Modela el error, no lo improvises

En el frontend, lanzar strings o any te deja sin información. Prefiero tipos de error explícitos que digan qué falló y cómo recuperarse:

type Result<T> =
  | { ok: true; value: T }
  | { ok: false; error: 'network' | 'unauthorized' | 'not_found' };

Quien consume la función está obligado por el tipo a manejar cada caso. Esta idea me llevó a publicar http-sentinel, justo para estructurar errores HTTP en el cliente.

La regla

Un error o se maneja (con una acción concreta) o se propaga (hacia arriba con contexto). Lo que nunca se hace es ignorarlo. El silencio en el manejo de errores siempre se paga con intereses.