Alessandro Garcia LES / DI / PUC-Rio Junho / 2016
Aula 22
Tratamento de Exceções
Avisos
• Trabalho T4 – enunciado completo está disponível:
– devem estar em andamento: • conclusão da implementação do jogo
• agora: teste do módulo Lista deve satisfazer critério de cobertura de arestas
– entrega: até 30/6
• Prova P2: 22/6
Sumário
• Definições básicas
– Como se encerram as funções?
– Problema: e quando funções não encerram corretamente?
• Tratamento de exceções
– Tipos de tratadores – Como tratar exceções
• Em C
• Em linguagens de programação contemporâneas
2
Relembrando - Definições básicas
• O que é uma função?– Funçãoé uma porção auto-contida de código que:
• possui um nomeque pode ser chamado (ativado) de diversos lugares
• pode retornarzero ou mais valores
• pode depender de e alterar zero ou mais parâmetros
• pode alterarzero ou mais valores do estadodo módulo
– variáveis internas – variáveis globais
• pode criar, ler e destruir arquivos, etc...
TIPO_RETORNO NOME_FUNCAO ( LISTA_PARAMETROS) {
CORPO_FUNCAO }
• Encerrando a execução de uma função:
– Chegar ao fim de uma função void
– O comando return <VALUE>
• Encerra a execução de uma função imediatamente
• Se um valor de retorno é informado, a função chamada (callee) retorna este valor para a função chamadora (caller)
• A transferência de controle após o return passa da função chamada para a função chamadora
– O comando exit(int)
• Encerra a execução do programa
1. Executa em ordem reversa todas as funções registradas pela função int atexit( void (*func)(void) )
2. Todos arquivos são fechados, todos arquivos temporários são apagados
3. O controle de execução retorna ao ambiente-hospedeiro (host enviornment) o valor inteiro passado como argumento
4
Problema
• Exceção: Uma exceção é um evento que ocorre durante a
execução de um programa (ou módulo) e que impede a sua execução normal
• E o que fazer quando uma exceção impede que uma função encerre sua execução corretamente?
Problema
• Exemplos:– Um parâmetro de entrada ocasiona uma divisão por zero – Um valor de um dos parâmetros ou variáveis viola assertivas
de entrada, saída
– Uma das assertivas estruturais é violada
– Não há memória disponível quando você tenta alocar memória dinamicamente
– Hardware para de funcionar corretamente
6
Tratamento de exceções
• Por que é importante tratar exceções?
• Por que é importante tratar exceções?
– Os usuários podem perder a confiança no seu produto – ... e você pode passar uma tremenda vergonha!
8
Tratamento de exceções
• Sistemas atuais são cada vez maiores e mais complexos
– Condições excepcionais podem surgir por diversos motivos e serem até mesmo difíceis de serem previstas
• Sistemas robustos e confiáveis devem prover seus serviços mesmo sob condições excepcionais
– Sistemas críticos: • Sistemas bancários
• Controladores de redes elétricas, vôo, usinas nucleares...
• Sistemas embarcados em aviões, submarinos, foguetes... • O tratamento de exceções é parte central do
Tratamento de exceções - Caso de insucesso
•
Em outubro de 1996 o foguete francês Ariane 501 se
autodestruiu 5 minutos após decolar
•
Motivo:
– cálculo errado do ângulo de ataque
•
Causa:
– O Ariane 5 reutilizou um módulo do Ariane 4 para calcular o “Alinhamento Interno”, elemento relacionado com a
componente horizontal da velocidade
– O valor gerado pelo módulo do Ariane 4 foi muito maior do que o esperado pelo módulo do Ariane 5, pois a trajetória do Ariane 5 difere da trajetória do Ariane 4
– O módulo do Ariane 5 tentou converter o valor do
“Alinhamento Interno”de um número de 64 bits em ponto
flutuante para um inteiro de 16 bits com sinal
– Valor em ponto flutuante era maior do que poderia ser representado por um inteiro
– Operação de conversão não estava protegida e ocasionou Overflow Exception
10
Tratamento de exceções
• Vídeo do Bill Gates:– http://www.youtube.com/watch?v=TrAD25V7ll8
• Vídeo do Ariane 5:
– http://www.youtube.com/watch?v=kYUrqdUyEpI
• Outros desastres famosos devido ao não tratamento de exceções
• Por que é importante tratar exceções?
– Pode custar milhões de dólares/reais/euros – Ou até mesmo vidas
12
Tratamento de exceções - Terminologia
• Exceção: Uma exceção é um evento que ocorre durante a execução de um programa (ou módulo) e que impede a sua execução normal
– Uma exceção indica que o estado interno do sistema está inconsistente e, por isso, não é possível prosseguir com sua execução normal
– Para restaurar a consistência interna do sistema, é necessário tomar ações corretivas, ou remediadoras
• Tratador: Um tratador é um conjunto de ações que visam corrigir ou remediar a ocorrência de uma exceção
• Mecanismo de tratamento de exceções: Um mecanismo de tratamento de exceções é um modelo que permite a
desenvolvedores:
– Representar os tipos de ocorrências de exceção
– Indicar a ocorrência de uma exceção na interface de funções/módulos
– Detectar a ocorrência de uma exceção na execução da função
– Estruturar ações de tratamento de exceções
Maio 2009 Alessandro Garcia © LES/DI/PUC-Rio 14 / 32
Estrutura de uma função
• Conjunto de exceções sinalizadas na interface – em C: condições de retorno excepcionais
– Exceções – exs.: nome de arquivo não existe, índice/chave fora do limite, conexão com servidor/banco-dados não pode ser estabelecida, etc...
Tratador E1 Tratador E2 Tratador E3
Função
Chamadas ChamadasTratamento de exceções
• Exemplos de tratadores:– Ignora a exceção: identifica a ocorrência de uma exceção e não toma nenhuma ação corretiva. Má prática e deve ser evitada.
– Re-sinaliza a exceção: identifica um tipo de exceção e reporta para a função chamadora outro tipo de exceção. Geralmente é usada para preservar abstração e encapsulamento.
– Delegação de controle: delega o controle da execução para outro módulo do sistema mais apto a lidar com a exceção.
– Notificação ao usuário: notifica o usuário a ocorrência da exceção, com possibilidade de pedir novo input, caso seja possível.
Preferencialmente, somente em casos que usuário deve ser envolvido.
– Armazenamento de erro: cria um registro da exceção e de informações adicionais em arquivo especial (log).
– Reconfiguração: reconfigura o estado do sistema com outros valores. – Nova tentativa: a mesma função é simplesmente invocada
• Exemplos de tratadores:
– Liberação de recursos: assegura a liberação de recursos alocados, como memória alocada dinamicamente, arquivos e conexões abertos, etc.
– Preparação para desligar: prepara o sistema para terminar sem causar efeitos colaterais. Geralmente é usada em situações
extremas.É necessário liberar todos os recursos alocados e reverter o sistema para um estado em que os dados estão consistentes.
– Recuperação por retrocesso: desfaz modificações no estado do sistema a fim de restaurá-lo a um estado válido. Comumente usado em sistemas de bancos de dados.
– Uso de redundância de software: ativa uma função com o mesmo propósito.
– Uso de redundância de dados: uso de elementos adicionais da estrutura que permitam navegar de outras formas na estrutura. Vide estruturas auto-verificáveis.
16
Como tratar exceções em módulos em C?
• A linguagem C não traz suporte específico para tratamento de exceções
• Fica sob responsabilidade do programador desenvolver uma forma de identificar e tratar exceções da aplicação
• Existem várias formas de realizar esta tarefa, cada um com seus prós e contras
• A fim de padronizar a identificação e o tratamento de exceções em C, usamos nesta disciplina
padrões/convenções de tratamento de exceções:
– Tipos enum e código de retorno das funções para indicar sob qual condição a função encerrou sua execução
– Se necessários, define-se parâmetros adicionais passados por referência e modifica-se seus valores para informações extra do erro
Usando o código de retorno das funções
• Este é o método mais comum e mais simples de tratamento de exceções em linguagem C
• Neste método, as funções tem duas responsabilidades:
– A função chamada deve usar o comando return para indicar sob qual condição (normal | excepcional) sua execução encerrou
– A função chamadora deve testar o código retornado pela função chamada a fim de tomar ações corretivas, caso necessário
• Preferencialmente, as condições de retorno devem ser declaradas como um elemento enum
18
Usando o código de retorno das funções
typedef enum {LIS_CondRetOK ,
/* Concluiu corretamente */ LIS_CondRetListaVazia ,
/* A lista não contém elementos */ LIS_CondRetFimLista ,
/* Foi atingido o fim de lista */ LIS_CondRetNaoAchou ,
/* Não encontrou o valor procurado */ LIS_CondRetFaltouMemoria
/* Faltou memória ao tentar criar um elemento de lista */
} LIS_tpCondRet ;
LIS_tpCondRet LIS_InserirElementoAntes
( LIS_tppLista pLista , void * pValor ) {
tpElemLista * pElem ;
pElem = CriarElemento( pLista , pValor ) ; if ( pElem == NULL ) {
return LIS_CondRetFaltouMemoria ;
} /* if */ ....
return LIS_CondRetOK ;
int main(void){ ...
LIS_tpCondRetcondRet= InserirElementoAntes( lista, pValor ); switch( condRet) { case LIS_CondRetFaltouMemoria: ... case LIS_CondRetOK: ... default :
printf(“Condição de retorno inesperada”); }
}
20
Usando parâmetro passado por referência
LIS_tpCondRet LIS_InserirElementoAntes( LIS_tppLista pLista , void * pValor, char ** errorMsg ) {
tpElemLista * pElem ;
pElem = CriarElemento( pLista , pValor ) ; if ( pElem == NULL ) {
char str[] = “Não foi possível alocar memória para um novo elemento”; int size = strlen( str ) + 1;
(*errorMsg) = (char*)malloc( sizeof(char) * size ); memcpy( (*errorMsg), str, size );
return LIS_CondRetFaltouMemoria ;
} /* if */ ....
return LIS_CondRetOK ;
Maio 2009 Alessandro Garcia © LES/DI/PUC-Rio 22 / 32
Exemplo de condição de retorno
• Servidortypedef enum {
CondRetLeuOK , /* Leitura normal */
CondRetLeuEOF , /* Encontrou fim de arquivo antes de ler */ CondRetLeuErro /* Ocorreu erro de leitura */
} tpCondRetLeu ;
. . .
tpCondRetLeu LerReg( FILE * pArq , tpReg * pReg ) {
if ( feof( pArq )) {
memset( pReg , 0xFF , sizeof( tpReg )) ; return CondRetLeuEOF ;
} /* if */
Conta = fread( pReg , pArq , sizeof( tpReg ) , 1 ) ; if ( ( ferror( pArq ) != 0 ) || ( Conta != 1 )) {
memset( pReg , 0xCC , sizeof( tpReg )) ; return CondRetLeuErro ;
} /* if */
return CondRetLeuOK ; } /* Função LerReg */
Exemplo de condição de retorno
• A função cliente contém o código:Cond = LerReg( pArqA , pBuffer ) ; switch ( Cond )
{
case CondRetLeuOK :
/* código que trata o caso de leitura normal */ break ;
case CondRetLeuEOF :
/* código que trata o caso de fim de arquivo */ break ;
case CondRetLeuErro :
printf( "\nErro de leitura no arquivo: %s código \'C\' %c" , NomeArqA , errno ) ;
printf( "\nPrograma será cancelado" ) ; Finalizar( CondRetLeuErro ) ;
default:
printf( "\Erro de execução: LerReg condição desconhecida: %i" , Cond ) ;
printf( "\nPrograma será cancelado" ) ; Finalizar( ErroDesconhecido ) ; } /* fim switch */ o break é desnecessário, pois Finalizar( ) não retorna
int main(void){ ...
char *errorMsg;
LIS_tpCondRetcondRet= InserirElementoAntes( lista, pValor, &errorMsg); switch( condRet) { case LIS_CondRetFaltouMemoria: printf( “%s”, errorMSG); case LIS_CondRetOK: ... default :
printf(“Condição de retorno inesperada”); }
}
24
Usando variáveis globais ou parâmetros
• Este método é complementar ao método de condições de retorno
– Bastante usado na GLIBC, biblioteca padrão do sistema GNU/Linux
• Neste método, as funções tem as seguintes responsabilidades:
– A função chamada deve modificar variáveis globais ou parâmetros passados por referência para indicar sob qual condição (normal | excepcional) sua execução encerrou – A função chamadora deve testar a variável global, ou o parâmetro passado por referência, a fim de tomar ações corretivas, caso necessário
Limitações de C
• A sinalização de uma exceção não é explícita
– Usa-se o comando return, parâmetros passados por referência ou variáveis globais
– Não há distinção entre encerramento sob condição normal ou excepcional
• O sistema não é interrompido no momento em que a exceção ocorre
– Mesmo sabendo-se que o estado é inconsistente
• Como prover mais informações a respeito do problema / exceção?
– Ex.: Qual a severidade? Que condições levaram a esta ocorrência?
– Uso de parâmetros encoraja quebra de abstração e encapsulamento
26
Limitações de C
• Há um overheadna criação de tipos enumeradospara cada módulo
– Para cada módulo é definido um tipo enumerado, mesmo que representem a mesma condição(EX.: Falta de memória)
• A associação entre as exceções descritas nos tipos enumerados e quais exceções que podem ser levantadas por uma funçãodepende exclusivamente da especificação da função
– Difícil entender o acoplamento excepcional entre funções: quais exceções devem ser tratadas?
• Não há separação textual do códigode tratamento de exceção
– Código torna-se rapidamente extenso, complexo e pouco compreensível
Maio 2009 Alessandro Garcia © LES/DI/PUC-Rio 28 / 32
• Comportamento exceptional de funções
– diferente de C, C++ e Java provêem suporte explícito a tratamento de exceções
Facilidades / Mecanismos para Tratamento de Exceções
Exceções Tratadores (Handlers)
Desvio do Fluxo e Busca de Tratadores
Definição de “Regiões Protegidas” Interfaces
Excepcionais
Usando o código de retorno das funções
•
Problemas:
– Chamadas encadeadas de funções podem resultar em uma estrutura muito
aninhada que é difícil de compreender, testar e fazer a manutenção ARQ_tpCondRet leArquivo(){ condRet = OK; abreArquivo(); se( arquivoAberto() ){ determineTamanhoArquivo(); se( determinouTamanho() ){ aloqueMemoria(); se( alocouMemoria() ){ copieDados(); se( ! copiouDados() ){ condRet = ERRO_COPIAR_DADOS; } } senão { condRet = ERRO_ALOCAR_MEM; } } senão { condRet = ERRO_DET_TAM; } fecheArquivo(); se( ! fechouArquivo() ){ condRet = ERRO_FECHAR_ARQ; } } senão { condRet = ERRO_ABRIR_ARQ; } }
Linguagens contemporâneas
• Linguagens como Java, JavaScript, C++, C#, Python ... proveem mecanismos de tratamento de exceções implementados na própria linguagem
• Elementos sintáticos específicos para tratamento de exceções:
– THROW – sinaliza a ocorrência de uma exceção
– TRY – define uma região protegida contra a ocorrência de exceções
– CATCH – define um tratador, i.e., um trecho de código que implementa um conjunto de ações de recuperação
30
Sinalização explícita de exceções
• Cláusula throw sinaliza a ocorrência de uma exceção
static void escreveArquivo(Arquivo a) ... { ...
Buffer bf = buscaArquivo( a ); if( bf == null )
throw new FileNotFoundException(); }
Maio 2009 Alessandro Garcia © LES/DI/PUC-Rio 32 / 32
• Exceções são levantadas/sinalizadas explicitamente
int funcaoX ( ...) { // código desprotegido try {
// código a ser tentado; região protegida if (x > 10) {throw excecao};
}
catch (Excecao) {
// código do tratador: a ser executado // em caso de exceção if (x > 10) {throw excecao2}; } } acoplamento: exceção sinalizada
Linguagens contemporâneas
• Linguagens como Java, JavaScript, C++, C#, Python ... provêem mecanismos de tratamento de exceções implementados na própria linguagem
• Elementos sintáticos específicos para tratamento de exceções:
– TRY – define uma região protegida contra a ocorrência de exceções
– CATCH – define um tratador, i.e., um trecho de código que implementa um conjunto de ações de recuperação
– THROW – sinaliza a ocorrência de uma exceção
– THROWS – especifica na interface de uma função quais as possíveis exceções que podem ser ocasionadas durante a execução daquela função do módulo
Maio 2009 Alessandro Garcia © LES/DI/PUC-Rio 34 / 32
Estrutura de uma função em Java
• Acoplamento – conjunto de exceções sinalizadas – exceções são sinalizadas explicitamente em Java
acoplamento: exceção sinalizada
class Count {
public static void main(String args[]) throws java.io.IOException
{ int count = 0; while (System.in.read() != -1) count++; System.out.println("Entrada:"+count+ "caracteres."); } }
Acoplamento excepcional explícito
• Cláusula throws indica quais exceções podem ocorrer durante a execução de uma função
void escreveArquivo(Arquivo) throws FileNotFoundException,
CharCodingException,
• Linguagens como Java, JavaScript, C++, C#, Python ... provêem mecanismos de tratamento de exceções implementados na própria linguagem
• Elementos sintáticos específicos para tratamento de exceções:
– TRY – define uma região protegida contra a ocorrência de exceções
– CATCH – define um tratador, i.e., um trecho de código que implementa um conjunto de ações de recuperação
– THROW – sinaliza a ocorrência de uma exceção
– THROWS – especifica na interface de uma função quais as possíveis exceções que podem ser ocasionadas durante a execução daquela função do módulo
– FINALLY – define um trecho de código que sempre será executado, mesmo quando exceções ocorrerem
36
Mecanismo para
“
“
“
“
código de limpeza
”
”
”
”
• O código definido no bloco
finally sempre será
executado, seja após a terminação normal, ou após a terminação
excepcional, de um bloco
try
– Usado especialmente para liberação de recursos, como memória, arquivos abertos, conexões abertas, etc leArquivo(){ try{ abreArquivo(); determineTamanhoArquivo(); aloqueMemoria(); copieDados(); } catch( abrirErro ){...} catch( determinarTamanhoErro ) {...} catch( alocarMemoriaErro ) {...} catch( copiarDadosErro ) {...} finally { try{ fecheArquivo(); } catch( fecharArquivoErro ){...} } }
Melhor separação textual
ARQ_tpCondRet leArquivo(){ condRet = OK; abreArquivo(); se( arquivoAberto() ){ determineTamanhoArquivo(); se( determinouTamanho() ){ aloqueMemoria(); se( alocouMemoria() ){ copieDados(); se( ! copiouDados() ){ condRet = ERRO_COPIAR_DADOS; } } senão { condRet = ERRO_ALOCAR_MEM; } } senão { condRet = ERRO_DET_TAM; } fecheArquivo(); se( ! fechouArquivo() ){ condRet = ERRO_FECHAR_ARQ; } } senão { condRet = ERRO_ABRIR_ARQ; } } leArquivo(){ try{ abreArquivo(); determineTamanhoArquivo(); aloqueMemoria(); copieDados(); } catch( abrirErro ){...} catch( determinarTamanhoErro ) {...} catch( alocarMemoriaErro ) {...} catch( copiarDadosErro ) {...} finally { try{ fecheArquivo(); } catch( fecharArquivoErro ){...} } } 38Linguagens contemporâneas
• Principais vantagens em relação a C:– Redução do aninhamento de estruturas if-then-else
– Melhor separação textual entre o código que implementa a lógica da aplicação e o código que implementa o tratamento de exceções
– Também há uma clara distinção entre o encerramento normal de uma função ( comando return ) e o encerramento excepcional ( comando throw )
– Tipos de exceção podem ser reutilizadas entre diferentes módulos
– Mecanismos que garantem a execução de determinados trechos de código tanto em situações normais, quanto em situações excepcionais
• Blocos finally
• Mesmo com os mecanismos de tratamento de exceções providos na própria linguagem, código de tratamento de exceções ainda é fonte de erros:
– Dificuldade em modificar métodos que tratam exceções – Tratadores vazios (catch block vazio)
– Exceções não tratadas
• Erros no código de tratamento de exceções são de difícil diagnose
– Código pouco executado e pouco testado
• Implementar tratamento de exceções não é trivial e requer esforço extra dos desenvolvedores
40
Tratamento de Exceções: Origem
1971
Parnas & Dijkstra Princípio de Encapsulamento e Separação de Interesses 1975 Goodenough EH Notation 1979 CLU Declaração explícita De interface excepcional Tratamento de Exceções em Linguagens Modernas 1980 1964 PL/I ON statements
indicação de ocorrência de exceção associação do tratador
Referência
• Cap. 8 do livro Programação Modular
• Leitura complementar:
– Tutorial sobre tratamento de exceções em Java
• http://download.oracle.com/javase/tutorial/essential/except ions/index.html
– Artigos
• “Discovering Faults in Idiom-Based Exception Handling”, Bruntink, M., van Deursen, A. and Tourwé, T. In
Proceedings of 28th ICSE, 2006.
• “Exception handling: a field study in Java and .NET”, Cabral, B. and Marques, P. In Proceedings of 21th ECOOP, 2007.
42