Paradigmas de Programação
- Paulo Ricardo Siqueira Soares
- Sep 20, 2023
- 18 min read
Uma linguagem de programação é a forma na qual o computador e o programador se comunicam, fazendo uma analogia simples é a mesma forma como os seres humanos comunicam-se, através de linguagens, sendo essas de gestos, escritas, faladas, ou seja, uma codificação na qual duas partes conseguem interpretar. Para cada linguagem ou forma de comunicação existe uma metodologia, essas metodologias para as linguagens de programação são os chamados paradigmas.
O Paradigma de programação oferece a visão que o programador possui da estrutura de execução do programa, por exemplo uma programação estruturada os programadores irão produzir a abstração do código como uma sequência de funções executadas de modo empilhado.
Paradigma Declarativo X Paradigma Imperativo
Existem 2 paradigmas dos quais todos os outros são derivados, o paradigma declarativo e o imperativo, algumas vezes também citados como estilos.
O paradigma imperativo é o mais antigo em programação, está diretamente ligado a linguagem Assembly que é conhecida como linguagem de máquina.
Paradigma imperativo: descreve-se todas as operações a serem realizadas para solucionar o problema. O resultado é uma consequência da realização destas operações. O Foco do paradigma imperativo é em como o programa deve conseguir o resultado.
Paradigma declarativo: é feita a descrição do problema. Cada sentença tem significado por si mesma e adiciona algumas informações diretamente associadas ao resultado. Elas podem ser apresentadas em qualquer ordem. O foco do paradigma declarativo é, o que o programa deve realizar.

Figura1: Imperativo Vs Declarativo - https://pt.slideshare.net/skosta/paradigmas-de-programacao

