A Filosofia de OOP e FP com JavaScript

28 de Maio, 20236 min de leitura
#javascript#programming#softwareengineering#webdev

Não sou filósofo, mas às vezes gosto de entender os porquês mais do que os comos, especialmente quando se trata de programação teórica.

OOP: O paradigma "Qual Entidade"

O que é OOP?

Programação Orientada a Objetos (OOP) é estruturada em torno de objetos—estruturas de dados que encapsulam propriedades e comportamento.

Propriedades são autoexplicativas (você entenderá melhor com o exemplo), enquanto o comportamento do programa é descrito por métodos.

OOP comunica de maneira clara e imperativa - cada método é uma diretiva, uma ação para realizar algo.

A primeira tarefa em OOP é identificar os objetos.

Esses objetos têm propriedades que agem como gavetas; eles armazenam sockets que, neste contexto, se referem a dados.

Em JavaScript, esses dados podem ser qualquer coisa: meias, camisetas, chapéus, luvas, cabos de televisão, televisões, mesas, cadeiras, casas, cidades, planetas, criaturas e quaisquer objetos tangíveis ou fictícios.

Todos esses itens podem ser armazenados como variáveis, ou na terminologia de OOP, classes.

Por exemplo, considere este objeto Drawer em JavaScript:

class Drawer {
  // Móvel!
  // Propriedades do Drawer são declaradas dentro do construtor.
  constructor() {
    this.socks = [];
    // Também é possível armazenar múltiplas propriedades, ou talvez, uma referência a outra classe.
  }

  addSock(sock) {
    // Propriedades da classe são acessadas usando a palavra-chave `this`
    this.socks.push(sock);
  }

  countSocks() {
    return this.socks.length;
  }
}

O Drawer representa uma entidade, vinculando propriedades relacionadas (socks) e comportamentos (métodos addSock e countSocks) juntos.

Como funciona?

Em OOP, você cria uma instância da sua classe (instancia um objeto) e interage com ela usando os métodos definidos.

Aqui está como você usaria o Drawer:

  const myDrawer = new Drawer();
  myDrawer.addSock('red');
  myDrawer.addSock('blue');
  console.log(myDrawer.countSocks()); // Output: 2

Por que usar?

A estrutura que OOP fornece torna realmente fácil e amigável para iniciantes representar objetos dentro do código.

Você descobre entidades do mundo real, define o que elas fazem (através de métodos), e então escreve isso no seu programa.

Com esse espelhamento do mundo real, OOP ajuda você a aplicar seu conhecimento cotidiano à sua programação. Você lida com objetos o tempo todo na vida real.

Usá-los no seu código simplesmente parece natural!

Programação Funcional: O Paradigma "Fluxo de Trabalho"

O que é?

FP é sobre quebrar cada processo em pequenos passos—quanto menor, melhor—que combinados, resolvem seu problema.

FP vê um programa como uma série de transformações aplicadas aos dados. Valoriza funções puras com responsabilidades únicas (sem efeitos colaterais), imutabilidade e funções de primeira classe.

Em FP, os passos são vistos como transformações de dados.

Vamos ver como poderíamos lidar com nosso problema de 'meias na gaveta' com FP:

let drawer = [];

const addSock = (drawer, sock) => [...drawer, sock];

const countSocks = (drawer) => drawer.length;

drawer = addSock(drawer, 'red');
drawer = addSock(drawer, 'blue');
console.log(countSocks(drawer)); // Output: 2

Esses passos mostram como podemos transformar nossos dados (gaveta e meias) para resolver o problema. Começamos com uma gaveta vazia, adicionamos meias a ela e depois as contamos. Cada passo é uma pequena transformação que nos aproxima da solução.

Como funciona?

Em FP, dados e ações são separados. É como cozinhar:

  • Seus ingredientes crus são seus dados 🥚🥛🌾
  • Seu processo de cozimento representa as ações 🍳
  • Você alimenta seus ingredientes crus (dados) no seu processo de cozimento (funções)
  • Essas funções produzem um novo prato (novos dados), mas seus ingredientes crus (dados) ainda estão crus. Eles não são modificados!
let rawIngredients = ['eggs', 'flour', 'milk'];
let cookPancakes = (ingredients) => { /*...process...*/ return 'pancakes'; }
let pancakes = cookPancakes(rawIngredients);
// Você agora tem panquecas, mas seus rawIngredients ainda são ['eggs', 'flour', 'milk']

Por que usar?

Torna seu código previsível e fácil de testar, ao trabalhar com funções determinísticas, há menos espaço para comportamento inesperado.

Mantendo suas funções pequenas, você garante que cada método resolve um único problema e a solução é alcançada encadeando múltiplos passos.

