Criando funções aninhadas por razões puramente estéticas?

16

Eu sempre me perguntei o que outros programadores pensam sobre a idéia de criar funções estéticas puras.

Digamos que eu tenha uma função que processa um pedaço de dados: Function ProcessBigData. Digamos que eu precisar de várias etapas do processo, só é válida para esses dados: Step1, Step2, Step3.

A abordagem normal que eu mais vejo no código fonte é escrever comentários da seguinte forma:

Function ProcessBigData:
    # Does Step1
    Step1..
    Step1..

    #Does Step2
    Step2..
    Step2..

O que eu costumo fazer, mas sempre me senti errado devido à falta desse estilo de codificação por colegas é:

Function ProcessBigData:
    Function Step1:
        Step1..
        Step1..

    Function Step2:
        Step2..
        Step2..

    Step1() -> Step2()

Estou principalmente preocupado se há alguma desvantagem para esse estilo em Javascript e Python

Existem alternativas que não estou vendo?

Slytael
fonte
3
Não posso dizer nada sobre Python, mas, para Javascript, há um custo de desempenho para funções aninhadas: a maioria dos mecanismos JavaScript usa uma estrutura semelhante a uma lista vinculada para representar o escopo variável. A adição de uma camada adicional de funções força o mecanismo a procurar uma estrutura de dados maior / maior ao resolver variáveis. Por outro lado, a raiz de todo mal é, obviamente, a otimização prematura. :)
Marco

Respostas:

4

Não é tão estranho quanto você imagina. Por exemplo, no Padrão ML, é habitual limitar o escopo das funções auxiliares. Concedido, o SML tem sintaxe para facilitar:

local
    fun recursion_helper (iteration_variable, accumulator) =
        ... (* implementation goes here *)
in
    fun recursive_function (arg) = recursion_helper(arg, 0);
end

Eu consideraria esse bom estilo, uma vez que 1) pequenas funções facilitam o raciocínio sobre o programa e 2) sinaliza ao leitor que essas funções não são usadas fora desse escopo.

Suponho que seja possível que exista alguma sobrecarga na criação de funções internas sempre que a função externa for chamada (não sei se JS ou Python otimizam isso), mas você sabe o que eles dizem sobre otimização prematura.

Doval
fonte
11

Geralmente é bom fazer isso sempre que possível, mas eu gosto de pensar nesse tipo de trabalho não como "etapas", mas como subtarefas .

Uma subtarefa é uma unidade específica de trabalho que pode ser executada: possui uma responsabilidade específica e entradas e saídas definidas (pense no "S" no SOLID ). Uma subtarefa não precisa ser reutilizável: algumas pessoas tendem a pensar "Eu nunca precisarei chamar isso de outra coisa; por que escrevê-lo como uma função?" mas isso é uma falácia.

Vou tentar também descrever os benefícios e também como isso se aplica a funções aninhadas (fechamentos) versus apenas outra função da classe. De um modo geral, eu recomendaria não usar encerramentos, a menos que você precise especificamente de um (existem muitos usos, mas separar o código em blocos lógicos não é um deles).

Legibilidade.

Mais de 200 linhas de código processual (corpo de uma função) são difíceis de ler. As funções de 2-20 linhas são fáceis de ler. Código é para humanos.

Aninhado ou não, você obtém o benefício da legibilidade, a menos que esteja usando muitas variáveis ​​do escopo pai; nesse caso, pode ser igualmente difícil de ler.

Limitar o escopo da variável

Ter outra função obriga a limitar o escopo da variável e a passar especificamente o que você precisa.

Isso geralmente melhora a estrutura do código, porque se você precisar de algum tipo de variável de estado de uma "etapa" anterior, poderá descobrir que há outra subtarefa que deve ser escrita e executada primeiro para obter esse valor. Ou, em outras palavras, torna mais difícil escrever trechos de código altamente acoplados.

Ter funções aninhadas permite acessar variáveis ​​no escopo pai de dentro da função aninhada (fechamento). Isso pode ser muito útil, mas também pode levar a erros sutis e difíceis de encontrar, pois a execução da função pode não ocorrer da maneira como está escrita. Esse é ainda mais o caso se você estiver modificando variáveis ​​no escopo pai (uma péssima idéia, geralmente).

Testes unitários

Cada subtarefa, implementada uma função (ou mesmo uma classe), é um trecho de código testável e independente. Os benefícios do teste de unidade e TDD estão bem documentados em outros lugares.

O uso de funções / fechamentos aninhados não permite testes de unidade. Para mim, este é um rompimento de acordos e a razão pela qual você deve apenas outra função, a menos que haja uma necessidade específica de fechamento.

Trabalhando em equipe / Design de cima para baixo

As subtarefas podem ser escritas por pessoas diferentes, independentemente, se necessário.

Mesmo você mesmo, pode ser útil ao escrever código para simplesmente chamar alguma subtarefa que ainda não existe, ao criar a funcionalidade principal e se preocupar em realmente implementar a subtarefa somente depois que você souber que obterá os resultados necessários em um maneira significativa. Isso também é chamado de design / programação de cima para baixo.

Reutilização de código

Ok, apesar do que eu disse anteriormente, às vezes, na verdade, acaba havendo uma razão para reutilizar uma subtarefa para outra coisa. Não estou de todo defendendo o isismo "astronauta da arquitetura", mas apenas que, ao escrever código pouco acoplado, você pode acabar se beneficiando mais tarde da reutilização.

Muitas vezes, essa reutilização significa refatoração, o que é perfeitamente esperado, mas refatorar os parâmetros de entrada para uma função autônoma pequena é MUITO mais fácil do que extraí-lo de mais de 200 funções de linha meses após a gravação, o que é realmente o meu ponto aqui.

Se você usar uma função aninhada, reutilizá-la geralmente é uma questão de refatorar para uma função separada, e, novamente, é por isso que eu argumentaria que aninhado não é o caminho a seguir.

gregmac
fonte
2
Esses são alguns pontos realmente válidos para o uso de funções em geral, mas não obtive sua resposta se você acha que as funções NESTED são uma boa idéia. Ou você obtém as funções no escopo upstream?
Slytael 11/04
Desculpe, bom ponto, fui pego em outros benefícios que esqueci de abordar nessa parte. :) Editado.
gregmac