Paradigmas de Programação

É comum haver debates sobre "linguagens prediletas", porém dificilmente há um aprofundamento em um tema talvez mais relevante que é os paradigmas de programação. São eles que determinam como desenvolvedores(as) codificam e estar consciente sobre o que propõem é essencial para produzir código legível, reutilizável e extensível. O tema apresenta os principais paradigmas sinalizando pontos fortes e de atenção ao programar.

Um paradigma, por definição, é apresentado como: um exemplo que serve como modelo, um padrão a ser seguido. Sendo assim, um paradigma de programação pode ser definido como sendo um modelo, padrão ou estilo ao qual linguagens de programação podem seguir.

Eles indicam a forma como a linguagem deve operar para possibilitar a resolução de problemas.

Os primeiros paradigmas de programação surgiram ainda entre as décadas de 50 e 60, em uma época onde a capacidade computacional era baixa, e a complexidade para passar instruções ao computador era alta. Os iniciais foram os de programação imperativa e declarativa, tratados na época apenas como “abordagens de programação” e só na década de 70 foram denominados como paradigmas.

Outros surgiram ao longo do tempo como evoluções para a forma de programar, sendo eles derivados dos dois iniciais. Atualmente há muitos paradigmas, sendo alguns mais antigos e já não tão utilizados e outros ainda recentes e pouco conhecidos. Dentre os vários existentes pode-se citar:

  • Estruturado;
  • Procedural;
  • Orientado Objeto;
  • Orientado a Eventos;
  • Orientado a Aspectos;
  • Concorrente;
  • Distribuído;
  • Funcional;
  • Lógico;
  • Matemático;
  • Reativo.

Para exemplificar os paradigmas, é utilizado no decorrer do tema um exemplo bastante simples.

A necessidade é selecionar os números pares em um intervalo de 1 a 10, e apresentar o resultado.

Imperativo vs Declarativo O código do exemplo está escrito em Python.

Na figura acima o código imperativo está estruturado e o declarativo é funcional. A diferença entre eles não está apenas na quantidade de código escrito, mas nas intenções expressadas em cada instrução. Na sequência tais diferenças são melhor detalhadas.

Cada paradigma foi criado para atender uma necessidade, alguns propõe evoluções a outros e todos têm aplicabilidade possibilitando que desenvolvedores(as) codifiquem programas que resolvam algum problema.

Toda linguagem é baseada em um paradigma, e há também linguagens baseadas em vários, sendo consideradas nestes casos multiparadigmas, são exemplos algumas como: Java, Javascript e Python.

No mundo do desenvolvimento de software há debates sobre qual o melhor paradigma, porém os paradigmas referem-se a como humanos escrevem e lêem as instruções enviadas ao computador, logo o melhor paradigma a ser utilizado é aquele que facilitará a compreensão e evolução o código.

Programar está associado a conhecer as possíveis formas de enviar instruções ao computador, logo compreender os paradigmas facilita o aprendizado de qualquer linguagem, a ponto de torná-las menos importantes.

Na sequência o tema não detalha todos os paradigmas citados, mas destaca os mais conhecidos e comuns atualmente, sinalizando pontos forte e de atenção.

O guia reforça que a compreensão dos paradigmas é tão relevante quanto a sintaxe de uma linguagem e para um conteúdo mais profundo sugere livros como:

Paradigma Imperativo

O paradigma imperativo determina que as instruções passadas ao computador devem especificar o passo a passo das ações que serão executadas. Tais ações mudam o estado do programa através do uso de variáveis que ficam armazenadas em memória, até chegar a um resultado esperado. O foco do paradigma está em determinar o “como” deve ser feito.

Em continuidade ao exemplo inicial, a necessidade agora é fazer a soma dos números pares em um intervalo de 1 a 10 e apresentar o resultado.

Abaixo um código considerando o paradigma imperativo puro:

Imperativo

Embora atualmente pareça não fazer sentido implementar um código assim, essa era a possibilidade há muitas décadas atrás. O programa seria executado e apresentaria o resultado.

A programação imperativa permitiu uma codificação menos focada no código de máquina, o que também abriu portas para a implementação de programas mais elaborados e menos dependentes do hardware.

Muitas linguagens famosas baseiam-se no paradigma imperativo, destacando-se algumas como: C, C++, Java, C#, PHP, Python, Ruby.

Estruturado

O paradigma estruturado determina que as instruções passadas ao computador podem ser formadas por 3 estruturas, sendo:

  • Sequência: as instruções são codificadas na sequência a serem executadas.
  • Condição: um bloco de código só é executado se uma condição for verdadeira (IF’s/ELSE’s, SWITCH com CASE’s).
  • Repetição: um trecho de código pode ser executado repetidas vezes. (FOR’s, WHILE’s, recursividade).

