Profile Image

Carlos Vigueras

Backend Developer

Refactoring – Cuándo usar Records en .Net

Como todo en la vida, usar Records en .Net puede ser beneficioso, aunque también, si no se usan adecuadamente, podrían ser contraproducentes.

En esta entrada, os voy a comentar cuándo podría ser beneficioso utilizar Records, y en otra posterior, cuándo no aconsejo usarlos, y si, digo, aconsejo, porque no hay nada que se ajuste 100% a una normativa exacta de uso.

En programación se trata de utilizar un poco la lógica y no usar ciertas prácticas o tecnologías por el mero hecho de haberlas aprendido recientemente o simplemente porque nos gustan; lo importante es saber determinar qué utilizar y en qué momento para adaptar nuestro software a sus necesidades, sin crear una sobreingeniería, introducir código innecesario (YAGNI) o no hacer escalable nuestro software.

Y digo esto anterior, porque al menos en mi caso, cuando empecé a programar aprendía algo y ya quería usarlo en todos los lados, sin pararme a analizar si «eso» que iba a usar era lo apropiado para el momento o no.

Sin más preámbulos, estas son las propiedades fundamentales de los Records y los motivos de su uso:

  • Inmutabilidad por defecto: Aunque creo que esto ya lo he comentado, los Records nos ofrecen inmutabilidad por defecto, es decir, cuando creamos un Record ya no podremos cambiar sus valores en su instancia por separado, al menos no en esa misma. Si lo que queremos es cambiarle los valores a un Record, tendremos que eliminar dicha instancia y crear una nueva con los valores que queramos.

  • Igualdad de valores: Cuando trabajamos con clases, si queremos comparar dos instancias de las mismas clases, compararemos las ubicaciones de referencia en la memoria y no el contenido de los objetos en sí. Sin embargo, con los Records se compara tan solo el valor de los mismos, y esto nos ofrece muchas ventajas; nos ahorramos código, por ejemplo.

  • Los Records son concisos: Los registros ofrecen una forma concisa de definir tipos de datos que no se pueden modificar. Con poco código, es posible declarar propiedades, constructores y otros elementos comunes, lo que disminuye la cantidad de código redundante.

  • Deconstrucción: Los registros permiten la deconstrucción, facilitando la extracción de valores de propiedades individuales. Esta característica es especialmente útil al trabajar con tuplas o cuando es necesario descomponer un registro en sus elementos.

  • Método ToString() incorporado: Los registros crean automáticamente un método ToString() significativo que muestra los valores de las propiedades, lo cual simplifica la depuración y el registro. Podremos comprobar dos valores de esta forma:
       Console.WriteLine(new Person("Juan", "Martinez"));  
       //Output: Person { Name = "Juan", "Apellido" = "Martinez" }Lenguaje del código: JavaScript (javascript)
  • Expresión With para copiar Records: La expresión with permite generar nuevas instancias de un registro con propiedades actualizadas, facilitando la creación de copias modificadas sin alterar la instancia original.
       var person = new Person("Juan", "Martinez");
       var personModified = original with { "Apellido" = "Gimenez" };
       Console.WriteLine(personModified);  
       //Output: Person { Name = "Juan", "Apellido" = "Gimenez" }Lenguaje del código: JavaScript (javascript)
  • Coincidencia de patrones: Los registros se integran perfectamente con la coincidencia de patrones en C#, ofreciendo un mecanismo poderoso para manejar estructuras de datos complejas.
      if (person is Person { Name: "Juan", "Apellido": "Martinez" })
      {
           Console.WriteLine("That's the correct person!!");
      }Lenguaje del código: JavaScript (javascript)
  • Mayor legibilidad: Los registros mejoran la claridad del código al destacar la finalidad del tipo. Esto resulta particularmente útil en contextos donde los datos son la principal prioridad. En tan solo una línea podremos declarar un nuevo Record con una sencillez clara, a diferencia de una clase en la que tenemos que escribir mucho más código para conseguir lo mismo.

  • Declaración e inicialización simplificadas: Los registros permiten declarar e inicializar instancias de manera concisa, disminuyendo la cantidad de líneas necesarias en situaciones comunes.

  • Generación de código: Los registros crean automáticamente métodos comunes, como las comprobaciones de igualdad y ToString, basados en sus propiedades. Esto disminuye la necesidad de implementarlos manualmente y reduce las posibles fuentes de errores.

Confío en que este repaso te haya servido para entender el valor real de los Records. Así, la próxima vez que te enfrentes a un refactor, sabrás determinar con criterio si una clase es la candidata ideal para dar el salto a un Record.

Tened en cuenta que si tenemos Suites de Tests y vamos a cambiar una clase por un Record, nos vamos a ver obligados a cambiar esos Tests muy posiblemente, ya que la inicialización no nos va a servir, pero también que casi seguro veremos que antes teníamos código innecesario tanto en los Tests como en código productivo.

Por normal general los Tests no hay que modificarlos a las bravas, pero si un cambio de implementación en la creación de un objeto cambia, no pasa nada por cambiar el Given de un Test por ejemplo, aunque esto como ya sabéis podremos tenerlo de antes solventado si usamos el Patrón Object Mother, para ese caso con cambiar la forma de crear el objecto en dicho patrón lo tendríamos solucionado en todos los Tests y evitaríamos a la par el Code Smell Shotgun Surgery.

Espero que os haya gustado la entrada y que podáis decirme en comentarios si conocéis alguna ventaja más.

¡Gracias por leerme!

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *