O que é programação estruturada?

No vasto universo do desenvolvimento de software, compreender os diferentes paradigmas de programação é crucial para qualquer profissional da área. Entre eles, a programação estruturada se destaca como um dos pilares que moldaram a forma como construímos sistemas. Mas o que exatamente ela significa e por que é tão relevante até hoje?

Neste texto, vamos entender o que é esse paradigma, como ele surgiu, quais são suas principais características e estruturas, e por que ele ainda é fundamental no aprendizado e, na prática da programação.

1 – O que é programação estruturada?

A programação estruturada é um paradigma de programação que preconiza a criação de programas organizados e de fácil entendimento, utilizando um conjunto limitado de estruturas de controle de fluxo. Ela surgiu como uma resposta aos desafios da programação não estruturada, que resultava em códigos complexos e difíceis de manter, popularmente conhecidos como “código espaguete”.

O objetivo principal da programação estruturada é simplificar o processo de desenvolvimento e depuração, promovendo a clareza e a modularidade do código. Em vez de saltos incondicionais (como o GOTO), ela incentiva o uso de construções bem definidas.

A programação estruturada parte da ideia central de que podemos desenvolver qualquer software, por mais complexo que ele seja, utilizando apenas três estruturas fundamentais de controle de fluxo.

  • Sequência: o sistema executa as instruções em ordem linear, uma após a outra. Essa é a forma mais básica de organizar um código, onde o sistema lê e processa cada comando na sequência em que ele aparece.
  • Seleção (ou Condição): permite que o programa execute diferentes blocos de código com base em uma condição. As estruturas mais comuns são o if/else (se/senão) e o switch/case (escolha/caso). Isso dá ao programa a capacidade de tomar decisões.
  • Iteração (ou Repetição/Loop): permite executar um bloco de código repetidamente enquanto uma condição for verdadeira ou por um número específico de vezes. Exemplos incluem for (para), while (enquanto) e do-while (faça-enquanto). Essas estruturas são essenciais para automatizar tarefas repetitivas.

Linguagens tradicionais como C, Pascal, Fortran, e ALGOL, por exemplo, adotam amplamente o paradigma da programação estruturada. Além delas, atualmente, muitas linguagens modernas como JavaScript, Python, PHP e outras também incorporam os princípios desse paradigma.

Em suma, a programação estruturada defende que a combinação dessas três estruturas básicas é suficiente para construir algoritmos eficientes, claros e fáceis de manter, independentemente da complexidade do problema.

2 – Quando esse paradigma de programação surgiu?

A programação estruturada ganhou força na década de 1960, impulsionada principalmente pelos trabalhos do cientista da computação Edsger W. Dijkstra. Na época, o desenvolvimento de software enfrentava uma crise de produtividade e qualidade. Os programas eram escritos de forma caótica, com muitos saltos GOTO que dificultavam o rastreamento do fluxo de execução e a identificação de erros. Desse modo, a manutenção e a expansão dos sistemas tornava-se um verdadeiro pesadelo.

Dijkstra, em seu famoso artigo “Go To Statement Considered Harmful” (Declaração Go To Considerada Prejudicial) de 1968, criticou severamente o uso indiscriminado do GOTO, argumentando que ele levava a programas “espalhafatosos” e ininteligíveis. Sua proposta era justamente a utilização das três estruturas de controle que mencionamos: sequência, seleção e iteração.

Assim, o surgimento da programação estruturada representou um avanço significativo para a engenharia de software, promovendo a modularidade, legibilidade e manutenibilidade do código. Ao dividir o programa em blocos menores e mais gerenciáveis (como funções e procedimentos), tornou-se mais fácil para os desenvolvedores entenderem, testarem e corrigirem partes específicas do sistema sem afetar o todo.

3 – Exemplo de programação estruturada

Com a finalidade de ilustrar os conceitos da programação estruturada, vamos analisar um exemplo simples de código que verifica se um número é par ou ímpar. Observe que, para fins didáticos, desenvolvemos esse código em três linguagens diferentes: JavaScript, Python e Java.

Ademais, note em cada linguagem utilizada, como a modularização das funções e o uso das estruturas de controle de fluxo acontecem na prática.

JSPythonC++

// Função para verificar se o número é par ou ímpar 
function verificarParOuImpar(numero) {

    // Utiliza uma estrutura condicional (if/else) para determinar a paridade
    if (numero % 2 === 0) {
        return "O número é par.";
    } else {
        return "O número é ímpar.";
    }
}

// Função principal para executar o programa 
function main() {
    var entradaValida = false;

    // Loop de repetição (while) para obter uma entrada numérica válida do usuário
    while (!entradaValida) {
        var numero = prompt("Digite um número inteiro: ");
        // Verifica se o valor inserido é de fato um número
        if (!isNaN(numero)) {
            entradaValida = true;
        } else {
            console.log("Por favor, digite um número válido.");
        }
    }

    // Invoca a função que checa a paridade do número (fluxo sequencial)
    console.log(verificarParOuImpar(numero));
}

// Inicia a execução do programa chamando a função principal (fluxo sequencial)
main();

# Função para verificar se o número é par ou ímpar
def verificar_par_ou_impar(numero):
    
    # Aplica uma condição (if/else) para checar se o número é par
    if numero % 2 == 0:
        return "O número é par."
    else:
        return "O número é ímpar."

# Função principal para executar o programa
def main():
    entrada_valida = False

    # Itera (loop while) até que o usuário forneça um número inteiro válido
    while not entrada_valida:
        numero_str = input("Digite um número inteiro: ")
        # Confere se a entrada fornecida é um número
        if numero_str.isdigit() or (numero_str.startswith('-') and numero_str[1:].isdigit()):
            numero = int(numero_str)
            entrada_valida = True
        else:
            print("Por favor, digite um número válido.")

    # Aciona a função que determina se o número é par ou ímpar (execução sequencial)
    print(verificar_par_ou_impar(numero))

# Garante que a função principal seja chamada ao executar o script
if __name__ == "__main__":
    main()

#include <iostream>
#include <string>  
#include <limits>   

// Função para verificar se o número é par ou ímpar
std::string verificarParOuImpar(int numero) {
    // Implementação de uma estrutura condicional (if/else) para validação
    if (numero % 2 == 0) {
        return "O número é par.";
    } else {
        return "O número é ímpar.";
    }
}

// Função principal para executar o programa
int main() {
    bool entradaValida = false;
    int numero = 0; // Inicializa a variável para ser usada no escopo

    // Estrutura de repetição (while) para garantir uma entrada numérica correta
    while (!entradaValida) {
        std::cout << "Digite um número inteiro: ";
        // Tenta ler um número inteiro
        if (std::cin >> numero) {
            entradaValida = true;
        } else {
            std::cout << "Por favor, digite um número válido." << std::endl;
            // Limpa o estado de erro do cin e descarta o restante da linha de entrada
            std::cin.clear();
            std::cin.ignore(std::numeric_limits::max(), '\n');
        }
    }

    // Chama a função que verifica a paridade do número (fluxo de execução)
    std::cout << verificarParOuImpar(numero) << std::endl;

    return 0; // Indica que o programa terminou com sucesso
}

