As “variáveis ​​devem viver no menor escopo possível” incluem o caso “as variáveis ​​não deveriam existir, se possível”?

32

De acordo com a resposta aceita em " Racional para preferir variáveis ​​locais em vez de variáveis ​​de instância? ", As variáveis ​​devem viver no menor escopo possível.
Simplifique o problema na minha interpretação, significa que devemos refatorar esse tipo de código:

public class Main {
    private A a;
    private B b;

    public ABResult getResult() {
        getA();
        getB();
        return ABFactory.mix(a, b);
    }

    private getA() {
      a = SomeFactory.getA();
    }

    private getB() {
      b = SomeFactory.getB();
    }
}

em algo como isto:

public class Main {
    public ABResult getResult() {
        A a = getA();
        B b = getB();
        return ABFactory.mix(a, b);
    }

    private getA() {
      return SomeFactory.getA();
    }

    private getB() {
      return SomeFactory.getB();
    }
}

mas de acordo com o "espírito" de "variáveis ​​deve viver no menor escopo possível", "nunca tem variáveis" tem escopo menor do que "tem variáveis"? Então, acho que a versão acima deve ser reformulada:

public class Main {
    public ABResult getResult() {
        return ABFactory.mix(getA(), getB());
    }

    private getA() {
      return SomeFactory.getA();
    }

    private getB() {
      return SomeFactory.getB();
    }
}

para que getResult()não tenha nenhuma variável local. Isso é verdade?

ocomfd
fonte
72
Criar variáveis ​​explícitas traz o benefício de precisar nomeá-las. A introdução de algumas variáveis ​​pode transformar rapidamente um método opaco em um método legível.
Jared Goguen 12/03
11
Honestamente, um exemplo em termos de nomes de variáveis ​​a e b é artificial demais para demonstrar o valor das variáveis ​​locais. Felizmente, você obteve boas respostas de qualquer maneira.
Doc Brown
3
No seu segundo trecho, suas variáveis ​​não são realmente variáveis . Eles são efetivamente constantes locais, porque você não os modifica. O compilador Java os tratará da mesma forma se você os definir final, porque eles estão no escopo local e o compilador sabe o que vai acontecer com eles. É uma questão de estilo e opinião, se você deve realmente usar finalpalavras-chave ou não.
hyde
2
@JaredGoguen A criação de variáveis ​​explícitas traz o ônus de precisar nomeá-las e o benefício de poder nomeá-las.
Bergi 12/03
2
Absolutamente zero das regras de estilo de código como "variáveis ​​devem viver no menor escopo possível" são universalmente aplicáveis ​​sem pensar. Como tal, você nunca deseja basear uma decisão no estilo de código no raciocínio do formulário nesta pergunta, onde você adota uma orientação simples e a estende para uma área menos obviamente coberta ("variáveis ​​devem ter escopos pequenos, se possível" -> "variáveis ​​não deveriam existir se possível"). Existem boas razões para apoiar sua conclusão especificamente ou sua conclusão é uma extensão inadequada; de qualquer maneira, você não precisa da diretriz original.
Ben

Respostas:

110

Não. Existem várias razões pelas quais:

  1. Variáveis ​​com nomes significativos podem facilitar a compreensão do código.
  2. Dividir fórmulas complexas em etapas menores pode facilitar a leitura do código.
  3. Armazenamento em cache.
  4. Mantendo referências a objetos para que eles possam ser usados ​​mais de uma vez.

E assim por diante.

