¿Por qué la inmutabilidad es tan importante (o necesaria) en JavaScript?


206

Actualmente estoy trabajando en los marcos React JS y React Native . En la mitad del camino me encontré con Immutability o la biblioteca Immutable-JS , cuando estaba leyendo sobre la implementación de Facebook Flux y Redux.

La pregunta es, ¿por qué es tan importante la inmutabilidad? ¿Qué hay de malo en mutar objetos? ¿No hace las cosas simples?

Dando un ejemplo, consideremos una aplicación simple de lector de noticias con la pantalla de apertura como una vista de lista de titulares de noticias.

Si configuro decir una matriz de objetos con un valor inicialmente, no puedo manipularlo. Eso es lo que dice el principio de inmutabilidad, ¿verdad? (Corrígeme si me equivoco). Pero, ¿qué pasa si tengo un nuevo objeto de Noticias que tiene que actualizarse? En el caso habitual, podría haber agregado el objeto a la matriz. ¿Cómo lo logro en este caso? ¿Eliminar la tienda y recrearla? ¿Agregar un objeto a la matriz no es una operación menos costosa?



2
La estructura de datos inmutable y la función pura conducen a una transparencia referencial, lo que hace que sea mucho más fácil razonar sobre el comportamiento de su programa. También obtienes retroceso de forma gratuita cuando usas una estructura de datos funcional.
WorBlux

Proporcioné un punto de vista de Redux @bozzmob.
prosti

1
Puede ser útil aprender sobre la inmurabilidad en general como un concepto de paradigma funcional en lugar de tratar de pensar que JS tiene algo que ver con eso. React está escrito por fanáticos de la programación funcional. Tienes que saber lo que saben para entenderlos.
Gherman

Respuestas:


196

Recientemente he estado investigando el mismo tema. Haré todo lo posible para responder sus preguntas y tratar de compartir lo que he aprendido hasta ahora.

La pregunta es, ¿por qué es tan importante la inmutabilidad? ¿Qué hay de malo en mutar objetos? ¿No hace las cosas simples?

Básicamente se reduce al hecho de que la inmutabilidad aumenta la previsibilidad, el rendimiento (indirectamente) y permite el seguimiento de la mutación.

Previsibilidad

La mutación oculta el cambio, que crea efectos secundarios (inesperados) que pueden causar errores desagradables. Cuando aplica la inmutabilidad, puede mantener la arquitectura de la aplicación y el modelo mental simples, lo que facilita razonar sobre su aplicación.

Actuación

Aunque agregar valores a un Objeto inmutable significa que se debe crear una nueva instancia donde se deben copiar los valores existentes y se deben agregar nuevos valores al nuevo Objeto que cuesta memoria, los Objetos inmutables pueden hacer uso del intercambio estructural para reducir la memoria gastos generales.

Todas las actualizaciones devuelven nuevos valores, pero las estructuras internas se comparten para reducir drásticamente el uso de memoria (y la agitación de GC). Esto significa que si agrega un vector con 1000 elementos, en realidad no crea un nuevo vector de 1001 elementos de longitud. Lo más probable es que internamente solo se asignen unos pocos objetos pequeños.

Puedes leer más sobre esto aquí .

Seguimiento de mutaciones

Además de la reducción del uso de memoria, la inmutabilidad le permite optimizar su aplicación haciendo uso de la igualdad de referencia y valor. Esto hace que sea realmente fácil ver si algo ha cambiado. Por ejemplo, un cambio de estado en un componente de reacción. Puede usar shouldComponentUpdatepara verificar si el estado es idéntico comparando objetos de estado y evitar renderizaciones innecesarias. Puedes leer más sobre esto aquí .

Recursos adicionales:

Si configuro decir una matriz de objetos con un valor inicialmente. No puedo manipularlo. Eso es lo que dice el principio de inmutabilidad, ¿verdad? (Corrígeme si me equivoco). Pero, ¿qué sucede si tengo un nuevo objeto de Noticias que debe actualizarse? En el caso habitual, podría haber agregado el objeto a la matriz. ¿Cómo lo logro en este caso? ¿Eliminar la tienda y recrearla? ¿Agregar un objeto a la matriz no es una operación menos costosa?

Si eso es correcto. Si está confundido sobre cómo implementar esto en su aplicación, le recomendaría que vea cómo redux hace esto para familiarizarse con los conceptos básicos, me ayudó mucho.

Me gusta usar Redux como ejemplo porque abarca la inmutabilidad. Tiene un solo árbol de estado inmutable (denominado store) donde todos los cambios de estado son explícitos al enviar acciones que son procesadas por un reductor que acepta el estado anterior junto con dichas acciones (una a la vez) y devuelve el siguiente estado de su aplicación . Puede leer más sobre sus principios básicos aquí .

Hay un excelente curso de redux en egghead.io donde Dan Abramov , el autor de redux, explica estos principios de la siguiente manera (modifiqué un poco el código para adaptarlo mejor al escenario):

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();