Nestes exemplos, podemos observar claramente os princípios da Programação Estruturada aplicados na prática:

  • Modularidade: nas três linguagens que utilizamos em nosso exemplo, o código é dividido em duas funções, cada uma delas com sua responsabilidade específica.
    • verificarParOuImpar: função para verificar se o número é par ou ímpar.
    • main: função principal para executar o programa.
  • Estruturas de Controle: utilizamos if/else para tomar decisões (verificar par/ímpar e validar a entrada) e um while para repetir a solicitação de entrada até que um número válido seja fornecido.
  • Sequência: observe em todos os códigos que, tanto a organização das instruções dentro de cada função e as suas respectivas chamadas ocorrem em uma ordem lógica e sequencial.

Apesar de novos paradigmas terem surgido, como a programação orientada a objetos, a programação estruturada continua sendo a base para a escrita de código legível, eficiente e fácil de manter. Portanto, entender seus conceitos é fundamental para qualquer desenvolvedor que busca construir sistemas robustos e de alta qualidade.

Conclusão

A programação estruturada representou um divisor de águas na história do desenvolvimento de software, ao propor uma abordagem mais clara, lógica e modular para a construção de programas.

Mesmo que outros paradigmas tenham surgido, como a programação orientada a objetos e programação funcional, por exemplo, os princípios da programação estruturada continuam sendo amplamente utilizados e valorizados.

Por fim, podemos compreender que dominar a programação estruturada é um passo essencial para qualquer desenvolvedor, pois ela fornece a base para escrever códigos mais organizados, legíveis e fáceis de manter — qualidades indispensáveis na criação de sistemas robustos e duradouros.

Espero que este conteúdo seja útil de alguma forma para você. Se gostou do conteúdo, compartilhe com seus amigos e aproveite para conhecer mais sobre programação aqui!

O que é um paradigma de programação?

Os paradigmas de programação representam diferentes estilos ou abordagens para escrever e organizar código. Cada paradigma define um modelo conceitual que influencia diretamente como problemas são analisados, como soluções são projetadas e como o software é implementado e mantido. Compreender esses paradigmas é essencial para todo desenvolvedor que deseja escrever código mais eficiente, legível e sustentável.

Neste artigo, vamos explorar os principais paradigmas de programação, suas características, exemplos e quando aplicá-los. Começamos com uma base conceitual.

1 – O que é um paradigma de programação?

Para entendermos o conceito de paradigma de programação, devemos primeiramente compreender o significado da palavra paradigma.  O termo “paradigma” vem do grego paradeigma, que significa modelo ou exemplo. De acordo com o dicionário Priberam, a palavra paradigma pode ser definida como: “Algo que serve de exemplo geral ou de modelo.” 

No contexto da computação, um paradigma de programação é um modelo conceitual que determina a forma de escrever códigos. Eles fornecem diretrizes sobre como estruturar algoritmos, organizar dados e controlar o fluxo de execução.

Linguagens de programação podem seguir um único paradigma (como Haskell, voltada exclusivamente ao paradigma funcional) ou ser multiparadigma, combinando diferentes abordagens (como Python e JavaScript, que suportam paradigmas imperativo, orientado a objetos e funcional).

2. Principais Paradigmas de Programação

Ao longo da evolução da computação, diversos paradigmas foram desenvolvidos para atender a diferentes demandas e contextos. A seguir, vamos conhecer os principais:

2.1 Programação imperativa

A programação imperativa é uma das formas mais tradicionais de programar. Nesse paradigma, o código é escrito como uma sequência de instruções que alteram o estado do sistema. É como dar ordens ao computador: determinando passo a passo, quais comandos ele deve executar e em qual ordem.

Características:

  • Foco em como resolver o problema.
  • Uso explícito de variáveis, laços e estruturas de controle.
  • Controle detalhado do fluxo de execução.

Exemplos de linguagens: C, C++, Java, Python, JavaScript, PHP.

2.2 Programação declarativa

A programação declarativa é conhecida como o oposto da imperativa. Nesse paradigma, o foco recai sobre o que deve ser feito, e não em como deve ser feito. Assim, o programador descreve ao computador a sequência lógica a executar e o resultado que se espera alcançar, sem determinar o fluxo de controle. O sistema se encarrega de executar as ações necessárias para alcançar esse resultado.

Portanto, na programação declarativa, as instruções possuem uma abordagem mais generalista e não detalham exatamente cada etapa da execução.

Características:

  • Abstrai o controle do fluxo de execução.
  • Minimiza efeitos colaterais.
  • Ideal para tarefas onde regras e resultados são mais importantes que o processo.

Exemplos: SQL, HTML, CSS, XML, Prolog.

2.3 Programação estruturada

Derivada do paradigma imperativo, a programação estruturada introduz boas práticas como a divisão do código em blocos lógicos (modularização do software) e está fundamentada no uso de estruturas básicas de controle de fluxo: sequências, decisões e repetições.

Na programação estruturada, entende-se que para resolver um problema de forma eficiente, ele deve ser quebrado em partes menores (subprogramas ou módulos). Cada uma dessas partes será responsável por resolver uma determinada fração do problema maior. Esse é o conceito de modularização de software, onde todo programa é composto por um conjunto de programas menores interconectados, chamados de módulos ou subprogramas. 

Os softwares construídos com esse paradigma usam as seguintes estruturas de controle de fluxo: 

Sequências: as instruções são escritas na sequência em que serão executadas.  

Decisões/condições: blocos de código são executados somente quando determinadas condições são cumpridas. Usa-se estruturas como IF – ELSE e SWITCH – CASE. 

Repetições: blocos de código são executados várias vezes até que uma condição seja cumprida. Usa-se estruturas como FOR, WHILE e Recursividade. 

Características:

  • Redução de código espaguete.
  • Organização por funções e blocos.
  • Uso de estruturas como if/else, switch, for, while.

Exemplos: C, C++, C#, Java, Python, PHP.

2.4 Programação procedural

Também derivada do paradigma imperativo, a programação procedural agrupa as instruções em procedimentos (também chamados de funções, métodos ou sub-rotinas). Cada procedimento realiza uma tarefa específica, promovendo modularização e reutilização de código. Esses procedimentos devem ser acionados sequencialmente, durante a execução do software.

Características:

  • Organização baseada em chamadas de funções.
  • Separação de responsabilidades.
  • Facilidade de manutenção e testes.

Exemplos: C, C++, PHP, Python, Go.

2.5 Programação orientada a objetos (POO)

A programação orientada a objetos também deriva da programação imperativa. É o paradigma mais difundido e usado na atualidade. A POO modela o software com base em objetos do mundo real. A ideia central é replicar o mundo real através de códigos que usam classes e objetos para representar tudo aquilo que existe. 

As classes são os modelos que representam coisas reais. Já os objetos são instâncias dessas classes, que encapsulam dados (atributos) e comportamentos (métodos). Esse paradigma favorece a reutilização, escalabilidade e organização do código.

Características:

  • Encapsulamento, herança, polimorfismo e abstração.
  • Interação entre objetos para compor o comportamento do sistema.
  • Facilita a modelagem de domínios complexos.

Exemplos: Java, C++, C#, Python, Ruby, PHP.

2.6 Programação funcional

Baseada em conceitos matemáticos, a programação funcional trata funções como cidadãos de primeira classe. Ela evita estados mutáveis e prioriza funções puras — que sempre produzem o mesmo resultado para os mesmos argumentos.

