Interpretación del principio DRY

10

En este momento estoy luchando con este concepto de DRY (Don't Repeat Yourself) en mi codificación. Estoy creando esta función en la que temo que se vuelva demasiado compleja, pero estoy tratando de seguir el principio DRY.

createTrajectoryFromPoint(A a,B b,C c,boolean doesSomething,boolean doesSomething2)

Esta función, digo, toma 3 parámetros de entrada, y luego la función hará algo ligeramente diferente dadas las combinaciones booleanas doesSomethingy doesSomething2. Sin embargo, el problema que tengo es que esta función está creciendo en complejidad en gran medida con cada nuevo parámetro booleano que se agrega.

Entonces, mi pregunta es, ¿es mejor tener un montón de funciones diferentes que compartan mucha de la misma lógica (por lo tanto, violando el principio DRY) o una función que se comporta de manera ligeramente diferente dada una serie de parámetros pero que la hace mucho más compleja (pero conservando SECO)?

Albinoswordfish
fuente
3
¿Se puede factorizar la lógica compartida / común en funciones privadas que createTrajectory...todas las funciones públicas llaman?
FrustratedWithFormsDesigner
Podría ser, pero esas funciones privadas aún necesitarían obtener esos parámetros booleanos
Albinoswordfish
2
Creo que esto sería / será mucho más fácil de responder dado algún tipo de ejemplo concreto. Mi reacción inmediata es que la dicotomía que representa no es del todo real, es decir, esas no son las dos únicas opciones. Como comentario, consideraría cualquier uso de a booleancomo parámetro algo sospechoso en el mejor de los casos.
Jerry Coffin
Relacionado: ¿Por qué es importante DRY?
Steven Jeuris
¿Por qué no estás factorizando las cosas condicionales en sus propias funciones?
Plataforma

Respuestas:

19

Los argumentos booleanos para activar diferentes rutas de código en una sola función / método es un terrible olor a código .

Lo que está haciendo viola los principios de acoplamiento flojo y alta cohesión y responsabilidad única , que son mucho más importantes que DRY en precedencia.

Eso significa que las cosas deberían depender de otras cosas solo cuando tienen que hacerlo ( Acoplamiento ) y que deberían hacer una cosa y solo una cosa (muy bien) ( Cohesión ).

Por su propia omisión, esto está demasiado estrechamente acoplado (todas las banderas booleanas son un tipo de dependencia del estado, ¡que es una de las peores!) Y tiene demasiadas responsabilidades individuales entremezcladas (demasiado complejas).

Lo que estás haciendo no está en el espíritu de DRY de todos modos. DRY tiene más que ver con la repetición (lo que Rsignifica REPEAT). Evitar copiar y pegar es su forma más básica. Lo que estás haciendo no está relacionado con la repetición.

Su problema es que su descomposición de su código no está en el nivel correcto. Si cree que tendrá un código duplicado, entonces esa debería ser su propia función / método que esté parametrizado de manera apropiada, no copiar y pegar, y los demás deberían nombrarse descriptivamente y delegarse en la función / método principal.

Comunidad
fuente
Ok, parece que la forma en que escribo no es muy buena (mi sospecha inicial) No estoy realmente seguro de qué es este 'Sprit of DRY'
Albinoswordfish
4

El hecho de que esté pasando booleanos para hacer que la función haga cosas diferentes es una violación del Principio de responsabilidad única. Una función debería hacer una cosa. Debería hacer una sola cosa, y debería hacerlo bien.

Huele como si necesita dividirlo en varias funciones más pequeñas con nombres descriptivos, separando las rutas de código correspondientes a los valores de esos valores booleanos.

Una vez que lo haga, debe buscar un código común en las funciones resultantes y factorizarlo en sus propias funciones. Dependiendo de lo complejo que sea esto, incluso puede factorizar una clase o dos.

Por supuesto, esto supone que está utilizando un sistema de control de versiones y que tiene un buen conjunto de pruebas, para que pueda refactorizar sin temor a romper algo.

Dima
fuente
3

¿Por qué no creará otra función que contenga toda la lógica en su función antes de decidir hacer algo o algo2 y luego tener tres funciones como:

createTrajectoryFromPoint(A a,B b,C c){...}

dosomething(A a, B b, C c){...}

dosomething2(A a, B b, C c){...}

Y ahora, al pasar tres tipos de parámetros a tres funciones diferentes, volverá a repetirse, por lo que debe definir una estructura o clase que contenga A, B, C.

Alternativamente , puede crear una clase que contenga los parámetros A, B, C y una lista de operaciones a realizar. Agregue qué operaciones (algo, algo2) desea que sucedan con estos parámetros (A, B, C) registrando operaciones con el objeto. Luego tenga un método para llamar a todas las operaciones registradas en su objeto.

public class MyComplexType
{
    public A a{get;set;}
    public B b{get;set;}
    public C c{get;set;}

    public delegate void Operation(A a, B b, C c);
    public List<Operation> Operations{get;set;}

    public MyComplexType(A a, B b, C c)
    {
        this.a = a;
        this.b = b;
        this.c = c   
        Operations = new List<Operation>();
    }

    public CallMyOperations()
    {
        foreach(var operation in Operations)
        {
            operation(a,b,c);
        }
    }
}
Mert Akcakaya
fuente
Para tener en cuenta las posibles combinaciones de valores para el booleano dosomething y dosomething2, necesitaría 4 funciones, no 2, además de la función base createTrajectoryFromPoint. Este enfoque no escala bien a medida que aumenta el número de opciones, e incluso nombrar las funciones se vuelve tedioso.
JGWeissman
2

DRY puede llevarse demasiado lejos, lo mejor es utilizar el principio de responsabilidad única (SRP) junto con DRY. Agregar banderas bool a una función para que haga versiones ligeramente diferentes del mismo código puede ser una señal de que está haciendo demasiado con una función. En este caso, sugeriría crear una función separada para cada caso que representan sus banderas, luego, cuando haya escrito cada función, debería ser bastante evidente si hay una sección común que se puede mover a una función privada sin pasar todas las banderas , si no hay una sección aparente de código, entonces realmente no te estás repitiendo, tienes varios casos diferentes pero similares.

Ryathal
fuente
1

Por lo general, paso por varios pasos con este problema, deteniéndome cuando no puedo descubrir cómo ir más allá.

Primero, haz lo que has hecho. Vaya duro con SECO. Si no termina con un gran desastre peludo, ya está. Si, como en su caso, no tiene un código duplicado pero cada valor booleano tiene su valor verificado en 20 lugares diferentes, vaya al siguiente paso.

En segundo lugar, divide el código en bloques. Se hace referencia a los booleanos solo una vez (bueno, tal vez dos veces a veces) para dirigir la ejecución al bloque correcto. Con dos booleanos, terminas con cuatro bloques. Cada bloque es casi idéntico. DRY se ha ido. No haga que cada bloque sea un método separado. Eso sería más elegante, pero poner todo el código en un método hace que sea más fácil, o incluso posible, que cualquiera que realice tareas de mantenimiento vea que tiene que hacer cada cambio en cuatro lugares. Con un código bien organizado y un monitor alto, las diferencias y los errores serán casi obvios. Ahora tiene un código que se puede mantener y se ejecutará más rápido que el lío enredado original.

Tercero, intente obtener líneas de código duplicadas de cada uno de sus bloques y convertirlas en métodos simples y agradables. A veces no puedes hacer nada. A veces no puedes hacer mucho. Pero cada poquito que haces te hace retroceder hacia SECO y hace que el código sea un poco más fácil de seguir y más seguro de mantener. Idealmente, su método original podría terminar sin código duplicado. En ese punto, es posible que desee dividirlo en varios métodos sin los parámetros booleanos o no. La conveniencia del código de llamada es ahora la principal preocupación.

Agregué mi respuesta al gran número que ya estaba aquí debido al segundo paso. Odio el código duplicado, pero si es la única forma inteligible de resolver un problema, hágalo de tal manera que cualquiera sepa de un vistazo lo que está haciendo. Use múltiples bloques y solo un método. Haga que los bloques sean lo más idénticos posible en nombres, espacios, alineaciones, ... todo. Las diferencias deberían saltar al lector. Podría hacer obvio cómo reescribirlo de manera SECA, y si no, mantenerlo será razonablemente sencillo.

RalphChapin
fuente
0

Un enfoque alternativo es reemplazar los parámetros booleanos con parámetros de interfaz, con código para manejar los diferentes valores booleanos refactorizados en las implementaciones de interfaz. Entonces tendrías

createTrajectoryFromPoint(A a,B b,C c,IX x,IY y)

donde tiene implementaciones de IX e IY que representan los diferentes valores para los booleanos. En el cuerpo de la función, donde sea que tenga

if (doesSomething)
{
     ...
}
else
{
     ...
}

lo reemplaza con una llamada a un método definido en IX, con las implementaciones que contienen los bloques de código omitidos.

JGWeissman
fuente