Passando funções para outras funções como parâmetros, más práticas?

40

Estamos mudando a maneira como nosso aplicativo AS3 se comunica com nosso back-end e estamos implementando um sistema REST para substituir o antigo.

Infelizmente, o desenvolvedor que iniciou o trabalho está agora de licença médica de longo prazo e foi entregue a mim. Estou trabalhando com ele há uma semana ou mais e entendo o sistema, mas há uma coisa que me preocupa. Parece haver muita passagem de funções para funções. Por exemplo, nossa classe que faz a chamada para nossos servidores assume uma função para a qual chamará e passará um objeto para quando o processo estiver concluído e os erros forem tratados etc.

Está me dando esse "mau pressentimento", onde eu sinto que é uma prática horrível e posso pensar em algumas razões, mas quero alguma confirmação antes de propor um novo trabalho no sistema. Fiquei me perguntando se alguém tinha alguma experiência com este possível problema?

Elliot Blackburn
fonte
22
Por que você se sente assim? Você tem alguma experiência com programação funcional? (Eu vou não assume, mas você deve dar uma olhada)
Phoshi
8
Então considere isso uma lição! O que a academia ensina e o que é realmente útil geralmente surpreendentemente tem pouca sobreposição. Existe um mundo inteiro de técnicas de programação por aí que não se enquadram nesse tipo de dogma.
Phoshi
32
Passar funções para outras funções é um conceito tão fundamental que, quando uma linguagem não a suporta, as pessoas se esforçam para criar "objetos" triviais, cujo único objetivo é conter a função em questão como um paliativo.
Doval
36
Na minha época nós chamamos estes passam através de funções "Callbacks"
Mike

Respostas:

84

Não é um problema.

É uma técnica conhecida. Essas são funções de ordem superior (funções que assumem funções como parâmetros).

Esse tipo de função também é um componente básico da programação funcional e é amplamente utilizado em linguagens funcionais como Haskell .

Tais funções não são boas ou ruins - se você nunca encontrou a noção e a técnica, elas podem ser difíceis de entender a princípio, mas podem ser muito poderosas e são uma boa ferramenta para se ter no cinto de ferramentas.

Oded
fonte
Obrigado por isso, eu não tenho nenhuma experiência real com programação funcional, então vou dar uma olhada. Apenas não funcionou bem porque, pelo meu pouco conhecimento, quando você declara algo como uma função privada, apenas essa classe deve ter acesso a ela e as públicas devem ser chamadas com referência ao objeto. Vou dar uma olhada nele corretamente e espero que eu aprecie um pouco mais!
Elliot Blackburn
3
Ler sobre continuações , estilo de passagem de continuação , mônadas (programação funcional) também deve ser útil.
Basile Starynkevitch
8
@BlueHat você declarar algo a ser privado, se você quer controle sobre como ela é usada, se esse uso é como um retorno de chamada para funções específicas, então tudo bem
catraca aberração
5
Exatamente. Marcá-lo como privado significa que você não quer que outra pessoa entre e acesse pelo nome a qualquer momento. Passar uma função privada para outra pessoa, porque você deseja que ela a chame da maneira como documentam esse parâmetro, é absolutamente aceitável. Ele não precisa ser diferente de passar o valor de um campo privado como um parâmetro para alguma outra função, que, presumivelmente, não se senta mal com você :-)
Steve Jessop
2
Vale ressaltar que se você se encontra escrevendo classes concretas com funções como parâmetros de construtor, provavelmente está fazendo algo errado tm.
Gusdor
30

Eles não são usados ​​apenas para programação funcional. Eles também podem ser conhecidos como retornos de chamada :

Um retorno de chamada é um pedaço de código executável que é passado como argumento para outro código, que deve retornar (executar) o argumento em algum momento conveniente. A chamada pode ser imediata como em um retorno de chamada síncrono ou pode ocorrer mais tarde, como em um retorno de chamada assíncrono.

Pense em código assíncrono por um segundo. Você passa uma função que, por exemplo, envia dados ao usuário. Somente quando o código for concluído, você invoca esta função com o resultado da resposta, que a função usa para enviar os dados de volta ao usuário. É uma mudança de mentalidade.

Eu escrevi uma biblioteca que recupera dados de Torrent do seu seedbox. Você está usando um loop de eventos sem bloqueio para executar esta biblioteca e obter dados e depois devolvê-los ao usuário (por exemplo, em um contexto de websocket). Imagine que você tem 5 pessoas conectadas nesse loop de eventos e uma das solicitações para obter os dados de torrent de alguém. Isso irá bloquear todo o loop. Então, você precisa pensar de forma assíncrona e usar retornos de chamada - o loop continua em execução e o "devolver os dados ao usuário" é executado apenas quando a função termina a execução, portanto, não há como esperar. Fogo e esqueça.

