Por que a imutabilidade é tão importante (ou necessária) no JavaScript?


206

Atualmente, estou trabalhando nas estruturas React JS e React Native . No meio do caminho, me deparei com o Immutability ou a biblioteca Immutable-JS , quando estava lendo sobre a implementação do Flux e Redux do Facebook.

A questão é: por que a imutabilidade é tão importante? O que há de errado na mutação de objetos? Isso não torna as coisas simples?

Para dar um exemplo, vamos considerar um aplicativo simples de leitura de notícias, com a tela de abertura sendo uma exibição de lista de notícias.

Se eu definir digamos uma matriz de objetos com um valor inicialmente, não posso manipulá-lo. É o que diz o princípio da imutabilidade, certo? (Corrija-me se estiver errado.) Mas, e se eu tiver um novo objeto de Notícias que precise ser atualizado? Normalmente, eu poderia ter adicionado o objeto à matriz. Como faço para conseguir neste caso? Excluir a loja e recriá-la? Adicionar um objeto à matriz não é uma operação mais barata?



2
Estrutura de dados imutável e função pura levam à transparência referencial, facilitando muito o raciocínio sobre o comportamento do seu programa. Você também recebe um retorno gratuito ao usar a estrutura de dados funcional.
WorBlux 23/12/15

Forneci um ponto de vista do Redux @bozzmob.
Prosti

1
Pode ser útil aprender sobre imurabilidade em geral como um conceito de paradigma funcional, em vez de tentar pensar que JS tem algo a ver com isso. React é escrito por fãs de programação funcional. Você tem que saber o que eles sabem para entendê-los.
Gherman

Não é necessário, mas oferece algumas vantagens legais. Estado mutável é para software como partes móveis são para hardware
Kristian Dupont

Respostas:


196

Eu tenho pesquisado recentemente o mesmo tópico. Farei o possível para responder às suas perguntas e tentar compartilhar o que aprendi até agora.

A questão é: por que a imutabilidade é tão importante? O que há de errado na mutação de objetos? Isso não torna as coisas simples?

Basicamente, tudo se resume ao fato de que a imutabilidade aumenta a previsibilidade, o desempenho (indiretamente) e permite o rastreamento de mutações.

Previsibilidade

A mutação oculta as mudanças, que criam efeitos colaterais (inesperados), que podem causar erros desagradáveis. Ao aplicar a imutabilidade, você pode manter a arquitetura e o modelo mental do aplicativo simples, o que facilita o raciocínio sobre o aplicativo.

atuação

Embora a adição de valores a um Objeto imutável signifique que uma nova instância precise ser criada onde os valores existentes precisam ser copiados e novos valores precisam ser adicionados ao novo Objeto que custa memória, os Objetos imutáveis ​​podem usar o compartilhamento estrutural para reduzir a memória a sobrecarga.

Todas as atualizações retornam novos valores, mas as estruturas internamente são compartilhadas para reduzir drasticamente o uso de memória (e o thrashing do GC). Isso significa que, se você anexar a um vetor com 1000 elementos, ele não criará realmente um novo vetor com 1001 elementos. Provavelmente, internamente, apenas alguns objetos pequenos são alocados.

Você pode ler mais sobre isso aqui .

Rastreamento de mutação

Além do uso reduzido de memória, a imutabilidade permite otimizar seu aplicativo usando a igualdade de referência e valor. Isso facilita muito ver se alguma coisa mudou. Por exemplo, uma mudança de estado em um componente de reação. Você pode usar shouldComponentUpdatepara verificar se o estado é idêntico comparando os objetos de estado e impedir a renderização desnecessária. Você pode ler mais sobre isso aqui .

Recursos adicionais:

Se eu definir dizer uma matriz de objetos com um valor inicialmente. Eu não posso manipular isso. É o que diz o princípio da imutabilidade, certo? (Corrija-me se estiver errado). Mas e se eu tiver um novo objeto Notícias que precise ser atualizado? Normalmente, eu poderia ter adicionado o objeto à matriz. Como faço para conseguir neste caso? Excluir a loja e recriá-la? Adicionar um objeto à matriz não é uma operação mais barata?

Sim isto está correcto. Se você está confuso sobre como implementar isso em seu aplicativo, recomendo que você analise como o redux faz isso para se familiarizar com os conceitos principais, isso me ajudou muito.

Eu gosto de usar o Redux como exemplo porque ele abraça a imutabilidade. Possui uma única árvore de estado imutável (referida como store), onde todas as alterações de estado são explícitas, despachando ações que são processadas por um redutor que aceita o estado anterior juntamente com as referidas ações (uma de cada vez) e retorna o próximo estado do seu aplicativo. . Você pode ler mais sobre seus princípios básicos aqui .

Existe um excelente curso de redux no egghead.io, em que Dan Abramov , autor do redux, explica esses princípios da seguinte maneira (modifiquei o código um pouco para melhor se adequar ao cenário):

import React from 'react';
import ReactDOM from 'react-dom';

// Reducer.
const news = (state=[], action) => {
  switch(action.type) {
    case 'ADD_NEWS_ITEM': {
      return [ ...state, action.newsItem ];
    }
    default: {
        return state;
    }
  }
};