Además, estos videos demuestran con más detalle cómo lograr la inmutabilidad para:


1
@naomik gracias por los comentarios! Mi intención era ilustrar el concepto y mostrar explícitamente que los Objetos no están siendo mutados y no necesariamente mostrar cómo implementarlo por completo. Sin embargo, mi ejemplo puede ser un poco confuso, lo actualizaré en un momento.
danillouz

2
@bozzmob de nada! No, eso no es correcto, debe imponer la inmutabilidad en el reductor usted mismo. Esto significa que puede seguir estrategias como las demostradas en los videos o usar una biblioteca como immutablejs. Puedes encontrar más información aquí y aquí .
danillouz

1
@naomik ES6 constno se trata de inmutabilidad. Mathias Bynens escribió un excelente artículo de blog al respecto.
Lea Rosema

1
@terabaud gracias por compartir el enlace. Estoy de acuerdo en que es una distinción importante. ^ _ ^
Gracias

44
Explique esto "La mutación oculta el cambio, que crea efectos secundarios (inesperados), que pueden causar errores desagradables. Cuando imponga la inmutabilidad, puede mantener la arquitectura de su aplicación y el modelo mental simple, lo que hace que sea más fácil razonar sobre su aplicación". Porque esto no es cierto en absoluto en el contexto de JavaScript.
Pavle Lekic

143

Una visión contraria de la inmutabilidad

TL / DR: la inmutabilidad es más una tendencia de moda que una necesidad en JavaScript. Si está utilizando React, proporciona una solución clara para algunas opciones de diseño confusas en la administración del estado. Sin embargo, en la mayoría de las otras situaciones, no agregará suficiente valor sobre la complejidad que presenta, sirviendo más para completar un currículum que para satisfacer una necesidad real del cliente.

Respuesta larga: lea a continuación.

¿Por qué la inmutabilidad es tan importante (o necesaria) en JavaScript?

¡Bien, me alegra que lo hayas preguntado!

Hace algún tiempo, un tipo muy talentoso llamado Dan Abramov escribió una biblioteca de administración de estado de JavaScript llamada Redux que usa funciones puras e inmutabilidad. También hizo algunos videos realmente geniales que hicieron que la idea fuera realmente fácil de entender (y vender).

El momento fue perfecto. La novedad de Angular se estaba desvaneciendo, y el mundo de JavaScript estaba listo para concentrarse en lo último que tenía el grado correcto de genialidad, y esta biblioteca no solo era innovadora sino que se integraba perfectamente con React, que estaba siendo promovida por otra potencia de Silicon Valley .

Triste como puede ser, las modas gobiernan en el mundo de JavaScript. Ahora Abramov está siendo aclamado como un semidiós y todos nosotros, simples mortales, tenemos que someternos al Dao de la Inmutabilidad ... Ya sea que tenga sentido o no.

¿Qué hay de malo en mutar objetos?

¡Nada!

De hecho, los programadores han estado mutando objetos durante er ... siempre que haya objetos para mutar. Más de 50 años de desarrollo de aplicaciones en otras palabras.

¿Y por qué complicar las cosas? Cuando tienes un objeto caty muere, ¿realmente necesitas un segundo catpara seguir el cambio? La mayoría de la gente simplemente diría cat.isDead = truey acabaría con eso.

¿No hace (objetos mutantes) que las cosas sean simples?

¡SI! .. ¡Claro que lo hace!

Especialmente en JavaScript, que en la práctica es más útil para representar una vista de algún estado que se mantiene en otro lugar (como en una base de datos).

¿Qué sucede si tengo un nuevo objeto de Noticias que debe actualizarse? ... ¿Cómo lo logro en este caso? ¿Eliminar la tienda y recrearla? ¿Agregar un objeto a la matriz no es una operación menos costosa?

Bueno, puedes seguir el enfoque tradicional y actualizar el News objeto, por lo que su representación en memoria de ese objeto cambia (y la vista que se muestra al usuario, o eso es lo que uno esperaría) ...

O alternativamente...

Puede probar el enfoque atractivo de FP / Inmutabilidad y agregar sus cambios al Newsobjeto a una matriz que rastrea cada cambio histórico para que pueda iterar a través de la matriz y descubrir cuál debería ser la representación de estado correcta (¡uf!).

Estoy tratando de aprender lo que está aquí. Por favor, ilumíneme :)

Las modas van y vienen, amigo. Hay muchas formas de despellejar a un gato.

Lamento que tenga que soportar la confusión de un conjunto de paradigmas de programación en constante cambio. Pero bueno, BIENVENIDOS AL CLUB !!

Ahora hay un par de puntos importantes para recordar con respecto a la Inmutabilidad, y te los arrojará con la intensidad febril que solo la ingenuidad puede reunir.

1) La inmutabilidad es increíble para evitar las condiciones de carrera en entornos de subprocesos múltiples .

