Una de las primeras buenas prácticas que aprendemos como programadores es a detectar código duplicado, y refactorizarlo para unirlo y que quede en un único sitio. Tiene bastante sentido, ya que si tenemos un código que realiza la misma función en distintas partes de la aplicación, si quisiéramos cambiarlo nos va a obligar a tocar en cada una de esas partes (y eso si nos acordamos).
El principio Don’t Repeat Yourself está instalado como un pilar innegociable en el imaginario colectivo de los programadores. Repetir es malo. Reutilizar es bueno. Si vemos oportunidad, nos hace click la cabeza y reutilizamos. De primero de refactoring.
Saber elegir cuándo reutilizar código
Por lo general, tener código duplicado es algo a evitar. Sin embargo, reutilizar no siempre es acertado. A veces, repetir código es correcto, y de hecho, reutilizarlo en determinadas situaciones crea más problemas que ventajas.
Existen dos tipos de duplicidad de código: la real y la accidental.
Duplicidad real
La duplicidad real es la aquella en la que el código no solo es idéntico, sino que además sirve para lo mismo. Si se produce un cambio, requiere que todos los sitios donde esté ese código se modifiquen de la misma manera, por tanto tiene una única razón para cambiar. Este tipo de código duplicado es el que debemos evitar y hay que unificar.
Duplicidad accidental
La duplicidad accidental es aquella que en la que el código puede parecer el mismo, pero sirve para cosas distintas. Si se produce un cambio, puede que solo sea necesario modificar alguno de los sitios donde esté ese código, por tanto tiene más de una razón para cambiar. Este tipo de código duplicado es el que no debemos caer en la tentación de unificar.
Ejemplos
En una aplicación dedicada a la educación, vamos a suponer que tenemos las figuras de profesor y alumno.
Para crear cada uno de ellos, tenemos dos funciones, *CreateTeacher *y CreateStudent. Como vemos que tanto profesores como alumnos comparten idéntica información, hemos unificado la información que reciben ambas funciones en una misma estructura de datos, evitando así tener código duplicado:
public void CreateTeacher(PersonData data) { ... }
public void CreateStudent(PersonData data) { ... }
...
public class PersonData
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
Sin embargo, pasado un tiempo nos toca implementar una nueva funcionalidad, como introducir un código promocional solo cuando creamos un nuevo alumno. Al estar utilizando una única estructura de datos, es muy probable que añadamos a la misma ese nuevo campo necesario para el caso de uso de de creación de estudiante, pero sigamos manteniendo la misma estructura también para el de profesor.
public class PersonData
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string SignupCode { get; set; }
}
Lo inadecuado de usar la misma estructura de datos, es que seguramente empecemos a forzar nuestro código para que siga siendo posible reutilizarla, enviando el nuevo campo SignupCode con valor null al caso de uso de crear profesor cuando no lo necesita, generando código menos legible y forzando a sus consumidores a enviar un dato innecesario.
Utilizando la misma estructura de datos para dos casos de uso distintos, hemos creado un acoplamiento entre dos componentes que deberían ser independientes. Cuando solo quería cambiar uno, he influido en el comportamiento del otro. Automáticamente, hemos conseguido que nuestro código sea más difícil de mantener, que es precisamente lo que estábamos buscando evitar usando el principio DRY.
Hay quien puede pensar que se puede arrancar así, y ya luego según evolucione la aplicación, se separa el código si fuera necesario. Es posible, supongo que en función de la experiencia y responsabilidad del equipo… pero yo lo evitaría. Si estamos siendo perezosos o tenemos poco tiempo ahora, es muy posible que el el futuro sigamos siendo perezosos o tengamos poco tiempo.
Otro caso muy claro de duplicidad accidental se da en las arquitecturas clean y hexagonal con los modelos de la capa de datos, que suelen ser muy parecidos o incluso idénticos a los modelos de la capa de dominio. De esta manera, es muy habitual que se tenga un único modelo para ambas capas, especialmente si se trabaja con un ORM. Como siempre, será el criterio y la experiencia del desarrollador lo que determine, en función del contexto y el tipo de aplicación, si se asume o no esa duplicidad.
Cómo distinguir duplicidad real vs accidental
Se trata de analizar, en el contexto de la aplicación, si los componentes que utilizan nuestra clase “unificada” representan varios conceptos diferentes, y por tanto pueden evolucionar cada uno por su camino. Aunque hoy día sean exactamente iguales, al ser distintas funcionalidades pueden evolucionar de manera independiente en el futuro.
Conclusión
Por lo general, aplicar el principio DRY es muy aconsejable ya que nos permite evitar errores y hacer aplicaciones más fáciles de mantener.
Sin embargo, evitar la duplicidad de código en algunas situaciones nos puede traer problemas si con ello acoplamos componentes de manera artificial, sin que realmente exista un único motivo para que cambie el código unificado.
Nota: Este texto fue publicado originalmente en el blog de Kirei Studio.