¿Es este un buen patrón: reemplazar una función larga con una serie de lambdas?

14

Recientemente me encontré con la siguiente situación.

class A{
public:
    void calculate(T inputs);
}

En primer lugar, Arepresenta un objeto en el mundo físico, que es un fuerte argumento para no dividir la clase. Ahora, calculate()resulta ser una función bastante larga y complicada. Percibo tres posibles estructuras para ello:

  • escríbalo como un muro de texto - ventajas - toda la información está en un solo lugar
  • escribir privatefunciones de utilidad en la clase y usarlas en calculateel cuerpo de la persona - desventajas - el resto de la clase no sabe / no se preocupa por esos métodos
  • escribe de calculatela siguiente manera:

    void A::calculate(T inputs){    
        auto lambda1 = () [] {};    
        auto lambda2 = () [] {};    
        auto lambda3 = () [] {};
    
        lambda1(inputs.first_logical_chunk);
        lambda2(inputs.second_logical_chunk);
        lambda3(inputs.third_logical_chunk);
    }

¿Se puede considerar esto una buena o mala práctica? ¿Este enfoque revela algún problema? Con todo, ¿debería considerar esto como un buen enfoque cuando me enfrento nuevamente a la misma situación?


EDITAR:

class A{
    ...
public:
    // Reconfiguration of the algorithm.
    void set_colour(double colour);
    void set_density(double density);
    void set_predelay(unsigned long microseconds);
    void set_reverb_time(double reverb_time, double room_size);
    void set_drywet(double left, double right);
    void set_room_size(double value);;

private:
    // Sub-model objects.
    ...
}

Todos esos métodos:

  • obtener un valor
  • calcular algunos otros valores, sin usar el estado
  • llame a algunos de los "objetos de submodelo" para cambiar su estado.

Resulta que, con excepción set_room_size(), esos métodos simplemente pasan el valor solicitado a los subobjetos. set_room_size(), por otro lado, hace un par de pantallas de fórmulas oscuras y luego (2) hace media pantalla para llamar a los colocadores de subobjetos para aplicar los diversos resultados obtenidos. Por lo tanto, he separado la función en dos lambdas y las llamo al final de la función. Si hubiera podido dividirlo en partes más lógicas, habría aislado más lambdas.

En cualquier caso, el objetivo de la pregunta actual es determinar si esa forma de pensar debe persistir o, en el mejor de los casos, no agrega valor (legibilidad, facilidad de mantenimiento, capacidad de depuración, etc.).

Vorac
fuente
2
¿Qué crees que usar lambdas te dará las llamadas de función que no?
Blrfl
1
Firstly, A represents an object in the physical world, which is a strong argument for not splitting the class up.Seguramente Arepresenta datos sobre un objeto que podría existir en el mundo físico. Puede tener una instancia de Asin el objeto real y un objeto real sin una instancia de A, por lo que tratarlos como si fueran uno y lo mismo no tiene sentido.
Doval
@Blrfl, encapsulación: nadie calculate()sabrá sobre esas subfunciones.
Vorac
Si todos esos cálculos son relevantes solo A, eso lo lleva un poco al extremo.
Blrfl
1
"En primer lugar, Arepresenta un objeto en el mundo físico, que es un fuerte argumento para no dividir la clase". Desafortunadamente, me dijeron esto cuando comencé a programar. Me llevó años darme cuenta de que es un montón de hockey sobre caballos. Es una razón terrible para agrupar cosas. No puedo expresar cuáles son buenas razones para agrupar cosas (al menos para mi satisfacción), pero esa es una que debes descartar ahora mismo. Al final, todo de "buen código" es que funciona correctamente, es relativamente fácil de entender y es relativamente fácil de cambiar (es decir, los cambios no tienen efectos secundarios extraños).
jpmc26

Respuestas:

13

No, este no es generalmente un buen patrón.

Lo que estás haciendo es dividir una función en funciones más pequeñas usando lambda. Sin embargo, hay una herramienta mucho mejor para dividir funciones: funciones.

Las lambdas funcionan, como has visto, pero significan mucho, mucho, mucho, mucho más que simplemente dividir una función en bits locales. Las lambdas hacen:

  • Cierres. Puede usar variables en el ámbito externo dentro de la lambda. Esto es muy poderoso y muy complicado.
  • Reasignación Si bien su ejemplo dificulta la tarea, siempre hay que prestar atención a la idea de que el código puede intercambiar funciones en cualquier momento.
  • Funciones de primera clase. Puede pasar una función lambda a otra función, haciendo algo conocido como "programación funcional".

En el instante en que incorporas lambdas a la mezcla, el siguiente desarrollador que mire el código debe cargar mentalmente de inmediato todas esas reglas en preparación para ver cómo funciona tu código. No saben que no vas a utilizar toda esa funcionalidad. Esto es muy costoso, en comparación con las alternativas.

Es como usar una azada trasera para hacer tu jardinería. Sabes que solo lo estás usando para cavar pequeños agujeros para las flores de este año, pero los vecinos se pondrán nerviosos.

Considere que todo lo que está haciendo es agrupar su código fuente visualmente. Al compilador en realidad no le importa que currieses cosas con lambdas. De hecho, esperaría que el optimizador deshaga inmediatamente todo lo que acaba de hacer cuando se compila. Está atendiendo al siguiente lector (¡Gracias por hacerlo, incluso si no estamos de acuerdo con la metodología! ¡El código se lee con mucha más frecuencia de lo que se escribe!). Todo lo que está haciendo es agrupar la funcionalidad.

  • Las funciones ubicadas localmente dentro de la secuencia de código fuente funcionarían igual de bien, sin invocar lambda. Nuevamente, todo lo que importa es que el lector pueda leerlo.
  • Los comentarios en la parte superior de la función dicen "estamos dividiendo esta función en tres partes", seguidos de grandes líneas largas // ------------------entre cada parte.
  • También puede poner cada parte del cálculo en su propio ámbito. Esto tiene la ventaja de demostrar inmediatamente, más allá de toda duda, que no existe un intercambio variable entre las partes.