Características:

  • Imutabilidade e ausência de efeitos colaterais.
  • Uso intensivo de funções e composição.
  • Paradigma adequado para concorrência e paralelismo.

Exemplos: Haskell, Elixir, Scala, JavaScript (parcialmente), Kotlin (parcialmente).

2.7 Programação lógica

A programação lógica também deriva da programação declarativa. Ela está baseada no uso de regras e lógica formal para resolução de problemas. O programador declara fatos e regras, e o motor de inferência da linguagem deduz as conclusões.

Características:

  • Usa lógica de predicados para inferir respostas.
  • Muito usada em inteligência artificial e sistemas especialistas.

Exemplo: Prolog.

2.8 Programação reativa

Também derivada da programação declarativa, a programação reativa possui foco na construção de sistemas orientados a eventos e fluxo de dados assíncronos. É ideal para aplicações que exigem respostas imediatas a mudanças de estado, como interfaces gráficas e aplicações em tempo real.

Características:

  • Fluxos de dados e propagação de mudanças.
  • Combina bem com programação funcional e orientada a eventos.

Exemplos: RxJS, Reactor (Java), Angular (RxJS), Kotlin Flow.

3. Qual o melhor paradigma de programação?

Depois de ler este artigo, talvez você esteja se perguntando qual o melhor paradigma de programação? E a resposta é: depende. Nenhum paradigma é universalmente melhor, tudo depende do contexto. A escolha do paradigma ideal inclui fatores como:

  • A natureza do problema.
  • Requisitos de desempenho e manutenção.
  • Equipe envolvida e conhecimento técnico.
  • Linguagens e tecnologias adotadas pela organização.

Na prática, muitos projetos adotam uma abordagem híbrida, usando múltiplos paradigmas conforme a necessidade. É comum, por exemplo, combinar POO com técnicas funcionais em linguagens modernas como Python, Kotlin ou JavaScript.

Portanto, cabe aos profissionais de desenvolvimento entender os conceitos básicos de cada paradigma e usá-los de acordo com as suas necessidades.

Conclusão

Conhecer os paradigmas de programação é essencial para desenvolver software de maneira mais eficaz. Eles moldam a maneira como pensamos e resolvemos problemas computacionais.

Ao dominar diferentes paradigmas, o desenvolvedor ganha flexibilidade, capacidade analítica e autonomia para escolher a melhor abordagem para cada desafio. Lembre-se: aprender novos paradigmas amplia sua visão como desenvolvedor e contribui para um código mais limpo, sustentável e adaptável.

Espero que este conteúdo seja útil de alguma forma para você. Se gostou do conteúdo, compartilhe com seus amigos e aproveite para conhecer mais sobre programação aqui!

Pilares da programação orientada a objetos

A programação orientada a objetos (POO) é um paradigma amplamente usado no desenvolvimento de softwares. Adotado por linguagens de programação populares como Java e C#, por exemplo, o paradigma é aplicável a uma ampla variedade de projetos, desde os mais simples até os mais complexos.

A orientação a objetos busca aproximar o mundo real ao mundo da programação de computadores. A ideia central deste paradigma é que tudo aquilo que existe no mundo real pode ser representado em código através de objetos.

Para exemplificar, pense no sistema de uma universidade. Neste sistema, um aluno é representado como um objeto, com atributos e comportamentos específicos. Os atributos do objeto ‘aluno’ podem incluir matrícula, nome, data de nascimento, endereço, curso, disciplinas, entre outros. Os comportamentos podem englobar a realização de matrículas em disciplinas, cancelamento de matrículas, listagem de disciplinas matriculadas ou concluídas, cálculo de médias semestrais, entre outros. Da mesma forma, um professor, uma disciplina, um curso e qualquer outra entidade do sistema universitário pode ser representada como um objeto, com seus atributos e comportamentos próprios. 

Este artigo não possui a finalidade de detalhar os conceitos da POO, o foco aqui serão os quatro pilares fundamentais deste paradigma. Se você estiver interessado em conhecer mais sobre POO, clique aqui para ler um artigo que escrevi sobre esse assunto.  

1 – Os 4 pilares da programação orientada a objetos 

Os quatro pilares da POO representam quatro características que linguagens de programação e projetos devem atender para que sejam classificados como orientados a objetos. Essas características que formam os pilares da orientação a objetos são: abstração, encapsulamento, herança e polimorfismo.  

Abaixo vamos conhecer cada uma dessas características e na sequência vamos ver um projeto simples de Java, que simula operações bancária, criado para fins didáticos e que com certeza irá lhe ajudar no entendimento dos conceitos aqui apresentados.  

1.1 – Abstração 

A abstração está relacionada a capacidade de representar as coisas que existem no mundo real como objetos em um software. Os objetos devem ser identificados (nomeados) conforme sua função e devem possuir atributos (ou propriedades) e métodos (ou comportamentos) devidamente definidos e configurados.  
Pense, por exemplo, em um sistema bancário. Nesse sistema há um objeto chamado ContaBancaria, no qual temos atributos como número da conta, titular e saldo e temos também métodos como sacar, depositar, transferir e consultar saldo. Esse objeto é uma abstração de algo do mundo real (contas bancárias) representado em um software. 

1.2 – Encapsulamento 

O encapsulamento está ligado, diretamente, a segurança das informações do sistema. Ele pode ser entendido como uma forma de ocultação de dados.  

O encapsulamento restringe os acessos aos atributos e métodos dos objetos, evitando que ocorram acessos desnecessários que possam ocasionar mudanças indesejadas no sistema. A ideia é que cada objeto é responsável por manter a integridade dos seus dados (atributos), permitindo que sejam acessados somente através de métodos públicos específicos para essa finalidade.  

Vamos pensar no objeto ContaBancaria que apresentamos na explicação do conceito de abstração. É prudente que os seus atributos número da conta, titular e saldo sejam encapsulados e, portanto, acessados somente pelos seus métodos, a fim de evitar a exposição e manipulação dessas informações em outras partes do código. 

Nas linguagens de programação orientadas a objetos, o encapsulamento é alcançado através do uso de métodos de acesso (getters e setters) e modificadores de acesso (como public, private e protected). 

1.3 – Herança 

Herança é a característica de um objeto ou classe receber (herdar) atributos e métodos de um objeto ou classe superior. Assim, surge o conceito de classe mãe e classes filhas, as quais herdam atributos e métodos da classe mãe.  

Pensando novamente no exemplo do sistema bancário, nele temos uma classe chamada ContaCorrente  que é uma classe filha de ContaBancaria. Desta forma, a classe ContaCorrente herda os atributos (número da conta, titular e saldo) e os métodos (sacar, depositar, transferir e consultar saldo) da sua classe mãe ContaBancaria. Essa mesma regra valeria para outras classes filhas de ContaBancaria como, por exemplo, ContaDigital e ContaSalario.  

Note que, apesar de conta corrente, conta poupança e conta salário serem tipos diferentes de contas bancárias, todas compartilham os mesmos atributos e métodos básicos da classe ContaBancaria. Essa relação entre as classes, permitida pela herança, possibilita a reutilização de código, deixando o projeto mais organizado, legível e performático. 

1.4 – Polimorfismo 

O polimorfismo pode ser entendido como a característica da POO em que um mesmo método pode ter comportamentos diferentes conforme o objeto em que foi herdado.  

Para que possamos entender esse conceito, vamos imaginar a seguinte situação: o método consultarSaldo da classe ContaBancaria é herdado pelos objetos de conta corrente, conta digital e conta salário. A princípio, o método consultarSaldo deveria ter o mesmo comportamento em todos os tipos de contas, porém, nas contas correntes ele deve apresentar além do saldo disponível, o saldo de cheque especial daquela conta. Para contas poupança e salário deve ser apresentado somente o saldo da conta, pois, não há oferta de produtos de crédito nessas contas. Veja que se trata do mesmo método apresentando comportamentos diferentes conforme o objeto em que ele é usado.

2 – Entendendo os pilares da programação orientadas a objetos 

Após conhecermos a definição dos 4 pilares da programação orientada a objetos, vamos ver abaixo uma sequência de códigos escritos em Java, que simulam algumas transações bancárias. Esses códigos nos ajudarão a visualizar os pilares de programação orientada a objetos de forma prática, melhorando nossa compreensão a respeito do assunto.

2.1 – A estrutura do projeto 

Para executar os códigos em sua máquina você vai precisar instalar: a JDK 17 ou superior e uma IDE compatível com Java. Eu recomendo utilizar o IntelliJ ou o Visual Studio Code (se optar por esta opção não esqueca de instalar a extensão da linguagem Java). 

Depois de instaladas as ferramentas necessárias, você pode criar um projeto com a seguinte estrutura ou fazer o download do projeto disponível aqui:

Estrutura do projeto do sistema do banco.

Como podemos ver na imagem acima, a estrutura é bem simples e fácil de entender:

– Contas: é a pasta onde estão os arquivos que representam os tipos de contas que o banco fornece; 

– Enums: como o próprio nome sugere, é a pasta onde fica o arquivo de Enums da aplicação; 

– Models: pasta onde está a classe principal da aplicação; 

– Service: pasta onde fica a função que executa as ações da aplicação; 

– BancoApplicaton: arquivo onde está a função main() responsável por executar a aplicação. 

2.2- Entendendo os pilares da programação orientada a objetos no código

Abaixo, vou compartilhar alguns trechos dos códigos do projeto onde poderemos visualizar a aplicação de cada um dos pilares da programação orientada a objetos. Vamos lá!

public class ContaBancaria { 

    // Atributos da classe 
    private int numeroConta; 
    private String titularConta; 
    private double saldo; 


    // Construtor da classe 
    public ContaBancaria(int numeroConta, String titularConta,
                        double saldoInicial, TipoConta tipoConta) { 
        this.numeroConta = numeroConta; 
        this.titularConta = titularConta; 
        this.saldo = saldoInicial; 
    } 
  

    // Métodos da classe 
    public void depositar(double valor) { 
        saldo += valor; 
        System.out.println("Conta "+numeroConta+" - Depósito de R$" + valor + " realizado com sucesso!"); 
    } 

  
    public void sacar(double valor) { 
        if (valor <= saldo) { 
            saldo -= valor; 
            System.out.println("Conta "+numeroConta+" - Saque de R$" + valor + " realizado com sucesso!"); 
        } else { 
            System.out.println("Saldo insuficiente para saque de R$" + valor); 
        } 
    } 


    public void consultarSaldo() { 
        System.out.println("Conta: "+numeroConta+" / "+"Titular: "+titularConta+" / "+"Saldo atual: R$" + saldo); 
    } 

  
    public void transferir(ContaBancaria destino, double valor) { 
        if (valor <= saldo) { 
            saldo -= valor; 
            destino.saldo += valor; 
            System.out.println("Conta "+numeroConta+" - Transferência de R$" + valor + " realizada para a conta " + destino.numeroConta); 
        } else { 
            System.out.println("Saldo insuficiente para transferência de R$" + valor); 
        } 
    } 

  
    public double getSaldo() { 
        return saldo; 
    } 

    public int getNumeroConta() { 
        return numeroConta; 
    } 

    public String getTitularConta() { 
        return titularConta; 
    } 
} 

A abstração pode ser vista no código da classe ContaBancaria que replica algo que existe no mundo real (contas bancárias) através de um código de programação que será usado em um sistema computacional.  

Observe que a classe ContaBancaria foi construída com três atributos (número da conta, titular da conta e saldo) e quatro métodos (depositar, sacar, consulta saldo e transferir) que compõem a estrutura de uma conta bancária. A conta bancária é algo que existe no mundo real e utilizamos em nosso dia a dia e está sendo abstraída em um código computacional. 

Nesta mesma classe, também conseguimos ver o encapsulamento. Observe que os atributos numeroConta, titularConta e saldo foram definidos como private e, portanto, não podem ser acessados fora da classe ContaBancaria. A única forma de serem manipulados por códigos externos a essa classe é através dos getters definidos no código: getNumeroConta(), getTitularConta() e getSaldo(). 

public class ContaCorrente extends ContaBancaria { 
    
    private double chequeEspecial; 

    public ContaCorrente(int numeroConta, String titularConta, double saldoInicial, double chequeEspecial) { 
        super(numeroConta, titularConta, saldoInicial, TipoConta.CORRENTE); 
        this.chequeEspecial = chequeEspecial; 
    } 

    public double getChequeEspecial() { 
        return chequeEspecial; 
    } 

  
    @Override 
    public void consultarSaldo() { 
        double saldoTotal = getSaldo() + chequeEspecial; 
        System.out.println("Conta: " + getNumeroConta() + " / " + "Titular: " + getTitularConta() + " / " +  "Saldo atual: R$" + getSaldo() + " / Cheque Especial: R$" + chequeEspecial + " / Saldo Total: R$" + saldoTotal); 
    } 
}
public class ContaDigital extends ContaBancaria { 
    public ContaDigital(int numeroConta, String titularConta, double saldoInicial) { 
        super(numeroConta, titularConta, saldoInicial, TipoConta.DIGITAL); 
    } 
} 
public class ContaSalario extends ContaBancaria { 
    public ContaSalario(int numeroConta, String titularConta, double saldoInicial) { 
        super(numeroConta, titularConta, saldoInicial, TipoConta.SALARIO); 
    } 
} 

A herança pode ser vista nas classes ContaCorrente, ContaDigital e ContaSalario. Observe que, em todas elas, possuímos o comando extends ContaBancaria. Quando usamos a palavra reservada extends, indicamos ao Java que aquela classe irá herdar os recursos de uma outra classe, que, em nosso exemplo, é a classe ContaBancaria.  

Já o polimorfismo pode ser visto na classe ContaCorrente. Nesta classe é usada a anotação @Override que indica o polimorfismo na linguagem Java. Note que abaixo desta anotação, o comportamento do método consultarSaldo da classe ContaBancária está sendo alterado para apresentar uma informação exclusiva das contas correntes, que é o saldo de cheque especial do cliente.  

Conclusão 

De forma simples e resumida, neste artigo conhecemos os quatro pilares da programação orientada a objetos, os quais representam as características básicas das linguagens de programação orientadas a objetos.   

Recomendo que você analise, estude e edite os códigos para conseguir uma melhor compreensão dos pilares da POO. Procure entender estes conceitos e aprofundar seus conhecimentos neste paradigma de programação que é um dos mais utilizados no desenvolvimento de softwares.