// Store.
const createStore = (reducer) => {
  let state;
  let listeners = [];

  const subscribe = (listener) => {
    listeners.push(listener);

    return () => {
      listeners = listeners.filter(cb => cb !== listener);
    };
  };

  const getState = () => state;

  const dispatch = (action) => {
    state = reducer(state, action);
    listeners.forEach( cb => cb() );
  };

  dispatch({});

  return { subscribe, getState, dispatch };
};

// Initialize store with reducer.
const store = createStore(news);

// Component.
const News = React.createClass({
  onAddNewsItem() {
    const { newsTitle } = this.refs;

    store.dispatch({
      type: 'ADD_NEWS_ITEM',
      newsItem: { title: newsTitle.value }
    });
  },

  render() {
    const { news } = this.props;

    return (
      <div>
        <input ref="newsTitle" />
        <button onClick={ this.onAddNewsItem }>add</button>
        <ul>
          { news.map( ({ title }) => <li>{ title }</li>) }
        </ul>
      </div>
    );
  }
});

// Handler that will execute when the store dispatches.
const render = () => {
  ReactDOM.render(
    <News news={ store.getState() } />,
    document.getElementById('news')
  );
};

// Entry point.
store.subscribe(render);
render();

Além disso, esses vídeos demonstram em mais detalhes como obter imutabilidade para:


1
@naomik obrigado pelo feedback! Minha intenção era ilustrar o conceito e mostrar explicitamente que os Objetos não estão sendo mutados e não necessariamente mostrar como implementá-lo completamente. No entanto, meu exemplo pode ser um pouco confuso, vou atualizá-lo um pouco.
Danillouz

2
@bozzmob de nada! Não, isso não está correto, você deve impor a imutabilidade no redutor. Isso significa que você pode seguir estratégias como demonstradas nos vídeos ou usar uma biblioteca como immutablejs. Você pode encontrar mais informações aqui e aqui .
Danillouz 21/12/2015

1
@naomik ES6 constnão é sobre imutabilidade. Mathias Bynens escreveu um ótimo artigo de blog sobre isso.
Lea Rosema

1
@terabaud obrigado por compartilhar o link. Concordo que é uma distinção importante. ^ _ ^
Obrigado

4
Por favor, explique isto "A mutação oculta as alterações, que criam efeitos colaterais (inesperados), que podem causar erros desagradáveis. Quando você reforça a imutabilidade, pode manter a arquitetura do aplicativo e o modelo mental simples, o que facilita o raciocínio sobre o aplicativo". Porque isso não é verdade no contexto do JavaScript.
Pavle Lekic

143

Uma visão contrária da imutabilidade

TL / DR: a imutabilidade é mais uma tendência da moda do que uma necessidade em JavaScript. Se você estiver usando o React, ele fornecerá uma solução clara para algumas opções de design confusas no gerenciamento de estado. No entanto, na maioria das outras situações, ele não agrega valor suficiente à complexidade que apresenta, servindo mais para suprir um currículo do que para atender às necessidades reais do cliente.

Resposta longa: leia abaixo.

Por que a imutabilidade é tão importante (ou necessária) em javascript?

Bem, estou feliz que você pediu!

Algum tempo atrás, um cara muito talentoso chamado Dan Abramov escreveu uma biblioteca de gerenciamento de estado javascript chamada Redux, que usa funções puras e imutabilidade. Ele também fez alguns vídeos muito legais que tornaram a idéia muito fácil de entender (e vender).

O momento foi perfeito. A novidade do Angular estava desaparecendo, e o mundo JavaScript estava pronto para se fixar na coisa mais recente que tinha o grau certo de legal, e essa biblioteca não era apenas inovadora, mas também se encaixava perfeitamente com o React, que estava sendo vendido por outra potência do Vale do Silício .

Por mais triste que seja, as modas dominam o mundo do JavaScript. Agora Abramov está sendo saudado como um semideus e todos nós, meros mortais, devemos nos sujeitar ao Dao da Imutabilidade ... Se faz sentido ou não.

O que há de errado na mutação de objetos?

Nada!

De fato, os programadores estão mudando objetos por er ... desde que haja objetos para mudar. Mais de 50 anos de desenvolvimento de aplicativos, em outras palavras.

E por que complicar as coisas? Quando você tem um objeto cate ele morre, você realmente precisa de um segundo catpara acompanhar a mudança? A maioria das pessoas apenas diz cat.isDead = truee acaba com isso.

(Objetos mutantes) não simplificam as coisas?

SIM! .. Claro que sim!

Especialmente em JavaScript, que na prática é mais útil usado para renderizar uma exibição de algum estado que é mantido em outro local (como em um banco de dados).

E se eu tiver um novo objeto de Notícias que precise ser atualizado? ... Como faço para alcançar neste caso? Excluir a loja e recriá-la? Adicionar um objeto à matriz não é uma operação mais barata?

Bem, você pode seguir a abordagem tradicional e atualizar o Newsobjeto, para que sua representação na memória desse objeto seja alterada (e a exibição exibida para o usuário, ou o que se esperaria) ...