EDITAR: al ver su edición con un código de ejemplo, me inclino hacia la notación de comentarios como la más limpia, con corchetes para imponer los límites que sugieren los comentarios. Sin embargo, si alguna de las funciones fuera reutilizable en otras funciones, recomendaría usar funciones en su lugar

void A::set_room_size(double value)
{
    {
        // Part 1: {description of part 1}
        ...
    }
    // ------------------------
    {
        // Part 2: {description of part 2}
        ...
    }
    // ------------------------
    {
        // Part 3: {description of part 3}
        ...
    }
}
Cort Ammon
fuente
Por lo tanto, no es un número objetivo, pero afirmaría subjetivamente que la mera presencia de funciones lambda me hace prestar aproximadamente 10 veces más atención a cada línea de código, porque tienen mucho potencial para volverse peligrosamente complicadas, rápidas, y así sucesivamente. una línea de código de aspecto inocente (como count++)
Cort Ammon
Gran punto Veo esas como las pocas ventajas del enfoque con lambdas: (1) código localmente adyacente y con alcance local (esto se perdería por las funciones de nivel de archivo) (2) el compilador asegura que no se compartan variables locales entre los segmentos de código. Por lo tanto, esas ventajas se pueden preservar separándolas calculate()en {}bloques y declarando datos compartidos en el calculate()ámbito. Pensé que al ver que las lambdas no capturaban, un lector no se vería afectado por el poder de las lambdas.
Vorac
"Pensé que al ver que las lambdas no capturaban, un lector no se vería afectado por el poder de las lambdas". Esa es en realidad una declaración justa, pero contenciosa, que golpea el corazón de la lingüística. Las palabras generalmente tienen connotaciones que se extienden más allá de sus denotaciones. Si mi connotación de lambdaes injusta o si estás obligando groseramente a las personas a seguir la estricta denotación no es una pregunta fácil de responder. De hecho, puede ser totalmente aceptable para usted usarlo de lambdaesa manera en su empresa, y totalmente inaceptable en mi empresa, ¡y ninguno de los dos tiene que estar equivocado!
Cort Ammon
Mi opinión sobre la connotación de lambda proviene del hecho de que crecí en C ++ 03, no en C + 11. He pasado años desarrollando una apreciación de los lugares específicos donde C ++ se ha visto afectado por la falta de lambda, como la for_eachfunción. En consecuencia, cuando veo lambdaque no encaja en uno de esos casos de problemas fáciles de detectar, la primera suposición a la que llego es que probablemente se usará para la programación funcional, porque de lo contrario no era necesario. Para muchos desarrolladores, la programación funcional es una mentalidad completamente diferente de la programación de procedimiento o OO.
Cort Ammon
Gracias por la explicación. Soy un programador novato y ahora me alegro de haberlo preguntado antes de que se acumulara el hábito.
Vorac
20

Creo que hiciste una mala suposición:

En primer lugar, A representa un objeto en el mundo físico, que es un fuerte argumento para no dividir la clase.

No estoy de acuerdo con esto. Por ejemplo, si tuviera una clase que represente un automóvil, definitivamente querría dividirlo, porque seguramente quiero una clase más pequeña que represente los neumáticos.

Debería dividir esta función en funciones privadas más pequeñas. Si realmente parece separado de la otra parte de la clase, entonces eso puede ser una señal de que la clase debería estar separada. Por supuesto, es difícil saberlo sin un ejemplo explícito.

Realmente no veo la ventaja de usar funciones lambda en este caso, porque realmente no hace que el código sea más limpio. Fueron creados para ayudar a la programación de estilo funcional, pero esto no es eso.

Lo que escribió se parece un poco a los objetos de función anidados de estilo Javascript. Lo cual es nuevamente una señal de que están estrechamente unidos. ¿Estás seguro de que no deberías hacer una clase separada para ellos?

En resumen, no creo que este sea un buen patrón.

ACTUALIZAR

Si no ve ninguna forma de encapsular esta funcionalidad en una clase significativa, puede crear funciones auxiliares de ámbito de archivo, que no son miembros de su clase. Esto es C ++ después de todo, el diseño OO no es obligatorio.

Gábor Angyal
fuente
Suena razonable, pero me resulta difícil imaginar la implementación. ¿Clase de alcance de archivo con métodos estáticos? ¿Clase definida dentro A(tal vez incluso functor con todos los demás métodos privados)? Clase declarada y definida dentro calculate()(esto se parece mucho a mi ejemplo lambda). Como aclaración, calculate()es uno de una familia de métodos ( calculate_1(), calculate_2()etc.) de los cuales todos son simples, solo este es 2 pantallas de fórmulas.
Vorac
@Vorac: ¿Por qué es calculate()mucho más largo que todos los otros métodos?
Kevin
@Vorac Es realmente difícil ayudar sin ver su código. ¿También puedes publicarlo?
Gábor Angyal
@ Kevin, las fórmulas para todo se dan en los requisitos. Código publicado
Vorac
44
Votaría esto 100 veces si pudiera. "Modelar objetos en el mundo real" es el comienzo de la espiral de la muerte del diseño de objetos. Es una bandera roja gigante en cualquier código.
Fred the Magic Wonder Dog