DEV Community

Ernesto λrroyo
Ernesto λrroyo

Posted on

Be immutable my friend!

En este artículo explico brevemente el concepto de inmutabilidad y porque deberías usarlo por defecto.

El concepto de inmutabilidad en la programación no es novedoso. El resurgir(*) de la programación funcional le ha dado una nueva vida, pero en otros paradigmas ha existido desde antaño.

No hay más que leer, por ejemplo, uno de los libros referente de la programación como es "Effective Java" (Joshua Bloch) que en su recomendación número 15 menciona el principio de minimizar la mutabilidad de los objetos.

An immutable class is simply a class whose instances cannot be modified. All of the information contained in each instance is fixed for the lifetime of the object, so no changes can ever be observed. The Java platform libraries contain many immutable classes, including String, the boxed primitive classes, and BigInteger and BigDecimal. There are many good reasons for this: Immutable classes are easier to design, implement, and use than mutable classes. They are less prone to error and are more secure. Bloch, Joshua. Effective Java.

(*) la programación bajo el paradigma funcional tiene su origen teórico en los años 30 y el que podría ser el primer lenguaje funcional, LISP, es de 1950.

Beneficios

Un clase inmutable es aquella cuyas instancias no podrán ser modificadas. Con este modelo evitamos errores y nuestras aplicaciones serán más seguras.

En entornos de alta concurrencia evitan errores de acceso compartido y no hace falta disponer de herramientas de protección para modificar objetos (mutex, locks, semáforos,...) porque, bueno, no se puede modificar nada.

Un objeto (mejor dicho, una de sus instancias) sólo puede estar en un estado, el estado en el que fue construido. Si hace falta modificarlo el proceso es crear una copia modificada.

Esto que puede parecer poco natural en realidad es un arma tremendamente poderosa y permite usar un estilo funcional de programación que es mucho más seguro y menos peligroso que la programación que suele utilizarse.

Podemos encontrar más razones para usar siempre objetos inmutables incluso en la misma Oracle:

An object is considered immutable if its state cannot change after it is constructed. Maximum reliance on immutable objects is widely accepted as a sound strategy for creating simple, reliable code.

Immutable objects are particularly useful in concurrent applications. Since they cannot change state, they cannot be corrupted by thread interference or observed in an inconsistent state.

https://docs.oracle.com/javase/tutorial/essential/concurrency/immutable.html

Un objeto mutable presenta muchos problemas para entornos de concurrencia.

Estas rodeado de inmutabilidad

Lo cierto es que incluso en lenguajes de OOP se usa profusamente la idea de inmutabilidad. En Java las clases como String, BigInteger, Long, UUID,...

En otros lenguajes más modernos, y vamos a dar el ejemplo con Kotlin, es más fácil trabajar con objetos inmutables e, incluso, las colecciones son inmutables por omisión.

Es decir Kotlin, al contrario que Java, prefiere la inmutabilidad y una lista no es modificable una vez creada; si necesitas que lo sea deberás usar otro tipo:

   val immutableList = listOf("one")
   val changingList = mutableListOf<String>("one")

Aún así en Java es trivial (aunque verboso) definir clases inmutables.

Nota: en Java 14 esto parece que puede cambiar gracias a la incorporación de los record. No me parece mal que Java haya decidido al final adoptar lo que sus primos de la JDK (Groovy, Scala, Kotlin,...) ya tienen desde su nacimiento. </ironic>

¡ah! pero siendo fácil podemos repasar antes una barbaridad.

Un mal ejemplo

El problema: necesitas un objeto que no pueda modificarse.

  • Solución adecuada: pasar copias del objeto, o crear el objeto como inmutable.

  • Solución "creativa" 🙀, crear un interfaz que tenga sólo métodos para lectura y pasar el interfaz a los consumidores.

Básicamente, en lugar de usar el lenguaje usas tu imaginación. 🤦🏻

Esta solución creativa implica que usaremos un interfaz -que define comportamiento o un contrato de un servicio- para lo que viene a ser un objeto de dominio o un DTO si lo usamos entre capas.

Es más que rara porque además pasando el interfaz "de setters" perdemos poder serializar el elemento

Una solución de bombero jubilado.

¡mis ojos! ¡mis ojos!

Creamos un objeto Something que será donde estén los datos.

class Something implements SomethingContext {

    @Getter @Setter String somethingId;

}

y cuando queremos usarlo pasamos SomethingContext y sí, lo has adivinado, la barbaridad es que lo pasarás haciendo un downcasting del objeto al interfaz.

public interface SomethingContext {
    String getSomethingId(); // HORROR!
    ... muchos más getters

👉 este modo de ataque y crítica nunca debe ser usado en situaciones reales, es una falta de respeto y no ayuda ni a mejorar a quien lo hizo mal, ni a quien lo utiliza. Es usado aquí como recurso literario, y me permito la licencia porque fui yo quien hizo esta escabechina en un proyecto hace algunos años. 🥳

Varias formas simples y correctas de tener inmutabilidad

En Java

Lombok

Puedes hacerlo a mano, está muy bien explicado en la documentación de la misma Oracle™, pero es que es mucho más sencillo con Lombok.

En lombok la anotación @Value implica que el objeto sólo puede ser leido tras su construcción. Es el hermano mayor de @Data.

@Value
class Something {  
    String somethingId;
}

No podrás más que leer objetos, no puedes alterar su valor.

Immutable

También tenemos la librería Immutable que ayuda en Java a crear objetos inmutables.

El problema de Lombok y de Immutable es que incorporamos librerías para un concepto que creo debería resolver el lenguaje y bueno, en modo estricto por ejemplo en arquitecturas hexagonales he encontrado gente que dice que en el modelo (dominio) debe ser tan puro que no deberíamos introducir ni siquiera estas utilidades.

Así que vamos a un lenguaje que tiene la inmutabilidad en su ADN.

Por ejemplo, por ejemplo,...

Kotlin

Llegamos a la solución más elegante y flexible con Kotlin.

data class Person (val name: String, 
                   val surname: String, 
                   val gender: String)

val earroyoron = Person(name="Ernesto",surname="Arroyo",gender="Male")

En Kotlin este objeto es inmutable. Si necesitamos modificarlo, no podemos. Y esto nos evita muchos problemas.

Además de paso te he mostrado otras cositas chulas del lenguaje como son los parámetros con nombre en el constructor.

Pero algo que no puede cambiar nunca tampoco es muy util así que, ¿que hacemos cuando va cambiando el estado?

En un estilo de programación funcional lo que se hace es una copia modificada, que no altera el original. Esto es muy fácil y nos lo ofrece el mismo lenguaje (y no, ¡ni se te ocurra pensar en el clone de Java!)

En Kotlin para tener una copia basada en el anterior objeto hacemos esto, por ejemplo pongamos que pudiera recuperar como primer apellido el hermoso segundo apellido de mi padre:

data class Person (val name: String, val surname: String, val gender: String)

fun main() {
    val earroyoron = Person(name="Ernesto",surname="Arroyo",gender="Male")
    val egarciadelasheras = earroyoron.copy(surname="Garcia de las Heras")
    println (earroyoron)
    println (egarciadelasheras)
}

Veremos:

Person(name=Ernesto, surname=Arroyo, gender=Male)
Person(name=Ernesto, surname=Garcia de las Heras, gender=Male)

Voy a concluir aquí este pequeño repaso inicial a la inmutabilidad.

Si hay una segunda parte deberíamos entrar a ver como trabajar con Kotlin de forma mucho más potente usando sus sealed class y empezaremos a pensar en modo funcional con los tipos de datos algebraicos o las ópticas,....

Top comments (0)