• Nenhum resultado encontrado

Tópico Avançado 5

49 */ 50 string tens_name(int n)

5.13 Pré-condições

O que deveria fazer uma função quando ela é chamada com entradas inadequadas? Por exemplo, como deveria uma sqrt(-1)reagir? O que deveria digit_name(-1)fazer? Existem duas escolhas.

•Uma função pode falhar seguramente. Por exemplo, a função digit_namesimples- mente retorna um string vazio quando ela é chamada com uma entrada inesperada. •Uma função pode terminar. Muitas funções matemáticas fazem isto. A documenta-

ção determina quais entradas são legais e quais entradas não são legais. Se a função é chamada com uma entrada ilegal; ela termina de algum modo.

Existem diferentes modos de terminar uma função. As funções matemáticas escolheram o mo- do mais brutal: imprimir uma mensagem e terminar todo o programa. C++ possui um mecanismo bastante sofisticado que permite a uma função terminar se enviar uma assim denominada exceção, que sinaliza ao recebedor apropriado que algo de muito errado ocorreu.

Desde que o recebedor esteja em seu lugar, ele pode tratar o problema e evitar o término do pro- grama. Entretanto, o tratamento de exceções é complexo — você vai encontrar uma breve discus- são no Capítulo 17. Por ora, vamos escolher um método mais simples, mostrado na Sintaxe 5.6: usar a macro assert(uma macro é uma instrução especial para o compilador inserir código com- plexo no texto do programa).

#include <cassert> ...

double future_value(double initial_balance, double p, int n) {

assert(p >= 0);

assert(n >= 0);

return initial_balance * pow(1 + p / 100, n); }

Se a condição dentro da macro é verdadeira quando a macro é encontrada, então nada aconte- ce. Entretanto, quando a condição é falsa, o programa aborta com uma mensagem de erro.

assertion failure in file fincalc.cpp line 49: p >= 0

Esta é uma mensagem mais útil do que a emitida por uma função matemática falhando. Aquelas funções apenas afirmam que um erro ocorreu em algum lugar. A mensagem assertfornece o núme- ro exato da linha em que ocorreu o problema. A mensagem de erro é exibida onde o testador pode vê- la: na tela do terminal para um programa texto ou em uma caixa de diálogo em um programa gráfico.

Mais importante, é possível alterar o comportamento de uma assertquando o programa foi inteiramente testado. Após uma certa opção ter sido configurada no compilador, os comandos as-

Sintaxe 5.6: Asserção

assert(expression); Exemplo:

assert(x >= 0); Finalidade:

Se a expressão é verdadeira, nada faz. Se a expressão é falsa, termina o programa, exi- be o nome do arquivo e a expressão.

sertsão simplesmente ignorados. Nenhum teste demorado é realizado, nenhuma mensagem de erro é gerada e o programa nunca aborta.

Ao escrever um função, como você deve tratar entradas incorretas? Você deve terminar ou vo- cê deve falhar com segurança? Considere a sqrt. Seria uma tarefa fácil implementar uma função de raiz quadrada que retornasse 0 para valores negativos e a raiz quadrada real para valores positi- vos. Suponha que você use esta função para calcular os pontos de interseção de um círculo e uma linha. Suponha que eles não se interceptam, mas você esqueceu de considerar esta possibilidade. Agora a raiz quadrada de um número negativo irá retornar um valor errado, exatamente 0, e você irá obter dois pontos de interseção espúrios (na realidade, você vai obter o mesmo ponto duas ve- zes). Você pode esquecer isso durante o teste e o programa faltoso pode entrar em produção.

Isso não é um grande problema para um programa gráfico, mas suponha que o programa diri- ge uma broca dentária robotizada. Ele poderia começar a furar em algum lugar fora da boca. Isto torna a terminação uma alternativa atraente. É duro negligenciar a terminação durante os testes, e seria melhor se a broca parasse antes de atingir as gengivas do paciente.

Aqui está o que você deve fazer ao escrever uma função:

1. Estabeleça pré-condições claras para todas as entradas. Escreva nos comentários @paramque valores você não deseja tratar.

2. Escreva comandos assertque garantam as pré-condições.

3. Certifique-se de fornecer resultados corretos para todas as entradas que atendem a pré-condição.

Aplique esta estratégia para a função future_value: /**

Calcular o valor de um investimento com taxa de juro composto @param initial_balance o valor inicial de um investimento @param p a taxa de juro por período, em percentagem

@param n a quantidade de períodos que o investimento é mantido @return o saldo após n períodos

*/

double future_value(double initial_balance, double p, int n) {

assert(p >= 0); assert(n >= 0);

return initial_balance * pow(1 + p / 100, n); }

Anunciamos que pendevem ser ≤ 0. Tal condição é a pré-condição da função future_va- lue. A função é responsável somente pelo tratamento de entradas que atendem a pré-condição. Ela é livre para fazer qualquer coisa se a pré-condição não é atendida. Seria perfeitamente legal se a fun- ção reformatasse o disco rígido cada vez que fosse chamada com uma entrada incorreta. Naturalmen- te, isto não é razoável. Em vez disso, verificamos a pré-condição com um comando assert. Se uma função é chamada com uma entrada incorreta, o programa termina. Isto pode não ser “gentil”, mas é legal. Lembre que uma função pode fazer qualquer coisa se a pré-condição não é atendida. Outra alternativa é deixar a função falhar com segurança, retornando um valor default quando a função é chamada com uma taxa de juro negativa.

/**

Calcular o valor de um investimento com taxa de juro composto @param initial_balance o valor inicial de um investimento @param p a taxa de juro por período, em percentagem

@param n a quantidade de períodos que o investimento é mantido @return o saldo após n períodos

double future_value(double initial_balance, double p, int n) {

if (p >= 0)

return initial_balance * pow(1 + p / 100, n); else

return 0; }

Existem vantagens e desvantagens nesta abordagem. Se o programa que chama a função futu- re_valuepossui alguns defeitos que causam a passagem de uma taxa de juro negativa como um valor de entrada, então a versão com a asserção vai tornar óbvios os defeitos durante os testes — é difícil ignorar quando o programa aborta. A versão falha-segura, por outro lado, irá silenciosamen- te retornar 0 e você pode não notar que ela executa alguns cálculos errados como conseqüência.

Bertre Meyer [1] compara pré-condições a contratos. Uma função promete calcular a resposta correta para todas as entradas que atendem a pré-condição. O invocador promete nunca chamar uma função com entradas ilegais. Se o invocador honra sua promessa e recebe uma resposta errada, ele pode levar a função ao tribunal dos programadores. Se o invocador não honra a sua promessa e algo terrível acontece como conseqüência, ele não tem a quem recorrer.