Los entornos de subprocesos múltiples (como C ++, Java y C #) son culpables de la práctica de bloquear objetos cuando más de un subproceso quiere cambiarlos. Esto es malo para el rendimiento, pero mejor que la alternativa de corrupción de datos. Y sin embargo, no es tan bueno como hacer que todo sea inmutable (¡Alabado sea Haskell!).

¡PERO AY! En JavaScript siempre operas en un solo hilo . Incluso los trabajadores web (cada uno se ejecuta dentro de un contexto separado ). Entonces, como no puede tener una condición de carrera relacionada con el hilo dentro de su contexto de ejecución (todas esas encantadoras variables globales y cierres), el punto principal a favor de la Inmutabilidad desaparece.

(Una vez dicho esto, no es una ventaja para el uso de funciones puras de los trabajadores web, que es que usted no tiene expectativas sobre jugueteando con objetos en el hilo principal.)

2) La inmutabilidad puede (de alguna manera) evitar las condiciones de carrera en el estado de su aplicación.

Y aquí está el quid de la cuestión, la mayoría de los desarrolladores (React) le dirán que la Inmutabilidad y FP pueden de alguna manera hacer funcionar esta magia que permite que el estado de su aplicación sea predecible.

Por supuesto, esto no significa que pueda evitar las condiciones de carrera en la base de datos , para lograrlo tendría que coordinar a todos los usuarios en todos los navegadores , y para eso necesitaría una tecnología push de back-end como WebSockets ( más sobre esto a continuación) que transmitirá los cambios a todos los que ejecuten la aplicación.

Tampoco significa que haya algún problema inherente en JavaScript en el que el estado de su aplicación necesita inmutabilidad para ser predecible, cualquier desarrollador que haya codificado aplicaciones front-end antes de React le diría esto.

Esta afirmación bastante confusa simplemente significa que con React su estado de aplicación será más propenso a las condiciones de carrera , pero que la inmutabilidad le permite eliminar ese dolor. ¿Por qué? Debido a que React es especial ... ha sido diseñado como una biblioteca de renderizado altamente optimizada con una administración de estado coherente que ocupa el segundo lugar, y por lo tanto, el estado de los componentes se administra a través de una cadena de eventos asincrónica (también conocida como "enlace de datos unidireccional") que no tiene control y confía en ti recordando no mutar el estado directamente ...

Dado este contexto, es fácil ver cómo la necesidad de inmutabilidad tiene poco que ver con JavaScript y mucho con las condiciones de carrera en React: si tiene un montón de cambios interdependientes en su aplicación y no hay una manera fácil de averiguar qué su estado se encuentra actualmente, se confundirá y, por lo tanto , tiene mucho sentido usar la inmutabilidad para rastrear cada cambio histórico .

3) Las condiciones de carrera son categóricamente malas.

Bueno, podrían ser si estás usando React. Pero son raros si elige un marco diferente.

Además, normalmente tienes problemas mucho más grandes con los que lidiar ... Problemas como el infierno de la dependencia. Como una base de código hinchada. Como si tu CSS no se cargara. Como un proceso de construcción lento o estar atascado en un back-end monolítico que hace que la iteración sea casi imposible. Al igual que los desarrolladores sin experiencia que no entienden lo que está sucediendo y hacen un desastre.

Ya sabes. Realidad. Pero oye, ¿a quién le importa eso?

4) La inmutabilidad utiliza los Tipos de referencia para reducir el impacto en el rendimiento del seguimiento de cada cambio de estado.

Porque en serio, si vas a copiar cosas cada vez que cambia tu estado, es mejor que te asegures de ser inteligente al respecto.

5) La inmutabilidad le permite deshacer cosas .

Porque er ... esta es la característica número uno que su gerente de proyecto va a pedir, ¿verdad?

6) El estado inmutable tiene un gran potencial en combinación con WebSockets

Por último, pero no menos importante, la acumulación de deltas de estado es un caso bastante convincente en combinación con WebSockets, lo que permite un consumo fácil de estado como un flujo de eventos inmutables ...

Una vez que el centavo cae sobre este concepto (el estado es un flujo de eventos , en lugar de un conjunto crudo de registros que representan la última vista), el mundo inmutable se convierte en un lugar mágico para habitar. Una tierra de maravillas y posibilidades derivadas de eventos que trasciende el tiempo mismo . Y cuando se hace bien esto definitivamente puede hacer en tiempo real aplicaciones EASI er lograr, que acaba de emitir el flujo de los acontecimientos a todos los interesados para que puedan construir su propia representación de la presente y escribir de nuevo sus propios cambios en el flujo comunal.

Pero en algún momento te despiertas y te das cuenta de que toda esa maravilla y magia no vienen gratis. A diferencia de sus entusiastas colegas, sus partes interesadas (sí, las personas que le pagan) se preocupan poco por la filosofía o la moda y mucho por el dinero que pagan para construir un producto que pueden vender. Y la conclusión es que es más difícil escribir código inmutable y más fácil de descifrar, además tiene poco sentido tener un front-end inmutable si no tiene un back-end para soportarlo. Cuando (¡y si!) Finalmente convence a sus partes interesadas de que debe publicar y consumir eventos a través de una tecnología de inserción como WebSockets, descubre lo difícil que es escalar la producción .


