Refatoração de método longo: deixando como está vs separando-se em métodos vs usando funções locais

9

Suponha que eu tenha um método longo como este:

public void SomeLongMethod()
{
    // Some task #1
    ...

    // Some task #2
    ...
}

Este método não possui partes repetitivas que devem ser movidas para método ou função local separada.

Há muitas pessoas (incluindo eu) que pensam que métodos longos são odores de código. Também não gosto da ideia de usar #regionaqui e há uma resposta muito popular explicando por que isso é ruim .


Mas se eu separar esse código em métodos

public void SomeLongMethod()
{
    Task1();

    Task2();
}

private void Task1()
{
    // Some task #1
    ...
}

private void Task2()
{
    // Some task #1
    ...
}

Vejo os seguintes problemas:

  • Poluindo âmbito definição de classe com as definições que são usados internamente por um único método e o que significa que deve documentar em algum lugar que Task1e Task2são destinados apenas para internos do SomeLongMethod(ou cada pessoa que lê meu código terá que deduzir essa idéia).

  • O IDE poluente preenche automaticamente (por exemplo, Intellisense) de métodos que seriam usados ​​apenas uma vez dentro do SomeLongMethodmétodo único .


Então, se eu separar esse código de método em funções locais

public void SomeLongMethod()
{
    Task1();

    Task2();

    void Task1()
    {
        // Some task #1
        ...
    }

    void Task2()
    {
        // Some task #1
        ...
    }
}

então isso não tem as desvantagens de métodos separados, mas isso não parece melhor (pelo menos para mim) do que o método original.


Qual versão do SomeLongMethodé mais sustentável e legível para você e por quê?