Robert Harvey
fonte
22
Também vale a pena mencionar: o valor será armazenado na memória independentemente, portanto, na verdade, ele acaba com o mesmo escopo. Pode também nomeá-lo (pelas razões que Robert menciona acima)!
Maybe_Factor 12/03
5
@ Maybe_Factor A diretriz não está realmente lá por razões de desempenho; é sobre como é fácil manter o código. Mas isso ainda significa que locais significativos são melhores do que uma expressão enorme, a menos que você esteja apenas compondo funções bem nomeadas e projetadas (por exemplo, não há motivo para fazê-lo var taxIndex = getTaxIndex();).
Luaan 12/03
16
Todos são fatores importantes. A minha maneira de ver: brevidade é uma meta; clareza é outra; robustez é outra; desempenho é outro; e assim por diante. Nenhum desses são objetivos absolutos , e às vezes conflitam. Portanto, nosso trabalho é descobrir a melhor forma de equilibrar esses objetivos.
gidds 12/03
8
O compilador cuidará de 3 e 4.
Stop Harming Monica
9
O número 4 pode ser importante para correção. Se o valor de uma chamada de função puder mudar (por exemplo now(),) remover a variável e chamar o método mais de uma vez pode resultar em erros. Isso pode criar uma situação realmente sutil e difícil de depurar. Pode parecer óbvio, mas se você estiver em uma missão cega de refatoração para remover variáveis, é fácil acabar apresentando falhas.
JimmyJames 12/03
16

Concordo, variáveis ​​que não são necessárias e que não melhoram a legibilidade do código devem ser evitadas. Quanto mais variáveis ​​estiverem no escopo em um determinado ponto do código, mais complexo esse código será o entendimento.

Eu realmente não vejo o benefício das variáveis ae bno seu exemplo, então eu escreveria a versão sem variáveis. Por outro lado, a função é tão simples em primeiro lugar que não acho que isso importe muito.

Torna-se mais um problema, quanto mais a função fica e mais variáveis ​​estão no escopo.

Por exemplo, se você tiver

    a=getA();
    b=getB();
    m = ABFactory.mix(a,b);

Na parte superior de uma função maior, você aumenta a carga mental de entender o restante do código, introduzindo três variáveis ​​em vez de uma. Você tem que ler o resto do código para ver se aou bsão utilizados novamente. Os locais que estão no escopo por mais tempo do que precisam são ruins para a legibilidade geral.

Claro que no caso em que uma variável é necessário (por exemplo, para armazenar um resultado temporário), ou quando uma variável faz melhorar a legibilidade do código, em seguida, ele deve ser mantido.

JacquesB
fonte
2
Obviamente, quando um método fica tão longo que o número de habitantes locais é muito grande para facilitar a manutenção, você provavelmente deseja dividi-lo de qualquer maneira.
Luaan 12/03
4
Um dos benefícios de variáveis ​​"estranhas", como var result = getResult(...); return result;é o caso de você poder colocar um ponto de interrupção returne aprender exatamente o que resulté.
Joker_vD 12/03
5
@Joker_vD: Um depurador digno de seu nome deve permitir que você faça tudo isso de qualquer maneira (veja os resultados de uma chamada de função sem armazená-la em uma variável local + definindo um ponto de interrupção na saída da função).
Christian Hackl
2
@ChristianHackl Pouquíssimos depuradores permitem fazer isso sem configurar possíveis relógios complexos que levam tempo para serem adicionados (e se as funções tiverem efeitos colaterais, isso pode quebrar tudo). Vou levar a versão with variables para depuração em qualquer dia da semana.
Gabe Sechan 12/03
1
@ChristianHackl e para desenvolver ainda mais, mesmo que o depurador não permita que você faça isso por padrão porque o depurador foi horrivelmente projetado, você pode modificar o código local na sua máquina para armazenar o resultado e depois verificá-lo no depurador . Ainda não há razão para armazenar um valor em uma variável apenas para esse fim.
The Great Duck
11

Além das outras respostas, gostaria de apontar outra coisa. O benefício de manter o escopo de uma variável pequeno não é apenas reduzir a quantidade de código sintaticamente disponível para a variável, mas também o número de caminhos possíveis do fluxo de controle que podem potencialmente modificar uma variável (atribuindo-lhe um novo valor ou chamando um método de mutação no objeto existente mantido na variável).

Variáveis ​​com escopo de classe (instância ou estática) têm significativamente mais caminhos de fluxo de controle possíveis do que variáveis ​​com escopo local porque podem ser mutadas por métodos, que podem ser chamados em qualquer ordem, qualquer número de vezes e geralmente por código fora da classe .

Vamos dar uma olhada no seu getResultmétodo inicial :

