Um problema do mundo real raramente chega pronto para ser resolvido por um computador. Uma pessoa pode dizer que precisa “organizar melhor as entregas”, “controlar o estoque”, “marcar consultas” ou “descobrir a rota mais rápida”. Para seres humanos, essas frases já carregam muitos significados implícitos. Para uma máquina, não. Antes que um computador possa ajudar, alguém precisa decidir quais partes da realidade serão representadas, quais informações serão usadas, quais regras deverão ser seguidas e o que exatamente será considerado uma solução.
Essa transformação é uma das atividades centrais da programação. O computador não recebe diretamente o problema como ele existe no mundo. Recebe uma representação do problema. Criamos uma versão mais organizada e limitada da realidade, adequada ao processamento computacional, e então construímos instruções capazes de trabalhar com essa representação.
Imagine uma clínica que deseja criar um sistema para organizar consultas. Na vida real existem pacientes, médicos, salas, horários, atrasos, cancelamentos, emergências, especialidades, exames e inúmeras situações particulares. Seria impossível começar simplesmente dizendo ao computador: “organize as consultas”. A expressão parece clara para uma pessoa porque conhecemos aproximadamente o funcionamento de uma clínica. Para a máquina, é necessário transformar essa intenção em informações e regras.
O primeiro passo é compreender o objetivo. Talvez a clínica queira apenas permitir que pacientes escolham horários disponíveis. Talvez também precise impedir que um médico tenha duas consultas ao mesmo tempo. Pode ser necessário considerar a duração de cada atendimento, permitir cancelamentos e reservar determinados horários para urgências. Cada nova necessidade modifica o problema que será representado.
Esse processo exige fazer perguntas que inicialmente podem parecer detalhes. O que é um horário disponível? Toda consulta possui a mesma duração? Um paciente pode marcar duas consultas no mesmo período? Quanto tempo antes é permitido cancelar? Um médico atende em mais de uma unidade? O sistema deve considerar feriados? O que acontece quando o médico falta? Essas perguntas revelam regras que estavam escondidas dentro da expressão aparentemente simples “marcar uma consulta”.
Depois de compreender melhor o problema, começa o trabalho de abstração. Abstrair significa escolher quais aspectos da realidade são importantes para o objetivo atual e deixar outros de lado. Não se trata de ignorar a realidade de maneira descuidada, mas de reduzir sua complexidade para conseguir trabalhar com ela.
Em um sistema de consultas, por exemplo, talvez seja importante representar o nome do médico, sua especialidade e os horários em que atende. A cor de seus olhos provavelmente não tem nenhuma utilidade para o agendamento. As duas características existem no mundo real, mas apenas uma delas participa do problema que estamos tentando resolver.
Toda representação computacional funciona dessa maneira. Um aplicativo de mapas não precisa reproduzir cada árvore, janela e rachadura existente nas ruas. Para calcular uma rota, precisa principalmente representar lugares, caminhos, conexões, distâncias, restrições de circulação e outras informações relevantes ao deslocamento. O mapa é útil justamente porque não tenta ser uma cópia completa do território.
Essa escolha do que representar produz um modelo. Um modelo é uma versão simplificada de alguma parte da realidade criada para determinado propósito. Um sistema bancário possui modelos de contas e transações. Uma loja possui modelos de produtos, clientes e pedidos. Um jogo pode possuir modelos de personagens, objetos e regras do ambiente.
O modelo não é a própria realidade. Um registro chamado “cliente” dentro de um sistema não contém uma pessoa. Contém determinadas informações que a organização decidiu utilizar para representar aquela pessoa naquele contexto. Talvez tenha nome, endereço, número de cadastro e histórico de pedidos. Muitas outras características do indivíduo simplesmente não fazem parte do sistema porque não são necessárias para sua função.
Essa diferença parece óbvia, mas possui consequências importantes. Quando um sistema toma decisões com base em seu modelo, ele trabalha apenas com aquilo que foi representado. Se uma informação importante ficou de fora, o computador não pode considerá-la espontaneamente. Para o programa, aquilo que não aparece nos dados ou nas regras pode simplesmente não existir.
Depois de decidir quais elementos serão representados, é necessário definir suas relações. Em um sistema de biblioteca, por exemplo, existem usuários, livros e empréstimos. Um empréstimo relaciona determinado usuário a determinado exemplar durante um período. Essa relação precisa ser registrada de alguma maneira para que o sistema consiga responder perguntas como “quem está com este livro?” ou “quais livros esta pessoa precisa devolver?”.
A organização dos dados, portanto, faz parte da modelagem. É necessário decidir quais informações serão armazenadas e como serão relacionadas. Um produto pode ter um código, nome, preço e quantidade disponível. Um pedido pode possuir um identificador, uma data, um cliente e vários itens. Cada item precisa indicar qual produto foi comprado e em qual quantidade.
Depois vêm as regras. Se a quantidade disponível de um produto for zero, o sistema deve permitir uma nova venda? Se o preço mudar depois da compra, o pedido antigo deve mostrar o valor atual ou aquele que foi pago originalmente? Se um cliente cancelar um pedido, o produto deve voltar imediatamente ao estoque? A resposta depende do funcionamento desejado para aquele negócio.
É nesse ponto que regras humanas começam a se transformar em lógica computacional. Uma afirmação como “não vender produtos sem estoque” pode ser representada por uma condição: antes de confirmar a venda, o sistema verifica se existe quantidade suficiente. Se existir, continua. Caso contrário, impede a operação ou oferece alguma alternativa.
Muitas instruções para computadores possuem essa estrutura. Se determinada condição for verdadeira, execute uma ação; caso contrário, execute outra. Repita uma operação enquanto determinada situação continuar. Para cada item de um conjunto, faça alguma coisa. Compare valores, registre informações e escolha caminhos conforme as regras.
Entretanto, transformar um problema em instruções não significa simplesmente escrever uma quantidade enorme de condições. Antes disso, é necessário encontrar uma estrutura que represente bem o problema. Uma modelagem ruim pode exigir inúmeras exceções e correções. Uma modelagem adequada costuma tornar várias regras mais naturais.
Imagine um estacionamento que cobra valores diferentes conforme o tempo de permanência. Se as regras forem simples, talvez seja possível descrevê-las diretamente. Mas suponha que existam categorias diferentes de veículos, períodos gratuitos, planos mensais, tarifas especiais em determinados dias e regras específicas para eventos. Antes de programar os cálculos, é necessário organizar essas condições de maneira coerente.
Também é preciso transformar conceitos vagos em critérios que possam ser avaliados. Pessoas utilizam expressões como “cliente frequente”, “entrega atrasada”, “preço alto” ou “atividade suspeita” com alguma flexibilidade. Um computador precisa de uma forma operacional de determinar quando cada condição se aplica.
Se uma empresa deseja oferecer um benefício a “clientes frequentes”, precisa definir o que isso significa para o sistema. São pessoas que fizeram pelo menos cinco compras no último mês? Gastaram mais de determinado valor no ano? Compraram em vários meses diferentes? Dependendo da definição, pessoas diferentes receberão o benefício.
Isso mostra que modelar não é uma atividade neutra. Transformar uma ideia humana em uma regra computacional exige fazer escolhas. Quando definimos uma categoria, também definimos quem ficará dentro e quem ficará fora dela. Em sistemas que afetam pessoas de maneira importante, essas escolhas precisam ser examinadas com cuidado.
Outro desafio é lidar com informações imperfeitas. No mundo real, dados podem faltar, estar incorretos ou mudar com o tempo. Um endereço pode estar desatualizado. Duas pessoas podem ter nomes iguais. Um sensor pode produzir uma leitura incorreta. Um usuário pode preencher um formulário de maneira inesperada. O modelo precisa decidir como representar essas situações.
Às vezes, um valor ausente é diferente de zero. Se um sistema registra a renda de uma pessoa, por exemplo, não informar a renda não significa necessariamente que ela não possui renda. Confundir essas duas situações pode produzir decisões erradas. Representar corretamente aquilo que sabemos e aquilo que não sabemos também faz parte da modelagem.
A identidade é outro exemplo. Utilizar apenas o nome para identificar uma pessoa pode causar problemas porque várias pessoas podem ter o mesmo nome e uma mesma pessoa pode alterar a forma como é registrada. Sistemas normalmente utilizam identificadores específicos justamente para distinguir entidades que poderiam parecer iguais por algumas características.
O tempo também precisa ser modelado. “Hoje”, “amanhã” e “daqui a uma hora” parecem expressões simples, mas sistemas utilizados em diferentes lugares precisam considerar datas, horários e fusos. Uma promoção que termina à meia-noite precisa deixar claro qual meia-noite está sendo considerada. Uma reunião internacional precisa representar corretamente o mesmo instante para pessoas em regiões diferentes.
Depois que dados e regras estão definidos, é possível construir algoritmos. Um algoritmo descreve um procedimento para transformar determinadas entradas em resultados. Ele pode receber uma lista de pedidos e organizá-los, receber dois lugares e procurar uma rota ou receber informações de uma compra e calcular o valor final.
Construir o algoritmo exige decompor a tarefa. Em vez de pedir ao computador “processe a compra”, podemos separar a operação em etapas: verificar os produtos, conferir quantidades, calcular valores, aplicar regras de desconto, registrar o pedido e iniciar o pagamento. Cada etapa pode ser dividida novamente até chegar a operações suficientemente claras para serem implementadas.
Essa decomposição possui outra vantagem: permite identificar dependências. Talvez o pagamento só possa ser confirmado depois que o valor final for conhecido. Talvez o estoque só deva ser reduzido definitivamente depois de determinada confirmação. A ordem das operações pode alterar o resultado, especialmente quando diferentes sistemas participam da mesma atividade.
Também é necessário pensar no que acontece quando alguma etapa falha. Suponha que um pedido seja registrado, mas a tentativa de pagamento seja recusada. O pedido deve desaparecer, permanecer aguardando ou ser marcado como não pago? E se o pagamento for aprovado, mas a comunicação com a loja for interrompida antes da confirmação chegar? O sistema precisa evitar, por exemplo, cobrar novamente sem necessidade.
Esses casos revelam uma diferença importante entre exercícios simples e problemas reais. Em um exercício, podemos imaginar entradas perfeitas e condições controladas. No mundo real, pessoas cometem erros, redes caem, serviços ficam indisponíveis e duas operações podem acontecer quase simultaneamente. Um modelo útil precisa considerar as situações que realmente podem ocorrer.
Depois vem a implementação. As ideias construídas durante a modelagem são expressas em uma linguagem de programação e transformadas em um programa executável. A linguagem fornece recursos para representar dados, criar condições, repetir operações, organizar funções e comunicar diferentes partes do sistema.
Mas a implementação não encerra o processo. É necessário verificar se o programa realmente representa o problema da maneira esperada. Testes podem mostrar que determinada regra foi implementada incorretamente. Usuários podem revelar uma situação que ninguém havia considerado. Dados reais podem mostrar que uma suposição feita durante o planejamento não era válida.
Por isso, modelagem é frequentemente um processo de revisão. Criamos uma representação, construímos o sistema, observamos seu funcionamento e ajustamos o modelo quando percebemos que ele não representa adequadamente alguma parte importante da realidade. Bons sistemas mudam porque a compreensão do problema também muda.
Um aplicativo de transporte oferece um exemplo interessante. Para calcular uma rota, o sistema pode representar a cidade como pontos e conexões entre eles. Ruas tornam-se caminhos possíveis, cruzamentos ajudam a formar conexões e cada trecho pode receber informações como distância ou tempo estimado. O problema físico de atravessar uma cidade é transformado em uma estrutura matemática que um algoritmo consegue analisar.
Essa representação inevitavelmente simplifica a realidade. O tempo de viagem pode mudar por causa do trânsito, do clima, de obras ou de acidentes. Por isso, modelos mais sofisticados incorporam informações adicionais e atualizações frequentes. Ainda assim, nenhum modelo contém literalmente a cidade inteira. Ele contém os aspectos considerados úteis para realizar determinada tarefa.
Essa é uma característica geral da computação. Computadores não trabalham diretamente com pessoas, ruas, dinheiro, doenças ou mercadorias. Trabalham com representações digitais desses elementos. Quanto melhor a representação corresponde às características relevantes do problema, maior a chance de o sistema produzir resultados úteis.
Por outro lado, um modelo pode ser matematicamente elegante e ainda representar mal a situação real. Um algoritmo pode funcionar perfeitamente de acordo com as regras programadas e produzir consequências indesejadas porque as regras estavam incompletas. Isso explica por que nem todo erro de um sistema é um erro de programação.
Às vezes o código executou exatamente aquilo que foi solicitado. O problema estava naquilo que foi solicitado. Talvez uma regra importante não tenha sido considerada, um dado tenha recebido significado inadequado ou uma situação real tenha sido simplificada demais. Corrigir esse tipo de problema exige revisar o modelo, e não apenas procurar uma linha defeituosa no programa.
Essa distinção se torna especialmente importante em sistemas que classificam pessoas, concedem benefícios, detectam riscos ou apoiam decisões importantes. Categorias aparentemente simples podem esconder situações humanas muito diferentes. Transformá-las em números e regras pode ser necessário para o funcionamento do sistema, mas a precisão do cálculo não garante que a representação seja adequada.
Por isso, abstração não significa eliminar todos os detalhes possíveis. Significa escolher conscientemente quais detalhes são importantes para o objetivo. Se incluirmos tudo, o modelo se torna tão complexo que perde sua utilidade. Se retirarmos demais, pode deixar de representar justamente aquilo que determina o resultado.
Encontrar esse equilíbrio é uma das habilidades centrais da programação e do desenvolvimento de sistemas. É necessário enxergar uma situação cheia de detalhes e identificar suas estruturas principais. Depois, essas estruturas precisam ser transformadas em dados, relações, regras e procedimentos que um computador consiga manipular.
No final, o código é apenas a etapa mais visível desse trabalho. Antes dele existe uma sequência de decisões sobre como enxergar o problema. O que será representado? O que pode ser ignorado? Quais situações precisam ser distinguidas? Quais regras definem um resultado correto? O que acontece quando faltam informações ou alguma etapa falha?
Transformar um problema real em instruções para uma máquina é, portanto, um processo de tradução entre dois mundos. De um lado está uma realidade cheia de contexto, exceções e significados implícitos. Do outro está um computador que opera sobre representações e procedimentos definidos. Abstração e modelagem constroem a ponte entre esses dois lados. Programar começa quando conseguimos reduzir a complexidade do mundo sem perder justamente as partes que fazem o problema ser aquilo que ele é.
