[Clean Code] Evita código duplicado

Seguro que te has encontrado más de una vez la casuística de tener 2 funciones con semejante código pero solo cambia una línea o un par de líneas de código, o un código común repetido por toda tu aplicación  ¿verdad? y por alguna razón u otra no te paras a refactorizar nunca, bien por tiempo o por desconocimiento. El problema es que si algo cambia en una aplicación ese es el código, así pues, tarde o temprano si tienes que cambiar ese código tendrás que hacerlo por todas las partes donde lo tengas, con el consiguiente problema de olvidar cambiarlo en alguna parte y por consiguiente tu aplicación tarde o temprano fallará (Sobre todo si no haces Test Unitarios).

A continuación os voy a mostrar como podemos refactorizar nuestro código cuando nos encontramos con este problema.

Suponemos que tenemos 2 métodos de una capa de aplicación que hacen lo mismo y solo cambia una línea:

Una entidad de dominio que llama a un método u otro.

Vamos a verlo.

public CommentDTO LikeComment(int commentId)

{

    ///Common code

    

    comment.Like(user);

 

    ///Common code

 

    return...

}

 

public CommentDTO UnLikeComment(int commentId)

{

    ///Common code

    

    comment.UnLike(user);

 

    ///Common code

 

    return...

}

Como podemos observar sólo cambia una línea de código y todo lo demás es igual, es decir, tenemos código duplicado y hay que evitarlo siempre!!!

¿Como podemos solucionarlo?

En este caso y usando las caracteristicas modernas del framework, podemos hacer uso de un delegado o una expression lambda (Si quieres aprender sobre estas, te recomiendo una serie del maestro Jose María AguilarC#: Desmitificando las expresiones lambda (I)), así pues, nuestro código una vez refactorizado quedaría así:

public CommentDTO LikeComment(int commentId)

{

    return CommentWrapper(commentId, (comment, user) => comment.Like(user));

}

 

public CommentDTO UnLikeComment(int commentId)

{

    return CommentWrapper(commentId, (comment, user) => comment.UnLike(user));

}

 

public CommentDTO CommentWrapper(int commentId, Action<Comment, User> user)

{

    ///Common code

 

    ///Specific code

    action.Invoke(comment, user);

 

    ///Common code

 

    return...

}

¿Mucho mejor no?

Ahora solo tenemos un único punto donde cambiar nuestro código común, además de poder reutilizarlo siempre que se traten de métodos relacionados con la entidad Comment y contenga esa misma firma, como en el caso anterior.

Espero comentarios, soluciones alternativas, críticas constructivas…

Un saludo

8 comentarios en “[Clean Code] Evita código duplicado”

  1. Hola Luis, me parece que has expuesto una muy buena solución del problema de suplicados. Y creo que es una buenísima idea que la gente exponga diferentes formas de evitar código duplicado, porque es muy edificante para todos.

    Así que voy a exponer una solución que suelo realizar cuando me encuentro este tipo de escenarios. Básicamente es el uso de un “BeginXXXX” y “EndXXXX”. Adaptándolo a tu ejemplo sería algo así:

    [code]
    public CommentDTO LikeComment(int commentId)
    {
    var comment = this.BeginCommentAction(commentId);

    comment.Like(user);

    return this.EndCommentAction(comment);
    }

    public CommentDTO UnLikeComment(int commentId)
    {
    var comment = this.BeginCommentAction(commentId);

    comment.UnLike(user);

    return this.EndCommentAction(comment);
    }

    public CommentDTO BeginCommentAction(int commentId)
    {
    // common code
    }

    public CommentDTO EndCommentAction(CommentDTO comment)
    {
    // common code
    }
    [/code]

    Muchas gracias! 😀

  2. Muchas gracias Fernando por tu comentario y por el ejemplo que has puesto. Es muy enriquecedor para todos compartir diferentes formas de hacer las cosas 🙂

    Un saludo y una muy buena solución 🙂

  3. A mi cuando me paso eso decidí usar un approach mas tradicional, podría haber usado Interfaces, pero en su lugar use una clase abstracta colocando en esta el código común, dejando para las clases “especificas” la implementación de la parte diferenciada. Me quedo compacto y elegante 🙂

  4. El tema de la refactorización es interesante. Me encuentro en ocasiones casos de 2 o 3 métodos prácticamente iguales, que difieren en 2 o 3 líneas y alguna condición en el if.

    Es bueno poder aplicar alguna técnica así. Con una línea que cambie se ve más claro, si hay unas pocas más habría que ver si se consigue la elegancia deseada.

    Lo importante sin duda es evitar a toda costa el código duplicado.

  5. Hola Luis,

    mirá, no puedo decir que los que haz expuesto esté mal pero me parece que solo se trata de un caso algo particular. Te explico mi punto:
    – Ese “common code” no es simplemente “algo” sino que debe estar desempeñando alguna función concreta! en ese caso me parece preferible (por sencillez y legibilidad) lo que propone Fernando.

    Si son solo 2 métodos entonces, refactorizar como Fer, si muchos métodos comparten el mismo código común entonces quizás convenga un aspecto, si es una familia de tipos (clases Like y Unlike) que solo cambian ese comportamiento entonces probablemente preferiría template method, o también puede que según el caso nos convenga crear un decorator o tal vez un interceptor específico para esos métodos y así. El punto es que hay muchas opciones y dependen del contexto (en este caso no lo tenemos). incluso, en este caso debamos considerar hasta un if.

    A esta línea de código solo la entiende a primera, quien la desarrolló:

    return CommentWrapper(commentId, (comment, user) => comment.UnLike(user));

    A la que propone Fernando la entiende cualquiera he incluso yo la simpleficaría aún más:

    public CommentDTO LikeComment(int commentId)
    {
    var curCommentId = commentId; //si todos lo necesitan…. perdón por esto
    BeginCommentAction();
    comment.Like(user);
    EndCommentAction();
    return …
    }

  6. @Lucas, muchas gracias por tu comentario, efectivamente este es una caso muy particular que se me ha dado y que he resuelto de esa manera. Me gusta mucho la solución de @Fernando e incluso la del template de method, ahora, sí un if supone pasar un booleno a la función, ahí ya no me gusta tanto.

    @JoséMaría, un plasé 😉

Deja un comentario

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