Por que as pessoas se opõem tão fortemente às tags #region nos métodos?

27

Eu ouço muito sobre manter métodos curtos e muitos programadores dizem que o uso de tags #region dentro de um método é um sinal claro de que é muito longo e deve ser refatorado em vários métodos. No entanto, parece-me que há muitos casos em que a separação de código com tags #region dentro de um método é a solução superior à refatoração em vários métodos.

Suponha que tenhamos um método cuja computação possa ser separada em três fases bastante distintas. Além disso, cada um desses estágios é relevante apenas para o cálculo desse método e, portanto, extraí-los em novos métodos não nos permite reutilizar o código. Quais são os benefícios de extrair cada fase em seu próprio método? Pelo que sei, tudo o que ganhamos é alguma legibilidade e um escopo variável separado para cada fase (o que ajudará a impedir que as modificações de uma determinada fase quebrem acidentalmente outra fase).

No entanto, ambos podem ser alcançados sem extrair cada fase em seu próprio método. As tags de região nos permitem recolher o código em um formulário que seja legível (com o benefício adicional de que não precisamos mais deixar nosso lugar neste arquivo, se decidirmos expandir e examinar o código) e simplesmente agrupar cada fase {}cria seu próprio escopo para trabalhar.

O benefício de fazer dessa maneira é que não poluimos o escopo no nível de classe com três métodos que são realmente relevantes apenas para o funcionamento interno de um quarto método. Refatorar imediatamente um método longo em uma série de métodos curtos me parece ser a reutilização de código equivalente à otimização prematura; você está introduzindo complexidade extra para resolver um problema que, em muitos casos, nunca surge. Você sempre pode extrair uma das fases em seu próprio método posteriormente, se surgir a oportunidade de reutilização de código.

Pensamentos?

jjoelson
fonte
4
Atualmente, estou trabalhando com uma classe que é bastante grande. Segue o princípio da responsabilidade única; tudo na classe é funcionalidade requerida. Possui muitos métodos particulares, principalmente devido à refatoração. Eu uso o #region para dividir esses métodos em categorias funcionais, o que funcionou muito bem para nós. Existem muitas outras classes neste projeto que não exigiram esse tratamento. A ferramenta certa para o trabalho certo, eu digo.
Robert Harvey
4
@RobertHarvey, usá-los em uma classe é diferente de usá-los dentro de um método.
CaffGeek
18
@ Chad: Ah, não percebi isso. Usá-los em um método parece um pouco extremo. Se um método é tão longo que requer # regiões, refatoro-o em métodos separados.
Robert Harvey
8
Não é realmente uma resposta, mas não apenas odeio todas as #regiontags, desativo completamente a dobragem de código no Visual Studio. Eu não gosto de código que tenta se esconder de mim.
Daniel Pryden
2
"Além disso, cada um desses estágios é relevante apenas para o cálculo desse método, e, portanto, extraí-los em novos métodos não nos permite reutilizar o código". OTOH, acho que, se eu dividir uma função em 3, frequentemente descobri mais tarde que as partes individuais são úteis posteriormente, por exemplo. Se alguns dados de entrada muda apenas uma fase, você pode testar se em isolamento, etc.
Jack V.

Respostas:

65

Tudo o que você deve se preocupar é que seu código seja utilizável, não reutilizável. Um macaco pode transformar código utilizável em código reutilizável, se houver alguma transformação a ser feita.

O argumento "só preciso disso aqui" é ruim, para ser educado. A técnica que você está descrevendo é frequentemente chamada de técnica de manchetes e geralmente é mal vista.

  1. Você não pode testar regiões, mas pode testar métodos verdadeiros isoladamente.
  2. Regiões são comentários, não elementos sintáticos. Na pior das hipóteses, o aninhamento de suas regiões e seus blocos se contradizem. Você deve sempre se esforçar para representar a semântica da sua estrutura com os elementos sintáticos da linguagem que você está usando.
  3. Após refatorar os métodos, não é mais necessário dobrar para ler o texto. Pode-se observar o código-fonte em qualquer ferramenta que não tenha dobras, como um terminal, um cliente de email, uma visualização na Web para o seu VCS ou um visualizador de diferenças.
  4. Após a refatoração dos métodos, o método resultante é pelo menos tão bom quanto nas regiões, além de ser mais simples mover as partes individuais.
  5. O Princípio da Responsabilidade Única sugere que qualquer unidade deve ter uma tarefa e apenas uma tarefa. A tarefa do método "principal" é compor os métodos "auxiliares" para obter o resultado desejado. Os métodos "auxiliares" resolvem um problema simples e discreto isoladamente. A classe que contém todos esses métodos também deve cumprir apenas uma tarefa e conter apenas os métodos relacionados a essa tarefa. Portanto, os métodos auxiliares pertencem ao escopo da classe ou o código não deve estar na classe em primeiro lugar, mas pelo menos em algum método global ou, melhor ainda, deve ser injetado .

