Aula 15 Tratamento de Exceções Eiji Adachi Barbosa LES / DI / PUC-Rio Setembro / 2011 Avisos • Próxima aula ( 3/outubro/2011 ) haverá exercício em sala de aula valendo ponto – Apenas o conteúdo da aula de hoje – Exercício em dupla – Sem consulta novembro de 15 Eiji Adachi Barbosa 2 Sumário • Definições básicas – O que é função? – Por que / para que usar funções? – Como se encerram as funções? – Problema: e quando funções não se encerram corretamente? • Tratamento de exceções – Tipos de tratadores – Caso de insucesso – Como tratar exceções • Em C • Em linguagens de programação contemporâneas novembro de 15 Eiji Adachi Barbosa 3 Definições básicas • O que é uma função? – Função é uma porção auto-contida de código que: • possui um nome que pode ser chamado (ativado) de diversos lugares • pode retornar zero ou mais valores • pode depender de e alterar zero ou mais parâmetros • pode alterar zero ou mais valores do estado do módulo – variáveis internas – variáveis globais • pode criar, ler e destruir arquivos, etc... TIPO_RETORNO NOME_FUNCAO ( LISTA_PARAMETROS) { CORPO_FUNCAO } novembro de 15 Eiji Adachi Barbosa 4 Definições básicas • Por que / para que usar funções? – Princípio dividir para conquistar • Dividir sistema em módulos Dividir algoritmo em funções • Evitar códigos monolíticos – Reuso e manutenibilidade novembro de 15 Eiji Adachi Barbosa 5 Definições básicas • 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 é local, i.e., após o return o controle do fluxo de execução 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 streams 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 novembro de 15 Eiji Adachi Barbosa 6 Definições básicas - Pilha de chamadas int main() { firstCall(); return 0; } void firstCall(){ printf("First Call\n"); secondCall(); return; } int secondCall(){ printf("Second Call\n"); thirdCall(); return 0; int printf(const char *...) } char thirdCall(){ void firstCall() printf("Third Call\nOK, That’s enough.\n"); int main() return '0’; } novembro de 15 Eiji Adachi Barbosa 7 Definições básicas - Pilha de chamadas int main() { firstCall(); return 0; } void firstCall(){ printf("First Call\n"); secondCall(); return; } int secondCall(){ printf("Second Call\n"); int printf(const char *...) thirdCall(); return 0; int secondCall() } char thirdCall(){ void firstCall() printf("Third Call\nOK, That’s enough.\n"); int main() return '0’; } novembro de 15 Eiji Adachi Barbosa 8 Definições básicas - Pilha de chamadas int main() { firstCall(); return 0; } void firstCall(){ printf("First Call\n"); secondCall(); return; } int printf(const char *...) int secondCall(){ printf("Second Call\n"); char thirdCall() thirdCall(); return 0; int secondCall() } char thirdCall(){ void firstCall() printf("Third Call\nOK, That’s enough.\n"); int main() return '0’; } novembro de 15 Eiji Adachi Barbosa 9 Problema • E o que fazer quando uma condição excepcional (também chamada de exceção) impede que uma função encerre sua execução corretamente? • Exemplos: – Não há memória disponível quando você tenta alocar memória dinamicamente – Um parâmetro de entrada ocasiona uma divisão por zero – Hardware pára de funcionar corretamente 05/11/2015 Eiji Adachi Barbosa 10 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 imprevisíveis • 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... • Tratamento de exceções é o mecanismo responsável pela detecção de condições excepcionais e pela associação de ações corretivas com condições excepcionais 05/11/2015 Eiji Adachi Barbosa 11 Tratamento de exceções • Por que é importante tratar exceções? – Você pode irritar o seu usuário novembro de 15 Eiji Adachi Barbosa 12 Tratamento de exceções • Por que é importante tratar exceções? – Você pode irritar o seu usuário novembro de 15 Eiji Adachi Barbosa 13 Tratamento de 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! novembro de 15 Eiji Adachi Barbosa 14 Tratamento de exceções • Vídeo do Bill Gates: – http://www.youtube.com/watch?v=TrAD25V7ll8 novembro de 15 Eiji Adachi Barbosa 15 Tratamento de exceções • Por que é importante tratar exceções? – Pode custar milhões de dólares/reais/euros – Ou até mesmo vidas – http://www.devtopics.com/20-famous-software-disasters/ novembro de 15 Eiji Adachi Barbosa 16 Tratamento de exceções • Vídeo do Ariane 5: – http://www.youtube.com/watch?v=kYUrqdUyEpI novembro de 15 Eiji Adachi Barbosa 17 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 novembro de 15 Eiji Adachi Barbosa 18 Tratamento de exceções • Terminologia básica – Exceção: Uma exceção (ou condição excepcional) é um evento que ocorre durante a execução de um programa (ou módulo) e que impede a sua execução normal – Tratador: Um tratador é um conjunto de ações que visam lidar com uma exceção novembro de 15 Eiji Adachi Barbosa 19 Tratamento de exceções • Exemplos de tratadores: – Armazenamento de erro: cria um registro da exceção e de informações adicionais em arquivo especial (log). – 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. – Reconfiguração: reconfigura o estado do sistema com outros valores. – Nova tentativa: a mesma função, ou uma diferente implementação, é invocada novamente. – Liberação de recursos: assegura a liberação de recursos alocados, como memória alocada dinamicamente, arquivos e conexões abertos, etc. – 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. – Delegação de controle: delega o controle da execução para outro trecho do sistema mais apto a lidar com a exceção. novembro de 15 Eiji Adachi Barbosa 20 Tratamento de exceções • Exemplos de tratadores: – 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 o acoplamento. – 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. – 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. novembro de 15 Eiji Adachi Barbosa 21 Como tratar exceções? • 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, usaremos nesta disciplina um idioma de tratamento de exceções: – Usando o código de retorno das funções e elementos enum para indicar sob qual condição a função encerrou sua execução – Modificando / testando variáveis globais ou parâmetros passados por referência novembro de 15 Eiji Adachi Barbosa 22 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 novembro de 15 Eiji Adachi Barbosa 23 Usando o código de retorno das funções typedef enum { LIS_tpCondRet LIS_InserirElementoAntes LIS_CondRetOK , ( LIS_tppLista pLista , void * pValor ) /* Concluiu corretamente */ { tpElemLista * pElem ; LIS_CondRetListaVazia , /* A lista não contém elementos */ pElem = CriarElemento( pLista , pValor ) ; if ( pElem == NULL ) { LIS_CondRetFimLista , return LIS_CondRetFaltouMemoria ; /* Foi atingido o fim de lista */ } /* if */ LIS_CondRetNaoAchou , .... /* Não encontrou o valor procurado */ return LIS_CondRetOK ; LIS_CondRetFaltouMemoria /* Faltou memória ao tentar criar um elemento de lista */ } /* Fim função: LIS &Excluir elemento */ } LIS_tpCondRet ; novembro de 15 Eiji Adachi Barbosa 24 Usando o código de retorno das funções int main(void){ ... LIS_tpCondRet condRet = InserirElementoAntes( lista, pValor ); switch( condRet ) { case LIS_CondRetFaltouMemoria: ... case LIS_CondRetOK: ... default : printf(“Condição de retorno inesperada”); } } novembro de 15 Eiji Adachi Barbosa 25 Usando o código de retorno das funções • Problemas – Uma função pode usar uma condição de retorno excepcional cujo valor reside dentro do intervalo de valores válidos calculados pela função. O problema é que a função chamadora não sabe se o valor retornado é um valor válido ou uma condição excepcional 05/11/2015 Eiji Adachi Barbosa 26 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 05/11/2015 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; } } Eiji Adachi Barbosa 27 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 novembro de 15 Eiji Adachi Barbosa 28 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 */ Usar uma variável global, seria análogo... .... return LIS_CondRetOK ; } /* Fim função: LIS &Excluir elemento */ novembro de 15 Eiji Adachi Barbosa 29 Usando parâmetro passado por referência int main(void){ ... char *errorMsg; LIS_tpCondRet condRet = InserirElementoAntes( lista, pValor, &errorMsg ); switch( condRet ) { case LIS_CondRetFaltouMemoria: printf( “%s”, errorMSG ); case LIS_CondRetOK: ... default : printf(“Condição de retorno inesperada”); } Usar uma variável global, seria análogo... } novembro de 15 Eiji Adachi Barbosa 30 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 • Nem sempre é possível retornar um elemento enumerado como condição de retorno – Ex.: Implemente uma função que receba os três parâmetros (Nome, Estado Civil, Idade) e retorne por referência a estrutura preenchida. • Como prover mais informações a respeito do problema / exceção? – Ex.: Qual a severidade? Que condições levaram a esta ocorrência? novembro de 15 Eiji Adachi Barbosa 31 Limitações de C • Há um overhead na criação de tipos enumerados para 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ção depende 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ódigo de tratamento de exceção – Código torna-se rapidamente extenso, complexo e pouco compreensível novembro de 15 Eiji Adachi Barbosa 32 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 – FINALLY – define um trecho de código que sempre será executado, mesmo quando exceções ocorrerem – THROW – sinaliza a ocorrência de uma exceção – THROWS – especifica na interface de um módulo / função quais as possíveis exceções que podem ser ocasionadas durante a execução daquele módulo / função novembro de 15 Eiji Adachi Barbosa 33 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, PermissionException; novembro de 15 Eiji Adachi Barbosa 34 Sinalização explícita de exceções • Cláusula throw sinaliza a ocorrência de uma exceção static void escreveArquivo(Arquivo a) throws FileNotFoundException, CharCodingException, PermissionException { Buffer bf = buscaArquivo( a ); if( bf == null ) throw new FileNotFoundException(); } novembro de 15 Eiji Adachi Barbosa 35 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; } } novembro de 15 leArquivo(){ try{ abreArquivo(); determineTamanhoArquivo(); aloqueMemoria(); copieDados(); } catch( abrirErro ){...} catch( determinarTamanhoErro ) {...} catch( alocarMemoriaErro ) {...} catch( copiarDadosErro ) {...} finally { try{ fecheArquivo(); } catch( fecharArquivoErro ){...} } } Eiji Adachi Barbosa 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 05/11/2015 leArquivo(){ try{ abreArquivo(); determineTamanhoArquivo(); aloqueMemoria(); copieDados(); } catch( abrirErro ){...} catch( determinarTamanhoErro ) {...} catch( alocarMemoriaErro ) {...} catch( copiarDadosErro ) {...} finally { try{ fecheArquivo(); } catch( fecharArquivoErro ){...} } } Eiji Adachi Barbosa 37 Execução e transferência não-local Lista preencheLista(){ void adiciona(Lista lista, Arquivo arq, Arquivo arq = null; int i ) throws RegistroException { Lista lista = null; try{ Registro r = leRegistro( arq, i ); arq = abreArquivo( “Dados.txt” ); Elemento e = criaElement( r ); lista = criaLista(); adicionaElemento( lista, e, i ); for( i=0; i<SIZE; i++ ){ } adiciona( lista, arq, i ); } //faz mais alguma coisa } catch( RegistroException ){ print( “Registro lido incorretamente” ); } finally { if( arq != null){ fechaArquivo( arq ); catch( RegistroException ) } finally } return lista; novembro de 15 preecheLista() Eiji Adachi Barbosa 38 Execução e transferência não-local Lista preencheLista(){ void adiciona(Lista lista, Arquivo arq, Arquivo arq = null; int i ) throws RegistroException { Lista lista = null; try{ Registro r = leRegistro( arq, i ); arq = abreArquivo( “Dados.txt” ); Elemento e = criaElement( r ); lista = criaLista(); adicionaElemento( lista, e, i ); for( i=0; i<SIZE; i++ ){ } adiciona( lista, arq, i ); } //faz mais alguma coisa } catch( RegistroException ){ print( “Registro lido incorretamente” ); } finally { if( arq != null){ abreArquivo fechaArquivo( arq ); catch( RegistroException ) } finally } return lista; novembro de 15 preecheLista() Eiji Adachi Barbosa 39 Execução e transferência não-local Lista preencheLista(){ void adiciona(Lista lista, Arquivo arq, Arquivo arq = null; int i ) throws RegistroException { Lista lista = null; try{ Registro r = leRegistro( arq, i ); arq = abreArquivo( “Dados.txt” ); Elemento e = criaElement( r ); lista = criaLista(); adicionaElemento( lista, e, i ); for( i=0; i<SIZE; i++ ){ } adiciona( lista, arq, i ); } //faz mais alguma coisa } catch( RegistroException ){ print( “Registro lido incorretamente” ); } finally { if( arq != null){ criaLista fechaArquivo( arq ); catch( RegistroException ) } finally } return lista; novembro de 15 preecheLista() Eiji Adachi Barbosa 40 Execução e transferência não-local Lista preencheLista(){ void adiciona(Lista lista, Arquivo arq, Arquivo arq = null; int i ) throws RegistroException { Lista lista = null; try{ Registro r = leRegistro( arq, i ); arq = abreArquivo( “Dados.txt” ); Elemento e = criaElement( r ); lista = criaLista(); adicionaElemento( lista, e, i ); for( i=0; i<SIZE; i++ ){ } adiciona( lista, arq, i ); } //faz mais alguma coisa } catch( RegistroException ){ print( “Registro lido incorretamente” ); } finally { leRegistro if( arq != null){ adiciona fechaArquivo( arq ); catch( RegistroException ) } finally } return lista; novembro de 15 preecheLista() Eiji Adachi Barbosa 41 Execução e transferência não-local Lista preencheLista(){ void adiciona(Lista lista, Arquivo arq, Arquivo arq = null; int i ) throws RegistroException { Lista lista = null; try{ Registro r = leRegistro( arq, i ); arq = abreArquivo( “Dados.txt” ); Elemento e = criaElement( r ); lista = criaLista(); adicionaElemento( lista, e, i ); for( i=0; i<SIZE; i++ ){ } adiciona( lista, arq, i ); Esse trecho de código não é executado! } //faz mais alguma coisa } catch( RegistroException ){ print( “Registro lido incorretamente” ); } finally { leRegistro if( arq != null){ adiciona fechaArquivo( arq ); catch( RegistroException ) } finally } return lista; novembro de 15 preecheLista() Eiji Adachi Barbosa 42 Execução e transferência não-local Lista preencheLista(){ void adiciona(Lista lista, Arquivo arq, Arquivo arq = null; int i ) throws RegistroException { Lista lista = null; try{ Registro r = leRegistro( arq, i ); arq = abreArquivo( “Dados.txt” ); Elemento e = criaElement( r ); lista = criaLista(); adicionaElemento( lista, e, i ); for( i=0; i<SIZE; i++ ){ } adiciona( lista, arq, i ); Esse trecho de código não é executado! } //faz mais alguma coisa } catch( RegistroException ){ print( “Registro lido incorretamente” ); } finally { if( arq != null){ print fechaArquivo( arq ); catch( RegistroException ) } finally } return lista; novembro de 15 preecheLista() Eiji Adachi Barbosa 43 Execução e transferência não-local Lista preencheLista(){ void adiciona(Lista lista, Arquivo arq, Arquivo arq = null; int i ) throws RegistroException { Lista lista = null; try{ Registro r = leRegistro( arq, i ); arq = abreArquivo( “Dados.txt” ); Elemento e = criaElement( r ); lista = criaLista(); adicionaElemento( lista, e, i ); for( i=0; i<SIZE; i++ ){ } adiciona( lista, arq, i ); Esse trecho de código não é executado! } //faz mais alguma coisa } catch( RegistroException ){ print( “Registro lido incorretamente” ); } finally { if( arq != null){ fechaArquivo( arq ); fechaArquivo } finally } return lista; novembro de 15 preecheLista() Eiji Adachi Barbosa 44 Linguagens 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 – Liberação de recursos 05/11/2015 Eiji Adachi Barbosa 45 Referência • Cap. 8 do livro Programação Modular • http://download.oracle.com/javase/tutorial/essential/exceptions/in dex.html novembro de 15 Eiji Adachi Barbosa 46 FIM novembro de 15 Eiji Adachi Barbosa 47