Esse é um bom padrão: substituir uma função longa por uma série de lambdas?

14

Recentemente, encontrei a seguinte situação.

class A{
public:
    void calculate(T inputs);
}

Em primeiro lugar, Arepresenta um objeto no mundo físico, que é um forte argumento para não dividir a classe. Agora, calculate()acaba por ser uma função bastante longa e complicada. Eu percebo três estruturas possíveis para isso:

  • escreva como uma parede de texto - vantagens - todas as informações estão em um só lugar
  • escrever privatefunções utilitárias na classe e usá-las no calculatecorpo da pessoa - desvantagens - o resto da classe não conhece / se importa / entende sobre esses métodos
  • escreva calculateda seguinte maneira:

    void A::calculate(T inputs){    
        auto lambda1 = () [] {};    
        auto lambda2 = () [] {};    
        auto lambda3 = () [] {};
    
        lambda1(inputs.first_logical_chunk);
        lambda2(inputs.second_logical_chunk);
        lambda3(inputs.third_logical_chunk);
    }

Isso pode ser considerado uma boa ou má prática? Essa abordagem revela algum problema? Em suma, devo considerar isso uma boa abordagem quando me encontrar novamente com a mesma situação?


EDITAR:

class A{
    ...
public:
    // Reconfiguration of the algorithm.
    void set_colour(double colour);
    void set_density(double density);
    void set_predelay(unsigned long microseconds);
    void set_reverb_time(double reverb_time, double room_size);
    void set_drywet(double left, double right);
    void set_room_size(double value);;

private:
    // Sub-model objects.
    ...
}

Todos esses métodos:

  • obter um valor
  • calcular alguns outros valores, sem usar state
  • chame alguns dos "objetos de submodelo" para alterar seu estado.

Acontece que, exceto set_room_size(), esses métodos simplesmente passam o valor solicitado para subobjetos. set_room_size(), por outro lado, faz algumas telas de fórmulas obscuras e (2) faz meia tela de chamada de setters de subobjetos para aplicar os vários resultados obtidos. Portanto, eu separei a função em duas lambdas e as chamo no final da função. Se eu fosse capaz de dividi-lo em pedaços mais lógicos, eu teria isolado mais lambdas.

Independentemente disso, o objetivo da pergunta atual é determinar se esse modo de pensar deve persistir ou, na melhor das hipóteses, não agrega valor (legibilidade, capacidade de manutenção, capacidade de depuração etc.).

Vorac
fonte
2
O que você acha que usar lambdas fará com que as chamadas de função não o façam?
Blrfl
1
Firstly, A represents an object in the physical world, which is a strong argument for not splitting the class up.Certamente Arepresenta dados sobre um objeto que poderia existir no mundo físico. Você pode ter uma instância Asem o objeto real e um objeto real sem uma instância de A, portanto, tratá-los como se fossem um e o mesmo não faça sentido.
Doval
@ Blrfl, encapsulamento - ninguém mais calculate()saberá sobre essas sub-funções.
Vorac
Se todos esses cálculos forem relevantes apenas A, isso levará um pouco ao extremo.
Blrfl
1
"Em primeiro lugar, Arepresenta um objeto no mundo físico, que é um forte argumento para não dividir a classe." Infelizmente, me disseram isso quando comecei a programar. Levei anos para perceber que é um monte de hóquei em cavalos. É uma terrível razão para agrupar as coisas. Não consigo articular quais são as boas razões para agrupar as coisas (pelo menos para minha satisfação), mas essa é uma que você deve descartar agora. No fim das contas, todo o "código bom" é que ele funciona corretamente, é relativamente fácil de entender e é relativamente fácil de mudar (ou seja, mudanças não têm efeitos colaterais estranhos).
Jpmc26

Respostas:

13

Não, esse geralmente não é um bom padrão

O que você está fazendo é dividir uma função em funções menores usando lambda. No entanto, existe uma ferramenta muito melhor para dividir funções: funções.

Os lambdas funcionam, como você viu, mas significam muito muito muito muito mais do que simplesmente dividir uma função em bits locais. Lambdas do:

  • Encerramentos. Você pode usar variáveis ​​no escopo externo dentro do lambda. Isso é muito poderoso e muito complicado.
  • Reatribuição. Enquanto seu exemplo dificulta a tarefa, é preciso sempre prestar atenção à idéia de que o código pode trocar funções a qualquer momento.
  • Funções de primeira classe. Você pode passar uma função lambda para outra função, fazendo algo conhecido como "programação funcional".

No instante em que você introduz lambdas no mix, o próximo desenvolvedor a examinar o código deve carregar imediatamente todas essas regras mentalmente, preparando-se para ver como seu código funciona. Eles não sabem que você não vai usar toda essa funcionalidade. Isso é muito caro, comparado às alternativas.

É como usar uma enxada para fazer sua jardinagem. Você sabe que só o está usando para cavar pequenos orifícios para as flores deste ano, mas os vizinhos ficam nervosos.

Considere que tudo o que você está fazendo é agrupar seu código-fonte visualmente. O compilador realmente não se importa que você tenha curry coisas com lambdas. Na verdade, espero que o otimizador desfaça imediatamente tudo o que você fez quando compila. Você está puramente atendendo ao próximo leitor (obrigado por fazer isso, mesmo se discordarmos da metodologia! O código é lido com muito mais frequência do que está escrito!). Tudo o que você está fazendo é agrupar a funcionalidade.

  • As funções colocadas localmente no fluxo do código-fonte também funcionariam, sem chamar o lambda. Novamente, tudo o que importa é que o leitor possa lê-lo.
  • Comentários na parte superior da função dizendo "estamos dividindo esta função em três partes", seguidos por grandes linhas longas // ------------------entre cada parte.
  • Você também pode colocar cada parte do cálculo em seu próprio escopo. Isso tem o bônus de provar imediatamente, sem sombra de dúvida, que não há compartilhamento variável entre as partes.

