Paradigmas de programação
Tags: beginners, codenewbie, learning, architecture
Published: 2023-11-28 13:36:36


Quando se deseja iniciar na programação a primeira coisa que se procura no Google é "Como programar?", "Como programar em x linguagem?" ou até "como ganhar 10 mil reais rápido na carreira". Essas perguntas são pertinentes, mas não há resposta óbvia (na última pergunta, a resposta deveria ser 'não é possível') muito pelo fato de que há diversas maneiras de se programar. O que chamamos de paradigmas da programação. 
Neste artigo, irei apresentar os 3 paradigmas iniciais e te dar uma breve introdução do mundinho da programação. 

## Programação estruturada

Para inicio de conversa precisamos falar do que realmente é programação. Programação nada mais é que um conjunto de instruções ao computador. Esse computador, temos que considerar que é uma unidade organizacional que possui uma memória (local onde salvar dados), um processador (local de interpretar/processar dados),entrada e saída de dados. Esse modelo é chamado de arquitetura de Von Neumann. Hoje, todos os dispositivos que são capazes de computar utilizam o mesmo modelo para a sua arquitetura.

![Computador de Von Neumann](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fa3vao7i7vttwunbf4rx.png)

Uma linha de programação na mais é do que uma ordem de que o processador execute uma instrução, essa instrução necessariamente deve está acessível na sua memória por meio de uma [fila](https://algoritmosempython.com.br/cursos/algoritmos-python/estruturas-dados/filas/) e segue por meio de barramentos até o processador que processa a informação e retorna um resultado, para a memória.

![ilustração do processo de dados](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6k944ayfp2o0di0fx9xn.gif)

Antes da programação que hoje conhecemos como estruturada, havia apenas uma programação de acontecimentos, ou uma tabela de comandos. Os programadores dinossauros faziam um conjunto de regras e de comandos e inseriam no computador por meio JOBs. Os JOBs levavam os dados a unidade de processamento e retornava um valor processado. O problema disso é coordenar tudo para ser feito em apenas um JOB. Esse rolê te parece complicado? É por que ele é complicado. Para te assustar, vamos a um exemplo de uma simples cópia de uma String para um outro local na memória. 

```assembly
la r8 , fte
la r9 , dst
add r4 , r0 , r0

add r18 , r8 , r4
lbu r5 , 0(r18)
beq r5 , r0 , end
add r19 , r9 , r4
sb r5 , 0(r19);
addi r4 , r4 , 1

sb r0 , 1(r19)
```

Então, não me peçam pra explicar o que cada linha faz (eu não sei, copiei esse caso do google) mas em uma explicação geral, todo esse comando era executado de maneira simultânea. Era uma instrução única para o computador. E daí que vem a pergunta de um milhão de pesos argentinos : "tá, mas e se eu precisasse utilizar o  resultado em outra instrução?". Eu vou te contar que até dá pra salvar e depois utilizarmos essa resposta, mas como o código acima não é agradável, imagina algo que faz o dobro do seu trabalho. 

Para facilitar a vida do dev, um cara legal chamado Edsger Dijkstra (eu tive que dá um google no nome dele) pensou bem e lançou para a comunidade a seguinte questão: "E se a gente simplificasse essas paradas executando um bloco de instruções de cada vez e não todas de maneira arbitrária?" Poxa, mano inteligente, né? não me admira que o cara tem um dos algoritmos mais utilizados com o seu nome (isso é papo para depois). Queria dizer que meu mano do nome esquisito foi bem ouvido pela comunidade, mas como todo disruptivo do Twitter, ele foi xingado por texto e descredibilizado pela comunidade da época. Levaram-se 10 anos para que o cara fosse escutado e a partir daí o mundo da programação virou do avesso. 

Com a ideia de Dijkstra (tive que copiar o nome dele novamente) agora temos 3 fundamentos para a estrutura. O primeiro é a sequência, todo algoritmo vai ser lido sequencialmente durante um bloco de comandos. 

```python
a = 1
b = 2
c = a + b 

print(c)
```

Neste algoritmo (que felizmente eu sei, eu juro que sei) temos uma cadeia básica de instruções, o **a** recebe um valor inicial, o **b** recebe outro valor inicial e o **c** depende dos valores anteriores para ter o seu próprio valor. 

Outro conceito que o Dijkstra (de novo um ctrl+v) nos trouxe, foi a seleção de blocos de código que poderiam ser executados. Para ele, de nada valia a execução sequencial se não pudéssemos pular algumas linhas de código dependendo do resultado. Nasceu então o **if** e o **else**. 

```python

a = 1
b = 2
c = a + b

if(c == 3):
  print('C eh 3')
else:
  print('o compilador eh doido')
```

No código acima, caso ocorra a seleção corretamente, não haverá xingamento ao coitado do compilador da máquina pois o **else** jamais será executado. 

> se você for comentar sobre a presença do **else** nesse mísero exemplo, eu vou na sua casa te encher de supapo. Fique avisado. 

Agora com a definição de bloco de instruções e de como podemos selecionar certas linhas, surgiu no mano Dijkstra (eu acertei de primeira, sem olhar) a necessidade de repetir comandos sem a necessidade de escrever os mesmos comandos novamente. Nascendo assim o **for** e o **while**. 

```python
a = 1
b = 2
c = a + b

if(c == 3):
  print('C eh 3')
else:
  print('o compilador eh doido')

for range(b):
  print('eu digo xuuu')
  print('eu digo xaaa')

while(c == 3):
  print('ao infinito e além...')
```

E com isso, temos a estrutura básica de qualquer linguagem de programação. Em algum nível, todos programamos estruturalmente. Mesmo que em alguns casos, essas estruturas sejam encapsuladas. 

## Programação orientada a objetos

Utilizando o que já conhecemos, a programação estruturada, nasceu a ideia de abstrair esses pedaços de códigos estruturados em uma organização que remetesse ao mundo real. Os blocos de código, agora separados por seções, arquivos ou seleções de código, ganharam o apelido de objetos. E desse objetos, os blocos de código ganharam dois tipos de classificação: os atributos e os métodos.

```dart
class Gato{
    int idade;
    String nome;
    String cor;

	Gato(idade, nome, cor);

    void miar(){
        print('Miau');
    }

    void serBonito(){}
}
```

Acima temos a classe (outro nome dado ao objeto, porque nem tudo que é representado é objeto) de um Gato, que tem como atributos **idade**, **nome** e **cor**. Que tem como método (ou funcionalidade) **miar** e **serBonito**. Nesse pequeno trecho de código, temos o encapsulamento dos dados do nosso querido Gato. Que nos garante que somente aquele objeto tem os valores dos seus atributos, e as suas funcionalidades. Para acessarmos qualquer um desses dados, é necessário termos um objeto Gato declarado. 

```dart
Gato fofuxo = Gato(4, 'fofuxo', 'laranja');

fofuxo.miar();

print('a idade do $fofuxo.nome é  $fofuxo.idade.toString()')
```


Além do encapsulamento, outro conceito forte para utilização da orientação a objetos é o conceito de Herança. Não, não é a herança daquela tia-avó distante que vivia com uma amiga e criava um gato chamado Fofuxo. Herança é um conceito de cópia de funcionalidade de uma classe antes declarada sem a necessidade de copiar todos os atributos e os seus métodos. 

```dart

class Herdeiro extends Gato{
	String nomeSolteiro;
	double valorHeranca;

	Herdeiro(nomeSolteiro, valorHeranca)
    Future<void> lamentarPartida()async{
	    Future.wait(Duration(minutes: 10));
    }

    void esbanjarRiqueza(){
	    while(valorHeranca > 0){}
    }
}
```

Pronto, agora nosso gato virou o nosso herdeiro! tudo isso sem precisar mais saber os seus atributos e agora o bichano pode lamentar a nossa partida (por um tempo saudável para a vida de um bichano) e esbanjar nosso suado dinheirinho. Tudo isso além de miar e ser bonito. 

Além do conceito de herança, temos o conceito de polimorfismo. O polimorfismo garante que o código se comporte de maneiras diferentes dependendo do tipo de objeto que estamos declarando. 

```dart
abstract class Gato{
	int idade;
	String nome;
    String cor;
    
	Gato(idade, nome, cor);

    void miar()
    void serBonito()
}

class GatoAmarelo implements Gato{
	int idade;
	String nome;
    String cor;
    
	Gato(idade, nome, cor);

    void miar(){
	    for(int i = 0; i < 10 ; i++){
		    print('Miaaaaaaaaaau');
	    }
    }

    void serBonito(){
    
       final modoDestruicao = Chaos();
       modoDestruicao.ativar();
    }
}

class GatoPreto implements Gato{
	int idade;
	String nome;
    String cor;
    
	Gato(idade, nome, cor);

    void miar(){
		print('Miau'); //educadamente
    }

    void serBonito(){
	    while(true){}
    }
}
```


Agora, como podemos reparar pelo nosso código. Caso tivermos um **GatoPreto** o método de miar dele será um tanto mais rebuscado que no caso do **GatoAmarelo**. Os dois objetos possuem os mesmo atributos, mas possuem blocos de instruções de métodos diferentes.

## Programação funcional

A programação funcional é o caso mais curioso de utilização da matemática na programação. Tudo vem dos primórdios do cálculo de newton, onde ele utilizava o conceito de _f(x)_ para definir um cálculo e ser reutilizado dentro da equação indefinidamente. A funcionalidade nasceu bem antes mesmo que da computação. Foi algo utilizado pela nossa Lady Lovelace para executar a máquina de Babbage (na teoria), utilizado pela Grace Hopper para criar os compiladores e hoje integra linguagens famosas de programação. 

Como nos outros paradigmas, a funcionalidade possui as suas próprias características. A primeira de todas é chamada de pureza, ou de funcionalidade pura. Tá, a função precisa casar virgem? você me pergunta, e não. Nada disso. Na verdade, essa safada tem que rodar, e rodar bastante, mas ser uma funcionalidade que gera valores sem mutabilidade entre uma execução ou outra. 

```javascript
const eh_maior = (valor_a, valor_b) => valor_a >= valor_b;

eh_maior(12, 1) //true

eh_maior(11, 2) //true

eh_maior(3, 10) //false

eh_maior(12, 1) //tem que continuar dando true
```

Outro conceito importante para o paradigma funcional é a imutabilidade. O que quer dizer isso? quer dizer que se você não atribuir o valor resultante de uma função a uma variável, o valor anterior continuará a ser o mesmo. Isso é importante para o controle dos dados em memória. 

```javascript

var a = 10
var b = 13
var maior = eh_maior(a, b) ? a : b

if(eh_maior(a, (b-10))){
	console.log('aqui o a eh maior')
}

if(eh_maior(b, a)){
   console.log('aqui o b eh maior')
}
```

No código acima, caso a função **eh_maior** tivesse um efeito de mudança, o bloco de código do segundo **if** nunca seria chamado. Porém, caso você precise de alguma mudança além da sua função, outro conceito conhecido das funcionalidades é o famoso efeito colateral. O que podemos definir por funções assíncronas. Seja uma chamada a API, um cálculo que depende de dados fora do escopo, ou até uma inserção na memória rígida do computador. Funções assíncronas ferem o conceito de imutabilidade em alguns casos. Pois algo vai mudar a partir da sua execução, quando ou como elas executarem.


### Conclusão

Por esse breve resumo (que não foi tão breve assim) dos paradigmas de programação, vemos que hoje em dia é bem mais raro utilizarmos um ou outro paradigmas. Algumas linguagens, como por exemplo o Python, o Go e o Dart são multiparadigmas, podendo utilizar uma mesclagem de todos esses paradigmas (e alguns outros) para auxilio de uma solução. Como programador, os paradigmas serão dispostos a sua necessidade. Não escolha um como um time de futebol, use-os como um conjunto de chaves philips, estrela, de fenda. Cada qual no seu momento.