“C++ is really a terrible language!”

Linus Torvalds

16 - Técnicas de Programação

16.1 - Desenvolvimento de algoritmos.

Algoritmos são sequências finitas e bem definidas de passos destinadas à resolução de um problema. Em geral, um algoritmo descreve a lógica da solução sem depender diretamente de uma linguagem de programação específica, embora possa ser expresso em pseudocódigo, fluxogramas ou em uma linguagem formal. Além de produzir uma resposta correta, um algoritmo também pode ser avaliado por critérios como tempo de execução, uso de memória, clareza e facilidade de manutenção.

Desenvolver um bom algoritmo exige compreender corretamente o problema antes de tentar codificá-lo. Isso inclui identificar entradas, saídas, restrições, casos especiais e critérios de sucesso. Em contextos reais, essa compreensão pode depender de entrevistas com usuários, leitura de especificações, análise de sistemas legados e refinamento sucessivo dos requisitos.

Durante a elaboração do algoritmo, é importante abstrair detalhes que não são essenciais naquele momento e concentrar-se apenas nos elementos fundamentais da solução. Essa separação entre o problema e sua implementação ajuda a evitar complexidade desnecessária e torna o raciocínio mais claro.

Quando o algoritmo possui ramificações, isto é, pontos em que uma condição leva a caminhos diferentes, convém prever explicitamente os cenários mais relevantes, inclusive situações de erro, ausência de dados, valores inválidos e casos de fronteira. Um algoritmo aparentemente correto pode falhar justamente por não considerar essas possibilidades.

De forma simplificada, muitos algoritmos podem ser descritos a partir de três etapas básicas:

  • entrada
  • processamento
  • saída

A entrada corresponde aos dados recebidos pelo sistema, que podem vir de usuários, arquivos, sensores, bancos de dados ou outros programas. O processamento é o conjunto de operações que transforma esses dados de acordo com os objetivos do problema. A saída é o resultado produzido, que pode ser exibido na tela, armazenado, transmitido pela rede ou utilizado por outro componente do sistema.

Depois de definida a solução em nível algorítmico, o programador escolhe a linguagem, as bibliotecas e o ambiente de desenvolvimento mais adequados para implementá-la. Ferramentas como IDEs e editores de código ajudam na escrita, organização, depuração e teste do programa, mas não substituem a necessidade de um algoritmo bem concebido.

16.2 - Tipos de dados básicos e estruturados.

Todo programa precisa armazenar e manipular valores. Para isso, utilizamos variáveis, constantes e estruturas de dados. Em nível mais baixo, esses valores são representados internamente por sequências de bits na memória; em nível de linguagem, porém, trabalhamos com tipos que dão significado a esses dados e determinam quais operações podem ser realizadas sobre eles.

Entre os tipos básicos mais comuns estão os inteiros, os números de ponto flutuante, os caracteres, os valores lógicos e, em muitas linguagens, cadeias de caracteres. Os nomes exatos variam conforme a linguagem, mas a ideia geral é semelhante:

  • int
  • long
  • float / double
  • char
  • bool
  • string

É importante consultar a especificação da linguagem para verificar intervalo de valores, precisão, forma de armazenamento e conversões permitidas entre tipos. Por exemplo, a representação de caracteres pode depender de padrões como ASCII ou Unicode, e a faixa de um tipo inteiro pode variar de uma plataforma para outra.

Além dos tipos básicos, também utilizamos tipos estruturados ou compostos, que agrupam múltiplos valores sob uma mesma abstração. Vetores, registros, estruturas, listas, pilhas, filas, árvores e objetos são exemplos de construções que organizam dados de forma mais adequada ao problema. Em uma estrutura, cada campo representa uma parte da informação agregada:

    struct Data {
        int dia;
        int mes;
        int ano;
        char* observacao;
    };

Nesse exemplo, uma data foi representada por um conjunto de campos relacionados. Essa ideia é importante porque permite modelar entidades do mundo real e separar melhor as responsabilidades do programa.

16.3 - Comandos de uma Linguagem de programação.

Cada linguagem de programação define sua própria sintaxe, mas há grupos de comandos e construções que aparecem com frequência na maior parte delas. Entre os elementos mais comuns, podemos destacar:

  • Declarações de variáveis e constantes;
  • Condicionais;
  • Funções, procedimentos ou métodos;
  • Laços (Loops);
  • Operadores (lógicos, aritméticos e relacionais)

Condicionais e laços pertencem ao grupo das estruturas de controle, pois determinam a ordem em que as instruções são executadas. Já declarações, atribuições e chamadas de função servem para construir a lógica detalhada do programa.

