Programação do zero: valores, decisões e repetições

O problema antes do código

Imagine uma ordem de serviço, ou OS, aberta porque um gerador apresentou superaquecimento. O sistema recebeu criticidade 4, indicação de risco à vida e três peças utilizadas. Ele precisa verificar os dados, classificar a prioridade e calcular a quantidade total de peças. Um ser humano pode explicar a regra assim:

  1. rejeite criticidade fora de 1 a 5;
  2. se houver risco à vida, a prioridade é imediata;
  3. senão, se a criticidade for 4 ou 5, a prioridade é alta;
  4. nos demais casos, a prioridade é normal;
  5. some as quantidades das peças.

Essa sequência finita e não ambígua é um algoritmo. Um programa é a representação executável desse algoritmo numa linguagem. Python será usado porque permite enxergar a regra com pouco ruído; os conceitos também existem em TypeScript, Java e outras linguagens.

O vocabulário mínimo

Um valor é um dado concreto: 4, True ou "Gerador". Um tipo classifica valores e determina operações possíveis. 4 é inteiro; True é booleano — verdadeiro ou falso —; "Gerador" é texto. Somar 4 + 2 faz sentido; somar "Gerador" + 2 não descreve uma operação definida por Python.

Uma variável associa um nome a um valor. Em criticidade = 4, o nome criticidade passa a referir-se ao inteiro 4. O símbolo = aqui significa atribuição, não uma pergunta matemática de igualdade. Uma expressão combina valores e operações e produz outro valor: criticidade >= 4 produz True ou False.

equipamento = "Gerador"       # texto: str
criticidade = 4               # inteiro: int
risco_vida = True              # booleano: bool
prioridade_alta = criticidade >= 4 or risco_vida

Leia a última linha da esquerda para a direita: compare a criticidade com 4; combine o resultado com o valor de risco_vida usando or (“ou”); associe o booleano final ao nome prioridade_alta. Os comentários após # explicam o código e não são executados.

O conjunto de valores relevantes em determinado momento é o estado do programa. Se depois executarmos criticidade = 5, o estado mudou: o mesmo nome passou a referir-se a outro valor. Essa alteração é uma mutação. Estado é necessário, mas muitas alterações espalhadas tornam difícil responder “quem mudou isso e quando?”.

Função: uma transformação com nome

Uma função reúne uma operação reutilizável. Ela recebe parâmetros, nomes disponíveis dentro dela, e pode produzir um retorno. O cabeçalho abaixo promete receber criticidade e risco; o bloco recuado contém a regra:

def classificar_prioridade(criticidade, risco_vida):
    if risco_vida:
        return "imediata"
    if criticidade >= 4:
        return "alta"
    return "normal"

Linha a linha:

  • def começa a definição; nada é classificado ainda.
  • classificar_prioridade é o nome escolhido para revelar a intenção.
  • os valores serão recebidos pelos parâmetros entre parênteses;
  • if significa “se”. Seu bloco executa somente quando a condição é verdadeira;
  • return encerra a função e entrega um valor ao chamador;
  • se as duas condições anteriores forem falsas, o último retorno produz "normal".

Uma chamada executa a função: classificar_prioridade(4, False) retorna "alta". O valor 4 ocupa o parâmetro criticidade; False ocupa risco_vida. O recuo não é decoração: em Python, ele delimita o bloco. A documentação oficial apresenta essas construções no tutorial de fluxo de controle.

O contrato da função inclui mais que seu nome. Devemos decidir: quais valores aceita, o que retorna e como sinaliza entrada inválida. A função atual aceita acidentalmente 999. O código executa, porém a regra do negócio fica errada. Tipos ajudam a explicar intenção, mas uma entrada externa ainda precisa ser validada.

Coleção e repetição: trabalhar com vários valores

Uma coleção agrupa valores. Uma lista preserva sequência e pode conter repetição. Cada peça da OS pode ser representada por um dicionário, coleção de pares chave–valor:

pecas = [
    {"codigo": "FUS-10", "quantidade": 2},
    {"codigo": "CAB-02", "quantidade": 1},
]

