Este é 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, A representa um objeto no mundo físico, o que é um strong argumento para não dividir a classe. Agora, calculate() acaba sendo 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
  • escreva private funções de utilidade na classe e use-as no corpo de calculate - desvantagens - o resto da classe não sabe / care / understand sobre esses métodos
  • escreva calculate da seguinte forma:

    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 prática boa ou ruim? Essa abordagem revela algum problema? Em suma, devo considerar isso como uma boa abordagem quando me deparo 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:

  • obtenha um valor
  • calcula alguns outros valores, sem usar o estado
  • chame alguns dos "objetos de submodelo" para alterar seu estado.

Acontece que, com exceção de set_room_size() , esses métodos simplesmente passam o valor solicitado para sub-objetos. set_room_size() , por outro lado, faz algumas telas de fórmulas obscuras e então (2) faz meia tela de chamar sub-objetos setters para aplicar os vários resultados obtidos. Portanto, eu separei a função em dois lambdas e os chamo no final da função. Se eu tivesse conseguido dividi-lo em partes mais lógicas, eu teria isolado mais lambdas.

Independentemente disso, o objetivo da questão atual é determinar se essa maneira de pensar deve persistir, ou se é melhor não agregar valor (legibilidade, facilidade de manutenção, capacidade de depuração etc.).

    
por Vorac 27.01.2015 / 12:07
fonte

2 respostas

13

Não, isso 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 muito ferramenta melhor para quebrar funções: funções.

Lambdas funcionam, como você viu, mas eles significam 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. Embora o seu exemplo dificulte a tarefa, sempre é preciso prestar atenção à ideia 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ê coloca lambdas na mistura, o próximo desenvolvedor a ver o código deve carregar mentalmente todas essas regras, preparando-se para ver como seu código funciona. Eles não sabem que você não usará toda essa funcionalidade. Isso é muito caro, comparado às alternativas.

É como usar uma enxada para fazer sua jardinagem. Você sabe que você só está usando para cavar pequenos buracos para as flores deste ano, mas os vizinhos ficarão nervosos.

Considere que tudo o que você está fazendo é agrupar seu código-fonte visualmente. O compilador não se importa com o fato de você curry coisas com lambdas. Na verdade, eu esperaria que o otimizador desfizesse imediatamente tudo que você acabou de fazer quando compilou. Você está puramente atendendo o 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 dentro do fluxo do código-fonte funcionariam da mesma maneira, sem invocar o lambda. Mais uma vez, tudo o que importa é que o leitor possa lê-lo.
  • Comentários na parte superior da função dizendo "estamos dividindo essa função em três partes", seguidas por grandes linhas longas de // ------------------ entre cada parte.
  • Você também pode colocar cada parte do cálculo em seu próprio escopo. Isto tem o bônus de provar imediatamente além de qualquer dúvida que não há compartilhamento de variáveis entre as partes.

EDIT: Desde ver sua edição com código de exemplo, estou inclinado para a notação de comentário ser a mais limpa, com colchetes para impor os limites sugeridos pelos comentários. No entanto, se alguma funcionalidade fosse 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}
        ...
    }
}
    
por 27.01.2015 / 16:29
fonte
20

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

Firstly, A represents an object in the physical world, which is a strong argument for not splitting the class up.

Eu não concordo com isso. Por exemplo, se eu tivesse uma classe que representasse um carro, eu definitivamente gostaria de dividi-lo, porque eu certamente quero uma classe menor para representar os pneus.

Você deve dividir essa função em funções privadas menores. Se realmente parece 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 neste caso, porque isso não torna o código mais limpo. Eles foram criados para ajudar na programação de estilo funcional, mas não é isso.

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

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

UPDATE

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

    
por 27.01.2015 / 12:42
fonte

Tags