Ao declarar variáveis ou constantes, normalmente informamos um identificador, um tipo e, opcionalmente, um valor inicial. Em muitas linguagens, isso aparece de forma semelhante a:

    int idade = 40;
    const int IDADE_MAXIMA = 150;

Nesse exemplo, `idade` é uma variável inteira inicializada com o valor `40`, enquanto `IDADE_MAXIMA` representa uma constante inteira. Dependendo da linguagem, a sintaxe exata pode mudar.

Estruturas condicionais são usadas quando o fluxo do programa depende de uma condição. O padrão mais comum é `if-else`, no qual uma expressão lógica é avaliada e, com base no resultado, um bloco de instruções é executado:

    if (idade == 40) {
        print("Parabéns quarentão!");
    } else if (idade < 40) {
        print("Você é jovem ainda!");
    } else {
        print("Lá vamos nós!");
    }

As estruturas de repetição permitem executar um mesmo bloco várias vezes. Entre as mais conhecidas estão `for`, `while` e `do-while`. Elas são úteis para percorrer coleções, repetir cálculos e processar dados até que certa condição seja satisfeita:

    for (int i = 0; i < 10000; i++) {
        print(i);
    }

Expressões são combinações de operandos e operadores. Os operandos podem ser variáveis, constantes, chamadas de função ou resultados intermediários. Os operadores mais comuns são:

  • Aritméticos (`+`, `-`, `*`, `/`, `%`): representam operações matemáticas;
  • Lógicos (`&&`, `||`, `!` ou equivalentes): combinam ou negam condições booleanas;
  • Relacionais (`<`, `>`, `<=`, `>=`, `==`, `!=` ou equivalentes): comparam valores.

Funções, procedimentos ou métodos encapsulam trechos reutilizáveis de comportamento. Em geral, recebem parâmetros, processam dados e, em muitos casos, retornam um resultado. Um exemplo clássico é a implementação de uma função para calcular as raízes de uma equação do segundo grau:

    Raizes bhaskara(float a, float b, float c) {
        float delta = b * b - 4 * a * c;
        float x1 = (-b - sqrt(delta)) / (2 * a);
        float x2 = (-b + sqrt(delta)) / (2 * a);
        return {x1, x2};
    }

O exemplo acima é apenas ilustrativo. Em um programa real, seria necessário tratar casos particulares, como `a = 0` ou `delta < 0`, além de adaptar a sintaxe à linguagem escolhida.

16.4 - Metodologia de desenvolvimento de programas.

O desenvolvimento de programas costuma seguir um processo iterativo, mesmo quando é apresentado de forma linear em livros introdutórios. Em linhas gerais, podemos dividir esse processo em algumas etapas: compreensão do problema, levantamento de requisitos, decomposição da solução, escrita do algoritmo, implementação, testes e manutenção.

Na fase de análise, o objetivo é entender o que o programa deve fazer, quais dados manipula, quais restrições existem e como o resultado será validado. Em seguida, procura-se decompor o problema em partes menores, que possam ser resolvidas separadamente e depois integradas. Essa decomposição costuma ser registrada em pseudocódigo, diagramas ou descrições textuais estruturadas.

A implementação transforma essa solução em código executável. Depois disso, entram atividades como compilação, depuração, testes com casos normais e extremos, revisão do comportamento obtido e refinamentos sucessivos. Mesmo programas simples raramente ficam prontos na primeira versão: corrigir defeitos, melhorar legibilidade e adaptar a solução a novas exigências faz parte do trabalho cotidiano de programação.

16.5 - Modularidade e abstração.

Modularidade é a técnica de dividir um programa em partes menores, chamadas módulos, funções, classes, componentes ou pacotes, de modo que cada parte tenha uma responsabilidade relativamente bem definida. Essa divisão facilita a leitura do código, o reaproveitamento, os testes e a manutenção.

Abstração, por sua vez, consiste em destacar apenas os aspectos relevantes de uma entidade ou operação, escondendo detalhes internos que não precisam ser conhecidos em todos os níveis do sistema. Quando usamos uma função para ordenar uma lista, por exemplo, normalmente nos preocupamos com o que ela faz, e não com cada detalhe interno de sua implementação.

Programas modulares e com bom uso de abstração tendem a apresentar menor acoplamento entre partes, maior coesão interna e mais facilidade de evolução. Esses princípios são fundamentais tanto em programas pequenos quanto em sistemas de grande porte.