Llamar al siguiente método de uno anterior ... ¿Por qué es este mal diseño?

7

Si createWorld() es realmente largo y necesito dividirlo, puedo dividirlo en createLight() , createEarth() , createPlants() y createAnimals() .

Entonces, naturalmente lo hago:

function createLight(){
    //work 1
}
function createEarth(){
    //work 2
}
function createPlants(){
    //work 3
}
function createAnimals(){
    //work 3
}

function createWorld(){
    createLight()
    createEarth()
    createPlants()
    createAnimals()
}

Pero veo que muchos desarrolladores más nuevos hacen lo que estoy llamando tentativamente " el paso de Stairstep antinatrón ". Va algo así como:

function createWorld(){
    //create light
    //work1
    createEarth()
}
function createEarth(){
    //work 2
    createPlants()
}
function createPlants(){
    //work 3
    createAnimals()
}
function createAnimals(){
    //work 4
}

Estoy seguro de que esto es peor, pero no creo que solo mi opinión pueda influir en mis colegas. Creo que si un paso es createButterfly() , entonces esa función no tiene ninguna actividad comercial al final, solo porque ese es el "siguiente paso".

E incluso si lo llama createGrasshopper() , sigue siendo un mal diseño porque no puede probar bien el código de createButterfly, y cuando tiene varios pasos, es difícil ver dónde se llaman las funciones.

Lo llamo el antipatrón Stairstep porque lo pienso así:

|
|createWorld(){
              |
              |createEarth(){
                            |
                            |createPlants(){
                                           |createAnimals()

¿Tengo razón al pensar que este es un mal diseño? ¿Por qué es este un mal diseño?

    
pregunta AwokeKnowing 05.12.2016 - 21:53

6 respuestas

18

Además del hecho obvio, en el caso que # 2 createEarth y createPlants no hacen lo que su nombre finge lo que están haciendo, creo que la principal falla aquí es la violación del nivel único de abstracción :

createEarth realiza algunos trabajos directamente, sin ninguna abstracción, y luego llama a createPlants , que es una abstracción separada que realiza algún trabajo adicional. Pero este último debe estar en el mismo nivel de abstracción que el trabajo realizado anteriormente. Así que se mezclan diferentes niveles de abstracción.

Intentando solucionar este problema, por lo general, uno primero refactoriza createPlants de # 2 de esta manera:

function createPlants(){
    doWork3()  //work 3
    createAnimals()
}

Ahora todo está en el mismo nivel de abstracción, así que podemos resolver el problema de los nombres: doWork3 debería cambiarse de nombre a createPlants y createPlants a createLife , ya que crea animales y plantas:

  function createLife(){
      createPlants()  
      createAnimals()
  }

Creo que ahora es obvio que después de algunos pasos de refactorización adicionales, uno termina automáticamente en el caso # 1, que sigue el principio de SLA: dentro de createWorld , cada llamada está en el mismo nivel de abstracción. Dentro de cada uno de los otros métodos, (con suerte) el trabajo realizado allí también se encuentra en un nivel de abstracción.

Tenga en cuenta que técnicamente, el caso # 2 también funcionará. El problema comienza cuando uno tiene que cambiar algo, cuando el código debe mantenerse o evolucionar. El caso # 1 crea bloques de construcción en su mayoría independientes, que se pueden entender, probar, reutilizar, extender o reordenar más fácilmente. En el caso # 2, cada bloque de construcción depende de otro, lo que arroja todas las ventajas anteriores a bordo.

    
respondido por el Doc Brown 06.12.2016 - 08:06
3

Este es un mal diseño porque está más estrechamente acoplado de lo que debe ser.

Cuando sus funciones están compuestas en un agregador, cada una de ellas puede usarse (y probarse) de forma aislada. Cuando CreateEarth llama a CreatePlants en la cadena, elimina esa flexibilidad de su código. obliga a crear las plantas, que es el proceso usual , pero lo que sucede cuando el negocio surge y quiere un mundo oceánico o un mundo volcánico que no necesita plantas ?

Al separar las funciones, puede adaptarse mejor a este cambio inevitable .

    
respondido por el Telastyn 06.12.2016 - 17:53
2

Lo llamaría algo así como "anidamiento de llamadas excesivas". El objetivo de tener un método separado es romper una sola pieza lógica del algoritmo, idealmente una pieza reutilizable. Si no es funcionalmente distinto o reutilizable, considere no dividirlo, a menos que facilitaría significativamente la legibilidad y dividiría una gran parte del código que abarca varias páginas en componentes más mantenibles. Por lo general, esto se puede hacer de manera tal que cada pieza tenga un grado razonable de independencia funcional, incluso si no se pretende que se llame desde más de un lugar.

Vea también ¿Cuándo es apropiado realizar una función separada cuando solo habrá una sola llamada a dicha función?

    
respondido por el Brad Thomas 05.12.2016 - 23:20
1

Uno de los problemas más grandes que veo es la restricción estricta de orden que el número 2 coloca en la construcción de un mundo. ¿Qué sucede cuando quiero createAnimals() antes de createPlants() ? ¿Qué pasa si decido que hay que hacer algo en el medio? En el primer ejemplo es tan simple como mover una sola línea de código. En el segundo ejemplo, necesito volver a escribir al menos 2 métodos.

    
respondido por el ToddR 06.12.2016 - 18:06
-1

Razones para seguir el camino # 1:

  1. Optimización de la pila de llamadas de función.

  2. Modularidad para la verificación.

  3. Modularidad para mantenimiento.

De Wikipedia en la pila de llamadas de función (no parece ser un gran problema con las computadoras de hoy en día, pero aún lo es con microcontroladores y otros sistemas integrados):

"En informática, una pila de llamadas es una estructura de datos de pila que almacena información sobre las subrutinas activas de un programa de computadora".

    
respondido por el user2807874 07.12.2016 - 20:34
-3

Se llama "mala denominación". Un método llamado createPlants que crea realmente los animales de ambas plantas y tiene un nombre engañoso. Posiblemente indica un malentendido de la abstracción de la función, ya que el escritor no se da cuenta de que createAnimals es parte de lo que hace createPlants .

Al descomponer una función grande en múltiples funciones más pequeñas, se puede hacer de forma secuencial o jerárquica. Ambos son completamente válidos, dependiendo de la semántica de las funciones. Pero cada función debe tener un propósito claro y bien definido que debe reflejarse en su nombre.

Si es difícil encontrar un nombre natural para una función, indica que el propósito de la función no está bien definido. Si necesita nombrar una función createAllAnimalsExceptGrashopper() para describir su propósito, las funciones no se descomponen de manera natural.

Pero no hay nada malo en sí mismo al llamar al "siguiente paso" al final de una función. Por ejemplo, si se llama a createEarth antes de que se pueda llamar a createPlants , yo diría que es mejor diseñar tener createEarth call createPlants en lugar de llamarlos secuencialmente en el nivel superior. (Posiblemente la Tierra podría ser un parámetro para createPlants para hacer explícita la dependencia).

En cuanto al nombre de este "antipatrón": creo que es erróneo pensar que cualquier instancia particular de mal diseño debe tener un nombre específico. Culpo al sistema educativo por darle mucha importancia a conocer y recordar terminología precisa, en lugar de entender los problemas subyacentes. Tratar de enseñar a las personas que no deberían escribir "métodos de escalera" sin que comprendan el problema subyacente solo causará la programación del culto de la carga.

    
respondido por el JacquesB 05.12.2016 - 23:28

Lea otras preguntas en las etiquetas