Os colchetes externos formam a lista; cada par de chaves forma um dicionário. A expressão pecas[0] acessa o primeiro item — índices começam em zero. A expressão pecas[0]["quantidade"] produz 2.

Uma repetição, também chamada laço, executa um bloco para vários itens:

def totalizar_pecas(pecas):
    total = 0
    for peca in pecas:
        total = total + peca["quantidade"]
    return total

Antes do laço, total vale zero. Em cada volta, peca representa o próximo dicionário e a expressão à direita calcula um novo total. Depois de duas voltas, os estados de total foram 0, 2 e 3. Finalmente, a função retorna 3.

Não confunda lista com conjunto. Um conjunto, set em Python, é adequado quando a pergunta dominante é “este valor já apareceu?” e não precisamos representar duplicatas. Um dicionário é adequado quando procuramos um valor por chave. A documentação de tipos nativos descreve as garantias dessas estruturas.

Entrada externa: texto não é confiança

Dados vindos de tela, arquivo, API — interface pela qual programas conversam — ou ERP — sistema integrado de gestão empresarial — estão fora do controle do núcleo. Mesmo que uma tela limite criticidade a 1–5, alguém pode chamar a API diretamente, uma versão antiga pode enviar outro formato ou uma integração pode estar defeituosa. A OWASP — Open Worldwide Application Security Project, comunidade que publica orientações de segurança, recomenda validar cedo tanto a forma quanto o significado, usando uma lista positiva do que é aceito (Input Validation Cheat Sheet).

def ler_criticidade(valor):
    if type(valor) is not int:
        raise ValueError("criticidade deve ser um inteiro")
    if valor < 1 or valor > 5:
        raise ValueError("criticidade deve estar entre 1 e 5")
    return valor

raise interrompe o caminho normal e produz uma exceção, um objeto que comunica falha. A primeira condição valida o tipo. A segunda valida o domínio permitido. Usamos type(valor) is not int intencionalmente porque, em Python, bool deriva de int; isinstance(True, int) seria verdadeiro, embora True não deva significar criticidade 1.

Validar não é autorizar. 4 pode ser uma criticidade válida, mas o usuário ainda pode não ter permissão para alterar aquela OS. Também não se deve concatenar uma entrada validada parcialmente numa consulta SQL — Structured Query Language, linguagem usada com bancos relacionais — ou comando de sistema; use parâmetros e APIs próprias da biblioteca. Limite ainda quantidade de itens e tamanho de texto para impedir consumo desproporcional de memória e processamento.

Núcleo puro e borda impura

Uma função pura depende apenas dos argumentos, não altera algo externo e, para a mesma entrada, devolve o mesmo resultado. classificar_prioridade pode ser pura. Gravar no ERP, ler relógio ou imprimir são efeitos colaterais, mudanças observáveis além do retorno.

Uma arquitetura simples separa:

entrada externa → validação → regra pura → gravação/saída

Isso não elimina efeitos; concentra-os nas bordas. A regra pode ser testada sem ERP, rede ou banco. Se amanhã a entrada deixar de vir do terminal e passar a vir por HTTP, a classificação não deveria mudar. Se mudar, transporte e negócio provavelmente estão acoplados.

Erro não é uma coisa só

Há três falhas iniciais importantes:

  1. Erro de sintaxe: o texto não forma um programa válido, como esquecer : depois de if. Python aponta arquivo e linha antes de executar a regra.
  2. Erro de execução: o programa começou, mas encontrou uma operação impossível naquele caminho, como acessar a chave inexistente peca["quantidades"]. A mensagem e o traceback mostram a sequência de chamadas.
  3. Erro lógico: o programa termina, mas entrega a resposta errada, como classificar criticidade 4 como normal. Só especificação, exemplo ou teste revela a divergência.

Diagnostique sem trocar código ao acaso: reproduza com a menor entrada; leia a última linha do erro; encontre a primeira linha do seu código no traceback; inspecione valores e tipos; formule uma hipótese; altere uma coisa; execute novamente. A referência oficial explica a anatomia de erros e exceções.

