SkillsTecnológicas
Menu
Conteúdo da trilha

Projeto final: da descrição do problema ao algoritmo

Construa um projeto completo, transformando requisitos em dados, funções, pseudocódigo, testes e análise de eficiência.

Conteúdo 52 de 52

Requisitos soltos atravessam validação, matriz e módulos até formar um relatório verificado

Construa uma solução completa, não apenas um trecho de pseudocódigo

Este projeto final reúne o percurso da trilha em uma entrega verificável. Você receberá uma descrição de problema, transformará expectativas em regras explícitas, modelará os dados, dividirá responsabilidades, escreverá o algoritmo, criará testes e justificará a eficiência.

O desafio é construir um analisador de consumo de energia. Ele recebe leituras organizadas por dia e período, calcula indicadores e sinaliza os dias cujo total ultrapassa um limite definido. Os dados são hipotéticos: o objetivo é praticar lógica de programação, não interpretar uma conta de energia real.

Ao terminar, você terá um pequeno portfólio técnico composto por:

  1. contrato do problema;
  2. exemplos e casos de teste com resultados esperados;
  3. decomposição em funções;
  4. pseudocódigo completo;
  5. teste de mesa;
  6. análise de correção e eficiência;
  7. reflexão sobre decisões e possíveis extensões.

Esse formato segue uma prática importante: uma solução precisa comunicar o algoritmo, demonstrar como ele funciona, sustentar sua correção e analisar seu custo. O projeto não termina quando o pseudocódigo parece plausível.

Seis etapas ligam a definição do problema à avaliação, com um retorno de revisão quando testes revelam ambiguidades
O processo é progressivo, mas não rígido: uma descoberta nos testes pode exigir a revisão do contrato ou da decomposição.

Comece pelo enunciado do projeto

Uma organização registra o consumo de energia de vários dias. Cada dia possui a mesma quantidade de períodos de medição. O algoritmo deve produzir um relatório com:

  • total consumido em todos os dias;
  • média por leitura;
  • total de cada dia;
  • maior leitura e sua posição;
  • dias cujo total ficou acima de um limite diário;
  • quantidade de dias sinalizados.

Considere a matriz abaixo, em quilowatt-hora, apenas como exemplo:

leituras = [
    [2, 3, 1],
    [4, 2, 2],
    [1, 5, 3]
]

limiteDiario = 7

Não escreva o algoritmo ainda. Primeiro identifique o que o texto afirma, o que ele deixa implícito e quais decisões precisam ser registradas. Requisitos bem formados reduzem interpretações incompatíveis entre quem pede, constrói e testa uma solução.

Transforme o enunciado em um contrato testável

Um contrato descreve entradas permitidas, resultados e comportamento diante de situações excepcionais. Ele não precisa mencionar variáveis locais nem antecipar todos os detalhes da implementação.

Use este contrato para o projeto:

Entradas válidas

  • leituras é uma matriz não vazia;
  • todas as linhas possuem a mesma quantidade positiva de colunas;
  • cada leitura é numérica, finita e maior ou igual a zero;
  • limiteDiario é numérico, finito e maior que zero.

Regras

  • um dia é sinalizado somente quando seu total é maior que o limite;
  • total igual ao limite não gera alerta;
  • se duas leituras tiverem o mesmo valor máximo, vence a primeira na ordem de percurso;
  • dias e períodos são apresentados a partir de 1 para o usuário;
  • qualquer entrada inválida interrompe o processamento antes da criação do relatório.

Saída

O relatório contém totalGeral, mediaPorLeitura, totaisPorDia, maiorLeitura, diaDoPico, periodoDoPico, diasAcimaDoLimite e quantidadeDeAlertas.

Entradas passam por regras explícitas e produzem o relatório, enquanto dados inválidos seguem para uma saída de erro
Decisões pequenas, como a fronteira do limite e o desempate, precisam estar no contrato para que o teste tenha um resultado único.

O contrato também delimita o escopo. Conversão de moedas, preço de tarifa, persistência, interface e previsão de consumo não pertencem a esta entrega. Esse recorte impede que o projeto cresça antes de sua base estar correta.

Se quiser revisar esse raciocínio, retome como validar entradas e regras de negócio.

Calcule o exemplo esperado antes de implementar

O resultado esperado funciona como oráculo de teste: a referência usada para decidir se a saída observada está correta. Calcule-o sem depender do algoritmo que será testado.

Para a matriz proposta:

MedidaResultado esperado
Totais por dia[6, 8, 9]
Total geral23
Quantidade de leituras9
Média por leitura23 ÷ 9 ≈ 2,56
Maior leitura5
Posição do picodia 3, período 2
Dias acima de 7[2, 3]
Quantidade de alertas2

Não arredonde dentro do algoritmo, a menos que o contrato defina essa regra. O relatório pode formatar a média com duas casas para exibição, enquanto preserva o valor calculado internamente.

Essa separação evita um erro frequente: fazer o teste repetir exatamente a mesma lógica da implementação e, assim, reproduzir o mesmo defeito dos dois lados.

Projete os testes antes da solução

Testar primeiro pressiona o contrato: ao tentar prever resultados, você descobre ambiguidades antes de investir na implementação. Escolha casos de famílias diferentes, não apenas matrizes maiores com o mesmo comportamento.

CasoEntrada essencialRegra verificadaResultado esperado
caminho comumexemplo de três diascálculos integradosrelatório calculado acima
fronteira[[3, 4]], limite 7igual não é acimanenhum alerta
acima por pouco[[3, 4.1]], limite 7comparação estritadia 1 sinalizado
empate no pico[[5, 1], [2, 5]]primeira ocorrência vencedia 1, período 1
mínimo válido[[0]], limite 1zero é leitura válidatotal 0, sem alerta
matriz irregular[[1, 2], [3]]formato retangularerro, sem relatório
domínio inválido[[2, -1]]consumo não negativoerro, sem relatório
limite inválido[[1]], limite 0limite positivoerro, sem relatório
Cinco famílias de testes associam caminho comum, fronteira, empate, formato e domínio às evidências esperadas
Uma suíte enxuta ganha força quando cada caso representa uma regra ou um risco diferente.

A técnica de particionar o espaço de entradas procura representantes de grupos com comportamento semelhante. Os limites merecem atenção especial porque uma troca entre > e >= costuma falhar exatamente na fronteira. A aula sobre casos extremos e entradas inválidas aprofunda essa distinção.

Modele dados e estados necessários

A matriz expressa duas dimensões independentes:

linhas   → dias
colunas  → períodos de cada dia

Durante o percurso, o algoritmo precisa manter apenas estados com finalidade clara:

  • totalGeral: soma de todas as leituras processadas;
  • quantidadeDeLeituras: divisor da média;
  • totaisPorDia: resultado agregado de cada linha;
  • maiorLeitura: maior valor conhecido até o momento;
  • diaDoPico e periodoDoPico: posição associada ao maior valor;
  • diasAcimaDoLimite: dias sinalizados após o fechamento de cada linha.

Inicialize maiorLeitura com a primeira leitura válida, não com zero por hábito. Neste projeto, zero funcionaria porque valores negativos são proibidos, mas depender silenciosamente dessa restrição torna a lógica menos reutilizável. A primeira leitura conecta a inicialização diretamente ao domínio aceito.

Escolha também quando cada estado pode mudar. O pico é atualizado durante o percurso das colunas; o total diário só pode ser comparado ao limite depois que a linha inteira foi somada.

Decomponha a solução por responsabilidade

Uma única função pode calcular tudo, mas mistura validação, percurso, desempate e montagem do relatório. Divida a solução em partes que possuam entrada, saída e motivo próprios:

validarEntradas(leituras, limiteDiario)
analisarDia(linha, indiceDoDia, estadoDoPico)
atualizarPico(valor, dia, periodo, estadoDoPico)
montarRelatorio(estados)
analisarConsumo(leituras, limiteDiario)

analisarConsumo coordena o fluxo. Ela não deve esconder todas as operações novamente; chama funções menores e conecta seus resultados.

A função analisarConsumo coordena quatro funções responsáveis por validação, soma diária, atualização do pico e montagem do relatório
A decomposição é útil quando reduz o número de razões para alterar ou testar cada parte.

Não fragmente operações triviais apenas para aumentar a quantidade de funções. O objetivo de dividir um algoritmo em partes é tornar responsabilidades observáveis, e não trocar uma função longa por dezenas de nomes sem significado.

Escreva primeiro a visão coordenadora

Comece pelo nível que conta a história do algoritmo:

função analisarConsumo(leituras, limiteDiario):
    validarEntradas(leituras, limiteDiario)

    totalGeral <- 0
    quantidadeDeLeituras <- 0
    totaisPorDia <- vetor vazio
    diasAcimaDoLimite <- vetor vazio

    maiorLeitura <- leituras[0][0]
    diaDoPico <- 0
    periodoDoPico <- 0

    para dia de 0 até quantidadeDeLinhas(leituras) - 1:
        totalDoDia <- 0

        para periodo de 0 até quantidadeDeColunas(leituras) - 1:
            valor <- leituras[dia][periodo]
            totalDoDia <- totalDoDia + valor
            totalGeral <- totalGeral + valor
            quantidadeDeLeituras <- quantidadeDeLeituras + 1

            se valor > maiorLeitura:
                maiorLeitura <- valor
                diaDoPico <- dia
                periodoDoPico <- periodo

        adicione totalDoDia em totaisPorDia

        se totalDoDia > limiteDiario:
            adicione dia + 1 em diasAcimaDoLimite

    mediaPorLeitura <- totalGeral / quantidadeDeLeituras

    retorne relatório com:
        totalGeral
        mediaPorLeitura
        totaisPorDia
        maiorLeitura
        diaDoPico + 1
        periodoDoPico + 1
        diasAcimaDoLimite
        tamanho(diasAcimaDoLimite)

A condição do pico usa > em vez de >=. Assim, um valor igual não substitui a posição já registrada e a primeira ocorrência vence, conforme o contrato.

Observe também a diferença entre índices internos e posições apresentadas. A matriz usa índices iniciados em zero; o relatório converte dia e período para números iniciados em um. Misturar essas convenções durante o percurso é uma fonte comum de erros de deslocamento.

Faça a validação proteger o restante do algoritmo

Escreva a validação de forma que, após seu sucesso, o restante possa confiar nas pré-condições:

função validarEntradas(leituras, limiteDiario):
    se leituras não é uma matriz ou quantidadeDeLinhas(leituras) = 0:
        gere erro "Informe ao menos um dia"

    se limiteDiario não é número finito ou limiteDiario <= 0:
        gere erro "O limite diário deve ser positivo"

    quantidadeDePeriodos <- tamanho(leituras[0])

    se quantidadeDePeriodos = 0:
        gere erro "Informe ao menos um período"

    para cada linha em leituras:
        se tamanho(linha) != quantidadeDePeriodos:
            gere erro "Todos os dias devem ter a mesma quantidade de períodos"

        para cada valor em linha:
            se valor não é número finito ou valor < 0:
                gere erro "Cada leitura deve ser um número não negativo"

Validar toda a matriz antes de produzir o relatório garante uma propriedade simples: ou há um resultado completo, ou há um erro. Não existe saída parcialmente calculada que possa ser confundida com sucesso.

Em uma implementação real, a linguagem define como representar erros, números não finitos e matrizes. Adapte a sintaxe sem alterar o contrato observável.

Rastreie o exemplo e localize divergências

Faça um teste de mesa com rastreamento de variáveis para o caso principal. Não registre cada detalhe mecânico; acompanhe os estados que sustentam o relatório.

Etapa concluídatotalDoDiatotalGeralmaiorLeituradiasAcimaDoLimite
dia 1663[]
dia 28144[2]
dia 39235[2, 3]

Ao fim, quantidadeDeLeituras vale 9, e a média é 23 / 9. O pico foi encontrado no segundo período do terceiro dia.

Uma matriz de três dias alimenta totais diários, total geral, pico e alertas atualizados após cada linha
Se o resultado final estiver errado, compare a primeira linha divergente em vez de examinar todo o algoritmo ao mesmo tempo.

Agora rastreie o caso de empate [[5, 1], [2, 5]]. O segundo 5 não atualiza o pico porque não é maior que o valor registrado. Se sua posição final for dia 2, período 2, a implementação descumpriu a regra de desempate.

Argumente por que a solução está correta

Testes aumentam a confiança, mas não cobrem todas as matrizes possíveis. Complete a verificação com propriedades que permanecem verdadeiras durante o percurso:

  • após processar uma leitura, totalGeral é a soma de todas as leituras vistas até ali;
  • após encerrar uma linha, totalDoDia é a soma de todos os períodos daquele dia;
  • maiorLeitura é o maior valor entre todas as posições já visitadas;
  • a posição do pico aponta para a primeira ocorrência desse valor;
  • diasAcimaDoLimite contém exatamente os dias completos cujo total é maior que o limite.

Essas propriedades são invariantes: afirmações preservadas a cada repetição. A inicialização as torna verdadeiras antes do percurso; cada atualização as mantém; quando os laços terminam, elas implicam os campos centrais do relatório.

Revise também o término. Os dois laços percorrem intervalos finitos, e seus índices avançam uma posição por iteração. Portanto, toda entrada que passou pela validação chega ao final.

Analise o custo sem contar apenas laços visíveis

Defina:

d = quantidade de dias
p = quantidade de períodos por dia
n = d × p = quantidade total de leituras

A validação visita as n leituras uma vez. O cálculo principal também visita as n leituras uma vez. Os dois percursos são sequenciais:

T(n) = n + n + trabalho constante

Logo, o tempo é Θ(n), ou Θ(d × p). Não é necessário multiplicar os dois percursos entre si; eles não estão aninhados um dentro do outro.

O relatório armazena um total para cada dia e, no pior caso, todos os dias na lista de alertas. Portanto, o espaço auxiliar é Θ(d). Os demais acumuladores ocupam espaço constante.

Esse tempo é apropriado ao requisito: para calcular a soma e descobrir o maior valor de dados arbitrários, cada leitura precisa ser observada pelo menos uma vez. A introdução à eficiência de algoritmos mostra como ligar a classe à contagem, em vez de deduzi-la apenas pelo formato do código.

Avalie a entrega com uma rubrica objetiva

Use a rubrica abaixo antes de considerar o projeto concluído. Cada critério vale de 0 a 2 pontos:

Critério012
contratoausenteparcial ou ambíguoentradas, regras, saídas e erros claros
modelagemestados sem propósitomaioria adequadadados e estados mínimos bem justificados
decomposiçãobloco único confusofunções pouco coesasresponsabilidades claras e conectadas
algoritmoincompletofunciona no caso comumcumpre contrato, fronteiras e desempate
testesexemplos sem oráculopoucos riscos cobertospartições, fronteiras, inválidos e empate
rastreamentoausenteapenas resultado finalestados intermediários localizam divergências
eficiênciarótulo sem justificativacontagem parcialdimensões, tempo e espaço explicados
comunicaçãodifícil de reproduzircompreensível com lacunasoutra pessoa consegue revisar e executar

Interpretação sugerida:

  • 13 a 16 pontos: entrega consistente;
  • 9 a 12 pontos: base funcional, com revisões localizadas;
  • 0 a 8 pontos: retorne ao contrato e aos testes antes de acrescentar recursos.

A pontuação serve para orientar revisão, não para esconder defeitos. Uma entrada inválida aceita silenciosamente continua sendo um problema mesmo que as outras categorias estejam boas.

Estenda somente depois da versão básica estar correta

Escolha uma extensão por vez e atualize contrato, testes e custo:

  1. aceitar limites diferentes para cada dia;
  2. informar também o período com maior total acumulado;
  3. permitir leituras ausentes, definindo se devem ser rejeitadas ou ignoradas;
  4. comparar a semana atual com outra matriz de mesma dimensão;
  5. receber os dados em fluxo, sem armazenar toda a matriz;
  6. implementar a solução em uma linguagem e automatizar os testes.

Cada mudança altera decisões. Se leituras ausentes forem ignoradas, por exemplo, a média deve dividir apenas pela quantidade válida. Se os dados chegarem em fluxo, talvez não seja possível validar toda a entrada antes de calcular; o contrato de erro precisa ser redesenhado.

Não considere uma extensão concluída porque o caso feliz funcionou. Acrescente seu caso de teste, atualize a análise de eficiência e registre o que mudou na rubrica.

Entregue evidências, não apenas a resposta final

Organize sua entrega nesta ordem:

1. problema reescrito em uma frase
2. contrato de entradas, regras, saídas e erros
3. tabela de casos de teste e resultados esperados
4. decomposição das responsabilidades
5. pseudocódigo completo
6. teste de mesa do caso principal e de uma fronteira
7. argumento de correção
8. análise de tempo e espaço
9. autoavaliação pela rubrica
10. uma extensão proposta, mesmo que ainda não implementada

Essa sequência permite que outra pessoa verifique a solução sem adivinhar suas intenções. Clareza não é acabamento decorativo: faz parte da qualidade técnica do algoritmo.

O que você consolidou na trilha

Você partiu de uma descrição informal e chegou a uma solução especificada, modular, testada e analisada. Para isso, combinou entrada e saída, tipos, matrizes, decisões, repetições, acumuladores, funções, validação, rastreamento e eficiência.

O resultado mais importante não é memorizar este pseudocódigo. É dominar um processo que pode ser reutilizado:

entender → especificar → exemplificar → decompor → construir → testar → analisar → comunicar

Este é o último conteúdo da sequência atual. Use o catálogo da trilha de Lógica de Programação como mapa de revisão: retorne ao conceito correspondente sempre que a rubrica revelar uma lacuna e refaça o projeto com outro domínio quando quiser consolidar o método.

Referências