O uso de blocos de escopo interno em uma função é ruim?

10

Existem alguns casos (bastante raros) em que existe o risco de:

  • reutilizar uma variável que não se destina a ser reutilizada (veja o exemplo 1),

  • ou usando uma variável em vez de outra, feche semanticamente (veja o exemplo 2).

Exemplo 1:

var data = this.InitializeData();
if (this.IsConsistent(data, this.state))
{
    this.ETL.Process(data); // Alters original data in a way it couldn't be used any longer.
}

// ...

foreach (var flow in data.Flows)
{
    // This shouldn't happen: given that ETL possibly altered the contents of `data`, it is
    // not longer reliable to use `data.Flows`.
}

Exemplo 2:

var userSettingsFile = SettingsFiles.LoadForUser();
var appSettingsFile = SettingsFiles.LoadForApp();

if (someCondition)
{
    userSettingsFile.Destroy();
}

userSettingsFile.ParseAndApply(); // There is a mistake here: `userSettingsFile` was maybe
                                  // destroyed. It's `appSettingsFile` which should have
                                  // been used instead.

Esse risco pode ser mitigado com a introdução de um escopo:

Exemplo 1:

// There is no `foreach`, `if` or anything like this before `{`.

{
    var data = this.InitializeData();
    if (this.IsConsistent(data, this.state))
    {
        this.ETL.Process(data);
    }
}

// ...

// A few lines later, we can't use `data.Flows`, because it doesn't exist in this scope.

Exemplo 2:

{
    var userSettingsFile = SettingsFiles.LoadForUser();

    if (someCondition)
    {
        userSettingsFile.Destroy();
    }
}

{
    var appSettingsFile = SettingsFiles.LoadForApp();

    // `userSettingsFile` is out of scope. There is no risk to use it instead of
    // `appSettingsFile`.
}

Parece errado? Você evitaria essa sintaxe? É difícil entender pelos iniciantes?

Arseni Mourzenko
fonte
7
Você poderia argumentar que cada "bloco de escopo" independente deveria ser seu próprio método ou função?
Dan Pichelman 6/06/2013
Eu diria que "frequentemente" não é tão bem implementado quanto poderia ser (mas provavelmente não é um problema de "design"). Às vezes, por design, é inevitável (por exemplo, escopo de exceção / recurso).
7113 JustinC

Respostas:

35

Se sua função é tão longa que você não consegue mais reconhecer efeitos colaterais indesejados ou reutilizar ilegalmente variáveis, é hora de dividi-la em funções menores - o que torna um escopo interno inútil.

Para apoiar isso com alguma experiência pessoal: há alguns anos, herdei um projeto legado em C ++ com ~ 150K linhas de código, e ele continha alguns métodos usando exatamente essa técnica. E adivinhem - todos esses métodos foram muito longos. À medida que refatoramos a maior parte desse código, os métodos se tornaram cada vez menores, e tenho certeza de que não existem mais métodos de "escopo interno" restantes; eles simplesmente não são necessários.

Doc Brown
fonte
4
Mas por que isso é ruim? Ter muitas funções nomeadas também é confuso.
Kris Van Bael
11
+1 Exatamente, se você precisar de um escopo, provavelmente está executando uma ação específica que pode ser extraída para uma função. Kris, não se trata de criar muitas e muitas funções; é quando esses casos ocorrem (onde o escopo de um bloco pode entrar em conflito com o de outro) que normalmente uma função deveria ter sido usada. Vejo isso em outras linguagens (JavaScript, com IIFE em vez de simples 'funções antigas) o tempo todo também.
Benjamin Gruenbaum
10
@KrisVanBael: "Ter muitas funções nomeadas também é confuso" - se você usar nomes descritivos, será exatamente o contrário. Criar funções muito grandes é a principal razão pela qual o código se torna difícil de manter e ainda mais difícil de extender.
Doc Brown
2
Se há tantas funções que nomeá-las distintamente se torna um problema, possivelmente algumas delas podem ser agrupadas em um novo tipo, pois podem ter independência significativa de seu corpo de função original. Alguns podem estar tão intimamente relacionados que podem ser eliminados. De repente, não há tantas funções.
7263 JustinC
3
Ainda não está convencido. Muitas vezes, funções longas são ruins de fato. Mas dizer que toda necessidade de escopo exige uma função separada é muito preto e branco para mim.
Kris Van Bael
3

É muito mais fácil para um iniciante (e qualquer programador) pensar em:

  • não usando uma variável que não é relevante

do que pensar em:

  • adicionando estruturas de código com o objetivo de impedir que ele não use uma variável que não é relevante.

Além disso, do ponto de vista do leitor, esses blocos não têm papel aparente: removê-los não afeta a execução. O leitor pode coçar a cabeça tentando adivinhar o que o codificador queria alcançar.

mouviciel
fonte
2

Usar um escopo é tecnicamente correto. Mas se você quiser tornar seu código mais legível, extraia essa parte em outro método e dê um nome completo a esse significado. Dessa forma, você pode fornecer um escopo de suas variáveis ​​e também um nome de sua tarefa.

DesenvolvedorArnab
fonte
0

Concordo que a reutilização de variável é mais frequentemente do que um cheiro de código. Provavelmente, esse código deve ser refatorado em blocos menores e independentes.

Há um cenário específico, OTOH, em C #, quando eles tendem a aparecer - ou seja, ao usar construções de casos de comutação. A situação é muito bem explicada em: declaração C # Switch com / sem colchetes ... qual é a diferença?

Dejan Stanič
fonte