Espero que o conteúdo aqui apresentado seja útil de alguma forma para você. Em caso de dúvidas, sugestões ou reclamações fique à vontade para entrar em contato. 

O que é programação orientada a objetos? 

A programação orientada a objetos (POO) é um dos paradigmas mais conhecidos e utilizados na construção de sistemas computacionais. Baseada na ideia de aproximar o mundo real com o mundo da computação, a orientação a objetos nos permite estruturar o código de forma organizada e reutilizável, algo essencial para a construção de softwares robustos e escaláveis.

Neste texto, vamos explorar os fundamentos da orientação a objetos, seus conceitos-chave, e como aplicá-los na prática por meio de exemplos em Java, uma das linguagens mais populares nesse contexto. 

1 – Entendendo o conceito de programação orientada a objetos 

A programação orientada a objetos (OOP – Object-oriented programming) é um paradigma baseado no conceito de representar aquilo que existe no mundo real em sistemas computacionais através de classes e objetos.  

Esse paradigma serve como um modelo de análise, projeto e desenvolvimento de sistemas que procura aproximar o mundo real ao mundo da programação de computadores, tornando mais fácil o entendimento e solução de problemas complexos. 

Para entender esse paradigma pense, por exemplo, em um sistema bancário: nesse sistema temos uma classe principal chamada Conta Bancária. A partir desta classe, serão criados todos os tipos de contas bancárias disponibilizadas pelo banco. Note no exemplo abaixo que temos três tipos de contas: corrente, digital e salário. Estas contas e todas as demais que o banco vier a oferecer serão objetos da classe principal Conta Bancária. 

Representação gráfica da relação entre classes e objetos.
Fonte: o autor

Essa relação entre classe e objeto é a base do paradigma da programação orientada a objetos. Uma classe pode ser entendida como um modelo, enquanto os objetos, são tudo aquilo que construímos com base nesse modelo (classe). Quando um objeto é construído ocorre o que chamamos de instância de classe.  

Dessa forma, quando usamos os princípios da orientação a objetos para desenvolver um sistema, transformamos cada um dos requisitos em classes e objetos. E para cada uma dessas classes e objetos definimos atributos e métodos próprios que serão utilizados para processar os dados recebidos e retornar as saídas do sistema. 

1.1 – O que são atributos e métodos? 

Atributo e métodos são dois conceitos fundamentais na programação orientada a objetos e podemos entendê-los da seguinte forma: os atributos são as características de cada classe e objeto, enquanto os métodos são as ações e comportamentos dessas classes e objetos. Atributos e métodos são definidos nas classes e compartilhados pelos objetos instanciados através delas.  

Pensando na classe conta bancária, alguns exemplos de atributos podem ser: número da conta, titular da conta, saldo. Enquanto, exemplos de métodos podem ser: depositar, sacar, consultar saldo, transferir.

Representação gráfica detalhada da relação entre classes e objetos
Fonte: o autor

Analisando a imagem acima, percebemos que os atributos e métodos são inseridos na classe Conta Bancária. Dessa forma, cada conta bancária criada, independe do seu tipo, será um objeto instanciado da classe principal Conta Bancária e herdará todos os atributos e métodos disponíveis nela. E isto gera uma das principais vantagens da orientação a objetos: a reutilização de códigos. 

Observe em nosso exemplo que, todas as contas criadas, independente do seu tipo, herdam da classe principal todas as características e comportamentos comuns. Assim, não é necessário, por exemplo, reescrever o código responsável por consultar saldo para cada tipo de conta existente, pois, todas as contas herdam esse método da classe principal. 

2 – Exemplo prático de programação orientada a objetos 

Para fins didáticos e um melhor entendimento desses conceitos, vamos ver um exemplo simples de Java para representar a classe conta bancária.  

Se você quiser executar esse projeto na sua máquina, precisará ter o Java 17 instalado e uma IDE compatível com a linguagem como o IntelliJ ou Visual Studio Code, por exemplo.  

O primeiro arquivo de todo projeto Java é o Main.java, responsável por executar a aplicação, vamos criá-lo:

@SpringBootApplication 
public class BancoApplication { 
public static void main(String[] args) { 
ApplicationContext context = SpringApplication.run(BancoApplication.class, args); 
        BancoService bancoService = context.getBean(BancoService.class); 
        bancoService.ExecutaContas(); 

}
 

No arquivo Main.java nós estamos chamando a função ExecutaContas() da classe BancoService. Vamos criar um arquivo chamado BancoService.java: 

@Service 
public class BancoService { 
        public void ExecutaContas () { 
               ContaCorrente conta1 = new ContaCorrente(1, "João", 700.0, 200.00 ); 
               ContaDigital conta2 = new ContaDigital(2, "Maria", 500.0); 
               ContaSalario conta3 = new ContaSalario(3, "José", 1000.0); 

                conta1.consultarSaldo(); 
                conta2.consultarSaldo(); 
                conta3.consultarSaldo(); 
                conta1.depositar(200.0); 
                conta1.transferir(conta2,700.00); 
                conta2.sacar(500.00); 
                conta3.transferir(conta1,200.00); 
                conta1.consultarSaldo(); 
                conta2.consultarSaldo(); 
                conta3.consultarSaldo(); 
        }       

Note que o arquivo BancoService.java instância três objetos e executa uma série de ações que simulam as transações realizadas em um sistema bancário. 
 
Agora vamos criar as classes que representam os tipos de contas que instanciamos em BancoService: 

ContaCorrente.java

 public class ContaCorrente extends ContaBancaria { 
    private double chequeEspecial; 


    public ContaCorrente(int numeroConta, String titularConta, double saldoInicial, double chequeEspecial) { 
        super(numeroConta, titularConta, saldoInicial, TipoConta.CORRENTE); 
this.chequeEspecial = chequeEspecial; 
    } 


    public double getChequeEspecial() { 
        return chequeEspecial; 
    } 


    @Override 
    public void consultarSaldo() { 
        double saldoTotal = getSaldo() + chequeEspecial; 
        System.out.println( "Conta: " + getNumeroConta() + " / " + "Titular: " + getTitularConta() + " / " + "Saldo atual: R$" + getSaldo() + " / Cheque Especial: R$" + chequeEspecial +  " / Saldo Total: R$" + saldoTotal); 
    } 

ContaDigital.java 

public class ContaDigital extends ContaBancaria { 
    public ContaDigital(int numeroConta, String titularConta, double saldoInicial) { 
        super(numeroConta, titularConta, saldoInicial, TipoConta.DIGITAL); 
    } 

ContaSalario.java 

public class ContaSalario extends ContaBancaria { 
    public ContaSalario(int numeroConta, String titularConta, double saldoInicial) { 
        super(numeroConta, titularConta, saldoInicial, TipoConta.SALARIO); 
    } 

Veja que as classes Conta Corrente, Conta Digital e Conta Salário possuem a palavra-chave extends, indicando que elas herdam atributos e métodos da classe Conta Bancária. 

Vamos agora criar o arquivo ContaBancaria.java  que possui a classe principal Conta Bancária: 

public class ContaBancaria { 
    // Atributos da classe 
    private int numeroConta; 
    private String titularConta; 
    private double saldo; 