Ahora para algunos consejos, si decide aceptarlo.

La opción de escribir JavaScript usando FP / Inmutabilidad también es una opción para hacer que la base de código de su aplicación sea más grande, más compleja y más difícil de administrar. Yo recomendaría encarecidamente limitar este enfoque a sus reductores Redux, a menos que sepa lo que está haciendo ... Y SI va a seguir adelante y usar la inmutabilidad de todos modos, aplique el estado inmutable a toda su pila de aplicaciones , y no solo del lado del cliente, ya que de lo contrario te estás perdiendo el valor real.

Ahora, si tiene la suerte de poder tomar decisiones en su trabajo, intente usar su sabiduría (o no) y haga lo correcto por la persona que le paga . Puede basar esto en su experiencia, en su instinto o en lo que sucede a su alrededor (es cierto que si todos usan React / Redux, entonces existe un argumento válido de que será más fácil encontrar un recurso para continuar su trabajo). Alternativamente, se puede tratar ya sea Reanudar Driven Development o bombo Driven Development se acerca. Podrían ser más tu tipo de cosas.

En resumen, lo que hay que decir sobre la inmutabilidad es que te pondrá de moda con tus compañeros, al menos hasta que llegue la próxima moda, momento en el que estarás contento de seguir adelante.


Ahora, después de esta sesión de autoterapia, me gustaría señalar que he agregado esto como un artículo en mi blog => Inmutabilidad en JavaScript: una visión contraria . Siéntete libre de responder allí si tienes sentimientos fuertes que también te gustaría quitarte de encima;).


10
Hola Steven, si. Tenía todas estas dudas cuando consideraba immutable.js y redux. ¡Pero tu respuesta es asombrosa! Agrega mucho valor y gracias por abordar cada punto sobre el que tenía dudas. Ahora es mucho más claro / mejor incluso después de trabajar durante meses en objetos inmutables.
bozzmob

55
He estado usando React with Flux / Redux durante más de dos años y no podría estar más de acuerdo contigo, ¡gran respuesta!
Pavle Lekic

66
Sospecho firmemente que las opiniones sobre la inmutabilidad se correlacionan bastante bien con los tamaños del equipo y la base de código, y no creo que sea una coincidencia que el defensor principal sea un gigante de Silicon Valley. Dicho esto, estoy respetuosamente en desacuerdo: la inmutabilidad es una disciplina útil, como no usar goto es una disciplina útil. O pruebas unitarias. O TDD. O análisis de tipo estático. No significa que los hagas todo el tiempo, siempre (aunque algunos lo hacen). También diría que la utilidad es ortogonal al bombo: en una matriz de útil / superfluo y sexy / aburrido hay muchos ejemplos de cada uno. "hyped" !== "bad"
Jared Smith el

44
Hola @ftor, buen punto, llevar las cosas demasiado lejos en la otra dirección. Sin embargo, dado que existe una profusión de artículos y argumentos de 'pro-inmutabilidad en JavaScript', sentí que necesitaba equilibrar las cosas. Entonces, los novatos tienen un punto de vista opuesto para ayudarlos a emitir un juicio de cualquier manera.
Steven de Salas

44
Informativo y brillantemente titulado. Hasta que encontré esta respuesta, pensé que era el único que tenía una opinión similar. Reconozco el valor de la inmutabilidad, pero lo que me molesta es que se ha convertido en un dogma opresor de otra técnica (por ejemplo, en detrimento de la unión bidireccional que es increíblemente útil para el formato de entrada implementado en KnockoutJS, por ejemplo).
Tyblitz

53

La pregunta es, ¿por qué es tan importante la inmutabilidad? ¿Qué hay de malo en mutar objetos? ¿No hace las cosas simples?

En realidad, lo contrario es cierto: la mutabilidad hace las cosas más complicadas, al menos a largo plazo. Sí, hace que su codificación inicial sea más fácil porque puede cambiar las cosas donde lo desee, pero cuando su programa aumenta, se convierte en un problema: si un valor cambió, ¿qué lo cambió?

Cuando hace que todo sea inmutable, significa que los datos ya no se pueden cambiar por sorpresa. Usted sabe con certeza que si pasa un valor a una función, no se puede cambiar en esa función.

En pocas palabras: si usa valores inmutables, es muy fácil razonar sobre su código: todos obtienen una copia única * de sus datos, por lo que no pueden confundirse y romper otras partes de su código. ¡Imagínese lo fácil que resulta trabajar en un entorno de subprocesos múltiples!

Nota 1: Existe un costo de rendimiento potencial para la inmutabilidad dependiendo de lo que esté haciendo, pero cosas como Immutable.js se optimizan lo mejor que pueden.

