Quando alguém começa a aprender programação, é comum pensar que o primeiro passo é escolher uma linguagem, aprender sua sintaxe e começar a escrever código. Eu mesmo vejo como esse caminho pode parecer natural para quem está entrando na área. Afinal, é fácil associar o trabalho de um desenvolvedor às linhas de código que aparecem na tela.
Mas existe uma habilidade que vem antes de tudo isso: saber resolver problemas.
Ao longo de mais de 10 anos trabalhando com desenvolvimento de software, aprendi que conhecer a sintaxe de uma linguagem não é suficiente para construir boas soluções. Você pode dominar JavaScript, Node.js ou qualquer outra tecnologia e, ainda assim, encontrar dificuldades quando precisa transformar um problema real em uma solução simples, eficiente e fácil de manter.
É nesse ponto que entra a lógica de programação.
A lógica de programação nos ajuda a organizar o pensamento, identificar as informações necessárias, definir regras e estabelecer uma sequência de passos para chegar a um resultado. Em vez de sair escrevendo código sem saber exatamente aonde queremos chegar, aprendemos a entender o problema primeiro e escolher uma abordagem adequada.
A boa notícia é que você não precisa ser um gênio da matemática para desenvolver essa habilidade. Com prática, exemplos e um pouco de paciência, qualquer pessoa pode melhorar sua capacidade de raciocínio e resolução de problemas.
Neste artigo, vamos explorar os principais conceitos da lógica de programação usando exemplos em JavaScript e Node.js. A ideia é entender não apenas como escrever o código, mas também por que ele funciona e como aplicar esse conhecimento em situações reais do desenvolvimento de software.
1. O Que É Lógica de Programação?
Lógica de programação é a capacidade de organizar instruções de maneira estruturada para resolver um problema ou realizar uma tarefa. Essas instruções precisam seguir uma sequência coerente para que o computador consiga executá-las e produzir o resultado esperado.
Imagine que você precisa desenvolver uma funcionalidade para verificar se um usuário pode acessar determinada área de um sistema. Antes de pensar em JavaScript, Node.js ou qualquer framework, você precisa definir as regras.
Por exemplo:
- Receber as informações do usuário.
- Verificar se ele está autenticado.
- Conferir se possui a permissão necessária.
- Permitir ou negar o acesso.
- Retornar uma resposta adequada.
Perceba que já temos uma solução organizada, mesmo sem escrever uma linha de código. Esse é o princípio da lógica de programação: entender o problema e definir os passos necessários para resolvê-lo.
Agora podemos transformar essa ideia em código:
function canAccessAdmin(user) {
if (!user) {
return false;
}
if (!user.authenticated) {
return false;
}
if (!user.isAdmin) {
return false;
}
return true;
}
const user = {
authenticated: true,
isAdmin: true
};
console.log(canAccessAdmin(user)); // true
Nesse exemplo, a função verifica três condições antes de permitir o acesso. Se alguma delas não for atendida, a função retorna false.
Esse código é simples, mas representa uma situação que encontramos frequentemente em aplicações reais: transformar regras de negócio em instruções que o computador consegue executar.
Vale lembrar que, em um sistema real, a autorização precisa ser aplicada no servidor e considerar as permissões confiáveis do usuário, não apenas informações recebidas diretamente do cliente.
2. Aprenda a Entender o Problema Antes de Escrever Código
Um dos erros mais comuns entre programadores iniciantes é começar a escrever código assim que recebem uma tarefa.
O problema é que nem sempre entendemos completamente o que precisa ser feito. Quando isso acontece, podemos desenvolver uma solução que funciona em um cenário específico, mas falha quando recebe informações diferentes das que imaginamos inicialmente.
Vamos considerar um exemplo.
Você precisa desenvolver uma função que calcule o valor total de uma compra. Parece simples, certo? Basta somar os preços dos produtos.
Mas algumas perguntas precisam ser respondidas:
- O que acontece quando o carrinho está vazio?
- Os produtos podem ter quantidades diferentes?
- Como lidar com preços inválidos?
- Existe algum desconto?
- O frete faz parte do cálculo?
- Os valores serão representados em reais, centavos ou outro formato?
Essas perguntas ajudam a definir o comportamento esperado da funcionalidade.
Considere esta implementação inicial:
function calculateTotal(products) {
let total = 0;
for (const product of products) {
total += product.price * product.quantity;
}
return total;
}
A função soma o preço de cada produto multiplicado pela quantidade comprada. Porém, ela pressupõe que products seja um array e que todos os produtos tenham valores válidos.
Uma implementação mais cuidadosa poderia validar essas informações:
function calculateTotal(products) {
if (!Array.isArray(products)) {
throw new TypeError("Products must be an array");
}
let total = 0;
for (const product of products) {
if (
!Number.isSafeInteger(product.priceInCents) ||
product.priceInCents < 0 ||
!Number.isSafeInteger(product.quantity) ||
product.quantity < 0
) {
throw new Error("Invalid product information");
}
total += product.priceInCents * product.quantity;
if (!Number.isSafeInteger(total)) {
throw new Error("Total exceeds the supported range");
}
}
return total;
}
Aqui estamos usando centavos para representar os valores monetários, evitando alguns problemas comuns de precisão dos números de ponto flutuante em JavaScript.
Também precisamos considerar que, dependendo das regras do negócio, quantidades iguais a zero podem ou não ser aceitas. O importante é definir essas regras antes de implementar a solução.
Esse exemplo mostra que programar não é apenas escrever instruções. É compreender os requisitos, antecipar situações inesperadas e tomar decisões conscientes.
3. O Que São Algoritmos?
Um algoritmo é uma sequência finita e organizada de passos utilizada para resolver um problema ou executar uma tarefa.
Você utiliza algoritmos mesmo fora do mundo da programação. Uma receita culinária, por exemplo, descreve uma sequência de ações que devem ser realizadas para chegar a um resultado. Uma rotina para calcular o valor de uma compra também pode ser descrita como um algoritmo.
Na programação, os algoritmos são representados por instruções que podem ser executadas por um computador.
Vamos imaginar que precisamos descobrir se uma pessoa pode se inscrever em determinado evento. A regra é simples: a inscrição está disponível para pessoas com pelo menos 18 anos.
Podemos descrever o algoritmo da seguinte maneira:
- Receber a idade da pessoa.
- Verificar se a idade é maior ou igual a 18.
- Se a condição for verdadeira, permitir a inscrição.
- Caso contrário, informar que a pessoa não atende ao requisito.
Em JavaScript, isso ficaria assim:
function canRegister(age) {
return age >= 18;
}
console.log(canRegister(20)); // true
console.log(canRegister(16)); // false
O algoritmo é pequeno, mas segue uma estrutura que pode ser aplicada em muitos outros cenários.
Podemos substituir a idade por saldo bancário, pontuação em uma avaliação, quantidade de produtos em estoque ou qualquer outra informação que precise ser comparada com uma regra.
Essa é uma das vantagens de aprender lógica de programação: você começa a reconhecer padrões que podem ser reutilizados em problemas diferentes.
4. Variáveis e Tipos de Dados
Para resolver problemas, precisamos trabalhar com informações. Na programação, utilizamos variáveis para armazenar valores que podem ser utilizados ao longo da execução de um programa.
Em JavaScript, podemos declarar variáveis usando const e let.
const name = "Carlos";
const age = 25;
const isActive = true;
let score = 10;
score = 15;
console.log(name);
console.log(age);
console.log(isActive);
console.log(score);
Nesse exemplo, temos diferentes tipos de dados:
string: representa textos.number: representa números.boolean: representa valores verdadeiros ou falsos.undefined: representa, entre outras situações, uma variável declarada sem valor atribuído.null: representa a ausência intencional de um valor.
Também existem estruturas importantes, como arrays e objetos.
const products = ["Notebook", "Mouse", "Keyboard"];
const user = {
name: "Carlos",
age: 25,
active: true
};
Os arrays permitem organizar coleções de valores, enquanto os objetos agrupam informações relacionadas a uma mesma entidade.
A escolha da estrutura adequada facilita a leitura do código e ajuda a evitar soluções desnecessariamente complexas.
Outro ponto importante é entender a diferença entre const e let. Utilizamos const quando não precisamos reatribuir a variável e let quando precisamos permitir essa reatribuição.
Isso não significa que um objeto declarado com const seja imutável. Podemos modificar suas propriedades, embora não possamos atribuir outro objeto à mesma variável.
const user = {
name: "Carlos"
};
user.name = "Camila";
console.log(user.name); // Camila
Entender esses detalhes ajuda a escrever código mais previsível e evita confusões quando começamos a trabalhar com aplicações maiores.
5. Condicionais: Como Tomar Decisões no Código
Os programas frequentemente precisam tomar decisões com base em determinadas condições.
Imagine uma aplicação que precisa verificar se um pedido pode ser enviado. Para isso, ela pode consultar o status do pagamento e a disponibilidade dos produtos.
Em JavaScript, utilizamos estruturas como if, else if e else para implementar esse tipo de comportamento.
function getOrderStatus(order) {
if (!order.paid) {
return "Awaiting payment";
}
if (!order.inStock) {
return "Waiting for stock";
}
return "Ready to ship";
}
const order = {
paid: true,
inStock: true
};
console.log(getOrderStatus(order)); // Ready to ship
O código verifica primeiro o pagamento. Se o pedido ainda não foi pago, a função retorna imediatamente. Caso contrário, verifica a disponibilidade dos produtos.
Se nenhuma das duas condições impedir o envio, o pedido estará pronto para ser enviado.
A ordem dessas verificações importa. Em sistemas reais, regras de negócio podem depender umas das outras, e uma condição avaliada antes de outra pode mudar o resultado final.
Também podemos utilizar operadores lógicos, como &&, || e !, para combinar ou inverter condições.
const isAuthenticated = true;
const hasPermission = true;
if (isAuthenticated && hasPermission) {
console.log("Access granted");
}
Nesse caso, as duas condições precisam ser verdadeiras para que a mensagem seja exibida.
Aprender a utilizar condicionais corretamente é essencial para implementar validações, permissões, regras comerciais e praticamente qualquer funcionalidade que dependa de decisões.
6. Loops: Como Repetir Operações
Imagine que você precisa calcular o valor total de mil produtos. Escrever uma operação manualmente para cada item não seria uma boa solução.
Para situações como essa, utilizamos estruturas de repetição, também conhecidas como loops.
Em JavaScript, existem diferentes alternativas, incluindo for, while, for...of e métodos de array, como map, filter e reduce.
Veja um exemplo utilizando for...of:
const prices = [100, 250, 75, 300];
let total = 0;
for (const price of prices) {
total += price;
}
console.log(total); // 725
O loop percorre cada elemento do array e adiciona seu valor ao total.
Podemos resolver o mesmo problema utilizando reduce:
const prices = [100, 250, 75, 300];
const total = prices.reduce(
(sum, price) => sum + price,
0
);
console.log(total); // 725
As duas soluções produzem o mesmo resultado. A escolha entre elas depende do contexto, da clareza e das convenções utilizadas no projeto.
É importante entender que nem sempre a solução mais curta é a melhor. Um código fácil de compreender costuma ser mais simples de revisar, testar e manter.
Também precisamos ter cuidado com loops que não terminam. Uma condição incorreta em um while, por exemplo, pode fazer uma operação continuar indefinidamente.
Ao trabalhar com grandes quantidades de dados, devemos considerar ainda o custo computacional das operações realizadas dentro de cada repetição.
7. Funções: Dividindo Problemas em Partes Menores
À medida que um programa cresce, concentrar toda a lógica em um único bloco de código torna a manutenção mais difícil.
As funções ajudam a dividir uma solução em partes menores, cada uma com uma responsabilidade bem definida.
Imagine que uma aplicação precisa calcular descontos para diferentes tipos de clientes.
function calculateDiscount(price, discountPercentage) {
return price * (discountPercentage / 100);
}
function calculateFinalPrice(price, discountPercentage) {
const discount = calculateDiscount(
price,
discountPercentage
);
return price - discount;
}
console.log(calculateFinalPrice(200, 10)); // 180
Nesse exemplo, a primeira função calcula o desconto, enquanto a segunda utiliza esse resultado para calcular o preço final.
Separar as responsabilidades facilita a reutilização do código e permite testar cada comportamento individualmente.
Também é importante evitar funções que fazem coisas demais. Uma função responsável por validar dados, consultar um banco, enviar um e-mail e gerar um relatório pode se tornar difícil de compreender e modificar.
Isso não significa que toda função precisa ter apenas uma linha ou que devemos criar funções para absolutamente tudo. O objetivo é encontrar uma divisão que torne o código mais claro e organizado.
Uma boa pergunta para fazer durante o desenvolvimento é: consigo explicar o que esta função faz em uma frase simples?
Se a resposta exigir uma explicação muito longa, talvez seja interessante avaliar se existem responsabilidades que poderiam ser separadas.
8. Decomposição de Problemas: Uma Habilidade Essencial
Decompor um problema significa dividi-lo em partes menores que sejam mais fáceis de entender e resolver.
Essa habilidade é especialmente importante quando trabalhamos com funcionalidades complexas.
Imagine que você precisa desenvolver uma API de pedidos para um e-commerce. Entre as responsabilidades envolvidas, podemos encontrar:
- Receber os dados do pedido.
- Validar os produtos e as quantidades.
- Consultar os preços.
- Verificar o estoque.
- Calcular os valores.
- Registrar o pedido no banco de dados.
- Iniciar o processamento do pagamento.
- Notificar o cliente.
Tentar implementar tudo isso de uma vez aumenta a chance de erros e torna mais difícil identificar a origem dos problemas.
Uma abordagem melhor é dividir o trabalho em etapas menores, definir as entradas e saídas de cada etapa e testar os comportamentos individualmente.
Por exemplo, podemos começar com uma função responsável apenas por validar os itens do pedido.
function validateOrderItems(items) {
if (!Array.isArray(items) || items.length === 0) {
throw new Error("The order must contain items");
}
for (const item of items) {
if (
!Number.isInteger(item.quantity) ||
item.quantity <= 0
) {
throw new Error("Invalid item quantity");
}
}
return true;
}
Essa função possui uma responsabilidade específica e pode ser testada sem precisar conectar a aplicação a um banco de dados ou a um serviço de pagamento.
Depois, podemos desenvolver as demais partes e integrá-las gradualmente.
É assim que transformamos um problema grande em um conjunto de problemas menores, com limites e responsabilidades mais claros.
9. Como Praticar Lógica de Programação com Node.js
A melhor maneira de desenvolver o raciocínio lógico é praticar com problemas progressivamente mais desafiadores.
Não é necessário começar com algoritmos complexos ou desafios difíceis de entrevistas técnicas. Pequenos exercícios já ajudam a desenvolver a capacidade de interpretar requisitos, definir regras e avaliar resultados.
Uma boa opção é utilizar o próprio Node.js para executar seus programas.
Depois de instalar o Node.js, você pode criar um arquivo chamado index.js e executar o código no terminal:
node index.js
Comece com exercícios como estes:
- Criar uma função que verifica se um número é par ou ímpar.
- Calcular a média de uma lista de notas.
- Encontrar o maior número de um array.
- Contar quantas vezes uma palavra aparece em um texto.
- Filtrar produtos que estão disponíveis em estoque.
- Calcular o valor total de um carrinho de compras.
- Validar os dados necessários para cadastrar um usuário.
- Agrupar pedidos por status.
- Identificar valores duplicados em uma lista.
- Implementar uma pequena API que permita cadastrar e consultar tarefas.
Para cada exercício, tente seguir uma sequência consistente:
- Escreva o problema com suas próprias palavras.
- Identifique as entradas necessárias.
- Defina qual resultado deve ser produzido.
- Liste as regras que precisam ser respeitadas.
- Pense nos casos comuns e nos casos extremos.
- Escreva o algoritmo antes de se preocupar com detalhes desnecessários.
- Implemente a solução em JavaScript.
- Execute testes com diferentes valores.
- Revise o código e procure maneiras de torná-lo mais claro.
Você também pode utilizar testes automatizados para verificar os resultados esperados.
Por exemplo, o Node.js oferece o módulo nativo node:test, que permite escrever testes sem instalar uma biblioteca adicional.
const test = require("node:test");
const assert = require("node:assert/strict");
function isEven(number) {
return number % 2 === 0;
}
test("should identify even numbers", () => {
assert.equal(isEven(4), true);
assert.equal(isEven(7), false);
assert.equal(isEven(0), true);
});
Salve esse código em um arquivo chamado index.test.js e execute:
node --test
O resultado permite verificar se a função está se comportando conforme o esperado.
Mais importante do que resolver muitos exercícios rapidamente é entender por que sua solução funciona, quais situações ela não contempla e como você poderia melhorá-la.
10. Erros Comuns ao Aprender Lógica de Programação
Durante o aprendizado, alguns comportamentos podem atrasar sua evolução. Conhecê-los ajuda a aproveitar melhor o tempo de estudo.
Decorar código sem entender o problema
Copiar soluções pode ser útil para conhecer novas abordagens, mas não substitui o entendimento. Sempre que encontrar um exemplo, tente explicar cada parte e modificar o código para resolver um problema semelhante.
Pular diretamente para frameworks
Frameworks como Express e NestJS são ferramentas úteis para desenvolver aplicações, mas não substituem o conhecimento sobre variáveis, funções, estruturas de controle, tratamento de erros e algoritmos.
Antes de depender de uma ferramenta para tudo, procure entender o que ela facilita e quais problemas está resolvendo.
Ignorar os casos extremos
Uma função pode funcionar perfeitamente com os dados de um exemplo e falhar com um array vazio, um valor nulo ou uma entrada inválida.
Pensar nessas situações desde o início ajuda a construir soluções mais confiáveis.
Procurar sempre a solução mais sofisticada
Nem todo problema precisa de uma estrutura de dados avançada ou de um algoritmo complexo. Muitas vezes, uma solução simples é suficiente.
O importante é encontrar um equilíbrio entre simplicidade, clareza, correção e desempenho.
Desistir quando a solução não aparece imediatamente
Nem sempre sabemos como resolver um problema na primeira tentativa. Isso faz parte do trabalho de desenvolvimento.
Quando estiver com dificuldades, reduza o problema, crie exemplos menores, teste suas hipóteses e observe os resultados. O processo de investigação também faz parte do aprendizado.
Conclusão
A lógica de programação é uma das bases mais importantes para quem deseja construir uma carreira sólida no desenvolvimento de software. Ela permite entender problemas, organizar informações, estabelecer regras e transformar ideias em soluções que podem ser executadas por um computador.
Ao longo deste artigo, vimos como algoritmos, variáveis, condicionais, loops, funções e decomposição de problemas se conectam para formar a base de um programa. Também exploramos exemplos em JavaScript e Node.js que mostram como aplicar esses conceitos em situações práticas.
Na minha experiência como desenvolvedor, aprender uma nova tecnologia se torna muito mais fácil quando você consegue reconhecer os conceitos que já conhece por trás dela. A sintaxe pode mudar, as ferramentas podem evoluir e os frameworks podem perder popularidade, mas a capacidade de analisar um problema e construir uma solução continua sendo extremamente valiosa.
Por isso, minha recomendação é não ter pressa para avançar para assuntos mais complexos. Pratique os fundamentos, escreva código, teste suas ideias e procure entender o motivo de cada decisão que toma durante o desenvolvimento.
Com o tempo, você vai perceber que resolver problemas deixa de ser apenas uma tarefa difícil e passa a ser uma habilidade que evolui a cada projeto.
E você, qual foi sua maior dificuldade ao aprender lógica de programação? Conte nos comentários quais conceitos foram mais difíceis de entender ou compartilhe um exercício que ajudou você a evoluir. Sua experiência também pode ajudar outros desenvolvedores que estão começando.