Usar funções aninhadas é uma coisa ruim?

9

Em uma tarefa recente, acabei chamando minhas funções de uma maneira feia; uglyReceipt(cashParser(cashInput()))o próprio programa funcionou perfeitamente, mas eu ainda sentia que estava fazendo algo errado.

A chamada está funcionando como esta prática ruim e, se sim: O que devo fazer?

Carl groth
fonte
Possível duplicação de funções aninhadas; permitir ou não?
mosquito
1
Essas funções estão aninhadas ou é um ninho de chamadas para funções? Se o último OP pode ter (re) inventado a programação funcional.
High Performance Mark
O que faz você pensar que isso seria uma prática ruim?
Ixrec 17/04
1
@gnat: Essa questão é completamente independente. Essa pergunta é sobre definições de função lexicamente aninhadas, é sobre passar o resultado de uma chamada de função como argumento para outra chamada de função.
Jörg W Mittag
Eu interpretei mal - obrigado por apontar @ JörgWMittag (votação retraída) #
gnat

Respostas:

11

Isso realmente depende de quanto aninhamento você usa. Afinal, você tem permissão para usar os resultados das funções diretamente nas expressões para melhorar a legibilidade. Tanto o código que não usa expressões aninhadas (como o código do assembler) quanto o código que usa expressões aninhadas em excesso são difíceis de ler. Um bom código tenta encontrar um equilíbrio entre os extremos.

Então, vamos dar uma olhada em alguns exemplos. O que você deu na sua pergunta parece bastante legítimo para mim, então nada para se preocupar aqui. No entanto, uma linha como

foo(bar(baz(moo, fab), bim(bam(ext, rel, woot, baz(moo, fab)), noob), bom, zak(bif)));

definitivamente não seria tolerável. Da mesma forma, código como

double xsquare = x*x;
double ysquare = y*y;
double zsquare = z*z;
double xysquare = xsquare + ysquare;
double xyzsquare = xysquare + zsquare;
double length = sqrt(xyzsquare);

não seria muito legível também. sqrt(x*x + y*y + z*z)é muito mais fácil de entender, apesar de combinar um total de seis operações diferentes em uma expressão.

Meu conselho é prestar atenção em quais expressões você ainda pode analisar facilmente em sua cabeça. No momento em que você precisa dar uma segunda olhada para entender o que uma única expressão faz, é hora de introduzir uma variável adicional.

cmaster - restabelece monica
fonte
4

Eu acho que se é bom ou ruim depende muito do contexto. A principal razão pela qual isso pode ser considerado ruim é que (sem dúvida) torna o código mais difícil de ler e depurar. Isto é especialmente verdade quando você está aprendendo a programar pela primeira vez. À medida que suas habilidades de codificação (e código) ficam mais avançadas, há momentos em que isso é aceitável.

Por exemplo, considere uma mensagem de erro como esta:

line 1492: missing argument "foo"

Como você sabe se o argumento ausente é cashInput, cashParserou uglyReceipt? Dependendo do idioma, a mensagem de erro pode informar, mas talvez não.

Se fosse para separar essas chamadas de função e a mensagem de erro ainda apontasse para a linha 1492, você saberia instantaneamente onde está o problema:

1491: input = cashInput()
1492: parsed_value = cashParser(input)
1493: receipt = uglyReceipt(parsed_value)

Com as etapas separadas separadamente, é muito mais fácil depurar, pois é possível definir um ponto de interrupção em qualquer etapa e você pode facilmente injetar valores alterando o valor das variáveis ​​locais.

Bryan Oakley
fonte
3

O conceito subjacente à sua pergunta é tão importante que acho que ela precisa de outra resposta em vez de apenas um comentário (como eu comecei a fazer).

As outras três respostas até o momento fornecem alguns pontos de consideração úteis sobre se uma determinada situação merece o uso do que você chama de "chamadas de funções aninhadas". Mas talvez um ponto mais importante esteja oculto nos comentários da sua pergunta: caso você tenha perdido a sutileza no que essas pessoas eruditas estão sugerindo, Carl, você descobriu por si mesmo o tópico realmente chamado de programação funcional . Se você nunca viu o termo, pode não ter pensado que era realmente uma "coisa" no comentário do @ HighPerformanceMark.

Mas de fato é! A programação funcional é escrita há décadas, desde o artigo seminal de John Hughes, Por que a Programação Funcional é Importante . Existem algumas linguagens que são funcionais (ou seja, elas apenas permitem escrever em um estilo de programação funcional), linguagens como Erlang, Lisp, OCaml ou Haskell. Mas há muito mais idiomas que são híbridos imperativos / funcionais. Ou seja, são linguagens tradicionalmente imperativas, mas também oferecem algum suporte para programação funcional, incluindo Perl, C ++, Java, C # e muito mais. A entrada da Wikipedia sobre programação funcional fornece uma boa seção que mostra uma comparação entre estilo funcional e estilo imperativo para vários idiomas.

Há muito a dizer sobre as diferenças entre estilos imperativos e funcionais, mas o principal ponto de partida é que, com programação funcional, funções ou métodos não têm efeitos colaterais , facilitando, em geral, a compreensão e a depuração de programas.

Para uma leitura mais aprofundada, você também pode dar uma olhada nos assuntos Por que "Por que a programação funcional é importante", de Reginald Braithwaite, e outro post interessante aqui no SO, Por que linguagens funcionais?

Michael Sorens
fonte
2

Não é absolutamente uma má prática em geral. A chamada de funções aceita valores e uma maneira de produzir um valor é chamando outra função.

Quando vejo uma variável sendo definida, como:

parsed_value = cashParser(input)

... Tenho que considerar que parsed_valuepode ser usado mais de uma vez e provavelmente terei que verificar se isso é verdade ou não (e se minha alteração quebrar algo em outro lugar?). Quando você começa a adicionar mais variáveis, pode se tornar mais complexo para o leitor acompanhar todas elas e como elas estão relacionadas. Então, fico aliviado quando vejo:

receipt = uglyReceipt(cashParser(input))

... porque o escopo / vida útil do valor intermediário é óbvio. Agora, como sempre, dividir uma expressão longa em instruções separadas pode ajudar, especialmente se um nome de variável puder fornecer mais precisão sobre a finalidade de um valor:

user_name = input()
coredump
fonte