    // Construtor da classe 
    public ContaBancaria(int numeroConta, String titularConta, 
                         double saldoInicial, TipoConta tipoConta) { 
        this.numeroConta = numeroConta; 
        this.titularConta = titularConta; 
        this.saldo = saldoInicial; 
    } 

    // Métodos da classe 
    public void depositar(double valor) { 
        saldo += valor; 
        System.out.println("Conta "+numeroConta+" - Depósito de R$" + valor + " realizado com sucesso!"); 
    } 

    public void sacar(double valor) { 
        if (valor <= saldo) { 
            saldo -= valor; 
            System.out.println("Conta "+numeroConta+" - Saque de R$" + valor + " realizado com sucesso!"); 
        } else { 
            System.out.println("Saldo insuficiente para saque de R$" + valor); 
        } 
    } 

    public void consultarSaldo() { 
        System.out.println( 
                "Conta: "+numeroConta+" / "+"Titular: "+titularConta+" / "+"Saldo atual: R$" + saldo);  } 
    public void transferir(ContaBancaria destino, double valor) { 
        if (valor <= saldo) { 
            saldo -= valor; 
            destino.saldo += valor; 
            System.out.println("Conta "+numeroConta+" - Transferência de R$" + valor + " realizada para a conta " + destino.numeroConta); 
} else { 
            System.out.println("Saldo insuficiente para transferência de R$" + valor); 
        } 
    }

    public double getSaldo() { 
        return saldo; 
    } 

    public int getNumeroConta() { 
        return numeroConta; 
    } 