Também relevante: os pensamentos de Jeff Atwoods sobre dobragem de código

back2dos
fonte
9
1. A maioria dos programadores não testa todos os métodos particulares. 2. Um nome de método não pode transmitir tudo o que há para saber sobre esse método; que exceções podem ser lançadas? Null é uma entrada válida? Às vezes, não há problema em usar construções não sintáticas para tornar seu código mais compreensível. Em muitos idiomas, os arquivos são um exemplo de um elemento não sintático que é amplamente aceito como um meio para organizar o código. 3. Mergulhar no funcionamento interno de um método é mais bem tentado em um IDE por várias razões; situações em que regiões podem morder você no seu VCS ou cliente de email são bastante raras.
9119 jjelson
3
4. Mas você acabou de exportar alguma complexidade séria para o resto da sua classe. Isso vale a pena? Além disso, as regiões podem ser movidas tão facilmente quanto uma chamada de método. 5. Agora você está falando sobre poluir seu espaço para nome com mais classes que serão usadas apenas em um único local. Por que classes e métodos são as únicas unidades de encapsulamento que você aceita? Você sempre pode criar essa nova classe mais tarde, quando (ou se) for necessário separá-la.
9132 jjoelson
7
@jjoelson: 1. E daí? 2. Exatamente o que quero dizer: não há problema em usar construções não sintáticas se (e somente se) apenas a sintaxe não for suficiente. 3. "Raro?" Suponho que você tenha números para mostrar isso. Posso dizer com certeza que a ferramenta diff (ou culpa ou o que quer que seja) fornecida com o TortoiseSVN não tem dobragem. 4. Como isso está relacionado ao argumento que fiz? 5. É por isso que a maioria das linguagens modernas possui namespaces aninhados. Você está recorrendo à arte ASCII em vez de usar uma ampla paleta de elementos de linguagem disponíveis para estruturar seu código.
back2dos
4
@jjoelson: 1. Essa é a sua opinião e está fora do escopo desta questão. 2. Por extensão do meu argumento, funções aninhadas são melhores que regiões, sim. 3. Porque às vezes preciso navegar por várias revisões para rastrear um problema. De qualquer forma, o código fonte é escrito para humanos, não para IDEs. 4. Por um lado, o argumento não tem relação com o meu argumento; em segundo lugar, isso é mais uma vez apenas a sua opinião; em terceiro lugar, está fora do escopo desta questão. 5. O C # possui outros meios de estruturar o código além da função de aninhamento. Você deve usá-los ou um idioma que permita funções de aninhamento.
Novidade
13
Vocês devem levar isso para conversar se ainda quiserem debater ... Não tenho mais nada a dizer ao OP, ele é um desenvolvedor júnior típico e opinativo, estabelecido em suas crenças. Nada vai mudar de idéia, mas mais experiência na IMO.
Maple_shaft
15

Não são as regiões nos métodos que são o problema, mas o caso em que há uma necessidade (ou mesmo um uso) de colocar regiões em métodos.

Tendo depurado muitos métodos de linha de 200 a 300, posso lhe dizer que não gostaria disso em ninguém. Se você está escrevendo e cuidando do seu próprio código, tudo bem - faça o que quiser. Mas uma vez que alguém o veja, deve ficar claro imediatamente o que um método está fazendo.

"Primeiro, ele pega as coisas e depois as faz girar; então, se elas precisam vibrar, isso acontece; caso contrário, verifica se elas são azuis e inverte seu valor de Copernicus. Então, se a segunda coisa for maior que a primeira, nós precisa pegar a caixa de suco e inseri-la ... "

