16.06
2020
Tratamento de erros em C pode se tornar um problema. É muito fácil se perder um conjuntos de ifs aninhados, aumentando cada vez mais a interpretação do código, dificultando, consequentemente, sua manutenção. Vamos ver um exemplo:
err = step1();
if (!err) {
trace("Step 1 executado com sucesso", err);
err = step2();
if (!err) {
trace("Step 2 executado com sucesso", err);
err = step3();
if (!err) {
trace("Step 3 executado com sucesso", err);
} else {
trace("Falha na execucao de Step 3");
} else {
trace("Falha na execucao de Step 2");
}
} else {
trace("Falha na execucao de Step 1");
}
if (err)
return false;
else
return true;
Note a dificuldade de visualização do código crescente conforme a quantidade de passos no exemplo vai aumentando, os ifs vão se aninhando, dificultando o entendimento do código, e encarecendo sua manutenção.
Falhar Cedo
Uma boa técnica é falhar cedo, assim que uma falha for detectada finalizar o processamento. Por exemplo:
err = step1();
if (err) {
trace("Falha na execucao de Step 1", err);
return false;
}
trace("Step 1 executado com sucesso");
err = step2();
if (err) {
trace("Falha na execucao de Step 2", err);
return false;
}
trace("Step 2 executado com sucesso");
err = step3();
if (err) {
trace("Falha na execucao de Steop 3", err);
return false;
}
trace("Step 3 executado com sucesso");
return true;
Note como o fluxo de execução fica mais fácil de ser visualizado. O resultado dos dois trechos de código são equivalentes, mas o segundo exemplo é mais claro.
Lidando com Recursos
Falhar cedo funciona muito bem quando não estamos lidando com recursos que precisam ser liberados, por exemplo, com alocação de memória:
p1 = malloc(size1);
if (!p1) {
trace("Falha na alocacao de memoria p1");
return false;
}
p2 = malloc(size2);
if (!p2) {
trace("Falha na alocacao de memoria p2");
free(p1);
return false;
}
p3 = malloc(size3);
if (!p3) {
trace("Falha na alocacao de memoria p3");
free(p2);
free(p1);
return false;
}
processar(p1, p2, p3);
free(p3);
free(p2);
free(p1);
return true;
O exemplo utiliza malloc, mas serve qualquer tipo de função que consuma recursos que precisam ser liberados manualmente.
Percebe a dificuldade na manutenção do código? Para cada nova alocação é necessário gerenciar a liberação de memória em cada bloco subsequente, e no final da função também.
Sim, Temos Controvérsias
Uma técnica muito controvérsia é a utilização de goto. Por que? Porque muitos programadores consideram que o goto quebra a estruturação do código, rompendo o fluxo contínuo do programa.
Na verdade goto não é o único que faz isso, continue, break e throw também o fazem, e nem por isso são tão controversos.
Mas vamos demonstrar a solução primeiro:
success = false;
p1 = malloc(size1);
if (!p1) {
trace("Falha na alocacao de memoria p1");
goto err1;
}
p2 = malloc(size2);
if (!p2){
trace("Falha na alocacao de memoria p2");
goto err2;
}
p3 = malloc(size3);
if (!p3) {
trace("Falha na alocacao de memoria p3");
goto err3;
}
processar(p1, p2, p3);
success = true;
err3:
free(p3);
err2:
free(p2);
err1:
free(p1);
return sucess
Note como o gerenciamento de memória ficou mais simples, em um único lugar, a chance de ocorrer um vazamento de recursos é mais reduzida, e o custo de manutenção também.
Um problema dessa construção é que muitas equipes de desenvolvimento não a aceitam, por não aceitarem o goto como um comando de programação estruturada.. Mas decisões de metodologias de desenvolvimento são de responsabilidade de cada equipe, portanto, quando permitido, tratamento de erro com goto é uma boa opção.
Em um post futuro vamos tratar a sintaxe e as restrições do goto no C. E o porquê dele não oferecer risco.
Comentários
Prefiro escrever assim… tô zoando! 😀
success = false;
if(p1 = malloc(size1)){
if(p2 = malloc(size2)){
if(p3 = malloc(size3)){
processar(p1, p2, p3)
success = true;
free(p3);
}
else{
trace(“Falha na alocacao de memoria p3”);
}
free(p2);
}
else{
trace(“Falha na alocacao de memoria p2”);
}
free(p1);
}
else{
trace(“Falha na alocacao de memoria p1”);
}
return success;