Figura 2: Derivação dos Paradigmas de Programação -https://www.differencebetween.com/difference-between-declarative-and-vs-imperative-programming/
Paradigma Estrutural
O paradigma declarativo foca em definir “como” o programa deve ser computado, e derivado dele foram criados os paradigmas procedimental e orientado a objetos. Contudo, parte da forma em como explicitamos o fluxo de controle do programa nestes dois paradigmas foi influenciada pelo paradigma estrutural.
Quando o paradigma imperativo era o único conhecido e se programava apenas em linguagens puramente imperativas, como Assembly, FORTRAN e COBOL, havia um recurso implementado pelo comando goto, que permitia saltos transferir a execução para qualquer linha específica dos programas. Fazendo uso deste recurso, era possível quebrar a linearidade do código, ou seja, a execução das linhas de código na ordem que foram escritas, o que tornava o software incrivelmente difícil de manter e compreender, porém criava recursos importantes como executar novamente uma ação sem a necessidade de escrever de novo a mesma linha ou pulando uma ação desnecessária, economizando linhas de código e tempo de processamento. Este estilo de programação era conhecido como código espaguete, uma vez que toda estrutura estava toda entrelaçada.
Em 1968 e 1987, Edsger W. Dijkstra e Niklaus Wirth respectivamente, publicaram artigos nos quais defendiam a abolição do goto nas linguagens de programação de alto nível, alegando que o comando induzia a vários erros de programação e promovia práticas ruins, além de tornar extremamente difícil a leitura de programas.
Este movimento deu origem à programação estruturada, que se fundamenta em três estruturas de controle de fluxo de código:
Estrutura de sequência: na qual um comando é executado após o outro, de forma linear, e, portanto, sem o uso de goto;
Estrutura de decisão: na qual trechos do programa podem ser executados dependendo do resultado de um teste lógico;
Estrutura de iteração: na qual determinado trecho de código é repetido por um número finito de vezes, enquanto um teste lógico for verdadeiro
Exemplo de programação imperativa não-estrutural:
INPUT "Entre umm número: ", N
FACTORIAL = 1
I = 1
// Loop usando GOTO
IF I > N THEN GOTO 70
FACTORIAL = FACTORIAL * I
I = I + 1
GOTO 50
PRINT "O fatorial de "; N; " é "; FACTORIAL
END
Um exemplo genérico, em uma linguagem fictícia, com o pior método possível, usando GOTO.
Paradigma Estruturado
O nome de programação estruturada é dado pela forma que os programas são escritos, possuindo uma estrutura básica formada por três etapas, que são: sequência, decisão e interação, modularização em especial em funções.
Programação Procedural/Procedimental
Outra forma de como programação estruturada é conhecida, nesse tipo de programação, os programas são criados baseados em quais funções, procedimentos e variáveis são necessárias para resolver um problema.
Em particular eu prefiro chamar esse tipo programação de programação orientada ao procedimento, que aliás era a matéria na qual aprendi esse paradigma, pelo simples fato de que realmente tudo é orientado ao procedimento a ser executado para conseguir o resultado esperado.
Na sequência, temos a ordem em que as coisas devem acontecer, como por exemplo, antes de atribuir um valor a uma variável é necessário que ela esteja declarada. Na decisão podemos usar desvios condicionais como IF ou SWITCH para tomar decisões baseadas em valores de variáveis, e na Interação há a possibilidade de o usuário interagir com o programa enviando informações e tomando decisões.
A programação estruturada pode também ser modularizada usando subprogramas ou sub-rotinas, mais conhecidos como procedimentos e funções. A modularização ou divisão do programa permite que vários programadores trabalhem em conjunto, cada um fazendo uma parte, uma sub-rotina, que depois será incluída ao programa principal, além de permitir a reutilização de código, assim não é necessário escrever os códigos do procedimentos e/ou funções todas as vezes que for necessário, bastando apenas chama-los para serem executados novamente, obedecendo a ordem sequencial lógica do programa. Todo programa possui sub-rotinas que realizam tarefas, sendo o correto fazer com que cada uma realize apenas tarefas coerentes com sua responsabilidade. Excesso de tarefas dentro e uma sub-rotina torna-se difícil a localização de falhas e manutenção do código, por isso a divisão de sub-rotinas e tarefas deve ser feita com muita cautela.
Existem muitas linguagens de programação que seguem a forma estruturada. Dentre as mais conhecidas estão as linguagens C e Pascal. A linguagem C foi criada por Ken Thompson na década de 60, originalmente desenvolvida para a implementação do sistema Unix. O nome C foi dado pelo fato de a linguagem ter sido desenvolvida com características semelhantes à outra linguagem de nome B, uma simplificação da linguagem BCPL.
Vale aqui nos lembrar que as linguagens de programação para CLP (Controlador Lógico Programável), são por definições da IEC 61131-3, todas linguagens estruturadas e procedimentais, a última atualização da IEC 61131-3 datada de Fevereiro de 2013, adiciona algumas características para a Orientação a Objetos, mas veja bem, apenas algumas características, porém não as torna Orientadas a Objeto. Um dos motivos é a própria característica do CLP quando foi editada pela primeira vez a norma IEC 61131-2 , de trabalhar através de ciclos por varredura (Scan), em um único sentido (de cima para baixo), o que torna a linguagem sequencial ideal para essa aplicação de hardware, além das definições do run-time que não permite alocação dinâmica de memória, limitar a criação de objetos pelo próprio software. Os hardwares de CLP atuais, apesar de manterem suas características de varredura, em virtude do aumento de capacidade computacional, atualmente podem executar rotinas através de interrupções, múltiplos threads, rotinas principais em paralelo (múltiplos runtimes em diferentes tempo de ciclo e execução), sendo assim, possuindo capacidade de sobra para orientação a objetos, dependendo apenas de atualização da normal, e das características das linguagens e editores pelos fabricantes.
Vale lembrar as 4 principais linguagens utilizadas em CLP como Texto Estruturado, Diagrama de Blocos, Ladder e Gráfico Sequencial (não necessariamente nessa ordem).
Programação Orientada a Objetos
O paradigma orientado a objetos tem suas raízes nas linguagens de programação Simula e Smalltalk nas décadas de 1960 e 1970, respectivamente. Contudo, este paradigma só veio a ser aceito realmente para uso comercial por volta dos anos 1990, quando surgiu a linguagem de programação Java, que ficou popularizada pelo fato de se poder programar para todas as plataformas da mesma maneira, o que até então não era possível.
A orientação a objetos também se apresentou com a esperança de suprir algumas das preocupações da indústria do software: a necessidade de criar software comercial mais rapidamente, mais confiável e a um custo baixo.
A programação Orientada a Objetos ou OO difere da linguagem estruturada inicialmente e principalmente pela forma de pensar. Em OO o programador deve conseguir abstrair do problema coisas que podem ser representadas como objetos, literalmente esse paradigma busca trazer o mundo real para dentro do computador através da abstração, como por exemplo, se deseja criar um programa para saber se um aluno está ou não aprovado em uma disciplina, o programa deverá criar objetos para representar o aluno e a disciplina. Os objetos são definidos em classes, onde cada objeto criado é denominado instância.
Uma classe define as características dos objetos, denominadas atributos e suas ações, denominadas métodos. A classe Aluno, por exemplo, possui atributos como nome, endereço, idade, serie e turma. Os métodos nos dizem o que pode ser feito com o objeto, e no caso do objeto aluno, podemos armazenar, alterar, excluir e buscar. A quantidade de atributos e métodos varia em cada programa e devem ser criados de acordo com a necessidade especificada durante a fase de abstração do problema. As classes também descrevem relacionamentos entre objetos (da mesma classe, ou de classes distintas). Ou seja, o software é representado por um conjunto de estruturas de dados que interagem entre si, cada uma delas contendo uma coleção de dados específicos e um conjunto de rotinas (métodos) para manipular esses dados (ações sobre dados).
Uma forma de representar um programa orientado a objetos é o Diagrama de Classes. Nele é possível especificar classes, atributos, métodos, herança e a associação existente entre as classes, comumente modelo em UML (Unified Modeling Language), Para uma linguagem de programação fazer parte inteiramente do paradigma orientado a objetos, deve implementar seus três mecanismos básicos, ou pilares da orientação a objeto, que são: herança, polimorfismo e encapsulamento.