Outros benefícios de usar são:

  • Previsibilidade: FP é como uma receita bem escrita. Você pode prever o resultado, dado o mesmo conjunto de ingredientes (dados) toda vez. Elimina surpresas no seu código 📖

  • Facilidade de teste: Em FP, você pode testar cada função (ou passo) separadamente, assim como testar o quão bem sua técnica de bater ovos funciona antes de prosseguir para o próximo passo de cozimento. Esse isolamento torna seu código fácil de testar e debugar 🥚➡️🍳

  • Confiabilidade: Para projetos onde alta confiabilidade é necessária, FP brilha. Reduz as chances de bugs inesperados, tornando-a uma escolha confiável para projetos cruciais 👍

  • Adequação para problemas modernos: Como usar o utensílio certo para o prato certo, FP fornece as ferramentas certas para lidar com questões centradas em dados na programação. Ao focar no fluxo de trabalho, transformações de dados (não os dados em si), você garante que seu código seja mais conciso e menos propenso a bugs.

Flexibilidade do JS

Com JavaScript, ambas as abordagens são válidas, embora eu recomendaria FP sobre OOP, pois acho que se adequa mais às características da linguagem.

Por que recomendo FP para JavaScript

Não me entenda mal, eu absolutamente aprecio o modelo mental de OOP, princípios SOLID, hierarquia e polimorfismo.

Na verdade, ao longo da minha carreira, escrevi principalmente código OOP em C#, .NET, Flutter, Xamarin Native, C e Java.

No entanto, à medida que mergulhei mais profundamente no mundo do JavaScript, me vi cada vez mais atraído pela Programação Funcional.

Essa transição não foi motivada por hype.

Em vez disso, originou-se de uma sensação genuína de alegria ao criar código de Programação Funcional.

No entanto, isso não foi impulsionado apenas pela satisfação pessoal. encontrando mais alegria em escrever código Funcional comparado a OOP, há três pontos técnicos concretos que solidificaram minha preferência.

Gerenciamento de Estado

Geralmente em uma aplicação web JavaScript você precisa lidar com estado local ou global.

Confiar apenas em princípios OOP para lidar com estado pode introduzir alguns desafios, especialmente quando se trata de rastrear mudanças.

FP defende imutabilidade, o conceito de que os dados não são alterados, mas sempre substituídos, uma vez criados.

Isso elimina bugs decorrentes de mutação de dados inesperada, levando a uma aplicação mais robusta e previsível.

Além disso, com a preferência de FP por funções puras, rastrear mudanças se torna transparente e intuitivo, pois a saída de cada função depende apenas de sua entrada.

// Em vez de mutar o estado diretamente,
// FP cria uma nova cópia com as mudanças desejadas
const newState = Object.assign({}, oldState, {updatedProp: newValue});

// Uma função pura em FP - dada a mesma entrada, a saída é sempre a mesma
const square = num => num * num;

Transformações de Dados

JavaScript frequentemente manipula respostas DOM ou AJAX. FP se destaca aqui, tratando essas operações como uma série de transformações de dados, o que pode ser muito útil quando você está trabalhando com algum Framework Reativo.

// Mapeando dados para elementos DOM
const elements = data.map(item => `<span key={item.id}>${item.name}</span>`);

A função map é uma função pura e determinística.

Recebe uma função callback (frequentemente uma arrow function, como usada aqui) como seu parâmetro e consistentemente retorna um novo array, deixando o array original inalterado, aplicando o conceito FP de Imutabilidade.

Frameworks Reativos

Frameworks reativos como React, Vue ou Svelte, abraçam a filosofia de programação reativa.

No coração dessa filosofia está escrever código que entra em ação mediante mudanças de estado - como ter uma cartela de bingo e procurar por todos os números selecionados.

Sempre que a lista de números selecionados muda, você "executará" um comportamento "reativo", que é:

  • Analisar sua cartela
  • Se o novo número selecionado está presente na sua cartela, circule-o
  • Se não, não faça nada

Esse comportamento reagirá à atualização de estado dos números selecionados do bingo.

Resumindo

Por que objetos?

Porque fornece uma maneira clara e intuitiva de representar comportamentos e relacionamentos do mundo real encapsulando-os dentro de objetos reconhecíveis.

Ao criar essas representações virtuais de entidades do mundo real, podemos facilmente conceitualizar, organizar e manipular nosso código de uma maneira que reflita a realidade.

Por que passos?

A filosofia central da Programação Funcional é simplificar problemas complexos quebrando-os em pedaços gerenciáveis.

Se você está lutando para resolver um problema, quebre-o em tarefas menores.

Se o problema persistir, quebre-o ainda mais até ter uma pergunta específica que pode ser respondida através de pesquisas online ou ferramentas como ChatGPT.

Essa abordagem capacita iniciantes e programadores experientes a enfrentar problemas complexos abordando progressivamente componentes menores e mais compreensíveis.

Ao quebrar desafios em tarefas gerenciáveis, você ganha uma compreensão mais clara do problema geral e encontra soluções mais facilmente enquanto ainda mantém a legibilidade do código.

No fim do dia, você acabará criando pipelines de solução que guiam os dados de mãos dadas através dos problemas e requisitos da sua aplicação.