    public String getTitularConta() { 
        return titularConta; 
    } 

Observe que a classe ContaBancaria possui três atributos (numeroConta, titularConta e saldo) e quatro métodos (depositar, sacar, consultarSaldo e transferir). Essa é a classe modelo de onde conta corrente, conta digital e conta salário irão herdar atributos e métodos.  

Para finalizar, vamos criar um arquivo chamado TipoConta.java que será um Enum de tipos de contas: 

public enum TipoConta { 
    CORRENTE, 
    SALARIO, 
    DIGITAL 

Após finalizar a criação dos arquivos e códigos do projeto vamos executá-lo. Teremos esse resultado no console da IDE: 

 
Conta: 1 / Titular: João / Saldo atual: R$700.0 / Cheque Especial: R$200.0 / Saldo Total: R$900.0 
Conta: 2 / Titular: Maria / Saldo atual: R$500.0 
Conta: 3 / Titular: José / Saldo atual: R$1000.0 
Conta 1 - Depósito de R$200.0 realizado com sucesso! 
Conta 1 - Transferência de R$700.0 realizada para a conta 2 
Conta 2 - Saque de R$500.0 realizado com sucesso! 
Conta 3 - Transferência de R$200.0 realizada para a conta 1 
Conta: 1 / Titular: João / Saldo atual: R$400.0 / Cheque Especial: R$200.0 / Saldo Total: R$600.0 
Conta: 2 / Titular: Maria / Saldo atual: R$700.0 
Conta: 3 / Titular: José / Saldo atual: R$800.0 

Esses prints mostram os resultados das ações executadas na função ExecutaContas() da classe BancoService. Essas ações são realizadas pelos objetos de ContaCorrente, ContaDigital e ContaSalario, instanciados de ContaBancaria. Apesar de ser um simples exemplo, observe que através dele conseguimos visualizar claramente os conceitos fundamentais da programação orientada a objetos e entender melhor esse paradigma amplamente usado no desenvolvimento de software.

3 – Usando programação orientada no dia a dia 

A programação orientada a objetos está presente em praticamente todos os sistemas e aplicativos que utilizamos diariamente. Amplamente usada em serviços back-end, mas não restrita somente a isso, a orientação a objetos está presente em redes sociais, streaming de vídeos, jogos, ERPs e muitas outras aplicações que fazem parte de nosso cotidiano. 

Aprender e entender os conceitos de orientação a objetos é fundamental para desenvolver sistemas funcionais e eficientes, que atendam aos objetivos e requisitos definidos nos projetos que trabalhamos.  

A orientação a objetos também é suportada por muitas linguagens e tecnologias. No exemplo acima, usamos Java que, certamente, é a linguagem orientada a objetos mais conhecida no mercado. Entretanto, outras linguagens como C++, C#, Python, PHP e Kotlin e vários frameworks de desenvolvimento como Laravel, Djano, .NET e Flutter, aceitam os conceitos de POO. A escolha de qual linguagem e tecnologia usar dependerá, diretamente, das preferências do desenvolvedor e dos requisitos do projeto, dispondo de um amplo leque de opções para atender a todos os públicos. 

Mas, é válido lembra que, a programação orientada a objetos vai além dos conceitos de classes, objetos, atributos e métodos, apresentados acima. Outros quatro conceitos se destacam na programação orientada a objetos, são eles: abstração, encapsulamento, herança e polimorfismo. Esses conceitos são conhecidos como pilares da programação orientada a objetos e se você quiser saber mais sobre esse assunto, clique aqui, para ler um artigo que escrevi sobre este assunto.   

Conclusão 

A programação orientada a objetos é uma poderosa ferramenta para desenvolver sistemas funcionais, robustos e escaláveis. Sua estrutura baseada em classes, objetos, atributos e métodos permite organizar o código de forma clara, coesa e reutilizável, agilizando o desenvolvimento de sistemas.  

Ao compreendermos e aplicarmos esses conceitos, estamos melhor preparados para criar soluções tecnológicas que as expectativas de nossos clientes e usuários. 

Espero que este artigo seja útil de alguma forma para você. Em caso de dúvidas, sugestões ou reclamações, fique à vontade para entrar em contato. 

E se você quiser aprender mais sobre programação, acesse aqui a seção que tenho dedicada ao assunto. 

O que é programação funcional? 

Ao trabalhar com desenvolvimento de software, diferentes paradigmas de programação oferecem abordagens distintas para a solução de problemas computacionais. Entre eles, destaca-se a programação funcional, uma metodologia que tem ganhado cada vez mais relevância devido à sua capacidade de produzir código mais previsível, testável e manutenível.  

Neste artigo iremos explorar os fundamentos da programação funcional e suas principais características.  Também veremos exemplos simples e práticos de códigos que nos ajudarão a entender melhor como esse paradigma pode ser aplicado no dia a dia dos programadores. 

1 – Entendendo o conceito de programação funcional 

A programação funcional (FP – Functional Programming) é um paradigma baseado no conceito de expressões e funções matemáticas. Ao aplicar a programação funcional em um projeto, os problemas serão decompostos em partes menores, as quais serão atribuídas para funções.  

As funções recebem valores de entrada, aplicam uma lógica de processamento e retornam novos valores como saída. Assim, um sistema é fracionado em um conjunto de funções, cujo somatório destas funções compõem um sistema maior.  

A programação funcional possui algumas características próprias. Abaixo vamos conhecer essas características e ver alguns exemplos de código escrito em JavaScript para facilitar o entendimento.  

2 – As características da programação funcional 

Funções puras: são funções que retornam sempre o mesmo valor quando passados os mesmos parâmetros.  

// Funções Puras+
const multiplicarPor2 = (numero) => numero * 2;

console.log(multiplicarPor2(3)); // Saída: 6  
console.log(multiplicarPor2(4)); // Saída: 8  
console.log(multiplicarPor2(3)); // Saída: 6  
console.log(multiplicarPor2(4)); // Saída: 8 

No exemplo acima, a função multiplicarPor2(), recebe um valor e multiplica-o por 2. Observe nas saídas que, sempre que uma função pura recebe o mesmo valor de entrada irá retornar o mesmo valor de saída.  

Funções de ordem superior: trata-se de uma função capaz de receber outras funções como argumento ou retornar  outras funções como resultado.  

// Funções de Ordem Superior  
const executarOperacao = (operacao, a, b) => operacao(a, b);  
const soma = (x, y) => x + y;  
const multiplicacao = (x, y) => x * y;

console.log(executarOperacao(soma, 5, 3)); // Saída: 8  
console.log(executarOperacao(multiplicacao, 5, 2)); // Saída: 10   

No exemplo acima, executarOperacao() assume o papel de uma função de ordem superior, enquanto soma() e multiplicação(), são funções de cálculos aritméticos que podem ser passadas como argumento para a função superior executarOperacao().  

Composição de funções: é a capacidade de criar uma função a partir da junção de outras funções.  

//Composição de funções  
const somar = (a, b) => a + b;  
const dobrar = (x) => x * 2;  
const composta = () => dobrar(somar(5, 10));  

console.log(composta()); // Saída: 30   

No exemplo acima, temos três funções distintas: somar(), responsável por realizar um cálculo de adição entre dois valores, dobrar() que recebe um valor e multiplica-o por 2, e composta(), a qual assume o papel de uma função composta de somar() e dobrar().   

Imutabilidade: os valores após serem atribuídos para as variáveis não sofrem mudanças, mantendo-se fixos durante toda a execução do código.  Ao trabalhar com dados imutáveis, novos valores são criados, ao invés de ocorrerem modificações nos valores existentes. Dessa forma, a imutabilidade torna o código mais previsível e estável, evitando efeitos colaterais inesperados no processamento. 

Para um melhor entendimento desse conceito, imagine que temos um array de números e queremos adicionar um novo número a ele.  Vamos visualizar dois exemplos: um utilizando uma função mutável e outro uma função imutável.  

A abordagem mutável seria assim: 

//Mutabilidade 
const listaNumeros = [1, 2, 3];
listaNumeros.push(4); // Modifica o array original 

console.log(listaNumeros); // Saída: [1, 2, 3, 4] 

Observe que, em uma abordagem mutável, a constante original listaNumeros, foi modificada durante a execução através da função push()

Agora vamos observar como seria na abordagem imutável: 

//Imutabilidade  
const listaNumeros = [1, 2, 3];
const adicionarNumero = (lista) => [...lista, 4];  

// Acrescenta um valor sem modificar o array original  
const numerosAtualizados = adicionarNumero(listaNumeros);  

console.log(listaNumeros); // Saída: [1, 2, 3]   
console.log(numerosAtualizados); // Saída: [1, 2, 3, 4]

Observe no exemplo acima que, a função adicionarNumero() recebe um array  de valores numéricos chamado listaNumeros e acrescenta o número 4. O resultado dessa operação é atribuído para a constante numerosAtualizados.  

A imutabilidade dos dados pode ser observada nas saídas do código, pois, apesar da constante listaNumeros ser utilizada mais de uma vez, ela não teve seus valores alterados. Foi gerada uma nova constante chamada numerosAtualizados para gravar os dados da operação realizada com a constante listaNumeros.     

Recursividade: na programação funcional, loops de repetição não são usados. No lugar dos loops é adotada a recursividade, que é a característica de uma função chamar a si mesma até que uma condição (chamada de caso base) seja atingida.  

//Recursividade  
const calcularFatorial = (n) => {  
if (n === 0) {  
   return 1; 
} else {  
   return n * calcularFatorial(n - 1);  
}};  
const numero = 5;  
const fatorial = calcularFatorial(numero); 
 
console.log(`O fatorial de ${numero} é: ${fatorial}`);  
//Saída: O fatorial de 5 é: 120   

No exemplo acima, calcularFatorial() é uma função recursiva que calcula o fatorial do valor informado em numero.   

3 – Usando paradigma funcional no dia a dia 

O paradigma funcional é suportado por diversas linguagens de programação. Entre as linguagens puramente funcionais destacam-se o Haskell e a família ML (Standard ML, OCaml e suas variantes). 

Nos exemplos acima, usamos JavaScript, uma linguagem multiparadigma, amplamente conhecida no mercado e compatível com os princípios da programação funcional. Mas além dela, existem outras opções disponíveis como C++, PHP, Python, TypeScript e Kotlin. Todas estas linguagens são multiparadigma e suportam a implementação de códigos seguindo o paradigma funcional. 

Na prática, a programação funcional encontra grande aplicação na Ciência de Dados, especialmente no processamento e análise de grandes volumes de dados. Isto se deve à natureza concisa e declarativa do código funcional, bem como à presença de funções de ordem superior como map, filter e reduce, que simplificam significativamente o tratamento de dados. 

Além disso, este paradigma mostra-se particularmente eficaz no desenvolvimento de algoritmos de busca e ordenação, assim como no processamento de eventos e sistemas reativos. 

Conclusão 

A programação funcional oferece uma abordagem poderosa e eficiente para o desenvolvimento de software, especialmente em aplicações que demandam alta confiabilidade e facilidade de manutenção. Ao adotar funções puras, imutabilidade, recursividade e outras características, este paradigma permite construir sistemas mais previsíveis e menos propensos a erros.  

O suporte a esse estilo de programação em diversas linguagens multiparadigma, usadas por desenvolvedores no dia a dia como JavaScript, Python e Kotlin, amplia ainda mais sua aplicabilidade em diversos contextos.  

Assim, a programação funcional não é apenas mais um recurso técnico, mas é um meio de transformar a maneira como abordamos problemas, tornando o código mais limpo, robusto e eficiente. 

Espero que este artigo seja útil de alguma forma para você. Em caso de dúvidas, sugestões ou reclamações, fique à vontade para entrar em contato. 

E se você quiser aprender mais sobre programação, acesse aqui a seção que tenho dedicada ao assunto.

Programação Imperativa e Programação Declarativa 

Ao falar em desenvolvimento de software, dois paradigmas de programação se destacam pela sua influência na forma como desenvolvedores abordam a criação de programas e sistemas: a programação imperativa e a programação declarativa. Cada um desses paradigmas oferece uma perspectiva única sobre como os problemas podem ser resolvidos e como o código deve ser estruturado para alcançar soluções eficientes. Este artigo irá explorar as principais características de cada paradigma, suas aplicações típicas e as considerações a serem feitas ao escolher entre eles, com o objetivo de fornecer uma visão abrangente que ajude desenvolvedores a tomar decisões informadas em seus projetos. 

Se você tem dúvidas sobre o que é um paradigma de programação, clique aqui, para ler um artigo que escrevi sobre esse assunto. 

1 – Programação Imperativa 

O paradigma da programação imperativa é baseado na codificação de instruções e comandos que dizem, exatamente, o que e como deve ser feito.  

Este paradigma está baseado no uso de laços de repetição, estruturas condicionais, atribuição de valores em variáveis e controle de estado. Instruções como if-else, while, switch e for, são características predominantes das linguagens de programação imperativas. Além disso, os códigos imperativos manipulam estados através do uso de variáveis que ficam armazenadas em memória, até que seja alcançado um resultado esperado. 

A programação imperativa pode ser encontrada em diversas linguagens como C, C++, C#, Java, PHP, Python, JavaScript, entre outras. Abaixo, temos um simples exemplo de um código imperativo, escrito em JavaScript: 

// Lista de frutas 
const frutas = ["Maçã", "Banana", "Laranja", "Limão"]; 

  
// Seleciona o elemento HTML onde a lista será inserida 
const listaElemento = document.getElementById("lista-de-frutas"); 

  
// Cria um elemento <ul> para a lista 
const ul = document.createElement("ul"); 


// Percorre a lista de frutas e cria um <li> para cada fruta 
for (let i = 0; i < frutas.length; i++) { 
   const li = document.createElement("li"); 
   li.textContent = frutas[i];  // Define o texto do <li> 
   ul.appendChild(li);  // Adiciona o <li> ao <ul> 
} 

// Adiciona o <ul> ao elemento HTML selecionado 
listaElemento.appendChild(ul); 

No exemplo acima, o código JavaScript está injetando em um documento HTML, uma lista com o nome de algumas frutas. Perceba que um código imperativo descreve, passo a passo, o que deve ser feito e como deve ser feito pelo computador. Desde localizar o elemento HTML onde serão inseridos os elementos até o comando de inserção das informações neste elemento, um código imperativo é detalhado, descrevendo passo a passo as ações a serem executadas. 

O paradigma imperativo é um dos mais usados no dia a dia dos programadores, sendo muito comum em uma ampla gama de projetos de softwares, dos mais simples aos mais complexos.  

2 – Programação Declarativa  

A principal característica da programação declarativa e principal diferença dela para a programação imperativa, é que os códigos declarativos dizem o que deve ser feito, sem detalhar como deve ser feito. 

Algumas das linguagens declarativas mais conhecidas são HTML, CSS, XML e o SQL. Além dessas linguagens, frameworks conhecidos no mercado, como o Flutter, usado no desenvolvimento mobile, e o ReactJS, usado no desenvolvimento web, são considerados declarativos.  

Abaixo, temos um exemplo simples de um código declarativo, escrito em HTML: 

<!DOCTYPE HTML> 
<html> 
  <head> 
     <title>Lista de Frutas</title> 
  </head> 

