stamatios
← Voltar ao feed
Throw, Result ou nenhum dos dois? Formas de tratar erros em software
Dev & Engenharia

Throw, Result ou nenhum dos dois? Formas de tratar erros em software

resumo de ~3 min

As três formas de tratar erros em software

Oskar Dudycz, autor do Architecture Weekly, responde a uma pergunta recorrente sobre seu código event-sourced: por que não usar Result? A resposta é que o tipo de retorno é apenas parte da equação. Ele propõe três opções para cenários inesperados:

  1. Lançar uma exceção (ex: OutOfStockError) e capturá-la na boundary da aplicação;
  2. Retornar um Result com o fracasso na trilha de erro;
  3. Retornar um evento (ex: ProductItemOutOfStock) e decidir separadamente o que fazer com ele.

Erro como fato de negócio

Algumas falhas merecem ser retidas como dados de negócio. Um pedido sem estoque pode alimentar um relatório de demanda não atendida. Um pagamento recusado pode iniciar outro processo. Gojko Adzic chama isso de lizard optimization: padrões de uso inesperados que revelam oportunidades de produto. Eventos dão à aplicação dados que podem ser consultados, projetados e usados em outros processos.

Quando lançar exceções é adequado

Nem toda falha precisa de um evento de negócio. Adicionar produto a um carrinho já confirmado é uma operação inválida. Um event store indisponível ou um timeout de serviço impede a conclusão confiável da operação. Esses casos vão para a exception boundary da aplicação.

O que Result adiciona (e o que não adiciona)

Result coloca sucesso e falhas esperadas em ramos separados, visíveis no tipo. Tecnicamente, o código fica parecido: ProductItemAdded no ramo de sucesso, os outros no ramo de falha. Porém, o wrapper não elimina a necessidade de try/catch (infraestrutura ainda lança exceções), não remove a decisão de persistência, e em linguagens sem pipe operator (como TypeScript) adiciona verbosidade. Scott Wlaschin, que popularizou railway-oriented programming, faz a mesma qualificação: Result modela alternativas esperadas, mas não deve envolver toda função nem substituir exceções.

Persistência seletiva

Retornar um evento não significa persisti-lo. ProductItemOutOfStock pode ser útil para medir demanda não atendida, mas se o negócio não precisa do fato, não há razão para gravá-lo. Dudycz chama isso de persistência seletiva: a decisão descreve o resultado, a aplicação decide se deve ser durável. No framework Emmett, isso é feito com middleware skipOn.

Múltiplas decisões em um stream

Em cenários de importação em batch, cada linha de um registro externo vira um comando no mesmo stream. O handler precisa de regras diferentes por tipo de evento: SKIP (ignora e continua), STOP (para e persiste o que já foi aceito), REJECT (descarta tudo). O importador pode reportar o resultado exato ao sistema de origem e continuar com o próximo carrinho.

Nunca lance exceções em handlers assíncronos

Handlers assíncronos (projeções, reatores, workflows) não têm nada acima para capturar a exceção. Um throw para o processador e deixa eventos subsequentes sem tratamento. Projeções constroem read models a partir de eventos já gravados - fatos não podem ser desfeitos. A solução é aceitar o evento e construir o melhor read model possível.

Conclusão

A escolha entre throw, Result ou evento depende do que deve ser persistido, do que o caller deve receber, e se a falha pertence ao caminho de exceção da aplicação. Eventos de negócio já distinguem os resultados; Result adicionaria classificação sem eliminar as decisões de persistência, continuação e retry. Às vezes, a resposta é: nenhum dos dois.