Ou alternativamente...

Você pode tentar a abordagem FP / Imutabilidade sexy e adicionar suas alterações ao Newsobjeto em uma matriz que rastreia todas as alterações históricas para que você possa percorrer a matriz e descobrir qual deve ser a representação correta do estado (ufa!).

Estou tentando aprender o que é certo aqui. Por favor, me esclareça :)

A moda vem e vai amigo. Existem muitas maneiras de esticar um gato.

Lamento que você tenha que suportar a confusão de um conjunto de paradigmas de programação em constante mudança. Mas ei, BEM-VINDO AO CLUBE !!

Agora, alguns pontos importantes a serem lembrados no que diz respeito à imutabilidade, e você os jogará com a intensidade febril que apenas a ingenuidade pode reunir.

1) A imutabilidade é incrível para evitar as condições de corrida em ambientes multithread .

Ambientes multithread (como C ++, Java e C #) são culpados da prática de bloquear objetos quando mais de um thread deseja alterá-los. Isso é ruim para o desempenho, mas melhor do que a alternativa de corrupção de dados. E, no entanto, não é tão bom quanto tornar tudo imutável (Deus louve Haskell!).

MAS ALAS! Em JavaScript, você sempre opera em um único encadeamento . Até trabalhadores da Web (cada um é executado em um contexto separado ). Portanto, como você não pode ter uma condição de corrida relacionada ao encadeamento dentro do seu contexto de execução (todas essas adoráveis ​​variáveis ​​e fechamentos globais), o principal ponto a favor da Imutabilidade sai pela janela.

(Dito isto, não é uma vantagem de usar funções puras em trabalhadores da web, o que é que você vai ter nenhuma expectativa sobre a brincar com objetos no segmento principal.)

2) A imutabilidade pode (de alguma forma) evitar as condições de corrida no estado do seu aplicativo.

E aqui está o verdadeiro cerne da questão, a maioria dos desenvolvedores do (React) dirá que o Immutability e o FP podem de alguma forma trabalhar essa mágica que permite que o estado do seu aplicativo se torne previsível.

Obviamente, isso não significa que você pode evitar as condições de corrida no banco de dados . Para isso, você teria que coordenar todos os usuários em todos os navegadores e, para isso, precisaria de uma tecnologia de back-end como o WebSockets ( mais sobre isso abaixo) que transmitirá as alterações para todos que executam o aplicativo.

Também não significa que exista algum problema inerente no JavaScript em que o estado do aplicativo precise de imutabilidade para se tornar previsível, qualquer desenvolvedor que codifique aplicativos de front-end antes que o React lhe diga isso.

Essa afirmação bastante confusa significa simplesmente que, com o React, o estado do seu aplicativo se tornará mais propenso a condições de corrida , mas essa imutabilidade permite que você remova essa dor. Por quê? Como o React é especial .. foi projetado como uma biblioteca de renderização altamente otimizada, com o gerenciamento de estado coerente em segundo lugar e, portanto, o estado do componente é gerenciado por meio de uma cadeia de eventos assíncrona (também conhecida como "ligação de dados unidirecional") que você não tem controle e confie em você lembrando de não mudar o estado diretamente ...

Diante desse contexto, é fácil ver como a necessidade de imutabilidade tem pouco a ver com JavaScript e muito a ver com as condições de corrida no React: se houver várias alterações interdependentes em seu aplicativo e nenhuma maneira fácil de descobrir o que seu estado está atualmente, você ficará confuso e, portanto , faz todo o sentido usar a imutabilidade para rastrear todas as mudanças históricas .

3) As condições da corrida são categoricamente ruins.

Bem, eles podem ser se você estiver usando o React. Mas eles são raros se você escolher uma estrutura diferente.

Além disso, você normalmente tem problemas muito maiores para lidar com problemas como o inferno da dependência. Como uma base de código inchada. Como se seu CSS não estivesse sendo carregado. Como um processo de construção lento ou ficar preso a um back-end monolítico que torna a iteração quase impossível. Como desenvolvedores inexperientes, não entendendo o que está acontecendo e fazendo uma bagunça nas coisas.

Você sabe. Realidade. Mas ei, quem se importa com isso?

4) A imutabilidade utiliza os tipos de referência para reduzir o impacto no desempenho do rastreamento de todas as alterações de estado.

Porque, sério, se você deseja copiar coisas toda vez que seu estado muda, é melhor ter certeza de que é inteligente.

5) Imutabilidade permite desfazer coisas .

Porque er .. esse é o recurso número um que seu gerente de projetos solicitará, certo?

6) O estado imutável tem muito potencial interessante em combinação com WebSockets

Por último, mas não menos importante, a acumulação de deltas de estado é um caso bastante atraente em combinação com o WebSockets, que permite um fácil consumo de estado como um fluxo de eventos imutáveis ...

