Um cheiro de código é um sintoma que indica que há um problema no design que potencialmente aumentará o número de bugs: esse não é o caso de regiões, mas as regiões podem contribuir para a criação de odores de código, como métodos longos.
Desde a:
Um antipadrão (ou antipadrão) é um padrão usado em operações sociais ou de negócios ou engenharia de software que pode ser comumente usado, mas é ineficaz e / ou contraproducente na prática
regiões são anti-padrões. Eles exigem mais trabalho que não aumente a qualidade ou a legibilidade do código, o que não reduz o número de bugs e que só pode tornar o código mais complicado de refatorar.
Não use regiões dentro de métodos; refatorar
Os métodos devem ser curtos . Se houver apenas dez linhas em um método, você provavelmente não usaria regiões para ocultar cinco delas ao trabalhar em outras cinco.
Além disso, cada método deve fazer uma única coisa . As regiões, por outro lado, pretendem separar coisas diferentes . Se o seu método faz A e B, é lógico criar duas regiões, mas essa é uma abordagem errada; em vez disso, você deve refatorar o método em dois métodos separados.
O uso de regiões nesse caso também pode dificultar a refatoração. Imagine que você tem:
private void DoSomething()
{
var data = LoadData();
#region Work with database
var verification = VerifySomething();
if (!verification)
{
throw new DataCorruptedException();
}
Do(data);
DoSomethingElse(data);
#endregion
#region Audit
var auditEngine = InitializeAuditEngine();
auditEngine.Submit(data);
#endregion
}
Colapsar a primeira região a se concentrar na segunda não é apenas arriscado: podemos facilmente esquecer a exceção que interrompe o fluxo (poderia haver uma cláusula de guarda com uma return
alternativa, que é ainda mais difícil de detectar), mas também teria um problema se o código deve ser refatorado desta maneira:
private void DoSomething()
{
var data = LoadData();
#region Work with database
var verification = VerifySomething();
var info = DoSomethingElse(data);
if (verification)
{
Do(data);
}
#endregion
#region Audit
var auditEngine = InitializeAuditEngine(info);
auditEngine.Submit(
verification ? new AcceptedDataAudit(data) : new CorruptedDataAudit(data));
#endregion
}
Agora, as regiões não fazem sentido e você não pode ler e entender o código na segunda região sem examinar o código na primeira.
Outro caso que vejo às vezes é este:
public void DoSomething(string a, int b)
{
#region Validation of arguments
if (a == null)
{
throw new ArgumentNullException("a");
}
if (b <= 0)
{
throw new ArgumentOutOfScopeException("b", ...);
}
#endregion
#region Do real work
...
#endregion
}
É tentador usar regiões quando a validação de argumentos começa a abranger dezenas de LOC, mas existe uma maneira melhor de resolver esse problema: aquele usado pelo código-fonte do .NET Framework:
public void DoSomething(string a, int b)
{
if (a == null)
{
throw new ArgumentNullException("a");
}
if (b <= 0)
{
throw new ArgumentOutOfScopeException("b", ...);
}
InternalDoSomething(a, b);
}
private void InternalDoSomething(string a, int b)
{
...
}
Não use regiões fora dos métodos para agrupar
Algumas pessoas as usam para agrupar campos, propriedades, etc. Essa abordagem está errada: se o seu código for compatível com StyleCop, os campos, propriedades, métodos privados, construtores, etc. já estarão agrupados e fáceis de encontrar. Caso contrário, é hora de começar a pensar em aplicar regras que garantam uniformidade em toda a sua base de código.
Outras pessoas usam regiões para ocultar muitas entidades semelhantes . Por exemplo, quando você tem uma classe com cem campos (que cria pelo menos 500 linhas de código se você contar os comentários e os espaços em branco), poderá ficar tentado a colocar esses campos em uma região, recolhê-los e esquecê-los. Novamente, você está fazendo errado: com tantos campos em uma classe, você deve pensar melhor sobre o uso de herança ou dividir o objeto em vários objetos.
Finalmente, algumas pessoas são tentadas a usar regiões para agrupar coisas relacionadas : um evento com seu delegado ou um método relacionado ao IO com outros métodos relacionados ao IO etc. No primeiro caso, torna-se uma bagunça difícil de manter , Leia e compreenda. No segundo caso, o melhor design provavelmente seria criar várias classes.
Existe um bom uso para regiões?
Não . Havia um uso herdado: código gerado. Ainda assim, as ferramentas de geração de código precisam usar classes parciais. Se o C # tem suporte para regiões, é principalmente porque esse legado é usado e porque agora muitas pessoas usavam regiões em seu código, seria impossível removê-las sem quebrar as bases de código existentes.
Pense nisso como sobre goto
. O fato de a linguagem ou o IDE suportar um recurso não significa que ele deva ser usado diariamente. A regra StyleCop SA1124 é clara: você não deve usar regiões. Nunca.
Exemplos
Atualmente, estou revisando o código do meu colega de trabalho. A base de código contém muitas regiões e é realmente um exemplo perfeito de como não usar regiões e por que regiões levam a códigos incorretos. aqui estão alguns exemplos:
4 000 monstro LOC:
Li recentemente em algum lugar no Programmers.SE que quando um arquivo contém muitos using
s (após executar o comando "Remover usos não utilizados"), é um bom sinal de que a classe dentro desse arquivo está fazendo muito. O mesmo se aplica ao tamanho do próprio arquivo.
Ao revisar o código, deparei-me com um arquivo LOC de 4.000. Parecia que o autor desse código simplesmente copiou e colou o mesmo método de 15 linhas centenas de vezes, alterando levemente os nomes das variáveis e o método chamado. Um regex simples permitiu cortar o arquivo de 4.000 LOC para 500 LOC, apenas adicionando alguns genéricos; Tenho certeza de que, com uma refatoração mais inteligente, essa classe pode ser reduzida a algumas dezenas de linhas.
Ao usar regiões, o autor encorajou-se a ignorar o fato de que o código é impossível de manter e mal escrito e a duplicar fortemente o código em vez de refatorá-lo.
Região "Fazer A", Região "Fazer B":
Outro excelente exemplo foi um método de inicialização de monstros que simplesmente executava a tarefa 1, depois a tarefa 2, depois a tarefa 3, etc. Havia cinco ou seis tarefas totalmente independentes, cada uma inicializando algo em uma classe de contêiner. Todas essas tarefas foram agrupadas em um método e agrupadas em regiões.
Isso tinha uma vantagem:
- O método era bem claro de entender, observando os nomes das regiões. Dito isto, o mesmo método, uma vez refatorado, seria tão claro quanto o original.
Os problemas, por outro lado, eram múltiplos:
Não era óbvio se havia dependências entre as regiões. Felizmente, não houve reutilização de variáveis; caso contrário, a manutenção poderia ser um pesadelo ainda mais.
O método era quase impossível de testar. Como você saberia facilmente se o método que faz vinte coisas de cada vez faz corretamente?
Região de campos, região de propriedades, região de construtor:
O código revisado também continha muitas regiões, agrupando todos os campos, todas as propriedades, etc. Isso tinha um problema óbvio: crescimento do código-fonte.
Quando você abre um arquivo e vê uma lista enorme de campos, você está mais inclinado a refatorar a classe primeiro e depois trabalhar com o código. Com as regiões, você tem o hábito de recolher coisas e esquecê-las.
Outro problema é que, se você fizer isso em qualquer lugar, estará criando regiões de um bloco, o que não faz sentido. Na verdade, esse foi o caso no código que revi, onde havia muitos que #region Constructor
continham um construtor.
Finalmente, campos, propriedades, construtores etc. já devem estar em ordem . Se eles são e correspondem às convenções (constantes começando com uma letra maiúscula, etc.), já está claro onde o tipo de elemento para e o outro começa, para que você não precise criar regiões explicitamente para isso.