Paradigmas de programação

loremimpsu

Lorem Impsu

Posted on November 28, 2023

Paradigmas de programação

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

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 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

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.

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)
Enter fullscreen mode Exit fullscreen mode

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.

a = 1
b = 2
c = a + b 

print(c)
Enter fullscreen mode Exit fullscreen mode

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.


a = 1
b = 2
c = a + b

if(c == 3):
  print('C eh 3')
else:
  print('o compilador eh doido')
Enter fullscreen mode Exit fullscreen mode

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.

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...')
Enter fullscreen mode Exit fullscreen mode

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.

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

    Gato(idade, nome, cor);

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

    void serBonito(){}
}
Enter fullscreen mode Exit fullscreen mode

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.

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

fofuxo.miar();

print('a idade do $fofuxo.nome é  $fofuxo.idade.toString()')
Enter fullscreen mode Exit fullscreen mode

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.


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){}
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

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){}
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

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
Enter fullscreen mode Exit fullscreen mode

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.


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')
}
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
loremimpsu
Lorem Impsu

Posted on November 28, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Paradigmas de programação
beginners Paradigmas de programação

November 28, 2023