Pasar funciones a otras funciones como parámetros, ¿mala práctica?

40

Hemos estado en el proceso de cambiar la forma en que nuestra aplicación AS3 se comunica con nuestro back-end y estamos en el proceso de implementar un sistema REST para reemplazar el anterior.

Lamentablemente, el desarrollador que comenzó el trabajo ahora está de baja por enfermedad a largo plazo y me lo ha entregado. He estado trabajando con él durante la última semana y entiendo el sistema, pero hay una cosa que me ha estado preocupando. Parece que se pasan muchas funciones a funciones. Por ejemplo, nuestra clase que realiza la llamada a nuestros servidores adopta una función a la que luego llamará y pasará un objeto cuando se complete el proceso y se hayan manejado los errores, etc.

Me está dando ese "mal presentimiento" donde siento que es una práctica horrible y puedo pensar en algunas razones, pero quiero una confirmación antes de proponer un nuevo trabajo en el sistema. Me preguntaba si alguien tenía alguna experiencia con este posible problema.

Elliot Blackburn
fuente
22
¿Por qué te sientes así? ¿Tienes alguna experiencia con la programación funcional? (Asumo que no, pero deberías echar un vistazo)
Phoshi
8
¡Entonces considera esto una lección! Lo que la academia enseña y lo que es realmente útil a menudo tiene una superposición sorprendentemente pequeña. Hay todo un mundo de técnicas de programación que no se ajustan a ese tipo de dogma.
Phoshi
32
Pasar funciones a otras funciones es un concepto tan fundamental que cuando un lenguaje no lo admite, las personas se desviven por crear "objetos" triviales cuyo único propósito es contener la función en cuestión como un recurso provisional.
Doval
36
En mi día, llamamos a estas funciones de transferencia "Callbacks"
Mike

Respuestas:

84

No es un problema

Es una técnica conocida. Estas son funciones de orden superior (funciones que toman funciones como parámetros).

Este tipo de función también es un componente básico en la programación funcional y se usa ampliamente en lenguajes funcionales como Haskell .

Estas funciones no son malas ni buenas: si nunca ha encontrado la noción y la técnica, puede ser difícil de comprender al principio, pero pueden ser muy poderosas y son una buena herramienta para tener en su cinturón de herramientas.

Oded
fuente
Gracias por eso, no tengo experiencia real con programación funcional, así que lo investigaré. Simplemente no se sentó bien porque, según mi pequeño conocimiento, cuando declaras algo como una función privada, solo esa clase debería tener acceso a ella y las públicas deberían llamarse con referencia al objeto. Lo examinaré correctamente y espero venir a apreciarlo un poco más.
Elliot Blackburn
3
La lectura sobre las continuaciones , el estilo de paso de continuación , las mónadas (programación funcional) también deberían ser útiles.
Basile Starynkevitch
8
@BlueHat declaras que algo es privado si quieres controlar cómo se usa, si ese uso es como una devolución de llamada para funciones específicas, entonces está bien
monstruo de trinquete
55
Exactamente. Marcarlo como privado significa que no desea que alguien más ingrese y acceda a él por nombre en cualquier momento que lo desee. Pasar una función privada a otra persona, ya que específicamente desea que llaman en la forma en que documentan para ese parámetro, es absolutamente bien. No tiene por qué ser diferente de pasar el valor de un campo privado como parámetro a alguna otra función, que presumiblemente no le sienta mal :-)
Steve Jessop
2
Vale la pena señalar que si te encuentras escribiendo clases concretas con funciones como parámetros de construcción, probablemente lo estés haciendo mal tm.
Gusdor
30

No solo se utilizan para la programación funcional. También pueden conocerse como devoluciones de llamada :

Una devolución de llamada es un fragmento de código ejecutable que se pasa como argumento a otro código, que se espera que devuelva la llamada (ejecute) en algún momento conveniente. La invocación puede ser inmediata como en una devolución de llamada síncrona o puede ocurrir más tarde, como en una devolución de llamada asíncrona.

Piensa en el código asincrónico por un segundo. Usted pasa una función que, por ejemplo, envía datos al usuario. Solo cuando el código se ha completado, invoca esta función con el resultado de la respuesta, que la función luego utiliza para enviar los datos al usuario. Es un cambio de mentalidad.

Escribí una biblioteca que recupera datos de Torrent de tu seedbox. Está utilizando un bucle de eventos sin bloqueo para ejecutar esta biblioteca y obtener datos, luego devolverlos al usuario (por ejemplo, en un contexto de websocket). Imagine que tiene 5 personas conectadas en este bucle de eventos y una de las solicitudes para obtener los puestos de datos de torrents de alguien. Eso bloqueará todo el ciclo. Por lo tanto, debe pensar de forma asíncrona y usar devoluciones de llamada: el bucle sigue ejecutándose y el "devolver los datos al usuario" solo se ejecuta cuando la función ha finalizado la ejecución, por lo que no hay que esperar. Dispara y olvida.