EDIT: Ao ver sua edição com código de exemplo, inclino-me para que a notação de comentário seja a mais limpa, com colchetes para impor os limites sugeridos pelos comentários. No entanto, se alguma funcionalidade foi reutilizável em outras funções, eu recomendaria o uso de funções.

void A::set_room_size(double value)
{
    {
        // Part 1: {description of part 1}
        ...
    }
    // ------------------------
    {
        // Part 2: {description of part 2}
        ...
    }
    // ------------------------
    {
        // Part 3: {description of part 3}
        ...
    }
}
Cort Ammon - Restabelecer Monica
fonte
Portanto, não é um número objetivo, mas eu alegaria subjetivamente que a mera presença de funções lambda me faz prestar 10 vezes mais atenção a cada linha de código, porque elas têm muito potencial para serem perigosamente complicadas, rápidas e assim por diante. uma linha de código inocente (como count++)
Cort Ammon - Restabelece Monica
Ótimo ponto. Eu vejo essas como as poucas vantagens da abordagem com lambdas - (1) código localmente adjacente e com escopo local (isso seria perdido pelas funções no nível do arquivo) (2) o compilador garante que nenhuma variável local seja compartilhada entre os segmentos de código. Portanto, essas vantagens podem ser preservadas separando-se calculate()em {}blocos e declarando dados compartilhados no calculate()escopo. Eu pensei que, vendo que as lambdas não capturam, um leitor não seria sobrecarregado pelo poder das lambdas.
Vorac
"Eu pensei que, vendo as lambdas não capturarem, um leitor não ficaria sobrecarregado pelo poder das lambdas". Essa é realmente uma afirmação justa, mas controversa, que atinge o coração da lingüística. As palavras geralmente têm conotações que vão além de suas denotações. Se minha conotação lambdaé injusta ou se você está forçando rudemente as pessoas a seguirem a estrita denotação, não é uma pergunta fácil de responder. De fato, pode ser totalmente aceitável você usar lambdadessa maneira na sua empresa e totalmente inaceitável na minha empresa, e nenhum deles realmente precisa estar errado!
Cort Ammon - Restabelece Monica
Minha opinião sobre a conotação de lambda decorre do fato de eu ter crescido em C ++ 03, não em C + 11. Passei anos desenvolvendo uma apreciação pelos lugares específicos em que o C ++ foi prejudicado por uma falta lambda, como a for_eachfunção. Consequentemente, quando vejo um lambdaque não se encaixa em um desses casos de problemas fáceis de detectar, a primeira suposição a que chego é que ele provavelmente será usado para programação funcional, porque não era necessário de outra forma. Para muitos desenvolvedores, a programação funcional é uma mentalidade completamente diferente da programação procedural ou OO.
Cort Ammon - Restabelece Monica
Obrigado por explicar. Sou um programador iniciante e agora estou feliz por ter perguntado - antes que o hábito se desenvolvesse.
Vorac
20

Eu acho que você fez uma suposição ruim:

Em primeiro lugar, A representa um objeto no mundo físico, que é um forte argumento para não dividir a classe.

Eu discordo disso. Por exemplo, se eu tivesse uma classe que representa um carro, eu definitivamente gostaria de dividi-la, porque certamente quero uma classe menor para representar os pneus.

Você deve dividir essa função em funções privadas menores. Se realmente parecer separado da outra parte da classe, isso pode ser um sinal de que a classe deve ser separada. Claro que é difícil dizer sem um exemplo explícito.

Eu realmente não vejo a vantagem de usar funções lambda nesse caso, porque realmente não torna o código mais limpo. Eles foram criados para ajudar na programação de estilos funcionais, mas não é isso.

O que você escreveu se assemelha um pouco aos objetos de função aninhada no estilo Javascript. O que é novamente um sinal de que eles pertencem firmemente juntos. Tem certeza de que não deve fazer uma aula separada para eles?

Para resumir, não acho que esse seja um bom padrão.

ATUALIZAR

Se você não encontrar nenhuma maneira de encapsular essa funcionalidade em uma classe significativa, poderá criar funções auxiliares com escopo no arquivo, que não são membros da sua classe. Afinal, este é C ++, o design do OO não é obrigatório.

Gábor Angyal
fonte
Parece razoável, mas é difícil para mim imaginar a implementação. Classe de escopo de arquivo com métodos estáticos? Classe, definida por dentro A(talvez até functor com todos os outros métodos privados)? Classe declarada e definida por dentro calculate()(isso se parece muito com o meu exemplo lambda). Como esclarecimento, calculate()é um de uma família de métodos ( calculate_1(), calculate_2()etc.) dos quais todos são simples, apenas este é 2 telas de fórmulas.
Vorac
@Vorac: Por que é calculate()muito mais longo que todos os outros métodos?
Kevin
@Vorac É realmente difícil ajudar sem ver seu código. Você também pode postar?
Gábor Angyal
@ Kevin, as fórmulas para tudo são fornecidas nos requisitos. Código postado.
Vorac
4
Eu votaria isso 100 vezes, se pudesse. "Modelagem de objetos no mundo real" é o início da espiral da morte no Design de objetos. É uma bandeira vermelha gigante em qualquer código.
Fred, o cão mágico das maravilhas