Em continuidade ao exemplo de código imperativo puro, a necessidade agora é fazer a soma dos números pares de um determinado intervalo, e apresentar o resultado.

A programação estruturada permite maior dinamismo ao software, desta forma é possível coletar os dados e não ficar preso a um conjunto estático.

Estruturado

O código da esquerda ainda atua sobre um conjunto estático de dados, o que fixa a execução para o intervalo de 1 a 10.

O código da direita amplia a capacidade do software e permite que agora ele seja aplicado a qualquer conjunto de números informados na execução. O que mostra a importância do paradigma estruturado para a evolução no desenvolvimento de software.

Destaca-se também neste paradigma a capacidade de modularização ou criação de subrotinas. Tal paradigma se popularizou por propor uma forma de raciocínio intuitivo, onde há legibilidade e compreensão de cada bloco de código e também por de certa forma, em época, se opor ao uso de GOTO. A programação estruturada dominou a forma de programar até década de 90, sendo desbancada com a popularização da OO (Orientação Objeto), porém ela ainda é bastante indicada para pessoas que estão aprendendo programação por ser uma forma simples de programar.

Muitos softwares ainda possuem sua codificação seguindo o paradigma estruturado, o que não caracteriza ser uma boa ou má codificação, porém normalmente não condizem com as intenções dos desenvolvedores. Isso porque muitas vezes estes creem estar usando paradigmas como o Procedural ou Orientação Objeto, mas o código continua seguindo o modelo estruturado. Exemplos:

  • Grandes blocos de código estruturado fazendo muitas coisas distintas em um único método.
  • Rotinas completas encapsuladas em classes mas que não representam um objeto de fato.
  • Classes/métodos com múltiplas responsabilidades, onde o que determina o código a ser executado ainda são as condicionais e repetições, muitas vezes utilizadas em excesso.

O problema com relação aos pontos citados está em que muitos times de desenvolvimento não conseguem distinguir a diferença entre o uso de Estruturado, Procedural e OO, o que na prática leva a implementação de baixa qualidade com códigos confusos e de alto custo de manutenção.

A programação estruturada é útil e aplicável, porém seu uso sem considerar paradigmas complementares pode contribuir para códigos de baixa legibilidade.

Procedural

O paradigma procedural, por vezes usado como sinônimo para o paradigma imperativo, determina que as instruções a serem passadas ao computador podem ser agrupadas em procedimentos (equiparável a métodos, funções, rotinas). Os procedimentos podem ser invocados durante a execução do software e visam a reutilização do código em pontos diferentes do mesmo, já que eles devem suportar a especificação de argumentos, variáveis locais, chamadas recursivas, dentre outros.

Em continuidade ao exemplo de código estruturado, a necessidade agora é fazer a soma dos números pares e ímpares de um determinado intervalo, e apresentar o resultado.

A programação procedural permite o uso de procedimentos, desta forma fica mais fácil reaproveitar lógica e código.

Procedural

Com mais requisitos a serem atendidos, o código começa a ficar extenso, o que mostra a importância do paradigma procedural para possibilitar o reaproveitamento e organização do código.

Esse paradigma surgiu no fim da década de 50, ganhando maior uso nas décadas seguintes e ainda hoje está presente em muitas linguagens, mesmo em algumas que também suportam OO (Orientação Objeto) ou outros paradigmas, como Python, PHP e JavaScript.

Com o surgimento de OO, muitos times de desenvolvimento marginalizam de certa forma o uso puro de procedimentos, porém esse paradigma ainda é bastante aplicável mediante necessidades mais simples que não requerem uma modelagem mais complexa de código.

Em soluções corporativas, as quais normalmente possuem times de desenvolvimento grandes, o uso massivo de programação procedural geralmente leva a códigos de difícil manutenção e evolução, o que não é necessariamente exclusividade ou culpa deste paradigma, e sim o pouco cuidado ou conhecimento de desenvolvedores(as). O fato é que para cenários mais complexos outros paradigmas oferecem mais recursos para contribuir com a organização e qualidade do código.

Orientação Objeto

A OO (Orientação Objeto) foi concebida ainda na década de 70, tendo como um de seus pais Alan Kay, o qual foi um dos inventores da linguagem de programação Smalltalk, linguagem essa considerada a pioneira no uso do paradigma de OO (embora alguns de seus conceitos já existissem em outras linguagens como Simula).