Nota 2: en el improbable caso de que no estuvieras seguro, Immutable.js y ES6 constsignifican cosas muy diferentes.

En el caso habitual, podría haber agregado el objeto a la matriz. ¿Cómo lo logro en este caso? ¿Eliminar la tienda y recrearla? ¿Agregar un objeto a la matriz no es una operación menos costosa? PD: Si el ejemplo no es la forma correcta de explicar la inmutabilidad, hágame saber cuál es el ejemplo práctico correcto.

Sí, su ejemplo de noticias es perfectamente bueno y su razonamiento es exactamente correcto: no puede simplemente modificar su lista existente, por lo que debe crear una nueva:

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

1
No estoy en desacuerdo con esta respuesta, pero no aborda su parte de la pregunta "Me gustaría aprender de un ejemplo práctico". Se podría argumentar que una sola referencia a la lista de encabezados de noticias que se utilizan en múltiples áreas es algo bueno. "Solo tengo que actualizar la lista una vez y todo lo que haga referencia a la lista de noticias se actualiza de forma gratuita". Creo que una mejor respuesta tomaría un problema común como el que presentó, y mostraría una alternativa valiosa que utiliza la inmutabilidad.
Gracias

1
¡Me alegra que la respuesta haya sido útil! Con respecto a su nueva pregunta: no intente adivinar el sistema :) En este caso exacto, algo llamado "intercambio estructural" reduce drásticamente la agresión de GC: si tiene 10,000 elementos en una lista y agrega 10 más, creo que es inmutable. js intentará reutilizar la estructura anterior lo mejor que pueda. Deje que Immutable.js se preocupe por la memoria y es probable que descubra que sale mejor.
TwoStraws

66
Imagine how much easier this makes working in a multi-threaded environment!-> Ok para otros idiomas, pero esto no es una ventaja en JavaScript de subproceso único.
Steven de Salas

1
@StevendeSalas tenga en cuenta que JavaScript es principalmente asíncrono y controlado por eventos. No es en absoluto inmune a las condiciones de carrera.
Jared Smith

1
@JaredSmith todavía mi punto sigue siendo. FP e Inmutabilidad son poderosos paradigmas útiles para evitar la corrupción de datos y / o bloqueos de recursos en entornos multiproceso, pero no así en JavaScript porque es un único subproceso. A menos que me falte alguna pepita sagrada de sabiduría, la principal compensación aquí es si estás preparado para hacer que el código sea más complejo (y más lento) en una búsqueda para evitar las condiciones de carrera ... que son mucho menos problemáticos que la mayoría de las personas pensar.
Steven de Salas

37

Aunque las otras respuestas están bien, para abordar su pregunta sobre un caso de uso práctico (de los comentarios en las otras respuestas) dejemos de lado su código de ejecución por un minuto y miremos la respuesta ubicua justo debajo de su nariz: git . ¿Qué sucedería si cada vez que empujara una confirmación sobrescribiera los datos en el repositorio?

Ahora nos encontramos con uno de los problemas que enfrentan las colecciones inmutables: la hinchazón de memoria. Git es lo suficientemente inteligente como para no simplemente hacer nuevas copias de archivos cada vez que realiza un cambio, simplemente realiza un seguimiento de las diferencias .

Si bien no sé mucho sobre el funcionamiento interno de git, solo puedo suponer que usa una estrategia similar a la de las bibliotecas a las que hace referencia: el intercambio estructural. Bajo el capó, las bibliotecas usan intentos u otros árboles para rastrear solo los nodos que son diferentes.

Esta estrategia también es razonablemente eficaz para las estructuras de datos en memoria, ya que existen algoritmos de operación de árbol bien conocidos que operan en tiempo logarítmico.

Otro caso de uso: digamos que desea un botón de deshacer en su aplicación web. Con representaciones inmutables de sus datos, implementarlos es relativamente trivial. Pero si confía en la mutación, eso significa que debe preocuparse por almacenar en caché el estado del mundo y realizar actualizaciones atómicas.

En resumen, hay un precio que pagar por la inmutabilidad en el rendimiento del tiempo de ejecución y la curva de aprendizaje. Pero cualquier programador experimentado le dirá que el tiempo de depuración supera el tiempo de escritura de código en un orden de magnitud. Y el ligero impacto en el rendimiento en tiempo de ejecución probablemente sea mayor que los errores relacionados con el estado que sus usuarios no tienen que soportar.


1
Un brillante ejemplo que digo. Mi comprensión de la inmutabilidad es más clara ahora. Gracias Jared En realidad, una de las implementaciones es el botón DESHACER: D Y me hiciste las cosas bastante simples.
bozzmob

3
El hecho de que un patrón tenga sentido en git no significa que lo mismo tenga sentido en todas partes. En git, en realidad te importa todo el historial almacenado y quieres poder fusionar diferentes ramas. En frontend no le importa la mayor parte de la historia del estado y no necesita toda esta complejidad.
Ski