Figura 3:Diagrama de Classes
Classe
Uma classe é uma estrutura que abstrai um conjunto de objetos do mesmo tipo . É nela que são definidas as características (estrutura) dos objetos daquele tipo, ou seja, que dados (atributos) são armazenados pelo objeto e que operações (métodos) podem ser efetuadas com estes dados.
É importante que cada classe tenha apenas uma ou um pequeno número de responsabilidades. Se uma classe possui excesso de responsabilidades, sua implementação se tornará muito confusa e difícil de manter e entender. Pois, nesse caso, ao alterar uma responsabilidade, você correrá o risco de alterar outro comportamento inadvertidamente. Ela também centralizará muito conhecimento, que seria melhor gerenciado se fosse espalhado. Quando uma classe fica grande demais, ela quase se torna um programa completo e pode cair nos problemas da programação procedimental.
Atributo
Os dados contidos em uma classe são conhecidos como atributos daquela classe e representam características presentes nos objetos do tipo da classe. Cada atributo deve ter um escopo de acessibilidade, um nome e ser de um tipo, que será ou um tipo de dado nativo da linguagem (geralmente tipos: inteiro, real, caractere, string, booleano), ou outra classe já existente na linguagem ou definida pelo programador.
Por exemplo, para a classe Pessoa, que representa uma abstração de uma pessoa, seus atributos poderiam ser os seguintes: nome (cadeia), idade (inteiro), peso (real) e rg (cadeia). Consequentemente cada objeto do tipo Pessoa teria estes atributos, cada um deles com um valor específico.
Vale ressaltar que um atributo de uma classe também pode ser declarado como sendo um objeto de outra classe. Por exemplo, na classe Pessoa, o atributo rg poderia ser da classe Rg e esta, por sua vez, teria os atributos nome, nome do pai, nome da mãe, data de nascimento, naturalidade, número do RG, etc.
Método
Métodos definem os comportamentos dos objetos da classe, sendo equivalentes às funções e/ou procedimentos do paradigma procedimental, mas diretamente ligados a um objeto ou uma classe. Ou seja, são trechos de código modularizados que executam uma ação com os atributos acessíveis ao seu contexto.
Assim como no paradigma procedimental, além dos métodos poderem ter suas próprias variáveis locais, estes também podem acessar as variáveis globais, que no caso da orientação a objetos são os atributos da classe ou da sua instância em questão. Estes também podem receber ou não parâmetros, e podem retornar um valor a quem o chamou (similar a uma função) ou apenas executar alguma ação com os atributos acessíveis a ele (similar a um procedimento).
Vale ressaltar que quando uma função ou procedimento do paradigma procedimental acessa uma variável global aquela variável é uma variável do programa e é visível a todo o programa, enquanto que quando um método acessa uma “variável global” (atributo definido na classe), ele acessa apenas os atributos do objeto em questão ou atributos próprios da classe, chamados estes de atributos estáticos (i.e., são atributos que não pertencem aos objetos, sendo acessáveis somente pela classe). Ou seja, quando invocamos algum método de um objeto da classe Pessoa, esse método só terá acesso aos atributos do seu objeto, não “enxergando” quaisquer atributos de outros objetos do tipo Pessoa.
Como vimos, métodos são extremamente necessários na manipulação dos atributos dos objetos e são utilizados para definir o comportamento destes. Consequentemente são o principal meio de comunicação pelo qual o ambiente externo interage com o ambiente interno do objeto.
Construtores e Destrutores
Construtores de instâncias, ou simplesmente construtores, são métodos chamados automaticamente quando ocorre uma instanciação de uma classe, ou seja, quando um objeto é criado. Estes realizam as ações necessárias para a inicialização correta do objeto, como por exemplo, atribuir valores aos atributos de controle interno do objeto, para que então este possa ser usado corretamente.
Em contraste com os métodos construtores, temos os métodos destrutores, que são executados automaticamente quando o escopo em que os objetos estão definidos é finalizado (i.e., quando uma instância de uma classe não pode ser mais referenciada, ela é removida da memória pelo mecanismo de coleta automática de lixo, também denominado Garbage Collector. Contudo, quando seu objeto utiliza recursos não gerenciáveis automaticamente, como arquivos, conexões de rede, etc. é necessário usar o destrutor para liberar esses recursos). Seu principal objetivo é a liberação de recursos alocados pelo objeto que será destruído (liberar arquivos, sockets, conexões com banco de dados, etc).
Objeto
Um objeto ou instância é uma materialização da classe, e pode ser usado para armazenar dados e realizar as operações definidas na classe. Na grande maioria das linguagens orientadas a objetos mais recentes, como Java e C# , a instanciação de um objeto é feita quando o operador new é seguido da chamada de algum método construtor. Para que os objetos ou instâncias possam ser manipulados, é necessária a criar uma instância da classe, que é basicamente criar variáveis do tipo da classe. Após alterar o valor de algum dos atributos do objeto é dito que o estado do objeto foi alterado.
Encapsulamento
Encapsulamento permite que detalhes complicados da implementação da classe (regras de negócio, atributos para controle interno, métodos, etc.) sejam escondidos de quem os usa, sendo que estes apenas interagem com o que precisam através de interfaces, permitindo assim ocultar do usuário toda a parte que é irrelevante para este.
Dessa maneira, podemos pensar em uma classe como uma caixa preta, pois sabemos o que ela faz, conhecemos e interagimos com sua interface externa, mas não nos preocupamos como o processo é feito lá dentro.
A ideia do encapsulamento é tornar o software mais flexível, fácil de modificar e de criar novas implementações, uma vez, que cada parte possui sua implementação e realiza seu trabalho independentemente das outras partes (i.e., pacotes, bibliotecas, componentes, classes, métodos, etc.).
Modificadores de Acesso
Nas linguagens de programação orientada a objetos (e.g., C++, Java, C#), em geral, os níveis de encapsulamento são indicados através das seguintes palavras-chaves:
public: é o modificador menos restritivo; ele permite que o membro da classe pode ser acessado externamente a ela, assim como das classes derivadas desta;
protected: esse modificador permite que o membro seja acessado apenas pela própria classe ou pelas classes que derivam desta;
private: é o modificador mais restritivo; ele permite que o membro da classe seja acessado somente de dentro desta, não podendo ser acessado nem mesmo por classes derivadas.
Essas palavras chaves são chamadas de “modificadores de acesso” e são utilizadas para definir quem terá acesso à classe e a seus membros (atributos e métodos), sendo assim, essenciais para o encapsulamento.
Geralmente atributos encapsulados são atributos que não podem ser alterados livremente, por estarem intimamente relacionado a outros atributos e a integridade do objeto, de tal forma que se forem alterados de forma indevida, podem tornar o estado do objeto inconsistente.
Para lidar com atributos encapsulados com o uso dos modificadores protected ou private, é preciso implementar métodos públicos que acessam e alteram esses atributos, fazendo todo tratamento para que a integridade do objeto seja mantida.
Herança
Herança Simples
O conceito de herança é construído sobre a ideia de que algumas classes estão intimamente ligadas a outras classes, e que por conta disso, compartilham características em comum (i.e., atributos, métodos, comportamentos, etc.).
Partindo dessa ideia, podemos estabelecer um relacionamento hierárquico entre classes, em que a classe no topo da árvore é a classe mais genérica (geralmente chamada de classe base, ou classe mãe) e à medida que descemos de nível na hierarquia, temos uma nova classe (classe derivada, ou classe filha), que é uma especialização da classe acima. Por conta disso, geralmente nos referimos à relação que vincula uma classe derivada a sua classe base pela frase "Y é um tipo de X" ou "Y é uma especialização da classe X", sendo X a classe base e Y a classe derivada.
Abaixo tem uma figura que torna simples o entendimento, a classe mãe Animal, contém o nível mais genérico, as classes filhas ou derivadas Mamífero e Ave, possuem um novo grau de especialização, porém não deixar ser uma classe animal, o mesmo acontecendo com os níveis subsequentes.

Figura4 Classe Mãe Anima e Suas Filhas
O mecanismo de herança permite que uma classe seja definida a partir de uma outra classe já existente, com isso a definição da classe derivada herda automaticamente atributos, métodos e comportamentos da classe base. Por conta disso, herança é considerada uma técnica poderosa, uma vez que encoraja a reusabilidade durante o desenvolvimento e provê uma grande economia de tempo, sem perda de qualidade no desenvolvimento (pois você não precisa reescrever, testar, depurar e manter o código herdado).
Herança múltipla
Algumas linguagens orientadas a objetos como C++, Scala e Python também oferecem o mecanismo de herança múltipla, que permite que uma classe herde de mais de uma classe ao mesmo tempo.
Contudo herança múltipla tem sido uma questão sensível por muitos anos, pois pode aumentar a complexidade na programação e gerar ambiguidade em situações onde mais de uma classe base possui atributos ou métodos como mesmo nome, embora hoje já haja abordagens que resolvem essa ambigüidade (i.e., em C++ por exemplo, é usado classePai::AtributoAmbiguo para explicitar de qual classe o atributo ambíguo será acessado).
Polimorfismo
A palavra polimorfismo vem do grego, πολύς (poli) que significa muitas e μορφή (morphos) que significa formas, ou seja, muitas formas. Em programação, é dito que um objeto é polimórfico quando o sistema de tipo da linguagem atribui mais de um tipo a este.
Linguagens tipadas convencionais, como Pascal, baseiam-se na ideia de que funções e procedimentos e, consequentemente, seus parâmetros, têm um tipo único. Tais linguagens são ditas monomórficas, no sentido de que cada variável pode ser interpretada como sendo de um tipo. Linguagens de programação monomórficas contrastam com as linguagens polimórficas em que algumas variáveis podem ter mais de um tipo.
Contudo, mesmo nas linguagens de programação mais convencionais há um certo grau de polimorfismo. Em muitas linguagens, por exemplo, o operador de soma (+), aceita tanto soma entre números inteiros (int x int -> int), quanto soma entre números reais (float x float -> float), e em algumas linguagens ainda, age como concatenador de strings (string x string -> string). Sendo assim, podemos dizer que operador de soma é polimórfico, pois possui mais de um tipo, e assume um deles de acordo com o contexto. Outro exemplo, seria a função length(), que retorna o número de elementos em um vetor de tipo T (T[] -> int), sendo T qualquer tipo.
Existem quatro tipos de polimorfismo que uma linguagem pode ter (note que nem toda linguagem orientada a objetos implementa todos os tipos de polimorfismo).
Sobrecarga
Sobrecarga ocorre quando definimos numa mesma classe métodos com o mesmo nome, mas com assinaturas diferentes, ou seja, que recebem e/ou retornam parâmetros de tipos diferentes. Esse tipo de polimorfismo ocorre em tempo de compilação, pois conseguimos saber qual definição do método será invocada para um dado grupo de parâmetros antes mesmo de o código ser executado.
Coerção
Coerção permite ao programador converter de maneira implícita alguns tipos para outros tipos, omitindo assim algumas conversões de tipo semanticamente necessárias.
Em C# por exemplo, é possível converter implicitamente um tipo int para double, como ilustrado abaixo:
int a = 2;
double b = a;
Esse tipo de polimorfismo nos permite, por exemplo, definir uma função de soma, para o tipo double, e usá-la para somar inteiros, bem como somar variáveis dos dois tipos.
Paramétrico
Polimorfismo paramétrico permite que uma função ou um tipo de dado seja escrito de forma genérica, de modo que ele possa lidar com valores uniformemente, sem dependendo do seu tipo.
O polimorfismo paramétrico é extremamente útil quando precisamos criar estruturas de dados genéricas, como listas, árvores, filas, pilhas, etc. Utilizando o polimorfismo paramétrico, podemos definir a estrutura de forma genérica, e especificar o tipo no momento da criação do objeto ou do uso da função.
Inclusão
Objetos de um subtipo (classe derivada) podem ser manipulados como sendo objetos de algum de seus supertipos (classe base).
Classe Abstrata
Classe abstrata é uma classe que não pode ser instanciada diretamente, mas que pode ser herdada por outras classes. Nela, definimos atributos, métodos e métodos abstratos, que são métodos com apenas a assinatura definida, sem implementação.
As classes derivadas (através de override) é que definirão a funcionalidade de cada atributo ou método abstrato. A ideia de uma classe abstrata é forçar que todas as suas classes derivadas implementem os atributos e métodos definidos nela.
Classes abstratas também podem herdar de outras classes abstratas e uma classe abstrata pode ser herdada por uma classe concreta (i.e., classes instanciáveis, como Pessoa, Trabalhador, Estudante, Pilha<T>, etc.).
Um bom exemplo de classe abstrata é a classe Animal, uma vez que esta classe em si é uma abstração de todas as espécies de animais, e consequentemente, não existe no mundo real um animal que seja apenas do tipo Animal, e sim “animais concretos” que são de alguma raça e espécie específica (e.g., pastor alemão, harpia, pônei, etc., note que esses animais também pertencem a outras classes abstratas, sendo estas cachorro, ave e cavalo, respectivamente, que também herdam de Animal), ou seja, que pertencem a alguma classe concreta derivada de Animal.
Interface
Na interface definimos atributos e métodos sem implementar nada do seu código (apenas assinaturas) e a classe que implementa a interface é obrigada a fornecer (implementar) os códigos definidos na interface. Vale também evidenciar que uma classe pode implementar mais de uma interface, sendo esta uma alternativa para compartilhamento de características entre classes em linguagens que não suportam herança múltipla.
Por exemplo, podemos pensar no código de barras de um produto como uma interface, uma vez que são estabelecidos padrões de código de barra, e é necessário que todo produto implemente esses padrões para poder ser identificado.
Comparação: Procedural VS Orientada a Objetos
Legibilidade
Uma vez que os paradigmas procedimental e orientado a objetos são imperativos, nestes o programador precisa definir exatamente como quer que o programa seja computado, explicitando, além da lógica do algoritmo, todo o fluxo de controle e manipulação dos dados. Por sua vez, nas abordagens dos paradigmas funcional e lógico, que são declarativos, o programador delega ao sistema todo o fluxo de controle da execução do programa, permitindo o mesmo apenas declarar as estruturas e funções necessárias para a solução do problema.
Baseado nesse fato podemos argumentar que a abordagem imperativa faz com que um programa seja mais difícil de ler se comparado a declarativa, pois uma vez que é necessário explicitar sobre qual fluxo de controle o programa deve ser computado, parte do código passar a ser mais para a máquina do que para o ser humano, o que pode inclusive dificultar a compreensão deste quanto a ideia do algoritmo em si, uma vez que para o ser humano compreender o processo de resolução de um problema, nem sempre é preciso explicitar todo o fluxo, bastando uma abstração adequada.
Vale ressaltar também que, como na programação declarativa os dados são imutáveis, o programa é livre de efeitos colaterais e possui transparência referencial, uma vez que você entende como algo funciona, você não precisa mais se preocupar se em algum momento especifico pode acontecer um erro naquele código caso o programa seja executado de alguma forma não planejada. Essa característica torna a leitura do código muito mais simples, uma vez que o resultado de todas as partes do programa não compartilham estados em comum e são independentes das demais partes (e de sua ordem de execução), permitindo o programador se concentrar em entender apenas a parte desejada, abstraindo as demais partes, sem receio de que o não conhecimento delas possa facilitar um erro.
Capacidade de Escrita
Podemos também analisar a expressividade dos paradigmas quanto à quantidade de esforço necessário para se escrever um programa nele, ou seja, por quão fácil é usar um determinado paradigma para representar um problema e sua solução.
Cada paradigma foi feito para expressar de forma simples e natural um tipo de problema, consequentemente um programa relativamente simples pode se tornar complexo num paradigma inapropriado.
Confiabilidade
Pelo fato de programas desenvolvidos no paradigma imperativo terem seu estado representado por variáveis, e essas poderem ser compartilhadas entre diferentes partes do programa (ou seja, podem ter efeitos colaterais) é possível que um método altere o estado do programa e, de forma não planejada, acabe corrompendo a integridade do sistema, o que muito provavelmente pode levar a um erro em tempo de execução. Esse é comum problema comum no paradigma imperativo, e pode ser contornado com o uso dos tratadores de exceção try-catch, presentes em linguagens imperativas mais recentes como C++, Java, C#, JavaScript, Python, etc.
No paradigma orientado a objetos, ainda pode ser usado o conceito de encapsulamento para tornar o programa mais modular, como uma caixa preta. Assim, é fácil desenvolver um módulo, testá-lo, e uma vez testado, usá-lo em outras partes do projeto com total confiabilidade, bem como em outros projetos. Dessa forma, é possível aumentar a produtividade sem perder confiabilidade.
Já no paradigma funcional o problema citado não existe, uma vez que o programa não tem estados, e todos os dados definidos são imutáveis. Propriedades como transparência referencial e ausência de efeitos colaterais tornam a programação funcional muito mais modular e fácil do seu código ser analisado e testado de forma isolada, sendo um enorme trunfo quanto à confiabilidade do programa. O mesmo também vale para programas desenvolvidos no paradigma lógico, que assim como o funcional, também tem transparência referencial e ausência de efeitos colaterais.
Eficiência
O paradigma imperativo é baseado na máquina de Turing e na arquitetura von Neumann de computadores, consequentemente esses programas são naturalmente eficientes quando executados nessa arquitetura; enquanto o paradigma declarativo é totalmente diferente deste modelo, sendo programas escritos neste geralmente menos eficientes no uso de CPU e memória que programas imperativos, como C, Pascal e Java.
Reusabilidade
Como vimos ao longo deste trabalho, nos paradigmas procedimental, funcional e orientado a objetos podemos escrever funções e / ou subrotinas, e por meio dessas encapsular uma determinada ação ou processo do sistema, bastando apenas chamá-la quando necessário; o que nos evita ter de reescrever o código em todos os lugares onde aquela ação se faz presente, aumentando assim altamente a reusabilidade em um projeto de software. Uma vez que já temos várias funções escritas, ainda podemos, a partir dessas criar bibliotecas de funções, possibilitando assim a reusabilidade de código não apenas em várias partes de um mesmo sistema, mas também entre vários sistemas.
O conceito de polimorfismo paramétrico adotado pelos paradigmas funcional e orientado a objeto também colaboram consideravelmente para uma maior reusabilidade durante o desenvolvimento de um projeto, pois é possível definir funções que aceitam mais de um tipo como parâmetro, o que nos permite escrever uma função genérica e usá-la para diversos tipos, ao invés de termos que reescrever o código para todos os tipos necessários.
No paradigma orientado a objetos, além do polimorfismo, podemos destacar o conceito de herança, que tem como objetivo justamente definir novas classes a partir de outras já implementadas, herdando todas as características (atributos, métodos, formas de construção, etc.) da classe mãe, evitando assim que o programador tenha reescrever toda a estrutura mais genérica para cada nova especialização.
Manutenabilidade
Manutenção em programas imperativos tende a ser uma tarefa mais difícil já que esses possuem estados mutáveis com dependências implícitas e, possivelmente, subrotinas pouco expressivas. Uma forma de minimizar essa dificuldade é fazer uso do conceito de encapsulamento, ocultando assim toda parte “sensível” do programa (e.g., controle interno de um objeto, validações da regra de negócio, etc.), tornando assim o programa mais modular e por tanto, mais fácil de testar e manter. No paradigma procedimental, que não possui explicitamente um conceito de encapsulamento, podemos modularizar o código por subrotinas e bibliotecas.
Para simplificar o código em programas imperativos e minimizar a dificuldade de manutenção, existe uma série de recomendações/boas práticas a seguir durante o desenvolvimento, como por exemplo: usar nomes significativos para variáveis, cada função ou subrotina deve realizar apenas uma tarefa, comentar o código quando for necessário evidenciar alguma regra do negócio, etc.
Bibliografia:
Programação de computadores em Java, Rui Rossi dos Santos, Nova Terrra, 2011
留言