Uma vez que o centavo cai nesse conceito (o estado é um fluxo de eventos - em vez de um conjunto bruto de registros representando a visão mais recente), o mundo imutável se torna um lugar mágico para habitar. Uma terra de maravilhas e possibilidades originadas por eventos que transcendem o próprio tempo . E quando bem feito este pode definitivamente fazer em tempo real aplicativos easi er para realizar, basta transmitir o fluxo de eventos a todos os interessados para que eles possam construir a sua própria representação do presente e escrever de volta suas próprias mudanças no fluxo comum.

Mas em algum momento você acorda e percebe que toda essa maravilha e magia não são de graça. Ao contrário de seus colegas ansiosos, seus stakeholders (sim, as pessoas que pagam a você) se importam pouco com filosofia ou moda e muito com o dinheiro que pagam para construir um produto que possam vender. E a conclusão é que é mais difícil escrever código imutável e mais fácil quebrá-lo, além de haver pouco sentido em ter um front-end imutável se você não tiver um back-end para suportá-lo. Quando (e se!) Você finalmente convence suas partes interessadas de que deve publicar e consumir eventos por meio de uma tecnologia push como o WebSockets, você descobre que sofrimento é escalar na produção .


Agora, para alguns conselhos, você deve aceitar.

Uma opção para escrever JavaScript usando FP / Immutability também é uma opção para tornar a base de código do seu aplicativo maior, mais complexa e mais difícil de gerenciar. Eu diria enfaticamente para limitar essa abordagem para seus redutores Redux, se você não sabe o que está fazendo ... e se você estiver indo para ir em frente e uso imutabilidade independentemente, em seguida, aplicar estado imutável em sua pilha de aplicativos inteira , e não apenas o do lado do cliente, caso contrário você perderá o valor real.

Agora, se você tiver a sorte de poder fazer escolhas em seu trabalho, tente usar sua sabedoria (ou não) e faça o que é certo pela pessoa que está lhe pagando . Você pode basear isso na sua experiência, no seu instinto ou no que está acontecendo ao seu redor (é certo que, se todos estiverem usando o React / Redux, existe um argumento válido de que será mais fácil encontrar um recurso para continuar seu trabalho). você pode tentar as abordagens Resume Driven Development ou Hype Driven Development . Eles podem ser mais o seu tipo de coisa.

Em suma, o que se pode dizer da imutabilidade é que isso fará com que você fique na moda com seus colegas, pelo menos até a próxima mania, e nesse ponto você ficará feliz em seguir em frente.


Agora, após esta sessão de autoterapia, gostaria de salientar que adicionei isso como um artigo no meu blog => Imutabilidade em JavaScript: uma visão contrária . Sinta-se à vontade para responder lá, se você tiver sentimentos fortes, também gostaria de sair do seu peito;).


10
Olá Steven, sim. Eu tinha todas essas dúvidas quando considerava immutable.js e redux. Mas, sua resposta é incrível! Agrega muito valor e obrigado por abordar todos os pontos em que eu duvidava. É muito mais claro / melhor agora, mesmo depois de trabalhar meses em objetos imutáveis.
bozzmob

5
Uso o React with Flux / Redux há mais de dois anos e não posso concordar mais com você, ótima resposta!
Pavle Lekic

6
Suspeito fortemente que as visões sobre imutabilidade se correlacionem de maneira bem ordenada com o tamanho das equipes e da base de código, e não acho que seja coincidência o principal proponente ser um gigante do vale do silício. Dito isto, discordo respeitosamente: a imutabilidade é uma disciplina útil, como não usar o goto é uma disciplina útil. Ou teste de unidade. Ou TDD. Ou análise de tipo estática. Não significa que você faça isso o tempo todo, sempre (embora alguns o façam). Eu diria também que a utilidade é ortogonal ao hype: em uma matriz de útil / supérflua e sexy / chata, existem muitos exemplos de cada um. "hyped" !== "bad"
Jared Smith

4
Oi @ftor, bom ponto, levando as coisas longe demais na outra direção. No entanto, como existe uma profusão de artigos e argumentos 'pró-imutabilidade em javascript', senti que precisava equilibrar as coisas. Portanto, os novatos têm um ponto de vista oposto para ajudá-los a julgar de qualquer maneira.
Steven de Salas

4
Informativo e com título brilhante. Até encontrar essa resposta, pensei que era o único com uma visão semelhante. Reconheço o valor da imutabilidade, mas o que me incomoda é que ele se tornou um dogma opressor a todas as outras técnicas (por exemplo, em detrimento da ligação bidirecional, que é incrivelmente útil para a formatação de entrada conforme implementada no KnockoutJS, por exemplo).
Tyblitz

53

A questão é: por que a imutabilidade é tão importante? O que há de errado na mutação de objetos? Isso não torna as coisas simples?

Na verdade, o oposto é verdadeiro: a mutabilidade torna as coisas mais complicadas, pelo menos a longo prazo. Sim, facilita sua codificação inicial porque você pode simplesmente mudar as coisas onde quiser, mas quando o programa aumenta, isso se torna um problema - se um valor mudou, o que mudou?

Quando você torna tudo imutável, significa que os dados não podem mais ser alterados de surpresa. Você tem certeza de que, se você passar um valor para uma função, ele não pode ser alterado nessa função.

