segunda-feira, 19 de setembro de 2011

Programando com Exceções

Escrevendo uma classe em C++ fiquei com uma dúvida. Como tratar um erro que acontece em um construtor? Imediatamente tive duas ideias, retornar NULL ou configurar uma flag na classe. A primeira logo descobri que não era possível, pois os construtores não permitem retornos. A segunda, apesar de possível, achei deselegante e muito suscetível a erros.

Foi a partir deste momento que me perguntei se exceções seria a forma correta de tratar o problema e após ler alguns livros esta é a forma unânime para se notificar um erro que acontece em um construtor, porém na busca desta resposta encontrei muito mais informações deste mecanismo que acabaram me convencendo que exceções são uma ótima forma de tratar erros de tempo de execução em uma aplicação.

É importante lembrar que alguns erros são de lógica e não deveriam acontecer durante a execução do software. Estes normalmente são falhas do programador, sendo assim, a forma correta de tratá-los é através de chamadas a macro assert, as quais podem ser desabilitadas facilmente em produção definindo a variável NDEBUG durante a compilação.

Através das exceções é possível separar o fluxo de código "normal" daquele que é responsável pelo tratamento do erro, tornando a execução de um determinado fluxo mais limpa e fácil de ler, ou seja, sem distrações. Além disso, um erro, muitas vezes, acontece em um determinado componente do sistema que não possui informações suficientes para saber como este deve ser tratado, pois seu contexto é muito limitado para identificar as condições que o provocaram, impossibilitando qualquer medida para corrigi-lo, neste caso, faz mais sentido lançar uma exceção e deixar uma parte da aplicação que tenha um contexto mais adequado tratar o erro.

Outra questão que tive quando comecei a pensar sobre exceções foi a respeito dos recursos que já haviam sido alocados durante a chamada do construtor. O que iriam acontecer com eles?

Caso uma exceção ocorra durante a execução de um construtor, o destrutor da classe não é chamado, sendo assim, seus recursos não são liberados. A solução para este problema é chamada por "resource allocation is initialization". Ao invés de fazer uma chamada a uma biblioteca específica para se obter um recurso, cria-se uma classe que irá representar este recurso que é inicializado através do construtor. A Wikipédia traz uma ótima descrição a respeito deste assunto: http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization

Outro benefício interessante do uso de exceções é a chamada dos destrutores das classes que estão na pilha, pois quando uma exceção é lançada, grosseiramente falando, é como se o seu código fosse executando vários "return" até encontrar uma parte do código que a trate. Isto evita que seus objetos fiquem perdido, causando memory leaks.

Também procurei informações sobre exceções para descobrir alguns conselhos de como utilizá-las, pois também tinha dúvidas se seria um bom recurso para utilizar na programação de jogos, devido ao overhead que adicionam. Pelo que li é totalmente plausível utilizá-las (além disso a engine Ogre3D as utilizam), porém é necessário entendê-las muito bem para conseguir visualizar o impacto que terão durante sua chamada.

Os conselhos apresentados por Bjarne Stroustrup no livro C++ Programming Language são muito bons:
  • Use exceções para tratamento de erros;
  • Não use exceções onde estruturas de controle locais são suficientes;
  • Use a técnica "resource allocation is initialization" para gerenciar recursos;
  • Nem todo programa precisa tratar exceções;
  • Use "resource allocation is initialization" e tratamento de exceções para manter invariantes;
  • Minimize o uso de try-blocks. Use "resource allocation is initialization" ao invés de código de tratamento explícito;
  • Nem toda função precisa tratar todos os erros possíveis;
  • Lance uma exceção para indicar uma falha em um construtor;
  • Evite lançar exceções em destrutores;
Além desses existem muitos outros conselhos, porém acredito que isto já é o bastante para atiçar o apetite de qualquer um que tenha lido até aqui. Os livros que achei mais interessantes sobre o assunto foram:
Ótimas referências!