2
@Ski solo es complejo porque no es el predeterminado. Por lo general, no uso mori o inmutable.js en mis proyectos: siempre dudo en tomar departamentos de terceros. Pero si ese fuera el valor predeterminado (a la clojurescript) o al menos tuviera una opción nativa opcional, la usaría todo el tiempo, porque cuando, por ejemplo, programo en clojure, no lo llevo todo inmediatamente a los átomos.
Jared Smith

Joe Armstrong diría que no se preocupe por el rendimiento, solo espere unos años y la ley de Moore se encargará de eso por usted.
ximo

1
@JaredSmith Tienes razón, las cosas solo se están volviendo más pequeñas y con más recursos limitados. Sin embargo, no estoy seguro de si ese será el factor limitante para JavaScript. Seguimos encontrando nuevas formas de mejorar el rendimiento (por ejemplo, Svelte). Por cierto, estoy completamente de acuerdo con tu otro comentario. La complejidad o dificultad de usar estructuras de datos inmutables a menudo se reduce a que el lenguaje no tiene soporte incorporado para el concepto. Clojure hace inmutabilidad sencilla , ya que se cuece en la lengua, todo el lenguaje fue diseñado alrededor de la idea.
ximo

8

La pregunta es, ¿por qué es tan importante la inmutabilidad? ¿Qué hay de malo en mutar objetos? ¿No hace las cosas simples?

Sobre mutabilidad

Nada tiene de malo la mutabilidad desde el punto de vista técnico. Es rápido, está reutilizando la memoria. Los desarrolladores están acostumbrados desde el principio (como lo recuerdo). El problema existe en el uso de la mutabilidad y los problemas que este uso puede traer.

Si el objeto no se comparte con nada, por ejemplo, existe en el alcance de la función y no está expuesto al exterior, entonces es difícil ver beneficios en la inmutabilidad. Realmente en este caso no tiene sentido ser inmutable. La sensación de inmutabilidad comienza cuando algo se comparte.

Dolor de cabeza por mutabilidad

La estructura compartida mutable puede crear fácilmente muchas trampas. Cualquier cambio en cualquier parte del código con acceso a la referencia tiene un impacto en otras partes con visibilidad de esta referencia. Tal impacto conecta todas las partes, incluso cuando no deben conocer los diferentes módulos. La mutación en una función puede bloquear una parte totalmente diferente de la aplicación. Tal cosa es un mal efecto secundario.

El siguiente problema a menudo con la mutación es el estado corrupto. El estado dañado puede ocurrir cuando el procedimiento de mutación falla en el medio, y algunos campos se modificaron y otros no.

Además, con la mutación es difícil seguir el cambio. La verificación de referencia simple no mostrará la diferencia, para saber qué cambio se necesita hacer una verificación profunda. También para monitorear el cambio, se necesita introducir algún patrón observable.

Finalmente, la mutación es la razón del déficit de confianza. Cómo puede estar seguro de que alguna estructura tiene valor deseado, si se puede mutar.

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

Como se muestra en el ejemplo anterior, pasar una estructura mutable siempre puede terminar teniendo una estructura diferente. La función doSomething está mutando el atributo dado desde afuera. No confías en el código, realmente no sabes lo que tienes y lo que tendrás. Todos estos problemas tienen lugar porque: Las estructuras mutables representan punteros a la memoria.

La inmutabilidad se trata de valores

La inmutabilidad significa que el cambio no se realiza en el mismo objeto, estructura, sino que el cambio se representa en uno nuevo. Y esto se debe a que la referencia representa el valor no solo el puntero de memoria. Cada cambio crea un nuevo valor y no toca el anterior. Tales reglas claras devuelven la confianza y la previsibilidad del código. Las funciones son seguras de usar porque, en lugar de la mutación, tratan con versiones propias con valores propios.

El uso de valores en lugar de contenedores de memoria da la certeza de que cada objeto representa un valor inmutable específico y que es seguro usarlo.

Las estructuras inmutables representan valores.

Me estoy sumergiendo aún más en el tema en un artículo medio: https://medium.com/@macsikora/the-state-of-immutability-169d2cd11310


6

¿Por qué la inmutabilidad es tan importante (o necesaria) en JavaScript?

La inmutabilidad se puede rastrear en diferentes contextos, pero lo más importante sería rastrearla contra el estado de la aplicación y contra la interfaz de usuario de la aplicación.

Consideraré el patrón JavaScript Redux como un enfoque muy moderno y moderno y porque lo mencionaste.

Para la interfaz de usuario, debemos hacerlo predecible . Será predecible si UI = f(application state).

Las aplicaciones (en JavaScript) cambian el estado a través de acciones implementadas utilizando la función reductora .

La función reductora simplemente realiza la acción y el estado anterior y devuelve el estado nuevo, manteniendo intacto el estado anterior.

new state  = r(current state, action)

ingrese la descripción de la imagen aquí