Simplificando: se você usa valores imutáveis, fica muito fácil argumentar sobre seu código: todos recebem uma cópia * exclusiva de seus dados, para que não possa mexer com eles e quebrar outras partes do seu código. Imagine como isso facilita o trabalho em um ambiente multithread!

Nota 1: Existe um custo potencial de desempenho para a imutabilidade, dependendo do que você está fazendo, mas coisas como Immutable.js otimizam da melhor maneira possível.

Nota 2: No caso improvável de você não ter certeza, Immutable.js e ES6 constsignificam coisas muito diferentes.

Normalmente, eu poderia ter adicionado o objeto à matriz. Como faço para conseguir neste caso? Excluir a loja e recriá-la? Adicionar um objeto à matriz não é uma operação mais barata? PS: Se o exemplo não for o caminho certo para explicar a imutabilidade, informe-me qual é o exemplo prático correto.

Sim, seu exemplo de notícia é perfeitamente bom e seu raciocínio é exatamente correto: você não pode simplesmente alterar sua lista existente, então você precisa criar uma nova:

var originalItems = Immutable.List.of(1, 2, 3);
var newItems = originalItems.push(4, 5, 6);

1
Não discordo desta resposta, mas ela não trata da parte da pergunta "gostaria de aprender com um exemplo prático". Alguém poderia argumentar que uma única referência à lista de cabeçalhos de notícias usados ​​em várias áreas é uma coisa boa. "Eu só tenho que atualizar a lista uma vez e tudo o que faz referência à lista de notícias é atualizado de graça" - acho que uma resposta melhor exigiria um problema comum como ele apresentou e mostraria uma alternativa valiosa que usa a imutabilidade.
Obrigado

1
Estou feliz que a resposta tenha sido útil! Com relação à sua nova pergunta: não tente adivinhar o sistema :) Nesse caso exato, algo chamado "compartilhamento estrutural" reduz drasticamente a agitação do GC - se você tiver 10.000 itens em uma lista e adicionar 10, acredito que seja Imutável. js tentará reutilizar a estrutura anterior da melhor maneira possível. Deixe o Immutable.js se preocupar com a memória e é provável que você descubra que fica melhor.
precisa saber é o seguinte

6
Imagine how much easier this makes working in a multi-threaded environment!-> Ok para outros idiomas, mas isso não é uma vantagem no JavaScript de thread único.
Steven de Salas

1
@StevendeSalas observe que o JavaScript é basicamente assíncrono e orientado a eventos. Não é de todo imune às condições de corrida.
Jared Smith

1
@JaredSmith ainda é meu argumento. FP e Imutabilidade são poderosos paradigmas úteis para evitar a corrupção de dados e / ou bloqueios de recursos em ambientes multithread, mas não no JavaScript, porque são de thread único. A menos que eu esteja perdendo alguma pepita de sabedoria, a principal desvantagem aqui é se você está preparado para tornar seu código mais complexo (e mais lento) em uma busca para evitar condições de corrida ... o que é muito menos problemático do que a maioria das pessoas pensar.
Steven de Salas

37

Embora as outras respostas sejam boas, para responder à sua pergunta sobre um caso de uso prático (dos comentários nas outras respostas), deixe o código de execução por um minuto e observe a onipresente resposta logo abaixo do seu nariz: git . O que aconteceria se toda vez que você enviasse uma confirmação, você substituísse os dados no repositório?

Agora, estamos diante de um dos problemas que as coleções imutáveis ​​enfrentam: a memória incha. O Git é inteligente o suficiente para não simplesmente fazer novas cópias de arquivos toda vez que você faz uma alteração, ele simplesmente acompanha as diferenças .

Embora eu não conheça muito sobre o funcionamento interno do git, posso apenas assumir que ele usa uma estratégia semelhante à das bibliotecas mencionadas: compartilhamento estrutural. Sob o capô, as bibliotecas usam tentativas ou outras árvores para rastrear apenas os nós diferentes.

Essa estratégia também tem desempenho razoável para estruturas de dados na memória, pois existem algoritmos de operação em árvore conhecidos que operam em tempo logarítmico.

Outro caso de uso: diga que deseja um botão de desfazer no seu aplicativo da web. Com representações imutáveis ​​de seus dados, implementá-las é relativamente trivial. Mas se você confiar na mutação, isso significa que você precisa se preocupar em armazenar em cache o estado do mundo e fazer atualizações atômicas.

Em resumo, há um preço a pagar pela imutabilidade no desempenho em tempo de execução e na curva de aprendizado. Mas qualquer programador experiente dirá que o tempo de depuração supera o tempo de escrita de código em uma ordem de magnitude. E o pequeno impacto no desempenho do tempo de execução provavelmente será superado pelos erros relacionados ao estado que seus usuários não precisam suportar.


1
Um exemplo brilhante eu digo. Minha compreensão da imutabilidade é mais clara agora. Obrigado Jared. Na verdade, uma das implementações é o botão UNDO: D E você simplificou as coisas para mim.
bozzmob