  <body> 
     <div id="lista-de-frutas"> 
       <ul>  
         <li>Maçã</li>  
         <li>Banana</li>  
         <li>Laranja</li> 
        </ul> 
     </div> 
  </body> 
</html> 

O código HTML acima, chega no mesmo resultado do código JavaScript apresentado, anteriormente, porém, é perceptível a diferença de abordagem entre eles. No HTML, percebe-se que a codificação descreve o que deve ser feito para apresentar a lista com o nome das frutas na tela, sem entrar em detalhes de como isso será feito. 

A programação declarativa está presente em muitos tipos de projetos, sendo muitas vezes usada em conjunto com outros paradigmas de programação, inclusive, o paradigma imperativo. Projetos de todos os portes podem ser construídos com linguagens declarativas, sendo, portanto, um paradigma versátil e adaptável a diferentes necessidades de desenvolvimento.  

3 – Quando usar cada paradigma? 

A escolha entre a programação imperativa ou declarativa depende das necessidades e objetivos de cada projeto. 

A programação imperativa tem como principais vantagens, o controle total sobre o fluxo de execução e o uso eficiente de recursos em situações de baixo nível (como programação de sistemas). Portanto, esse paradigma é a melhor escolha quando é necessário ter um controle detalhado sobre o fluxo de execução, visualizando passo a passo como o software deve operar para atingir um determinado objetivo. Assim, esse paradigma é, especialmente, útil em projetos que envolvem manipulação complexa de dados como, por exemplo, em jogos e softwares de sistema. 

Porém, há algumas desvantagens que devem ser levadas em consideração ao optar pela programação imperativa, como a complexidade crescente com a escala do código e o aumento do risco de erros devido à manipulação direta de estados. 

Por outro lado, a programação declarativa é recomendada quando o foco está em especificar o resultado a ser atingido, sem se preocupar com os detalhes do caminho para alcançá-lo. Esse paradigma possui como principais vantagens, a geração de códigos mais simples e legíveis, que serão mais fáceis de serem mantidos em médio e longo prazo. 

O paradigma declarativo é, amplamente, utilizado em aplicações web, na construção de interfaces de usuário (UI) e na manipulação de bancos de dados. Por exemplo, ao usar HTML para estruturar o conteúdo de uma página web, o desenvolvedor se preocupa em definir “o que” deve ser mostrado, deixando para o navegador a responsabilidade de “como” isso será feito. De forma similar, ao usar SQL para consultar um banco de dados, o foco está em “o que” se quer obter de dados, sem a necessidade de definir “como” o banco deve executar essa busca. 

Como desvantagens do paradigma declarativo, devemos considerar uma menor flexibilidade para otimizações específicas e a dependência de bibliotecas ou frameworks para funcionar em certos contextos. 

Tendo em vista, que cada projeto é único e possui seus próprios objetivos, custos e prazos, ao escolher por um ou outro paradigma, devemos levar em conta as suas características, vantagens e desvantagens e compará-las ao que se espera do projeto, procurando entender qual paradigma se encaixa melhor ao contexto daquele projeto. 

Muitas vezes, uma combinação entres esses dois paradigmas e, até mesmo, a combinação com outros paradigmas é a abordagem mais eficaz em um projeto. Enquanto a programação imperativa pode fornecer o controle necessário em partes críticas do código, a programação declarativa pode simplificar a definição de regras e layouts, melhorando a legibilidade e manutenção do software. Portanto, a escolha dos paradigmas de programação deve procurar um equilíbrio entre as características desses paradigmas e as necessidades do projeto, gerando escolhas mais assertivas e eficazes. 

Conclusão 

A escolha entre programação imperativa e declarativa não é apenas uma questão de preferência pessoal, mas sim uma decisão estratégica que deve ser guiada pelas necessidades específicas do projeto e pelos objetivos definidos.  

Enquanto a programação imperativa oferece um controle minucioso sobre o fluxo de execução, sendo ideal para situações que exigem manipulação detalhada de dados e recursos, a programação declarativa proporciona simplicidade e clareza, sendo adequada para especificar resultados sem se preocupar com os detalhes de implementação.  

Em muitos casos, uma abordagem híbrida que combina elementos de ambos os paradigmas pode ser mais eficaz, aproveitando o controle detalhado da programação imperativa junto com a legibilidade e simplicidade da programação declarativa. A compreensão profunda de cada paradigma e suas características permitirá que desenvolvedores escolham a abordagem mais adequada para suas necessidades, otimizando o desenvolvimento e manutenção do software. 

Se você quiser conhecer um pouco mais sobre outros paradigmas de programação, aproveite e acesse a categoria do site que é dedicada a esse assunto.

Espero que este artigo seja útil de alguma forma para você. Sinta-se à vontade para explorar os outros materiais do meu blog e, caso tenha dúvidas, sugestões ou reclamações, não hesite em entrar em contato.