El beneficio es: viaja en el tiempo por los estados, ya que se guardan todos los objetos de estado, y puedes renderizar la aplicación en cualquier estado desde UI = f(state)

Para que pueda deshacer / rehacer fácilmente.


Parece que la creación de todos estos estados aún puede ser eficiente en la memoria, una analogía con Git es excelente, y tenemos una analogía similar en el sistema operativo Linux con enlaces simbólicos (basados ​​en los inodes).


5

Otro beneficio de la inmutabilidad en Javascript es que reduce el acoplamiento temporal, que tiene beneficios sustanciales para el diseño en general. Considere la interfaz de un objeto con dos métodos:

class Foo {

      baz() {
          // .... 
      }

      bar() {
          // ....
      }

}

const f = new Foo();

Es posible que baz()se requiera una llamada a para que el objeto se encuentre en un estado válido para que una llamada bar()funcione correctamente. ¿Pero cómo sabes esto?

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

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

Para resolverlo, debe examinar las partes internas de la clase porque no es inmediatamente evidente al examinar la interfaz pública. Este problema puede explotar en una gran base de código con muchos estados y clases mutables.

Si Fooes inmutable, esto ya no es un problema. Es seguro asumir que podemos llamar bazo baren cualquier orden porque el estado interno de la clase no puede cambiar.


4

Había una vez un problema con la sincronización de datos entre subprocesos. Este problema fue un gran dolor, había más de 10 soluciones. Algunas personas trataron de resolverlo radicalmente. Era un lugar donde nació la programación funcional. Es como el marxismo. No podía entender cómo Dan Abramov vendió esta idea a JS, porque es de un solo subproceso. El es un genio.

Puedo dar un pequeño ejemplo. Hay un atributo__attribute__((pure)) en gcc. Los compiladores intentan resolver si su función es pura o no, si no la descifrará especialmente. Su función puede ser pura, incluso su estado es mutable. La inmutabilidad es solo una de las más de 100 formas de garantizar que su función sea pura. En realidad, el 95% de sus funciones serán puras.

No debe usar ninguna limitación (como la inmutabilidad) si en realidad no tiene una razón seria. Si desea "Deshacer" algún estado, puede crear transacciones. Si desea simplificar las comunicaciones, puede enviar eventos con datos inmutables. Es tu decision.

Estoy escribiendo este mensaje desde la república del post marxismo. Estoy seguro de que la radicalización de cualquier idea es un camino equivocado.


El tercer párrafo tiene mucho sentido. Gracias por eso. "¡Si quieres" Deshacer "algún estado, puedes crear transacciones"!
bozzmob

La comparación con el marxismo también se puede hacer para OOP, por cierto. ¿Recuerdas Java? Heck, ¿los trozos extraños de Java en JavaScript? El bombo nunca es bueno, causa radicalización y polarización. Históricamente, la OOP era mucho más publicitada que la publicidad de Redux por parte de Facebook. Aunque seguro hicieron todo lo posible.
ximo

4

Una toma diferente ...

Mi otra respuesta aborda la pregunta desde un punto de vista muy práctico, y todavía me gusta. Decidí agregar esto como otra respuesta en lugar de una adición a esa porque es una diatriba filosófica aburrida que con suerte también responde a la pregunta, pero que realmente no encaja con mi respuesta existente.

TL; DR

Incluso en proyectos pequeños, la inmutabilidad puede ser útil, pero no asuma que porque existe, es para usted.

Respuesta mucho, mucho más larga

NOTA: para el propósito de esta respuesta, estoy usando la palabra 'disciplina' para significar abnegación para algún beneficio.

Esto es similar en forma a otra pregunta: "¿Debo usar Typecript? ¿Por qué los tipos son tan importantes en JavaScript?". Tiene una respuesta similar también. Considere el siguiente escenario:

Usted es el único autor y mantenedor de una base de código JavaScript / CSS / HTML de unas 5000 líneas. Su jefe semi-técnico lee algo sobre el Calor mecanografiado como nuevo y sugiere que tal vez queramos pasar a él, pero le deja a usted la decisión. Entonces lo lees, juegas con él, etc.

Entonces, ahora que tiene que hacer una elección, ¿pasa a Typecript?

El mecanografiado tiene algunas ventajas convincentes: inteligencia, detección temprana de errores, especificación de sus API por adelantado, facilidad para arreglar las cosas cuando la refactorización las rompe, menos pruebas. El mecanografiado también tiene algunos costos: ciertos modismos de JavaScript muy naturales y correctos pueden ser difíciles de modelar en su sistema de tipos no especialmente poderoso, las anotaciones aumentan la LoC, el tiempo y el esfuerzo de reescribir la base de código existente, un paso adicional en la tubería de compilación, etc. Más fundamentalmente, crea un subconjunto de posibles programas JavaScript correctos a cambio de la promesa de que su código es más probable sea ​​correcto. Es arbitrariamente restrictivo. De eso se trata: impones cierta disciplina que te limita (con suerte de dispararte en el pie).