3
Só porque um padrão que faz sentido no git não significa que a mesma coisa faça sentido em todos os lugares. No git, você realmente se preocupa com todo o histórico armazenado e deseja mesclar diferentes ramificações. No frontend, você não se importa com a maior parte da história do estado e não precisa de toda essa complexidade.
Ski

2
@ Ski é apenas complexo, porque não é o padrão. Normalmente, não uso mori ou immutable.js nos meus projetos: sempre hesito em enfrentar deps de terceiros. Mas se esse fosse o padrão (à la clojurescript) ou pelo menos tivesse uma opção nativa de aceitação, eu a usaria o tempo todo, porque quando por exemplo programa no clojure não coloco tudo imediatamente em átomos.
Jared Smith

Joe Armstrong diria que não se preocupe com o desempenho, apenas espere alguns anos e a lei de Moore cuidará disso para você.
Ximo

1
@JaredSmith Você está certo, as coisas estão ficando cada vez menores e com mais recursos limitados. Não tenho certeza se esse será o fator limitante do JavaScript. Continuamos encontrando novas maneiras de melhorar o desempenho (Svelte, por exemplo). A propósito, concordo plenamente com seu outro comentário. A complexidade ou dificuldade de usar estruturas de dados imutáveis ​​geralmente se resume ao idioma que não possui suporte interno para o conceito. O Clojure simplifica a imutabilidade, pois é incorporada ao idioma; todo o idioma foi projetado em torno da ideia.
ximo

8

A questão é: por que a imutabilidade é tão importante? O que há de errado na mutação de objetos? Isso não torna as coisas simples?

Sobre mutabilidade

Nada está errado na mutabilidade do ponto de vista técnico. É rápido, está reutilizando a memória. Os desenvolvedores estão acostumados com isso desde o início (como eu me lembro). Existe um problema no uso de mutabilidade e problemas que esse uso pode trazer.

Se o objeto não é compartilhado com nada, por exemplo, existe no escopo da função e não é exposto ao exterior, é difícil ver benefícios na imutabilidade. Realmente, neste caso, não faz sentido ser imutável. A sensação de imutabilidade começa quando algo é compartilhado.

Dor de cabeça de mutabilidade

A estrutura compartilhada mutável pode facilmente criar muitas armadilhas. Qualquer alteração em qualquer parte do código com acesso à referência tem impacto em outras partes com visibilidade dessa referência. Esse impacto conecta todas as partes, mesmo quando elas não devem estar cientes dos diferentes módulos. A mutação em uma função pode travar parte totalmente diferente do aplicativo. Tal coisa é um efeito colateral ruim.

Em seguida, muitas vezes o problema com mutação é o estado corrompido. O estado corrompido pode ocorrer quando o procedimento de mutação falha no meio, e alguns campos foram modificados e outros não.

Além disso, com a mutação, é difícil acompanhar a mudança. A verificação simples de referência não mostrará a diferença; para saber o que mudou, uma verificação profunda precisa ser feita. Também para monitorar a mudança, é necessário introduzir algum padrão observável.

Finalmente, a mutação é a razão do déficit de confiança. Como você pode ter certeza de que alguma estrutura deseja valor, se puder ser modificada.

const car = { brand: 'Ferrari' };
doSomething(car);
console.log(car); // { brand: 'Fiat' }

Como mostra o exemplo acima, a passagem de estrutura mutável sempre pode terminar por ter estrutura diferente. A função doSomething está modificando o atributo dado de fora. Não há confiança no código, você realmente não sabe o que tem e o que terá. Todos esses problemas ocorrem porque: Estruturas mutáveis ​​estão representando ponteiros para a memória.

Imutabilidade é sobre valores

Imutabilidade significa que a mudança não é feita no mesmo objeto, estrutura, mas a mudança é representada em um novo. E isso ocorre porque a referência representa valor e não apenas ponteiro de memória. Toda mudança cria novo valor e não toca a antiga. Essas regras claras devolvem a previsibilidade de confiança e código. As funções são seguras de usar porque, em vez de mutação, elas lidam com versões próprias com valores próprios.

Usar valores em vez de recipientes de memória garante que cada objeto represente um valor imutável específico e que seja seguro usá-lo.

Estruturas imutáveis ​​estão representando valores.

Estou mergulhando ainda mais no assunto no artigo médio - https://medium.com/@macsikora/the-state-of-immutability-169d2cd11310


6

Por que a imutabilidade é tão importante (ou necessária) no JavaScript?

A imutabilidade pode ser rastreada em diferentes contextos, mas o mais importante seria rastrear o status do aplicativo e a interface do usuário do aplicativo.

Considerarei o padrão JavaScript Redux como uma abordagem muito moderna e moderna e porque você mencionou isso.

Para a interface do usuário, precisamos torná-la previsível . Será previsível se UI = f(application state).

Os aplicativos (em JavaScript) alteram o estado por meio de ações implementadas usando a função redutora .

A função redutora simplesmente executa a ação e o estado antigo e retorna o novo estado, mantendo o estado antigo intacto.

new state  = r(current state, action)

insira a descrição da imagem aqui

