Introdução
No desenvolvimento de aplicações, existem vários casos de
uso que devem ser implementados durante uma sprint. Dentro dos casos de uso, existem
dois tipos de fluxo: o fluxo principal em que o caso de uso executa sem exceções
e o fluxo alternativo que pode ser a realização de outro procedimento ou o disparo
de um erro (ver This
is how to write the flow of use cases | by Chaitanya Joshi | Medium).
Todo o programador, já deve ter ouvido falar do try/catch
para tratamento de erros. No entanto talvez nunca tenha refletido sobre as
boas práticas do uso do try/catch.
Evitar o try catch
Deve-se evitar ao máximo o uso do try/catch. Por
exemplo, imaginemos que estamos a implementar o acesso a uma base de dados. Por
vezes, há erros comuns que são cometidos como fechar a conexão de uma base de
dados que já foi fechada. Se executarmos esse comando, é lançada uma exceção
indicando que a conexão já foi encerrada. Para isso, só temos de verificar se a
conexão foi fechada e se não foi fechada, executar a operação (ver Best
practices for exceptions - .NET | Microsoft Learn). Por isso, a melhor prática
é evitar usar try/catch.
A desvantagem de usar try/catch é que o código fica
muito mais confuso de ler. Existem várias formas de devolver um erro. Uma delas
é devolver um objeto com o erro dentro, o pattern Result (ver Result Pattern. Photo by John Schnobrich on Unsplash
| by Ben Witt | Medium).
Outra maneira é criar um middleware que captura os erros e
devolva uma mensagem padrão.
Isso é possível em qualquer linguagem, mas em algum lugar o try catch terá de aparecer e provavelmente será na entrada do programa.
Middleware de tratamento de erro no Express V.5.X.X
Em sistemas orientados a serviços, é possível capturar os
erros de forma padronizada. Tal acontece com a biblioteca Express do Node.js. Desta
forma existe uma redução da quantidade de linhas de código escritas (ver Express
5 Brings Built-in Promise Support for Error Handling - DEV Community).
Este middleware funciona tanto para funções assíncronas e para funções síncronas, mas só foi adicionada esta funcionalidade na versão 5 do Express. Na versão 4 era necessário instalar uma biblioteca adicional, mas agora isto é nativo.
Implementação
Na imagem seguinte, temos uma implementação desse middleware:
app.use(function(err: Error, req: Request, res: Response, next: NextFunction) {
if (err instanceof HTTPException) {
const error = err as HTTPException
res.status(error.statusCode).send({ error: err.message })
return;
}
else {
res.status(500).send({ error: err.message })
return;
}
next();
})
Para isto criamos uma exception chamada: HTTPException.
export class HTTPException extends Error {
public readonly statusCode: number
constructor(message: string, statusCode: number) {
super(message);
this.name = "HTTPException";
this.statusCode = statusCode
}
}
Depois nas rotas, basta fazer throw da exception:
async function createTask() {
await wait();
throw new HTTPException("Error creating task", 500);
}
app.post("/tasks", authMiddleware, async(req: Request, res: Response) => {
const mappedRequest = fromExpressRequestToRegisterTaskDto(req);
await createTask();
res.status(201).json(mappedRequest)
})
Nesta função é executada a função createTask() que devolve uma
exception com erro 500.
O erro 500 é devolvido na resposta HTTP com a respetiva
descrição do erro.
Se eu remover a “description” do body o erro 400 é devolvido:
Conclusão
Com este método temos apenas um único local onde os erros
são tratados. E desta forma, evitamos o uso de muitos try/catch.