Falha provocada:

def totalizar_pecas_com_falha(pecas):
    total = 0
    for peca in pecas:
        total = peca["quantidade"]  # substitui em vez de acumular
    return total

Com quantidades 2 e 1, o resultado é 1, não 3. Não há exceção: é erro lógico. Uma tabela de rastreamento evidencia o problema.

Momento total correto total com falha
início 0 0
depois da primeira peça 2 2
depois da segunda 3 1

A correção é total = total + peca["quantidade"]. O teste impede o retorno acidental da falha.

Teste: exemplo que decide sozinho

Um teste automatizado prepara uma entrada, executa uma unidade e compara observado com esperado. assert significa “esta condição precisa ser verdadeira”:

assert classificar_prioridade(4, False) == "alta"
assert classificar_prioridade(1, True) == "imediata"
assert totalizar_pecas([]) == 0
assert totalizar_pecas([{"codigo": "F", "quantidade": 2}]) == 2

Esses testes cobrem decisões e a fronteira vazia. Ainda precisamos testar criticidades 1 e 5, tipo inválido, quantidade negativa e coleção excessiva. A biblioteca unittest organiza casos maiores e seus resultados na documentação oficial.

Custo: quanto o trabalho cresce

Se uma função percorre uma lista uma vez, o número de passos cresce aproximadamente junto com a entrada: chamamos isso de O(n), crescimento linear. Se para cada item ela percorre a lista inteira, o crescimento tende a O(n²). Essa complexidade assintótica não significa segundos e não prevê constantes, máquina ou tamanho pequeno. Uma solução O(n²) pode vencer para dez itens; em escala, sua curva cresce mais depressa.

Para detectar códigos duplicados, lista.count(codigo) dentro de um laço repete buscas e tende a O(n²). Guardar códigos já vistos num conjunto exige memória extra, mas oferece pertencimento com custo O(1) esperado — aproximadamente constante — e uma passagem O(n). O requisito decide: se a ordem da primeira duplicata importa, combine conjunto para busca e lista para saída. O laboratório mede as duas alternativas sem transformar “conjunto é melhor” em dogma.

Critérios de aceite e prática

Você dominou esta etapa quando consegue, sem copiar:

  • explicar cada termo destacado com um exemplo diferente de OS;
  • prever o retorno de uma função e registrar o estado a cada repetição;
  • validar tipo, faixa e tamanho antes da regra;
  • escrever testes de caminho feliz, fronteira e entrada inválida;
  • distinguir sintaxe, execução e lógica a partir de evidência;
  • trocar a borda terminal por HTTP, protocolo usado na comunicação web, sem modificar o núcleo da regra.

Exercício: modele uma OS com horas_parada, risco_vida e uma lista de peças. Escreva ler_horas_parada, classificar_prioridade e totalizar_pecas. Provoque uma falha trocando + por -, capture o teste que falha, corrija e registre antes/depois. Como transferência, use a mesma estrutura para triagem de chamados de TI: quais nomes mudam e quais conceitos permanecem?

Recuperação ativa: feche o capítulo e responda: um valor e uma variável são a mesma coisa? O que uma expressão produz? Quando um if não executa? Qual estado muda num laço? Por que tipo interno não substitui validação externa? Qual diferença entre erro de execução e lógico? Por que O(n) não quer dizer “um segundo”?

Continue no caderno operacional, onde você executará, quebrará, diagnosticará e medirá um programa completo.

Teste de fixação

Comprove o que você aprendeu

Responda todas as questões. O gabarito comentado só aparece depois do envio.

1. Um campo TypeScript é declarado `criticidade: number`. Por que a API ainda deve validar a entrada?
2. Uma implementação `O(n)` levou mais tempo que uma `O(n²)` para dez itens. Isso contradiz a análise?
3. A regra de prioridade muda quando a entrada sai de CLI e passa a HTTP. Qual sinal arquitetural isso oferece?

Consulta universal

O que você quer encontrar?

Títulos, capítulos, conceitos, termos, laboratórios e ferramentas em uma única busca.

Digite pelo menos dois caracteres.