O benefício é: você viaja no tempo pelos estados desde que todos os objetos de estado são salvos e pode renderizar o aplicativo em qualquer estado desde UI = f(state)

Assim, você pode desfazer / refazer facilmente.


Acontece que a criação de todos esses estados ainda pode ser eficiente em termos de memória, uma analogia com o Git é ótima, e temos a analogia semelhante no Linux OS com links simbólicos (com base nos inodes).


5

Outro benefício da imutabilidade no Javascript é que ele reduz o acoplamento temporal, o que geralmente traz benefícios substanciais para o design. Considere a interface de um objeto com dois métodos:

class Foo {

      baz() {
          // .... 
      }

      bar() {
          // ....
      }

}

const f = new Foo();

Pode ser que uma chamada baz()seja necessária para obter o objeto em um estado válido para que uma chamada bar()funcione corretamente. Mas como você sabe disso?

f.baz();
f.bar(); // this is ok

f.bar();
f.baz(); // this blows up

Para descobrir isso, você precisa examinar os internos da classe, porque isso não é aparente imediatamente no exame da interface pública. Esse problema pode explodir em uma grande base de código com muitos estados e classes mutáveis.

Se Fooé imutável, isso não é mais um problema. É seguro assumir que podemos ligar bazou barem qualquer ordem, porque o estado interno da classe não pode mudar.


4

Era uma vez um problema com a sincronização de dados entre threads. Esse problema foi um grande problema, havia mais de 10 soluções. Algumas pessoas tentaram resolvê-lo radicalmente. Era um lugar onde a programação funcional nasceu. É como o marxismo. Eu não conseguia entender como Dan Abramov vendeu essa idéia para JS, porque é de thread único. Ele é um gênio.

Eu posso dar um pequeno exemplo. Há um atributo __attribute__((pure))no gcc. Os compiladores tentam resolver se sua função é pura ou não, se você não a desarmará especialmente. Sua função pode ser pura, mesmo que seu estado seja mutável. A imutabilidade é apenas uma das mais de 100 maneiras de garantir que você funcione será puro. Na verdade, 95% de suas funções serão puras.

Você não deve usar nenhuma limitação (como imutabilidade) se realmente não tiver um motivo sério. Se você quiser "Desfazer" algum estado, poderá criar transações. Se você deseja simplificar as comunicações, pode enviar eventos com dados imutáveis. É com você.

Estou escrevendo esta mensagem da república pós-marxismo. Estou certo de que a radicalização de qualquer ideia é um caminho errado.


O terceiro parágrafo faz muito sentido. Obrigado por isso. 'Se você deseja "Desfazer" algum estado, pode criar transações' !!
bozzmob

A comparação com o marxismo também pode ser feita para OOP, a propósito. Lembra do Java? Heck, os bits ímpares de Java em JavaScript? Hype nunca é bom, causa radicalização e polarização. Historicamente, o OOP era muito mais sensacional que o do Redux no Facebook. Embora eles com certeza tentassem o seu melhor.
Ximo

4

Uma Tomada Diferente ...

Minha outra resposta aborda a questão de um ponto de vista muito prático, e eu ainda gosto disso. Decidi adicionar isso como outra resposta, e não como adendo, porque é um discurso filosófico entediante que, esperançosamente, também responde à pergunta, mas realmente não se encaixa na minha resposta existente.

TL; DR

Mesmo em pequenos projetos, a imutabilidade pode ser útil, mas não assuma que, porque existe, é para você.

Resposta muito, muito mais longa

NOTA: para os fins desta resposta, estou usando a palavra 'disciplina' para significar abnegação por algum benefício.

Isso é semelhante em forma a outra pergunta: "Devo usar o Typecript? Por que os tipos são tão importantes no JavaScript?". Tem uma resposta semelhante também. Considere o seguinte cenário:

Você é o único autor e mantenedor de uma base de código JavaScript / CSS / HTML de cerca de 5000 linhas. Seu chefe semi-técnico lê algo sobre o Typcript como a nova novidade e sugere que podemos mudar para ele, mas deixa a decisão para você. Então você lê, brinca, etc.

Então agora você tem uma escolha a fazer, você se muda para o Typecript?

O TypeScript tem algumas vantagens atraentes: intellisense, capturando erros com antecedência, especificando suas APIs antecipadamente, facilidade de corrigir coisas quando a refatoração as quebra, menos testes. O TypeScript também tem alguns custos: certos idiomas JavaScript muito naturais e corretos podem ser difíceis de modelar em seu sistema de tipos não especialmente poderoso, as anotações aumentam o LoC, o tempo e o esforço de reescrever a base de código existente, etapa extra no pipeline de construção etc. Mais fundamentalmente, ele cria um subconjunto de possíveis programas JavaScript corretos em troca da promessa de que seu código é mais probabilidade de estar correto. É arbitrariamente restritivo. Esse é o ponto: você impõe alguma disciplina que o limita (espero que se atire no pé).

Voltando à pergunta, reformulada no contexto do parágrafo acima: vale a pena ?