Vadim Ovchinnikov
fonte
@gnat minha pergunta é específica para c # , não java . Minha principal preocupação é sobre o método clássico versus a função local, não apenas separar os métodos ou não.
Vadim Ovchinnikov
@gnat Também o AFAIK Java (ao contrário do C #) não suporta funções aninhadas.
Vadim Ovchinnikov
11
a palavra-chave privada certamente protege seu 'escopo de definição de classe'?
Ewan
11
espera ...... quão grande é a sua turma?
Ewan

Respostas:

13

Tarefas independentes devem ser movidas para métodos independentes. Não há desvantagem grande o suficiente para substituir esse objetivo.

Você diz que eles poluem o espaço de nomes da classe, mas não é esse o compromisso que você deve observar ao considerar seu código. Um futuro leitor da classe (por exemplo, você) tem muito mais probabilidade de perguntar "O que esse método específico faz e como devo alterá-lo para que ele faça o que dizem os requisitos alterados?" do que "Qual é o propósito e a sensação de ser de todos os métodos da classe? " Portanto, tornar cada um dos métodos mais fácil de entender (ao ter menos elementos) é muito mais importante do que tornar toda a classe mais fácil de entender (fazendo com que tenha menos métodos).

Obviamente, se as tarefas não forem realmente independentes, mas exigirem que algumas variáveis ​​de estado sejam movidas, um objeto de método, um objeto auxiliar ou uma construção semelhante pode ser a escolha certa. E se as tarefas são totalmente independentes e não estão totalmente ligadas ao domínio do aplicativo (por exemplo, apenas a formatação de cadeias de caracteres, é possível que elas entrem em uma classe totalmente diferente (de utilidade). Mas isso não muda o fato de que " manter os métodos por classe "é, na melhor das hipóteses, um objetivo subsidiário.

Kilian Foth
fonte
Então você é a favor de métodos, não de funções locais, sim?
Vadim Ovchinnikov
2
@VadimOvchinnikov Com um IDE moderno para dobrar códigos, há pouca diferença. A maior vantagem de uma função local é que ela pode acessar variáveis ​​que de outra forma você teria que passar como parâmetros, mas se houver muitas delas, as tarefas não eram realmente independentes para começar.
Kilian Foth
6

Eu não gosto de nenhuma das abordagens. Você não forneceu um contexto mais amplo, mas na maioria das vezes fica assim:

Você tem uma classe que faz algum trabalho combinando o poder de vários serviços em um único resultado.

class SomeClass
{
    private readonly Service1 _service1;
    private readonly Service2 _service2;

    public void SomeLongMethod()
    {
        _service1.Task1();
        _service2.Task2();       
    }
}

Cada um dos seus serviços implementa apenas uma parte da lógica. Algo especial, que somente este serviço pode fazer. Se ele tiver outros métodos particulares, além dos que suportam a API principal, você poderá classificá-los facilmente e não deve haver muitos deles.

class Service1
{
    public void Task1()
    {
        // Some task #1
        ...
    }

    private void SomeHelperMethod() {}
}

class Service2
{
    void Task2()
    {
        // Some task #1
        ...
    }
}

A linha inferior é: se você começar a criar regiões no seu código para usar métodos gorup, é hora de começar a pensar em encapsular sua lógica com um novo serviço.

t3chb0t
fonte
2

O problema com a criação de funções dentro do método é que não reduz o tamanho do método.

Se você quiser que o nível extra de proteção seja 'privado para o método', poderá criar uma nova classe

public class BigClass
{
    public void SomeLongMethod();

    private class SomeLongMethodRunner
    {
        private void Task1();
        private void Task2();
    }
}

Mas as aulas são muito feias: /

Ewan
fonte
2

Minimizar o escopo de construções de linguagem, como variáveis ​​e métodos, é uma boa prática. Por exemplo, evite variáveis ​​globais. Isso facilita a compreensão do código e ajuda a evitar o uso indevido, pois a construção pode ser acessada em menos locais.

Isso fala a favor do uso das funções locais.

fsl
fonte
11
hmm certamente reutilizando código == compartilhando um método entre muitos chamadores. ou seja, maximizar o seu alcance
Ewan
1

Eu concordo que métodos longos geralmente são cheiros de código, mas não acho que esse seja o cenário, é por isso que o método é longo e indica um problema. Minha regra geral é que, se o código estiver executando várias tarefas distintas, cada uma dessas tarefas deverá ser seu próprio método. Para mim, importa se essas seções do código são repetitivas ou não; a menos que você tenha certeza absoluta de que ninguém iria querer chamá-los individualmente separadamente no futuro (e isso é algo muito difícil de se ter certeza!), coloque-os em outro método (e torne-o privado - o que deve ser um indicador muito forte) você não espera que seja usado fora da classe).

Devo confessar que ainda não usei funções locais, por isso meu entendimento está longe de ser completo aqui, mas o link que você forneceu sugere que elas geralmente seriam usadas de maneira semelhante a uma expressão lambda (embora haja vários casos de uso onde eles podem ser mais apropriados que lambdas ou onde você não pode usar um lambda, consulte Funções locais em comparação com expressões lambda para obter detalhes). Aqui, então, acho que poderia se resumir à granularidade das "outras" tarefas; se forem razoavelmente triviais, talvez uma função local esteja OK? Por outro lado, vi exemplos de expressões lambda gigantescas (provavelmente onde um desenvolvedor descobriu recentemente o LINQ) que tornaram o código muito menos compreensível e sustentável do que se tivesse sido chamado simplesmente de outro método.

A página Funções locais declara que:

As funções locais tornam clara a intenção do seu código. Qualquer pessoa que esteja lendo seu código poderá ver que o método não pode ser chamado, exceto pelo método que o contém. Para projetos de equipe, eles também tornam impossível para outro desenvolvedor chamar o método por engano diretamente de outros lugares da classe ou estrutura. '

Não tenho certeza se estou realmente com a EM neste como motivo de uso. O escopo do seu método (junto com o nome e os comentários) deve deixar claro qual era a sua intenção naquele momento . Com uma Função Local, eu pude ver isso, outros desenvolvedores podem vê-la como mais sacrossanta, mas potencialmente a ponto de, se ocorrer uma mudança no futuro que exija que a funcionalidade seja reutilizada, eles escrevem seu próprio procedimento e deixem a Função Local no lugar. ; criando assim um problema de duplicação de código. A última frase da citação acima pode ser relevante se você não confiar que os membros da equipe entendam um método privado antes de chamá-lo e o chame de forma inadequada (para que sua decisão possa ser afetada por isso, embora a educação seja uma opção melhor aqui) .

Em resumo, em geral, eu preferiria a separação em métodos, mas a MS escreveu as Funções Locais por um motivo, então eu certamente não diria que não as uso,

d219
fonte