A OO determina que o código deve ser modelado de forma a se aproximar ao mundo real, e que o mesmo em execução no computador tenha tal modelagem representada por uma estrutura de objetos, características e ações. Tais objetos podem interagir uns com os outros modificando o estado do programa até que resultem nas saídas esperadas.

Para viabilizar essa estrutura, OO propõe o uso de Classes, Atributos e Métodos, unido a características como Abstração, Encapsulamento, Herança e Polimorfismo. Os objetos são instâncias das classes e possuem um estado durante a execução do software podendo as mesmas serem modificadas, persistidas e recarregadas de acordo com a lógica implementada.

Em continuidade ao exemplo de código procedural, a necessidade agora é fazer a soma dos números pares, ímpares e primos de um determinado intervalo, e apresentar o resultado.

A programação Orientada Objeto prevê o uso de classes para modelar o código. Além disso, oferece a capacidade de herança, onde uma classe herda os recursos da classe pai, o que contribui para a reutilização de código.

O uso de OO é comum em modelagens complexas, que envolvem entidades e que visam soluções mais elaboradas. Algumas linguagens inclusive recomendam que “tudo seja um objeto”.

O exemplo abaixo atende as mesmas necessidades dos exemplos anteriores mas agora através de uma modelagem OO, o que para tal exemplo é uma modelagem bastante simples.

Procedural

A classe Calculator é a que contém a lógica principal. Nesse cenário, sempre que houver a necessidade de uma calculadora que filtre um novo conjunto de números, basta apenas criar uma nova classe filha especializada em filtrar tais números, sem haver necessidade de modificar outros pontos do software.

O código em OO pode ser tornar mais extenso do que em outros paradigmas, mas também pode ficar mais organizado. Uma das indicações é conhecer e aplicar os princípios SOLID e Padrões de projeto (Design Patterns), os quais contribuem para códigos mais elegantes e um melhor uso de OO.

A popularização do uso deste paradigma se deu na década de 90, motivada pela também popularização do uso da linguagem Java. Desde então OO se tornou o paradigma mais utilizado, e embora hoje tenha concorrência da programação funcional, ainda é o dominante.

Ao aproximar do código a visão do mundo real, OO buscou tornar mais rápida a implementação de programas, considerando que ao modelar objetos igual ao mundo real faria com que os códigos implementados se tornassem mais legíveis e reaproveitáveis.

De fato isso trouxe uma evolução na forma de programar, principalmente para soluções corporativas onde o uso de OO elevou a capacidade de implementação de código elegantes, extensíveis e que impulsionaram o mercado de software.

A ideia central de OO está sobre a capacidade do(a) desenvolvedor(a) modelar objetos. Mas depois de algumas décadas, analisando o código gerado, pode-se avaliar que OO ainda não foi bem compreendida por muitos times de desenvolvimento, que continuam a gerar códigos mais próximos dos paradigmas estruturado e procedural do que com uma modelagem adequada que represente de fato objetos do mundo real.

Além da capacidade em modelar, OO também demanda de desenvolvedores(as) conhecimentos complementares como os princípios SOLID e Padrões de projeto (Design Patterns), para que possam usufruir de todas as vantagens deste paradigma. Com pouca compreensão sobre como usar OO, é comum softwares apresentarem cenários como:

  • Modelagem de objetos que não condizem com o negócio atendido pelo software.
  • Modelagem com alto acoplamento e baixa coesão.
  • Pouco uso de Interfaces ou Classes abstratas, e quando utilizadas as mesmas não focam nas generalizações.
  • Uso inadequado de herança para reaproveitamento de código.
  • Código repetidos e pouco extensíveis.

A Orientação Objeto foi e ainda é um paradigma de grande importância para o crescimento da área de desenvolvimento de software. Embora outras alternativas como uso de protótipos ou outras estruturas de dados estejam ganhando espaço, optar pelo uso de OO ainda é uma boa escolha, mas suas reais vantagens se destacam a partir de um uso consciente de suas características.

Paradigma Declarativo

O paradigma declarativo determina que as instruções passadas ao computador devem especificar a sequência lógica e o resultado esperado, sem determinar o fluxo de controle. O foco da resolução está em determinar “o quê” deve ser resolvido, o que é um contraste a programação imperativa que determina “o como”.

Historicamente o paradigma declarativo tem menos destaque do que o imperativo, porém ainda assim possui algumas linguagens famosas baseadas no seu conceito, cita-se algumas como: SQL, HTML, JavaScript, Erlang, LISP.

Em continuidade aos exemplos anteriores, mas transportando o cenário para uma modelagem de dados relacional, considere (mesmo não sendo um exemplo comum) uma tabela NUMBERS a qual contém registros de 1 a 100.