No cenário descrito, eu diria que, se você estiver familiarizado com uma base de código JS pequena a mediana, a opção de usar o Typescript é mais estética do que prática. E tudo bem , não há nada errado com a estética, eles simplesmente não são necessariamente convincentes.

Cenário B:

Você muda de emprego e agora é um programador de linha de negócios na Foo Corp. Você está trabalhando com uma equipe de 10 pessoas em uma base de código JavaScript / HTML / CSS 90000 LoC (e contando) com um pipeline de construção bastante complicado envolvendo babel, webpack , um conjunto de polyfills, reage com vários plug-ins, um sistema de gerenciamento de estado, ~ 20 bibliotecas de terceiros, ~ 10 bibliotecas internas, plug-ins de editor como um linter com regras para o guia de estilo interno etc. etc.

Quando você era um cara / garota de 5k LoC, isso não importava muito. Documentação ainda não foi que muito grande, mesmo voltando para uma parte específica do código após 6 meses você poderia descobrir isso com bastante facilidade. Mas agora a disciplina não é apenas agradável, mas necessária . Que a disciplina não pode envolver Typescript, mas irá provavelmente envolvem alguma forma de análise estática, bem como todas as outras formas de disciplina codificação (documentação, guia de estilo, scripts de construção, testes de regressão, CI). A disciplina não é mais um luxo , é uma necessidade .

Tudo isso se aplicava GOTOem 1978: seu pequeno jogo de blackjack em C podia usar GOTOlógica s e espaguete, e isso não era grande coisa para você escolher sua própria aventura, mas à medida que os programas aumentavam e uso mais ambicioso, bem e indisciplinado deGOTO não poderia ser sustentado. E tudo isso se aplica à imutabilidade hoje.

Assim como os tipos estáticos, se você não estiver trabalhando em uma grande base de código com uma equipe de engenheiros que a mantém / amplia, a opção de usar a imutabilidade é mais estética do que prática: seus benefícios ainda estão lá, mas ainda não podem compensar os custos.

Mas, como em todas as disciplinas úteis, chega um momento em que não é mais opcional. Se eu quiser manter um peso saudável, a disciplina que envolve o sorvete pode ser opcional. Mas se eu quero ser um atleta competitivo, minha escolha de tomar ou não sorvete é incluída na minha escolha de objetivos. Se você deseja mudar o mundo com o software, a imutabilidade pode fazer parte do que você precisa para evitar que ele desmorone sob o seu próprio peso.


1
+1 eu gosto. Muito mais sobre Jared. E, no entanto, a imutabilidade não salvará uma equipe de sua própria falta de disciplina. 😉
Steven de Salas

@StevendeSalas é uma forma de disciplina. E, como tal, acho que está correlacionado com (mas não substitui) as outras formas de disciplina de engenharia de software. Complementa, em vez de suplantar. Mas, como eu disse em um comentário sobre a sua resposta, não estou surpreso com o fato de estar sendo empurrado por um gigante da tecnologia com um bando de engenheiros trabalhando na mesma enorme base de código :) eles precisam de toda a disciplina que puderem obter. Na maioria das vezes, não mudo objetos, mas também não uso nenhuma forma de aplicação, pois sou eu.
Jared Smith

0

Eu criei uma biblioteca MIT (open source) independente de framework para um estado mutável (ou imutável) que pode substituir todo o armazenamento imutável como libs (redux, vuex etc ...).

Os estados imutáveis ​​eram feios para mim porque havia muito trabalho a ser feito (muitas ações para operações simples de leitura / gravação), o código era menos legível e o desempenho para grandes conjuntos de dados não era aceitável (re-renderização do componente inteiro: /).

Com o observador de estado profundo, posso atualizar apenas um nó com notação de ponto e usar caracteres curinga. Também posso criar histórico do estado (desfazer / refazer / viagem no tempo) mantendo apenas os valores concretos que foram alterados{path:value} = menos uso de memória.

Com o observador de estado profundo, eu posso ajustar as coisas e tenho controle de grãos sobre o comportamento dos componentes, para que o desempenho possa ser drasticamente aprimorado. O código é mais legível e a refatoração é muito mais fácil - basta pesquisar e substituir as cadeias de caminho (não é necessário alterar o código / lógica).


-1

Eu acho que a principal razão de objetos imutáveis ​​é manter o estado do objeto válido.

Suponha que tenhamos um objeto chamado arr. Este objeto é válido quando todos os itens são da mesma letra.

// this function will change the letter in all the array
function fillWithZ(arr) {
    for (var i = 0; i < arr.length; ++i) {
        if (i === 4) // rare condition
            return arr; // some error here

        arr[i] = "Z";
    }

    return arr;
}

console.log(fillWithZ(["A","A","A"])) // ok, valid state
console.log(fillWithZ(["A","A","A","A","A","A"])) // bad, invalid state

se arrtornar um objeto imutável, teremos certeza de que arr está sempre em um estado válido.


Eu acho que arrse transformou cada vez que você chamarfillWithZ
rodrigo-silveira

se você usar immutable.js, receberá uma nova cópia do objeto toda vez que o alterar. de modo que o objeto original mantém intocado
bedorlan
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.