James
fuente
1
+1 Para usar la terminología de devolución de llamada. Encuentro este término con mucha más frecuencia que "funciones de orden superior".
Rodney Schuler
Me gusta esta respuesta, porque el OP parecía estar describiendo devoluciones de llamada, específicamente, y no solo funciones de orden superior en general: "Por ejemplo, nuestra clase que realiza la llamada a nuestros servidores toma una función que luego llamará y pasará un objeto a cuándo se completa el proceso y se han manejado los errores, etc. "
Ajedi32
11

Esto no es algo malo. De hecho, es algo muy bueno.

Pasar funciones a funciones es tan importante para la programación que inventamos las funciones lambda como una abreviatura. Por ejemplo, uno puede usar lambdas con algoritmos de C ++ para escribir código muy compacto pero expresivo que permita a un algoritmo genérico la capacidad de usar variables locales y otros estados para hacer cosas como buscar y ordenar.

Las bibliotecas orientadas a objetos también pueden tener devoluciones de llamada que son esencialmente interfaces que especifican un pequeño número de funciones (idealmente una, pero no siempre). Entonces se puede crear una clase simple que implemente esa interfaz y pasar un objeto de esa clase a una función. Esa es una piedra angular de la programación dirigida por eventos , donde el código de nivel de marco (quizás incluso en otro hilo) necesita llamar a un objeto para cambiar el estado en respuesta a una acción del usuario. La interfaz ActionListener de Java es un buen ejemplo de esto.

Técnicamente, un functor de C ++ también es un tipo de objeto de devolución de llamada que aprovecha el azúcar sintáctico operator()()para hacer lo mismo.

Finalmente, hay punteros de función de estilo C que solo deben usarse en C. No voy a entrar en detalles, solo los menciono para completarlos. Las otras abstracciones mencionadas anteriormente son muy superiores y deben usarse en idiomas que las tengan.

Otros han mencionado la programación funcional y cómo pasar funciones es muy natural en esos lenguajes. Las lambdas y las devoluciones de llamada son la forma en que los lenguajes de procedimiento y OOP imitan eso, y son muy potentes y útiles.


fuente
Además, en C #, los delegados y las lambdas se usan ampliamente.
Evil Dog Pie
6

Como ya se dijo, no es una mala práctica. Es simplemente una forma de desacoplar y separar la responsabilidad. Por ejemplo, en OOP harías algo como esto:

public void doSomethingGeneric(ISpecifier specifier) {
    //do generic stuff
    specifier.doSomethingSpecific();
    //do some other generic stuff
}

El método genérico es delegar una tarea específica, de la que no sabe nada, a otro objeto que implemente una interfaz. El método genérico solo conoce esta interfaz. En su caso, esta interfaz sería una función a llamar.

Philipp Murry
fuente
1
Este modismo es simplemente envolver la función en una interfaz, lograr algo muy similar a pasar una función sin procesar.
Sí lo es, solo quería mostrar que esta no es una práctica poco común.
Philipp Murry
3

En general, no hay nada de malo en pasar funciones a otras funciones. Si está haciendo llamadas asincrónicas y quiere hacer algo con el resultado, necesitaría algún tipo de mecanismo de devolución de llamada.

Sin embargo, existen algunos inconvenientes potenciales de las simples devoluciones de llamada:

  • Hacer una serie de llamadas puede requerir un anidamiento profundo de las devoluciones de llamada.
  • El manejo de errores puede necesitar repetición para cada llamada en una secuencia de llamadas.
  • Coordinar múltiples llamadas es incómodo, como hacer múltiples llamadas al mismo tiempo y luego hacer algo una vez que todas hayan terminado.
  • No hay una forma general de cancelar un conjunto de llamadas.

Con servicios web simples, la forma en que lo hace funciona bien, pero se vuelve incómodo si necesita una secuencia de llamadas más compleja. Sin embargo, hay algunas alternativas. Con JavaScript, por ejemplo, ha habido un cambio hacia el uso de promesas ( ¿Qué tiene de bueno las promesas de JavaScript? ).

Todavía implican pasar funciones a otras funciones, pero las llamadas asincrónicas devuelven un valor que toma una devolución de llamada en lugar de tomar una devolución de llamada directamente. Esto proporciona más flexibilidad para componer estas llamadas juntas. Algo como esto se puede implementar con bastante facilidad en ActionScript.

fgb
fuente
También agregaría que el uso innecesario de devoluciones de llamada puede dificultar el seguimiento del flujo de ejecución para cualquiera que lea el código. Una llamada explícita es más fácil de entender que una devolución de llamada que se estableció en una parte distante del código. (¡Puntos de ruptura al rescate!) Sin embargo, así es como se desencadenan los eventos en el navegador y otros sistemas basados ​​en eventos; entonces necesitamos comprender la estrategia del emisor de eventos para saber cuándo y en qué orden se llamarán los controladores de eventos.
joeytwiddle