James
fonte
11
+1 Para usar a terminologia de retorno de chamada. Encontro esse termo com muito mais frequência do que "funções de ordem superior".
Rodney Schuler
Gosto dessa resposta, porque o OP parecia descrever retornos de chamada, especificamente, e não apenas funções de ordem superior em geral: "Por exemplo, nossa classe que faz a chamada para nossos servidores assume uma função que chamará e passará um objeto para quando o processo estiver concluído e os erros forem tratados etc. "
precisa saber é o seguinte
11

Isso não é uma coisa ruim. De fato, é uma coisa muito boa.

Passar funções para funções é tão importante para a programação que inventamos as funções lambda como uma abreviação. Por exemplo, pode-se usar lambdas com algoritmos C ++ para escrever código muito compacto, mas expressivo, que permite a um algoritmo genérico a capacidade de usar variáveis ​​locais e outro estado para fazer coisas como pesquisar e classificar.

As bibliotecas orientadas a objetos também podem ter retornos de chamada que são essencialmente interfaces que especificam um pequeno número de funções (idealmente uma, mas nem sempre). Pode-se criar uma classe simples que implementa essa interface e passar um objeto dessa classe para uma função. Essa é uma pedra angular da programação orientada a eventos , em que o código no nível da estrutura (talvez até em outro encadeamento) precisa chamar um objeto para alterar o estado em resposta a uma ação do usuário. A interface ActionListener do Java é um bom exemplo disso.

Tecnicamente, um functor C ++ também é um tipo de objeto de retorno de chamada que aproveita o açúcar sintático operator()(), para fazer a mesma coisa.

Por fim, existem ponteiros de função no estilo C que devem ser usados ​​apenas em C. Não entrarei em detalhes, apenas os mencionei por completude. As outras abstrações mencionadas acima são muito superiores e devem ser usadas nos idiomas que as possuem.

Outros mencionaram programação funcional e como a passagem de funções é muito natural nessas linguagens. Lambdas e retornos de chamada são a maneira como as linguagens processuais e OOP imitam isso, e são muito poderosas e úteis.


fonte
Além disso, em C #, delegados e lambdas são usados ​​extensivamente.
Mal Dog Pie
6

Como já foi dito, não é uma prática ruim. É apenas uma maneira de dissociar e separar responsabilidades. Por exemplo, no OOP, você faria algo assim:

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

O método genérico está delegando uma tarefa específica - sobre a qual nada sabe - para outro objeto que implementa uma interface. O método genérico conhece apenas essa interface. No seu caso, essa interface seria uma função a ser chamada.

Philipp Murry
fonte
11
Esta expressão é simplesmente envolvendo a função em uma interface, realizando algo muito semelhante ao passar uma função cru em.
Sim, eu só queria mostrar que isso não é uma prática incomum.
Philipp Murry
3

Em geral, não há nada errado em passar funções para outras funções. Se você estiver fazendo chamadas assíncronas e quiser fazer alguma coisa com o resultado, precisará de algum tipo de mecanismo de retorno de chamada.

Existem algumas desvantagens em potencial de simples retornos de chamada:

  • Fazer uma série de chamadas pode exigir aninhamento profundo de retornos de chamada.
  • O tratamento de erros pode precisar de repetição para cada chamada em uma sequência de chamadas.
  • Coordenar várias chamadas é complicado, como fazer várias chamadas ao mesmo tempo e depois fazer alguma coisa depois que todas estiverem concluídas.
  • Não existe uma maneira geral de cancelar um conjunto de chamadas.

Com serviços web simples, a maneira como você está fazendo isso funciona bem, mas fica estranho se você precisar de um seqüenciamento de chamadas mais complexo. Existem algumas alternativas embora. Com o JavaScript, por exemplo, houve uma mudança no uso de promessas (o que há de tão bom nas promessas de javascript ).

Eles ainda envolvem a passagem de funções para outras funções, mas as chamadas assíncronas retornam um valor que recebe um retorno de chamada, em vez de receber diretamente um retorno de chamada. Isso oferece mais flexibilidade para compor essas chamadas em conjunto. Algo assim pode ser implementado facilmente no ActionScript.

fgb
fonte
Eu também acrescentaria que o uso desnecessário de retornos de chamada pode dificultar o acompanhamento do fluxo de execução para quem lê o código. Uma chamada explícita é mais fácil de entender do que um retorno de chamada que foi definido em uma parte distante do código. (Pontos de interrupção para o resgate!) No entanto, é assim que os eventos são disparados no navegador e em outros sistemas baseados em eventos; precisamos entender a estratégia do emissor de eventos para saber quando e em que ordem os manipuladores de eventos serão chamados.
precisa saber é o seguinte