Volviendo a la pregunta, reformulada en el contexto del párrafo anterior: ¿ vale la pena ?

En el escenario descrito, diría que si está muy familiarizado con una base de código JS de pequeña a mediana, la elección de utilizar Typecript es más estética que práctica. Y está bien , no hay nada de malo en la estética, simplemente no son necesariamente convincentes.

Escenario B:

Cambias de trabajo y ahora eres un programador de línea de negocios en Foo Corp. Estás trabajando con un equipo de 10 personas en una base de código JavaScript / HTML / CSS 90000 LoC (y contando) con una tubería de compilación bastante complicada que involucra babel, paquete web , un conjunto de polyfills, reaccionan con varios complementos, un sistema de gestión de estado, ~ 20 bibliotecas de terceros, ~ 10 bibliotecas internas, complementos de editor como un linter con reglas para la guía de estilo interna, etc.

Cuando eras 5k LoC chico / chica, simplemente no importaba demasiado. Incluso la documentación no era que una gran cosa, incluso volviendo a una parte particular del código después de 6 meses que podría averiguarlo con bastante facilidad. Pero ahora la disciplina no solo es agradable sino necesaria . Es posible que esa disciplina no implique el Script mecanografiado, pero es probable que implique alguna forma de análisis estático, así como todas las demás formas de disciplina de codificación (documentación, guía de estilo, scripts de compilación, pruebas de regresión, IC). La disciplina ya no es un lujo , es una necesidad .

Todo esto se aplicaba GOTOen 1978: su pequeño juego de blackjack en C podría usar GOTOla lógica de los espaguetis y simplemente no era un gran problema elegir su propia aventura, pero a medida que los programas se hicieron más grandes y El uso más ambicioso, indisciplinado de GOTOno podría ser sostenido. Y todo esto se aplica a la inmutabilidad hoy.

Al igual que los tipos estáticos, si no está trabajando en una gran base de código con un equipo de ingenieros que la mantiene / extiende, la opción de usar la inmutabilidad es más estética que práctica: sus beneficios aún existen, pero es posible que aún no superen los costos.

Pero como con todas las disciplinas útiles, llega un punto en el que ya no es opcional. Si quiero mantener un peso saludable, entonces la disciplina con helado puede ser opcional. Pero si quiero ser un atleta competitivo, mi elección de comer o no helado está subsumida por mi elección de objetivos. Si desea cambiar el mundo con software, la inmutabilidad podría ser parte de lo que necesita para evitar que se colapse por su propio peso.


1
+1 me gusta Mucho más sobre el punto Jared. Y, sin embargo, la inmutabilidad no salvará a un equipo de su propia falta de disciplina. 😉
Steven de Salas

@StevendeSalas es una forma de disciplina. Y como tal, creo que está correlacionado con (pero no reemplaza) las otras formas de disciplina de ingeniería de software. Complementa, en lugar de suplantar. Pero como dije en un comentario sobre su respuesta, no me sorprende en absoluto que esté siendo empujado por un gigante de la tecnología con una pandilla de ingenieros todos trabajando en la misma enorme base de código :) necesitan toda la disciplina que puedan obtener. En su mayor parte, no muto objetos, pero tampoco uso ninguna forma de aplicación ya que, bueno, soy solo yo.
Jared Smith

0

He creado una biblioteca de código abierto agnóstico de código abierto (MIT) para el estado mutable (o inmutable) que puede reemplazar todos esos almacenamiento inmutable como libs (redux, vuex, etc.).

Los estados inmutables eran feos para mí porque había demasiado trabajo que hacer (muchas acciones para operaciones simples de lectura / escritura), el código era menos legible y el rendimiento para grandes conjuntos de datos no era aceptable (renderizado de componentes completos: /).

Con el observador de estado profundo puedo actualizar solo un nodo con notación de puntos y usar comodines. También puedo crear un historial del estado (deshacer / rehacer / viaje en el tiempo) manteniendo solo esos valores concretos que se han cambiado {path:value}= menos uso de memoria.

Con el observador de estado profundo puedo ajustar las cosas y tengo control de grano sobre el comportamiento de los componentes para que el rendimiento se pueda mejorar drásticamente. El código es más legible y la refactorización es mucho más fácil: solo busque y reemplace las cadenas de ruta (no es necesario cambiar el código / lógica).


-1

Creo que la razón principal por la que los objetos inmutables son para mantener válido el estado del objeto.

Supongamos que tenemos un objeto llamado arr. Este objeto es válido cuando todos los artículos son la misma 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

si se arrconvierte en un objeto inmutable, nos aseguraremos de que arr esté siempre en un estado válido.


Creo que arrse muta cada vez que llamasfillWithZ
rodrigo-silveira

si usa immutable.js, obtendrá una nueva copia del objeto cada vez que lo cambie. así que el objeto original se mantiene intacto
bedorlan
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.