public ABResult getResult() {
    getA();
    getB();
    return ABFactory.mix(this.a, this.b);
}

Agora, os nomes getAe getBpode sugerir que eles vão atribuir a this.ae this.b, não podemos saber com certeza a partir de apenas olhando getResult. Assim, é possível que os valores this.ae this.bpassados ​​para o mixmétodo venham do estado do thisobjeto de antes que getResultfoi invocado, o que é impossível prever, pois os clientes controlam como e quando os métodos são chamados.

No código revisado com o local ae as bvariáveis, fica claro que existe exatamente um fluxo de controle (sem exceção) da atribuição de cada variável ao seu uso, porque as variáveis ​​são declaradas logo antes de serem usadas.

Portanto, há um benefício significativo em mover variáveis ​​(modificáveis) do escopo de classe para o escopo local (bem como mover variáveis ​​(modificáveis) do exterior de um loop para o interior), pois simplifica o raciocínio do fluxo de controle.

Por outro lado, eliminar variáveis ​​como no seu último exemplo tem menos benefícios, porque realmente não afeta o raciocínio do fluxo de controle. Você também perde os nomes dados aos valores, o que não acontece ao simplesmente mover uma variável para um escopo interno. Este é um trade-off que você deve considerar; portanto, a eliminação de variáveis ​​pode ser melhor em alguns casos e pior em outros.

Se você não quiser perder os nomes das variáveis, mas ainda assim reduzir o escopo das variáveis ​​(no caso delas serem usadas em uma função maior), considere agrupar as variáveis ​​e seus usos em uma instrução de bloco ( ou movê-los para sua própria função ).

YawarRaza7349
fonte
2

Isso depende um pouco da linguagem, mas eu diria que um dos benefícios menos óbvios da programação funcional é o fato de incentivar o programador e o leitor de código a não precisar deles. Considerar:

(reduce (fn [map string] (assoc map string (inc (map string 0))) 

Ou algum LINQ:

var query2 = mydb.MyEntity.Select(x => x.SomeProp).AsEnumerable().Where(x => x == "Prop");

Ou Node.js:

 return visionFetchLabels(storageUri)
    .then(any(isCat))
    .then(notifySlack(secrets.slackWebhook, `A cat was posted: ${storageUri}`))
    .then(logSuccess, logError)
    .then(callback)

A última é uma cadeia de chamar uma função no resultado de uma função anterior, sem nenhuma variável intermediária. Apresentá-los tornaria muito menos claro.

No entanto, a diferença entre o primeiro exemplo e os outros dois é a ordem implícita de operação . Pode não ser o mesmo que a ordem em que realmente é calculada, mas é a ordem na qual o leitor deve pensar sobre isso. Nos dois segundos, isso é deixado para a direita. Para o exemplo Lisp / Clojure, é mais da direita para a esquerda. Você deve ser um pouco cauteloso ao escrever código que não esteja na "direção padrão" do seu idioma, e expressões "intermediárias" que misturam os dois definitivamente devem ser evitadas.

O operador de canal do F # |>é útil em parte porque permite que você escreva da esquerda para a direita coisas que de outra forma teriam que ser da direita para a esquerda.

pjc50
fonte
2
Comecei a usar sublinhado como meu nome de variável em expressões LINQ simples ou lambdas. myCollection.Select(_ => _.SomeProp).Where(_ => _.Size > 4);
Graham
Não tenho certeza se isso responde à menor pergunta do OP ...
AC
1
Por que os votos negativos? Parece uma boa resposta para mim, mesmo que não responda diretamente à pergunta, ainda assim é uma informação útil
reggaeguitar 12/03
1

Eu diria que não, porque você deve ler o "menor escopo possível" como "entre escopos existentes ou razoáveis ​​para adicionar". Caso contrário, isso implicaria que você deveria criar escopos artificiais (por exemplo, {}blocos gratuitos em linguagens do tipo C) apenas para garantir que o escopo de uma variável não se estenda além do último uso pretendido e que geralmente seria desaprovado como ofuscação / confusão, a menos que já exista uma boa razão para o escopo existir independentemente.

R ..
fonte
2
Um problema com o escopo em muitos idiomas é que é extremamente comum ter algumas variáveis ​​cujo último uso está no cálculo dos valores iniciais de outras variáveis. Se uma linguagem tiver construções explicitamente para fins de escopo, que permitam que valores calculados usando variáveis ​​de escopo interno sejam usados ​​na inicialização de escopos externos, esses escopos poderão ser mais úteis do que os escopos estritamente aninhados que muitas linguagens exigem.
supercat 12/03
1

Considere funções ( métodos ). Lá, ele não está dividindo o código na menor subtarefa possível, nem o maior pedaço de código único.

É um limite de mudança, com a delimitação de tarefas lógicas, em partes consumíveis.

O mesmo vale para variáveis . Apontando estruturas lógicas de dados, em partes compreensíveis. Ou também simplesmente nomear (declarando) os parâmetros:

boolean automatic = true;
importFile(file, automatic);

Mas é claro que, tendo uma declaração no topo, e duzentas linhas a mais, o primeiro uso é hoje aceito como mau estilo. É claramente o que "variáveis ​​devem viver no menor escopo possível" pretende dizer. Como um muito próximo "não reutilize variáveis".

Joop Eggen
fonte
1

O que está faltando como motivo para NÃO é a depuração / capacidade de leitura. O código deve ser otimizado para isso, e nomes claros e concisos ajudam muito, por exemplo, imagine uma maneira de se

if (frobnicate(x) && (get_age(x) > 2000 || calculate_duration(x) < 100 )

esta linha é curta, mas já é difícil de ler. Adicione mais alguns parâmetros, e isso se estender por várias linhas.

can_be_frobnicated = frobnicate(x)
is_short_lived_or_ancient = get_age(x) > 2000 || calculate_duration(x) < 100
if (can_be_frobnicated || is_short_lived_or_ancient )

Acho esse caminho mais fácil de ler e comunicar significado - por isso não tenho problemas com variáveis ​​intermediárias.

Outro exemplo seria idiomas como R, onde a última linha é automaticamente o valor de retorno:

some_func <- function(x) {
    compute(x)
}

isso é perigoso, o retorno é extinto ou necessário? isso é mais claro:

some_func <- function(x) {
   rv <- compute(x)
   return(rv)
}

Como sempre, esta é uma decisão judicial - elimine variáveis ​​intermediárias se elas não melhorarem a leitura, caso contrário, mantenha ou introduza-as.

Outro ponto pode ser a depuração: se os resultados do intermediário forem interessantes, pode ser melhor simplesmente introduzir um intermediário, como no exemplo R acima. A frequência com que isso é solicitado é difícil de imaginar e tome cuidado com o check-in - muitas variáveis ​​de depuração são confusas - novamente, uma decisão judicial.

Christian Sauer
fonte
0

Referindo-se apenas ao seu título: absolutamente, se uma variável for desnecessária, ela deverá ser excluída.

Mas “desnecessário” não significa que um programa equivalente possa ser escrito sem o uso da variável; caso contrário, seríamos informados de que deveríamos escrever tudo em binário.

O tipo mais comum de variável desnecessária é uma variável não utilizada, quanto menor o escopo da variável, mais fácil é determinar que ela é desnecessária. Se uma variável intermediária é desnecessária, é mais difícil determinar, porque não é uma situação binária, é contextual. De fato, código-fonte idêntico em dois métodos diferentes pode produzir uma resposta diferente pelo mesmo usuário, dependendo da experiência anterior em corrigir problemas no código ao redor.

Se o seu código de exemplo estivesse exatamente como representado, eu sugeriria se livrar dos dois métodos privados, mas teria pouca ou nenhuma preocupação sobre se você salvou o resultado das chamadas de fábrica em uma variável local ou apenas os usou como argumentos para a mistura método.

A legibilidade do código supera tudo, exceto o trabalho correto (inclui corretamente critérios de desempenho aceitáveis, que raramente são "o mais rápido possível").

jmoreno
fonte