Notas sobre o Problema 1

Depois de uma primeira "vista de olhos" pelas resoluções do Problema 1 queríamos alertá-los para alguns dos erros mais comuns (ou mais graves).

1.  É fundamental  ler atentamente o enunciado e cumprir escrupulosamente as instruções de cada uma das alíneas.  Cada alínea tem determinados objectivos que não podem ser avaliados quando as soluções ignoram o enunciado e "dão a volta à situação" de um outro modo.  Em certos casos torna-se difícil perceber se o aluno não conseguiu responder à questão ou se implementou outra solução porque "achava que era melhor".  Neste problema as "fugas" ao enunciado são levemente penalizadas.  Em problemas futuros serão penalizadas de um modo mais severo.

2.  A utilização de chamadas recursivas para evitar a utilização de um ciclo não é uma boa ideia.  Em vez de:

void menu()
{
    ...

    if(opcao_errada)
        menu()

    ...
}

deve escrever algo como:
void menu()
{
    ...

    do {
        ...
    } while(opcao_errada);

    ...
}

3.  Quando são chamadas funções, o seu resultado deve ser tido em conta.  Chamar, por exemplo, a função int inteiroPedidoEntre(int n, int m) sem utilizar o resultado devolvido é uma perda de tempo dado que aquilo que foi "calculado" pela função se perdeu depois do seu retorno.

4.  A utilização da instrução goto é absolutamente proibida.  Todas as situações em que tiver a tentação de utilizar um goto podem ser resolvidas utilizando ciclos ou chamadas a funções/procedimentos.  O uso de goto pode dar origem a erros graves (de difícil detecção), dado que é complicado saber em qualquer ponto do código quais são as pré-condições válidas antes da execução de uma instrução que esteja próxima de uma etiqueta.  Além disso, código escrito com goto é praticamente ilegível

5.  As variáveis criadas devem ser inicializadas tão próximo quanto possível do momento da sua definição (construção).  Não acatar esta regra pode dar origem a situações como a seguinte:

int i;

while(i != 0) {
    ...
}

este código poderá ter efeitos completamente diferentes em cada execução (ou em máquina, sistemas operativos ou com compiladores diferentes), dado que o valor de i não é conhecido no momento da avaliação da guarda.

6.  As instruções de asserção (assert()) servem para assinalar erros do programador consumidor das  funções e procedimentos (podemos ser nós próprios ou outra pessoa), ou erros do programador produtor dessa mesma função ou procedimento, e não para assinalar erros do utilizador.  Pode ser uma boa ferramenta para verificar a validade das pré-condições ou das condições objectivo, mas não para terminar abruptamente o programa quando um utilizador se engana numa tecla.  Os erros do utilizador devem ser tratados de modo a que o utilizador possa corrigir o erro cometido ou a que o programa termina graciosamente.

7.  Os programas devem ser coerentemente indentados, de um modo que torne mais fácil a leitura.  Não escreva instruções à frente de uma chaveta de abertura ({) nem antes de uma chaveta de fecho (}).  Use a tecla <tab> no editor Emacs para indentar automaticamente uma linha do seu programa, ou o <ctrl-c s> para indentar todo o programa.  Uma falha na indentação automática pode, em muitos casos, alertá-lo para um erro.  Siga os conselhos sobre indentação, nome de funções, procedimentos e variáveis que constam das folhas teóricas.

8.  Não usem a mesma instrução no final das instruções compostas alternativas de uma instrução de selecção como no exemplo abaixo:

if(...) {
    ...
    fazQualquerCoisa();
} else {
    ...
    fazQualquerCoisa();
}
É muito mais prático, elegante e eficiente substituir o código acima por
if(...) {
    ...
} else {
    ...
}
fazQualquerCoisa();
uma vez que o procedimento fazQualquerCoisa() é executado quer a condição do if seja verdadeira, quer seja falsa.

9.  Não deve alterar a variável de controlo de um ciclo, excepto através do progresso.  Exemplos como o que se segue são perigosos:

for(int i = 0; i != n; ++i) {
    ...
    if(...) {
        i = n;
    }
    ...
}
Repare que neste caso estamos a dar origem a um ciclo infinito!  Ao executar o progresso, após ter executado a atribuição i = n, o valor de i passa a ser n + 1 e o ciclo não irá parar até atingir de novo o valor n (cerca 4 mil milhões de iterações depois, se alguém não terminar o programa antes com falta de paciência para esperar...).  Usar a guarda i <= n parece resolver este problema, mas impede a detecção do erro causado por esta atribuição infeliz, o que poderá ter consequências nefastas noutro ponto da execução do
programa.

O código acima deve ser substituido por algo como:

bool termina = false;

for(int i = 0; i != n and !termina; ++i) {
    ...
    if(...) {
        termina = true;
    }
    ...
}

ou por uma outra solução ainda mais adequada, desde que todas as condições que provocam a saída do ciclo estejam bem patentes na guarda do ciclo.

10.  Uma das principais razões para dividir o código em funções e procedimentos é a possibilidade da sua reutilização.  Quando constrói uma função (ou procedimento) pense sempre se o código que está a escrever é reutilizável em outras situações.  Uma função como a que se segue:

int soma(int x, int y)
{
    cout << x + y << endl;
    return x + y;
}
só pode ser utilizada caso o programador queira fazer a soma e enviar o resultado para o ecrã.  Imagine que todas as operações aritméticas que precisa de fazer nos seus programas enviavam automaticamente dados para o ecrã...  Os seus menus iriam ter montes de números pelo meio, ninguém perceberia nada do que estava a fazer.

É muito mais adequado escrever a função sem o cout, podendo o programador, se o desejar, mostrar o resultado no ecrã fazendo:

int soma(int x, int y)
{
    return x+y;
}

...

cout << soma(2, 3) << endl;

...

Ao programador fica também a opção de utilizar a mesma função sem mostrar nada no ecrã, apenas para calcular uma soma:
int z = soma(4, 5);
Assim, sempre que desenvolvemos uma função ou procedimento, não devemos inserir nem extrair nada de um canal, a não ser que tal seja parte integrante do funcionamento do módulo em causa (cujo nome deve reflectir esse facto, e.g., void escreveFracção(2, 3)).

11.  Sugere-se que contactem os docentes das aulas prática (Luis Nunes e Ricardo Ribeiro) durante os horários de dúvidas para verem os erros que fizeram no Problema 1 de modo a não os repetirem no Problema 2.