O padrão mais perigoso que vejo em código de produção não é um bug: é um catch vazio. Um erro silenciado não desaparece, apenas reaparece mais tarde e mais longe de onde aconteceu.
Falhe rápido e alto
Se algo não deveria acontecer, não esconda. Um erro visível em desenvolvimento é um bug que você corrige hoje; um erro engolido é um ticket de suporte na semana que vem.
// ❌ o erro evapora
try {
await saveUser(user);
} catch {}
// ✅ pelo menos você sabe o que aconteceu
try {
await saveUser(user);
} catch (err) {
logger.error('saveUser failed', { userId: user.id, err });
throw err;
}
Modele o erro, não improvise
No frontend, lançar strings ou any te deixa cego. Prefiro tipos de erro explícitos que digam o que falhou e como se recuperar:
type Result<T> =
| { ok: true; value: T }
| { ok: false; error: 'network' | 'unauthorized' | 'not_found' };
Quem consome a função é obrigado pelo tipo a tratar cada caso. Essa ideia me levou a publicar o http-sentinel, justamente para estruturar erros HTTP no cliente.
A regra
Um erro ou é tratado (com uma ação concreta) ou é propagado (para cima, com contexto). O que você nunca faz é ignorá-lo. O silêncio no tratamento de erros sempre é pago com juros.