Es peligroso duplicar conceptos, no código

Una de las primeras cosas que se aprenden cuando se empieza a programar es que hay que evitar el código duplicado. Hay muchas formas de evitar código repetido, desde introducir una variable para almacenar un cálculo hasta crear una librería reutilizable, pasando por extraer métodos o definir clases que agrupen una funcionalidad determinada.

Los beneficios de evitar duplicar código son bien conocidos por todos. Evitando la duplicidad de código hacemos que las aplicaciones sean más legibles, más sencillas de modificar y, en general, conseguimos aplicaciones más fáciles de mantener.

Mientras programamos estamos constantemente tratando de buscar puntos en los que podamos introducir abstracciones que nos permitan evitar duplicidad de código en aras de conseguir esa mayor mantenibilidad, pero a veces vamos demasiado lejos en nuestro afán por evitar repetir código.

En ocasiones tenemos código similar, incluso idéntico, en distintas partes de la aplicación, pero que representa conceptos diferentes. Puede que la estructura sea la misma, pero el concepto subyacente es completamente diferente. En esos casos, no suele ser una buena idea evitar la duplicidad de código.

Veamos un ejemplo muy básico:

public class Person
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

public class Movie
{
    public Guid Id { get; set; }
    public string Title { get; set; }
    public int Rating { get; set; }
}

En este ejemplo tenemos dos clases con exactamente la misma estructura, tres propiedades de tipo Guid, string e int. Si tenemos varios puntos de la aplicación en los que trabajamos con estas clases, es probable que tengamos código muy similar para tratarlas, por ejemplo cargando o guardando esas propiedades en una base de datos o enlazándolas a un formulario web para editarlas.

A simple vista, puede resultar tentador evitar todo ese código duplicado introduciendo una abstracción que represente «cosas que tienen un guid, un string y un int«, y aprovechar esa abstracción para gestionar tanto Persons como Movies; sin embargo, lo más probable es que eso sea una mala idea.

Extraer parte de código para implementar una funcionalidad común tiene algunas implicaciones que en ocasiones pasamos por alto.

Si antes teníamos varias clases con fragmentos de código repetidos y extraemos ese código a una clase común, automáticamente acabamos de establecer una dependencia entre todas esas clases. Ya no podremos modificar libremente el código compartido porque ya no pertenecerá únicamente a una clase, sino que tendremos que tener en cuenta el uso que se hace del mismo en todas y cada una de las clases que lo referencian.

En un caso como el del ejemplo, en que las clases representan conceptos diferentes, es muy probable que con el tiempo evolucionen por caminos distintos, lo que nos traerá problemas por haberlas acoplado artificialmente con el único objetivo de evitar código duplicado, sin que realmente exista una idea común.

Y eso nos lleva a otro factor importante, el código que extraemos pasa a representar una nueva abstracción dentro de nuestra aplicación. Cada abstracción que introducimos en una aplicación es algo más que tenemos que comprender para poder entender el funcionamiento de la aplicación.

Un buen indicador para saber si realmente estamos ante un concepto que debemos abstraer o si únicamente se trata de código repetido «por casualidad», es ver si podemos encontrar un buen nombre para la abstracción que estamos introduciendo. Si a la hora de nombrar esa nueva abstracción te cuesta encontrarle un nombre adecuado, tal vez sea porque no existe como tal y estás creando una abstracción artificial que antes o después se volverá contra ti.

Lo que sí debemos evitar a toda costa en una aplicación es duplicar conceptos. A veces los conceptos duplicados se manifestarán como código duplicado, y otras veces serán más difíciles de detectar, pero la duplicidad de conceptos es un problema mucho más grave que la mera repetición de código.

Veamos un ejemplo con un cálculo (muy simplificado) del importe total de un pedido:


// En un método cualquiera
var orderTotal = 0;
foreach (var line in order.Lines)
   orderTotal += line.Quantity * line.UnitPrice;

// En otro método distinto
var total = order.Lines.Sum(x => x.UnitPrice * x.Quantity);

Aunque en ambos casos obtendríamos el mismo valor, el código realmente es distinto. Hay mucha más duplicidad de código en el ejemplo de Person y Movie que veíamos antes que en este ejemplo. Sin embargo, resulta muy claro que este código es más peligroso, porque estamos duplicando un concepto (la forma de calcular el total de un pedido) en dos puntos diferentes de la aplicación, y eso a la larga nos puede traer problemas si, por ejemplo, cambiamos las reglas de cálculo en un punto pero no en otro.

Este es un caso claro en que tiene sentido introducir una nueva abstracción GetOrderTotal(), que represente la forma en que se calcula el importe total de un pedido.

Con todo esto no pretendo decir que debamos escribir todo el código seguido sin preocuparnos de buscar abstracciones y evitar duplicidad, pero creo que es más importante ser consciente de lo que representa cada fragmento de código dentro de una aplicación y no ver únicamente características estructurales.

Como he dicho ya unas cuantas veces, el código importa, pero el contexto más. A veces estamos tocando partes de una aplicación en las que pasamos mucho tiempo programando a base de «copiar/pegar/reemplazar» y podemos estar tentados de busca una abstracción que nos evite eso, pero puede que sea mejor dejar ese código duplicado porque los conceptos que se representan en el código son independientes y así deben seguir siendo.

3 comentarios en “Es peligroso duplicar conceptos, no código

  1. Muy buena entrada, no mucha gente ve la importancia de lo que comentas (o se limitan a salmodiar con TDD sin pensar).

    Quizás, reemplazaría «Un buen indicador» por «Pensar» y anotaría que conseguir abstraer cosas como el ejemplo de los totales no siempre es fácil (aunque tengamos muy claro el concepto).

    Gracias!

  2. Gracias, no conocía el artículo de Uncle Bob, pero está muy bien, es una idea similar.

    El ejemplo que él pone es más funcional (es un algoritmo lo que está duplicado) mientras que el caso que yo exponía con Person y Movie era más estructural (es la forma de los datos más que la lógica).

    Quizá no estoy del todo de acuerdo en lo de que «Duplicate code always represents a missing abstraction». Si es duplicidad estructural, es posible que realmente no haya una abstracción detrás (o que la abstracción no tenga sentido en el contexto de la aplicación).

    En cualquier caso, interesante :)

Comentarios cerrados.