A necessidade é obter a soma dos números pares entre 1 e 10.

No exemplo, um código declarativo em SQL:

Procedural

Um código reduzido, onde as instruções estão focadas no resultado esperado e não em como o computador chegará a esse resultado.

Muitas linguagens e frameworks dão suporte a uma “mistura” de código relacionado ao paradigma imperativo e declarativo, o que tem uma aplicabilidade e pode auxiliar desenvolvedores(as) na resolução de problemas, mas que precisa ser utilizado de forma consciente. O uso inadequado pode gerar códigos de difícil manutenção e evolução, como em cenários abaixo:

  • Código declarativo com código imperativo “embutido”, com o uso de condicionais ou outras estruturas de características imperativas.
  • Composição de código declarativo através do uso de strings, que são montadas a partir de código imperativo.
  • Códigos declarativos misturados a imperativo cuja lógica busca indicar ao computador “o como fazer”, o que contraria o propósito do paradigma.

Em linguagens como SQL e HTML, é comum as situações apresentadas. Para estes casos, o indicado é ter atenção a como o paradigma declarativo trabalha. Normalmente uma linguagem declarativa possibilitará o recebimento de parâmetros, os quais podem ser utilizados, mas sem que haja lógica imperativa na resolução. Toda lógica imperativa deve ser executada antes da execução do código declarativo, e este deve apenas saber trabalhar com os parâmetros recebidos.

Atualmente o paradigma declarativo tem ganho destaque através do uso de um outro paradigma que deriva dele, o funcional, o qual dá mais poder para desenvolvedores(as), inclusive evitando a inadequada mistura de declarativo e imperativo.

Funcional

O paradigma funcional difere dos demais citados por não ser derivado da programação imperativa e sim da declarativa, onde o objetivo é declarar ao computador o resultado esperado, e não o passo a passo para construção deste resultado. Essa forma de compor o código traz duas características que a tornam bastante diferente das demais, que são a imutabilidade e por consequência a não mudança de estado do software.

Em continuidade ao exemplo de código orientado objeto, a necessidade se mantém em fazer a soma dos números pares, ímpares e primos de um determinado intervalo, e apresentar o resultado.

A programação funcional permite um código mais reduzido, focado em instruções relacionadas ao resultado esperado.

Procedural

Não há modificação sobre qualquer variável, seguindo a linha da imutabilidade. Todas as instruções são através da invocação de funções passando um valor de entrada e esperando um valor de saída, que pode ser o valor de entrada da função seguinte.

Para viabilizar essa estrutura, a programação funcional apoia-se sobre o uso de funções, as quais ao serem executadas mapeiam os valores de entrada para valores de saída, sem atualizar o estado do software, e quando usadas de forma combinada explicitam o poder do paradigma declarativo. As funções são na programação funcional “cidadãs de primeira classe”, assim como as Classes são em OO.

O uso de funções tem como base o uso do cálculo lambda, conceito desenvolvido ainda na década de 30 por Alonzo Church. Mas foi no final da década de 50 com o surgimento da linguagem LISP que o paradigma funcional foi aplicado pela primeira vez. Historicamente esse paradigma tem sido menos popular, mas atualmente ganhou força entre times de desenvolvimento, e o suporte já presente em linguagens como Lisp, Miranda, Haskell, Erlang e Elixir também se estende a linguagens como JavaScript, Python, Java, Ruby, dentre outras.

Um exemplo final, apresenta o mesmo cenário usando Orientação Objeto com código de características funcionais.

Isso é possível em linguagens que suportam esses dois paradigmas, porém pelo fato de não haver imutabilidade, é um equívoco determinar que o código abaixo é funcional.

Procedural

Há redução de código, e parte da implementação faz uso de funções que não modificam o estado. É uma abordagem interessante que contribui para código mais legível, porém é necessário que desenvolvedores(as) tenham consciência sobre essa condição, principalmente com relação a mutabilidade/imutabilidade.

Assim como os demais paradigmas, qualquer problema pode ser resolvido com o paradigma funcional. O desafio para aqueles que desejam trabalhar com funcional puro, está em compreendê-lo a ponto de utilizar os mecanismos adequados para lidar com a composição de lógicas e estruturas de dados, de forma que não contenham códigos amarrados ao modelo imperativo e estruturado de programar.



Os paradigmas existentes são um resultado das necessidades e recursos atuais na computação. Sempre que novas necessidades surgem e a capacidade computacional aumenta, cria-se o espaço para novos paradigmas. O guia focou nos paradigmas de maior utilização atualmente, e poderá detalhar outros paradigmas conforme evolução e adoção dos mesmos.

Histórico