Não, isso não é bom o suficiente. Divida em regiões o quanto você quiser, mas ainda estou balançando a cabeça só de pensar nisso.

Se você adotar métodos, obtém muitos benefícios:

  • Compreensão mais clara do que está acontecendo onde, mesmo que seja apenas pelo nome do método. E você é errado que um método não pode ser totalmente documentado - adicione o bloco de documentação (hit ///) e digite a descrição, parâmetros, tipo de retorno e também exception, example, remarksnós aos detalhes desses cenários. É para onde vai, é para isso!
  • Não há efeitos colaterais ou problemas de escopo. Você pode dizer que declara todas as suas variáveis ​​em um sub-escopo {}, mas preciso verificar todos os métodos para garantir que você não "trapaceou"? É isso dodgyObjectque estou usando aqui apenas porque usado nesta região ou veio de outro lugar? Se eu estivesse no meu próprio método, seria capaz de ver facilmente se foi passado para mim ou se eu mesmo o criei (ou melhor, se você o criou).
  • Meu log de eventos me diz em que método ocorreu uma exceção. Sim, talvez eu consiga encontrar o código incorreto com um número de linha, mas saber o nome do método (especialmente se eu os nomeei corretamente) me fornece uma indicação muito boa de o que aconteceu e o que deu errado. "Oh, o TurnThingsUpsideDownmétodo falhou - provavelmente está falhando ao transformar uma coisa ruim" é muito melhor do que "Oh, o DoEverythingmétodo falhou - poderia ter sido 50 coisas diferentes e eu vou ter que procurar por uma hora para encontrá-lo" .

Tudo isso antes de qualquer reutilização ou improvabilidade. Métodos adequadamente separados obviamente facilitam a reutilização e também permitem que você substitua facilmente um método que não está executando (muito lento, muito buggy, dependência alterada etc.). Você já tentou refatorar algum desses métodos enormes? Não há como saber que o que você está mudando não afetará mais nada.

Encapsule corretamente sua lógica em métodos de tamanho razoável. Eu sei que é fácil para eles ficarem fora de controle e argumentar que não vale a pena redesenhar uma vez nesse momento é outra questão. Mas você deve aceitar o fato de que não há dano e, pelo menos, benefício potencial em ter métodos adequadamente encapsulados, escritos de forma limpa e simplesmente projetados.

Kirk Broadhurst
fonte
A análise de comentários XML do Visual Studio, junto com as marcas #region, se enquadra na categoria de recursos do Visual Studio. Meu ponto principal é que não é errado confiar nesses recursos se todos no seu projeto estiverem usando o Visual Studio. Quanto aos efeitos colaterais e problemas de escopo, você ainda pode estragar tudo com campos globais. Em ambos os casos, você deve confiar nos programadores para não fazer coisas tolas com efeitos colaterais.
jjoelson
5
@jjoelson Você ainda pode ler os comentários XML sem o visual studio, mas as tags da região são quase completamente inúteis fora do VS. A extensão lógica do seu argumento é um método gigante que executa todo o aplicativo, desde que você o tenha separado em regiões!
precisa
2
@joelson, por favor, não nos deixe saber como os campos globais são ruins. Dizer algo não é útil porque você tem uma "solução alternativa" para estragar tudo de qualquer maneira, é bobagem. Apenas mantenha seus métodos curtos e as variáveis ​​com escopo definido.
Olivers
@OliverS, Kirk foi quem criou o estado compartilhado como um problema no meu esquema de regiões em um método. Basicamente, eu estava dizendo exatamente o que você está dizendo: o fato de um programador poder abusar do estado compartilhando muitas variáveis ​​entre regiões não é uma acusação de todo o esquema, assim como a capacidade de estragar o estado compartilhado entre os métodos via globais não é '' t uma acusação do esquema de métodos múltiplos.
jjoelson
7

Se o seu método é complexo o suficiente para poder ser separado em três fases distintas, então esses não devem ser apenas três métodos separados ... mas seu método deve ser de fato uma classe separada, dedicada a esse cálculo.

"Mas então minha turma terá apenas um método público!"

Você está certo, e não há nada de errado nisso. A idéia do princípio da responsabilidade única é que sua classe faça uma coisa .

Sua turma pode chamar cada um dos três métodos por vez e retornar o resultado. Uma Coisa.

Como um bônus, agora seu método é testável porque não é mais um método privado.

Mas não importa uma classe; na realidade, você deve ter quatro : um para cada parte do seu método, mais um que tome cada uma das três como uma dependência e as chame na ordem correta, retornando o resultado.

Isso torna suas aulas ainda mais testáveis. Também facilita a troca de uma dessas três peças. Basta escrever uma nova classe herdada da antiga e substituir o comportamento alterado. Ou crie uma interface para o comportamento de cada uma das suas três partes; e, para substituir uma, basta escrever uma nova classe implementando essa interface e substituí-la pela antiga na configuração do contêiner de injeção de dependência.

O que? Você não está usando injeção de dependência? Bem, você provavelmente ainda não viu a necessidade, porque não está seguindo o princípio da responsabilidade única. Depois de iniciar, você descobrirá que a maneira mais fácil de atribuir a cada classe suas dependências é usar um contêiner de DI.

EDIT :

OK, vamos deixar de lado a questão da refatoração. O objetivo #regioné ocultar o código , o que significa principalmente código que não precisa ser editado em nenhuma circunstância normal. Código gerado é o melhor exemplo. Ele deve estar oculto nas regiões, porque normalmente você não precisa editá-lo. Se você precisar, o ato de expandir uma região fornecerá um aviso no estilo "Here be dragons" para garantir que você saiba o que está fazendo.

Portanto, eu diria #regionque não deve ser usado no meio de um método. Se eu abrir um método, quero ver o código. Usar #region me dá outro nível de ocultação que não preciso, e isso se torna um aborrecimento: desdobro o método ... e ainda não consigo ver nenhum código.

Se você estiver preocupado com as pessoas vendo a estrutura do método (e se recusar a refatorá-lo), adicione comentários em banner como este:

//
// ---------- COMPUTE SOMETHING OR OTHER ----------
//

Isso ajudará alguém que está navegando no código a ver as partes individuais do seu método sem precisar lidar com as #regiontags de expansão e recolhimento .

Kyralessa
fonte
2
Tudo isso para um método que é chamado em um só lugar? Mesmo a maioria dos Lispers hardcore não abstraem uma função de ordem superior até identificarem pelo menos dois lugares onde ela pode ser usada para definir mais facilmente uma função.
jjoelson
1
@jjoelson É tão difícil fazer uma nova aula? O que é isso, uma linha, uma chave de abertura e uma chave de fechamento. Oh, e você tem que pressionarctrl+n
Kirk Broadhurst
2
Não é difícil fazer uma nova aula, mas há alguma sobrecarga em ter uma aula extra. É mais uma camada de indireção que torna mais difícil para um mantenedor de código encontrar o que está procurando. Não é que de um grande negócio, mas também não comprar nada no caso em que este pedaço de código é improvável de ser chamado a partir de qualquer outro lugar. E, como você mencionou, é bastante fácil de fazer essa nova classe e colocar o código lá, se ele sair você fazer necessidade de chamar este pedaço de código em vários lugares.
precisa saber é o seguinte
2
@joelson, esta é uma reação bastante comum. Era minha, quando eu aprendi sobre coisas como SRP e DI, e foi a reação dos meus colegas de trabalho quando começamos a quebrar dependências. Se você estiver analisando um caso individual , não parece valer a pena. O benefício está no agregado. Depois de começar a executar o SRP com todas as suas classes, você descobre que é muito mais fácil alterar uma parte do seu código sem que as alterações afetem metade da sua aplicação.
Kyralessa
@ Kyralessa, esses são métodos usados ​​apenas em um único local. Mudar esse método não afetará metade da sua aplicação. Se esse código for implementado como uma região no método que o utiliza, você terá um encapsulamento perfeito; somente o método que contém está usando o código e, portanto, você pode alterar tudo o que quiser sem se preocupar com o efeito, exceto nesse método.
jjoelson
4

Eu acho que o exemplo que você dá no seu argumento é menos sobre o YAGNI (você não precisará dele) e mais sobre como escrever um código de qualidade adequado que seja facilmente sustentável.

Nos primeiros cursos de programação, aprendemos que, se um método tem 700 LOC de comprimento, provavelmente é muito grande e certamente seria uma dor gigantesca tentar depurar.

Em segundo lugar, se eu escrever os métodos A, B e C que são usados ​​apenas no método D, posso testar com mais facilidade a unidade AB e C independentemente de D, permitindo testes de unidade mais robustos nos quatro métodos.

maple_shaft
fonte
O teste de unidade é certamente um ponto justo, mas não tenho certeza se justifica a poluição do escopo da classe em todos os casos. Realisticamente falando, alguém realmente faz testes de unidade a cada pequeno método privado que escreve? Eu diria que as pessoas tendem a testar por unidade os métodos privados que são muito usados, ou seja, métodos privados que seriam candidatos à reutilização de código de qualquer maneira.
precisa saber é o seguinte
@jjoelson Se o método tem lógica significativa e não está agrupando outras funcionalidades, eu sempre escrevo um teste de unidade. Os métodos de teste de unidade não têm nada a ver com a reutilização de código. Você deve aplicar métodos de teste de unidade para verificar se, para determinadas entradas, você obtém resultados esperados de maneira consistente e repetível. Se você está percebendo que o escopo da classe está ficando poluído como você o chama, talvez sua classe esteja violando o Princípio de Responsabilidade Única.
maple_shaft
2
A maioria dos programadores que eu conheci discorda da idéia de testar a unidade de todos os métodos particulares. Simplesmente não vale o tempo necessário para implementar e manter testes em todos os métodos particulares.
9132 jjoelson
3

Suponha que tenhamos um método cuja computação possa ser separada em três fases bastante distintas. Além disso, cada um desses estágios é relevante apenas para o cálculo desse método e, portanto, extraí-los em novos métodos não nos permite reutilizar o código. Quais são os benefícios de extrair cada fase em seu próprio método? Pelo que sei, tudo o que ganhamos é alguma legibilidade e um escopo variável separado para cada fase (o que ajudará a impedir que as modificações de uma determinada fase quebrem acidentalmente outra fase).

Esses são dois benefícios. Mas o importante, para mim, é a debuggability . Se você precisar rastrear esse código, é muito mais simples poder passar por duas das três seções e rastrear apenas a que mais lhe interessa. A refatoração em métodos menores permite isso; tags de região não.

Mason Wheeler
fonte
1
ctrl-f10- corra para o cursor
Steven Jeuris
2

Minha regra pessoal é que, se um método tiver mais de uma tela, digamos 40 linhas, será muito longo. Se uma classe tiver mais de 500 linhas, é muito grande. Eu quebro essas regras algumas vezes, às vezes até por um grande múltiplo, mas geralmente me arrependo dentro de um ano.

Mesmo um método de 20 a 30 linhas está ficando um pouco longo na maioria dos casos. Então, eu diria que usar regiões em um método geralmente é uma má ideia, simplesmente porque quase nunca faria sentido recolher uma região de 20 a 30 linhas em várias sub-regiões.

A divisão de funções que são longas por esta definição tem pelo menos dois benefícios:

  1. Você pode colocar um nome no que essas subfunções estão fazendo, o que esclarece seu pensamento atual e simplifica a tarefa dos mantenedores posteriores.

  2. A decomposição em funções menores obriga a passar apenas os parâmetros necessários para essas funções menores; portanto, o escopo dos dados que eles exigem e modificam é muito claro. Isso pressupõe que você não está acessando e modificando o estado do objeto em centenas de funções folha diferentes, o que eu também desencorajaria.

Na minha experiência, essas regras produzem código que é mais fácil de escrever sem cometer erros comuns e pode ser entendido e modificado posteriormente sem interromper comportamentos sutis. Não importa se a pessoa que precisa entender / modificar o código é outra pessoa ou depois de eu ter dormido e esquecido tudo.

Portanto, consideraria regiões em métodos como um "cheiro de código", indicando que práticas insustentáveis ​​estão sendo aplicadas. Como sempre, pode haver exceções, mas ocorrem com tão pouca frequência que quase não valem a pena discutir.

PeterAllenWebb
fonte
2

Aqui vamos nós de novo ... Existem muitos outros tópicos aqui sobre programadores que acabaram na mesma discussão.

Você aponta alguns argumentos muito interessantes relacionados a funções curtas. Não é uma declaração popular, mas estou com você sobre isso . Depois de discuti-lo muitas vezes antes, tenho a impressão de que a discussão geralmente se resume a se você se concentra principalmente no encapsulamento adequado ou leva ao máximo o desenvolvimento orientado a testes. Esses são os dois principais candidatos ao que parece estar acontecendo ainda mais uma guerra santa, e temo que estejamos do lado fraco.

Contudo ...

A solução na minha opinião definitivamente não está envolvendo as 'fases' nas regiões. A dobragem de código é usada para varrer o código para debaixo do tapete. Deixe o código como está, pois poderá lembrá-lo de que você pode refatorá-lo posteriormente! Para relacioná-lo a um argumento em sua pergunta: como você poderá ver uma oportunidade de reutilização de código se não encontrar nenhum código? Segmentos longos de código devem ser exatamente isso, um espinho no olho que faz com que você queira refatorá-lo.

Como substituto apropriado para regiões, sugiro o uso de parágrafos de código, como Alex Papadimoulis os nomeia em um comentário . Você já deve estar usando uma abordagem semelhante. São simplesmente blocos de código com um cabeçalho de comentário, explicando o que todo o bloco faz. Você tem a legibilidade das funções, mas é capaz de usar frases em inglês normalmente espaçadas e não perde nenhum encapsulamento.

Steven Jeuris
fonte
Bem, eu essencialmente uso regiões para delimitar parágrafos de código (as regiões podem ter comentários visíveis mesmo quando o código é dobrado). Eu gosto de usar regiões para isso, porque isso dá ao mantenedor a opção de ver o código, se ele quiser examinar a implementação, ou dobrá-lo, se ela quiser apenas ver o "quadro geral".
jjoelson
1

Embora eu concorde que geralmente é melhor refatorar o código do que usar #region, nem sempre é possível.

Para apoiar essa afirmação, deixe-me dar um exemplo.

class Foo : IComparable
{
  private String name;
  private int foo;
  private Bar bar;

  public int CompareTo(Foo other)
  {
#region Ascending on name
     if (this.name < other.name) return -1;
     if (this.name > other.name) return 1;
#endregion
#region Usual order on bar
     int compBar = bar.compareTo(other.bar);
     if (compBar != 0) return compBar;
#endregion
#region Descending on foo
     if (this.foo > other.foo) return -1;
     if (this.foo < other.foo) return 1;
#endregion
     return 0; // equal
  }

É fácil reajustar as regiões para alterar a ordem ou estender quando campos adicionais são adicionados à classe. Os comentários são necessários de qualquer maneira para ver rapidamente como a ordem deve funcionar. E acima de tudo: não vejo como a refatoração ajudará a legibilidade neste caso.

No entanto, essas exceções são raras. Geralmente é possível refatorar, como muitas das outras respostas dizem.

Sjoerd
fonte
0

Eu não acho que exista uma resposta única para o motivo pelo qual certas pessoas ou equipes decidem não usar, #regionmas se eu tivesse que listar algumas, essas seriam as principais razões que eu já vi.

  • Os nomes e a ordem das regiões tornam mais explícita a ordem e organização do código que impõe um estilo específico que pode se tornar uma fonte de disputa e ciclismo.
  • A maioria dos conceitos não se encaixa perfeitamente em um pequeno número de regiões. Normalmente, você começa com as regiões modificando o acesso público , privado e, talvez, por tipo de membro (por exemplo, método, propriedade, campo), mas o que dizer dos construtores que abrangem esses modificadores, variedades estáticas de todos esses membros protegidos, membros internos e internos protegidos membros, membros implícitos da interface, eventos etc. No final, você teria as classes mais bem projetadas em que possui um método ou método em cada região devido à categorização granular.
  • Se você é capaz de manter pragmático e agrupar apenas as coisas em três ou quatro tipos de regiões consistentes, então isso pode funcionar, mas que valor ele realmente agrega? Se você está projetando classes bem, não precisa peneirar as páginas de código.
  • Conforme sugerido acima, as regiões podem ocultar códigos feios, o que pode torná-lo menos problemático do que é. Mantê-lo dolorido pode ajudar a resolvê-lo.
jpierson
fonte