Devemos eliminar as variáveis locais, se pudermos?

91

Por exemplo, para manter uma CPU no Android, posso usar um código como este:

PowerManager powerManager = (PowerManager)getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "abc");
wakeLock.acquire();

mas acho que as variáveis locais powerManager e wakeLock podem ser eliminadas:

((PowerManager)getSystemService(POWER_SERVICE))
    .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakelockTag")
    .acquire();

cena semelhante aparece na visualização de alerta do iOS, por exemplo: de

UIAlertView *alert = [[UIAlertView alloc]
    initWithTitle:@"my title"
    message:@"my message"
    delegate:nil
    cancelButtonTitle:@"ok"
    otherButtonTitles:nil];
[alert show];

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    [alertView release];
}

para:

[[[UIAlertView alloc]
    initWithTitle:@"my title"
    message:@"my message"
    delegate:nil
    cancelButtonTitle:@"ok"
    otherButtonTitles:nil] show];

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    [alertView release];
}

É uma boa prática eliminar uma variável local se ela for usada apenas uma vez no escopo?

    
por ggrr 04.01.2017 / 04:35
fonte

14 respostas

229

O código é lido com muito mais frequência do que está escrito, então você deve ter pena da pobre alma que terá que ler o código daqui a seis meses (pode ser você) e lutar pelo código mais claro e fácil de entender. . Na minha opinião, a primeira forma, com variáveis locais, é muito mais compreensível. Eu vejo três ações em três linhas, em vez de três ações em uma linha.

E se você acha que está otimizando alguma coisa se livrando de variáveis locais, você não está. Um compilador moderno colocará powerManager em um registro 1 , seja uma variável local usada ou não, para chamar o método newWakeLock . O mesmo é verdade para wakeLock . Então você acaba com o mesmo código compilado em ambos os casos.

1 Se você tivesse muitos códigos intermediários entre a declaração e o uso da variável local, ela poderia ficar na pilha, mas é um pequeno detalhe.

    
por 04.01.2017 / 07:49
fonte
90

Alguns comentários altamente votados afirmaram isso, mas nenhuma das respostas que vi fez, então vou adicioná-lo como uma resposta. Seu principal fator para decidir esse problema é:

Depuração

Geralmente, os desenvolvedores gastam muito mais tempo e esforço de depuração do que escrever código.

Com variáveis locais, você pode:

  • Ponto de interrupção na linha em que foi atribuído (com todas as outras decorações do ponto de interrupção, como ponto de interrupção condicional, etc ...)

  • Inspecione / observe / imprima / altere o valor da variável local

  • Veja os problemas que surgem devido a conversões.

  • Tenha legíveis rastreios de pilha (a linha XYZ tem uma operação em vez de 10)

Sem variáveis locais, todas as tarefas acima são muito mais difíceis, extremamente difíceis ou impossíveis, dependendo do seu depurador.

Como tal, siga a infame máxima (escreva o código de uma forma que você faria como se o próximo desenvolvedor a manter depois de si mesmo é um lunático enlouquecido que sabe onde você mora), e errar ao lado de mais fácil debuggability , o que significa usar variáveis locais .

    
por 04.01.2017 / 16:20
fonte
45

Apenas se facilitar o entendimento do código. Em seus exemplos, acho que isso dificulta a leitura.

Eliminar as variáveis no código compilado é uma operação trivial para qualquer compilador respeitável. Você pode inspecionar a saída para verificar.

    
por 04.01.2017 / 04:54
fonte
28

Sua pergunta "é uma boa prática eliminar uma variável local se ela for usada apenas uma vez no escopo?" está testando os critérios errados. O utilitário de uma variável local não depende do número de vezes que é usado, mas se torna o código mais claro. Rotular valores intermediários com nomes significativos pode melhorar a clareza em situações como a que você apresenta, pois divide o código em partes menores e mais fáceis de digerir.

Na minha opinião, o código não modificado que você apresentou é mais claro do que o seu código modificado e, portanto, deve ser deixado inalterado. Na verdade, eu consideraria retirar uma variável local do seu código modificado para melhorar a clareza.

Eu não esperaria nenhum impacto de desempenho das variáveis locais, e mesmo se houver um, provavelmente será muito pequeno para ser considerado, a menos que o código esteja em um loop muito apertado em uma parte crítica de velocidade do programa.

    
por 04.01.2017 / 10:02
fonte
14

Talvez.

Pessoalmente, eu hesitaria em eliminar variáveis locais se um typecast estivesse envolvido. Acho sua versão compacta menos legível à medida que o número de colchetes começa a se aproximar de um limite mental que eu tenho. Ele até introduz um novo conjunto de colchetes que não eram necessários na versão um pouco mais detalhada usando uma variável local.

O realce de sintaxe fornecido pelo editor de código pode atenuar tais preocupações até certo ponto.

Aos meus olhos, este é o melhor compromisso:

PowerManager powerManager = (PowerManager)getSystemService(POWER_SERVICE);
powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "abc").acquire();

Mantendo assim uma variável local e descartando a outra. Em C #, usaria a palavra-chave var na primeira linha, já que a tipografia já fornece as informações de tipo.

    
por 04.01.2017 / 05:03
fonte
12
Embora eu aceite a validade das respostas que favorecem as variáveis locais, eu vou bancar o advogado do diabo e apresentar uma visão contrária.

Pessoalmente, sou contra o uso de variáveis locais apenas para fins de documentação, embora a própria razão indique que a prática tem valor: as variáveis locais efetivamente pseudo-documentam o código.

No seu caso, o principal problema é o recuo e não a ausência de variáveis. Você poderia (e deveria) formatar o código da seguinte forma:

((PowerManager)getSystemService(POWER_SERVICE))
        .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"MyWakelockTag")
        .acquire();

Compare isso com a versão com variáveis locais:

PowerManager powerManager=(PowerManager)getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"abc");
wakeLock.acquire();

Com as variáveis locais: os nomes adicionam algumas informações ao leitor e os tipos são claramente especificados, mas as chamadas de método reais são menos visíveis e (na minha opinião) não são lidas claramente.

Sem usar variáveis locais: é mais conciso e as chamadas de método são mais visíveis, mas você pode solicitar mais informações sobre o que está sendo retornado. Alguns IDEs podem mostrar isso no entanto.

Mais três comentários:

  1. Na minha opinião, adicionar código que não tem uso funcional pode, às vezes, ser confuso, pois o leitor deve perceber que ele não tem uso funcional e está simplesmente disponível para "documentação". Por exemplo, se esse método tivesse 100 linhas, não seria óbvio que as variáveis locais (e, portanto, o resultado da chamada do método) não fossem necessárias em algum momento posterior, a menos que você leia todo o método.

  2. Adicionando as variáveis locais e significa que você deve especificar seus tipos, e isso introduz uma dependência no código que, de outra forma, não estaria lá. Se o tipo de retorno dos métodos mudou trivialmente (por exemplo, ele foi renomeado), você teria que atualizar seu código, enquanto que, sem as variáveis locais, você não o faria.

  3. A depuração será mais fácil com variáveis locais. Mas a correção para isso é corrigir as deficiências nos depuradores e não alterar o código.

por 04.01.2017 / 16:25
fonte
6

Há uma tensão de motivações aqui. A introdução de variáveis temporárias pode tornar o código mais fácil de ler. Mas eles também podem impedir e dificultar a visualização de outras possíveis refatorações, como Extrair método e Substituir Temp por Consulta . Esses últimos tipos de refatoração, quando apropriado, geralmente fornecem benefícios muito maiores do que a variável temp.

Sobre as motivações para estas últimas refatorações, Fowler escreve :

"The problem with temps is that they are temporary and local. Because they can be seen only in the context of the method in which they are used, temps tend to encourage longer methods, because that’s the only way you can reach the temp. By replacing the temp with a query method, any method in the class can get at the information. That helps a lot in coming up with cleaner code for the class."

Então, sim, use os temporários se eles tornarem o código mais legível, especialmente se for uma norma local para você e sua equipe. Mas esteja ciente de que isso pode, às vezes, dificultar a identificação de melhorias alternativas maiores. Se você puder desenvolver sua capacidade de sentir quando vale a pena ficar sem esses temporários, e se sentir mais confortável ao fazê-lo, então isso é provavelmente uma coisa boa.

FWIW Eu pessoalmente evitei ler o livro de Fowler "Refatorar" por dez anos porque imaginei que não havia muito a dizer sobre tópicos relativamente simples. Eu estava completamente errado. Quando finalmente li, mudou minhas práticas diárias de codificação, muito para melhor.

    
por 05.01.2017 / 09:28
fonte
3

Bem, é uma boa idéia eliminar variáveis locais se você puder tornar o código mais legível. Esse seu longo one-liner não é legível, mas segue um estilo OO bastante detalhado. Se você pudesse reduzi-lo para algo como

acquireWakeLock(POWER_SERVICE, PARTIAL, "abc");

então eu diria que provavelmente seria uma boa ideia. Talvez você possa introduzir métodos auxiliares para reduzi-lo a algo semelhante; Se um código como esse aparecer várias vezes, talvez valha a pena.

    
por 04.01.2017 / 18:09
fonte
2

Vamos considerar a Lei de Deméter aqui. No artigo da Wikipedia sobre LoD, o seguinte é afirmado:

The Law of Demeter for functions requires that a method m of an object O may only invoke the methods of the following kinds of objects:[2]

  1. O itself
  2. m's parameters
  3. Any objects created/instantiated within m
  4. O's direct component objects
  5. A global variable, accessible by O, in the scope of m

Uma das conseqüências de seguir esta Lei é que você deve evitar invocar métodos de objetos retornados por outros métodos em uma cadeia longa pontilhada, conforme mostrado no segundo exemplo acima:

((PowerManager)getSystemService(POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"MyWakelockTag").acquire();

É realmente difícil descobrir o que isso está fazendo. Para entendê-lo, você precisa entender o que cada procedimento faz e depois decifrar qual função está sendo chamada em qual objeto. O primeiro bloco de código

PowerManager powerManager=(PowerManager)getSystemService(POWER_SERVICE);
WakeLock wakeLock=powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"abc");
wakeLock.acquire();

mostra claramente o que você está tentando fazer - você está recebendo um objeto PowerManager, obtendo um objeto WakeLock do PowerManager e adquirindo o wakeLock. O acima segue a regra # 3 do LoD - você está instanciando esses objetos dentro do seu código e, portanto, pode usá-los para fazer o que você precisa.

Talvez outra maneira de pensar seja lembrar que, ao criar um software, você deve escrever com clareza, não por brevidade. 90% de todo desenvolvimento de software é de manutenção. Nunca escreva um pedaço de código que você não ficaria feliz em manter.

Melhor da sorte.

    
por 04.01.2017 / 13:35
fonte
2

Uma coisa a notar é que o código é lido com freqüência, para diferentes "profundidades". Este código:

PowerManager powerManager = (PowerManager)getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "abc");
wakeLock.acquire();

é fácil de "skim". São 3 declarações. Primeiro, chegamos a um PowerManager . Então chegamos a um WakeLock . Então nós acquire the wakeLock . Eu posso ver isso muito facilmente apenas olhando para o início de cada linha; Atribuições de variáveis simples são realmente fáceis de reconhecer parcialmente como "Digite varName = ..." e apenas mentalmente passe o "...". Da mesma forma, a última afirmação obviamente não é a forma da atribuição, mas envolve apenas dois nomes, portanto a "essência principal" é imediatamente aparente. Muitas vezes é tudo o que eu preciso saber se eu estava apenas tentando responder "o que esse código faz?" em um nível altíssimo.

Se eu estou perseguindo um bug sutil que acho que está aqui, então, obviamente, eu precisarei passar por isso com muito mais detalhes, e realmente me lembrarei dos "..." s. Mas a estrutura de instruções separada ainda me ajuda a fazer essa declaração de cada vez (especialmente útil se eu precisar ir mais fundo na implementação das coisas que estão sendo chamadas em cada declaração; quando eu voltar, eu entendi completamente "uma unidade" e pode então passar para a próxima declaração).

((PowerManager)getSystemService(POWER_SERVICE))
    .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakelockTag")
    .acquire();

Agora é tudo uma declaração. A estrutura de nível superior não é tão fácil de ler; na versão original do OP, sem quebra de linha e recuo para comunicar visualmente qualquer estrutura, eu teria que contar os parênteses para decodificá-lo em uma sequência de 3 etapas. Se algumas das expressões de várias partes estiverem aninhadas umas nas outras, em vez de serem organizadas como uma cadeia de chamadas de método, ela ainda pode parecer semelhante a isso, portanto, preciso ter cuidado ao confiar nisso sem contar os parênteses. E se eu confio no recuo e apenas deslizo para a última coisa como o ponto presumido de tudo isso, o que .acquire() me diz por si mesmo?

Às vezes, isso pode ser o que você quer. Se eu aplicar sua transformação no meio do caminho e escrever:

WakeLock wakeLock =
     ((PowerManeger)getSystemService(POWER_SERVICE))
    .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakeLockTage");
wakeLock.acquire();

Agora, isso se comunica para uma rápida descoberta "receba WakeLock e, em seguida, acquire it". Ainda mais simples que a primeira versão. É imediatamente óbvio que a coisa que é adquirida é um WakeLock . Se obter o PowerManager é apenas um sub-detalhe que é bastante insignificante para o ponto deste código, mas o wakeLock é importante, então ele pode realmente ajudar a enterrar o material PowerManager , então é natural você está apenas tentando rapidamente ter uma ideia do que esse código faz. E não nomear comunica que é usado apenas uma vez, e às vezes isso é importante (se o resto do escopo for muito longo, eu teria que ler tudo isso para saber se ele é usado novamente, embora o uso de subescopos explícitos possa ser outra maneira de resolver isso, se o seu idioma oferecer suporte a eles).

O que explica que tudo depende do contexto e do que você deseja comunicar. Como em escrever prosa em linguagem natural, sempre existem muitas maneiras de escrever uma determinada parte do código que é basicamente equivalente em termos de conteúdo de informação. Assim como escrever a prosa em linguagem natural, a maneira como você escolhe entre eles deve geralmente não ser aplicar regras mecanicistas como "eliminar qualquer variável local que só ocorre uma vez". Em vez disso, como você decide escrever seu código irá enfatizar certas coisas e não enfatizar outras. Você deve se esforçar para fazer essas escolhas conscientemente (incluindo a escolha de escrever código menos legível por razões técnicas às vezes), com base no que você realmente quer enfatizar. Especialmente pense no que servirá aos leitores que precisam apenas "obter a essência" de seu código (em vários níveis), já que isso acontecerá com muito mais frequência do que uma leitura expressão por expressão muito próxima.

    
por 06.01.2017 / 05:13
fonte
2

Este é até mesmo um antipadrão com seu próprio nome: Train Wreck . Vários motivos para evitar a substituição já foram indicados:

  • Mais difícil de ler
  • Mais difícil de depurar (tanto para monitorar variáveis quanto para detectar locais de exceções)
  • Violação da lei de Demeter (LoD)

Considere se esse método sabe muito de outros objetos. O encadeamento de métodos é uma alternativa que pode ajudar você a reduzir o acoplamento.

Lembre-se também que as referências a objetos são realmente baratas.

    
por 05.01.2017 / 11:22
fonte
1

A questão declarada é "Devemos eliminar variáveis locais se pudermos"?

Não, você não deve eliminar apenas porque você pode.

Você deve seguir as diretrizes de codificação em sua loja.

Na maior parte, as variáveis locais tornam o código mais fácil de ler e depurar.

Eu gosto do que você fez com PowerManager powerManager . Para mim, uma letra minúscula da classe significa que é apenas uma utilização única.

Quando você não deve é se a variável vai reter recursos caros. Muitas linguagens têm sintaxe para uma variável local que precisa ser limpa / liberada. Em c # está usando.

using(SQLconnection conn = new SQLconnnection())
{
    using(SQLcommand cmd = SQLconnnection.CreateCommand())
    {
    }
}
    
por 05.01.2017 / 17:47
fonte
1

Um aspecto importante não foi mencionado nas outras respostas: sempre que você adiciona uma variável, você introduz um estado mutável. Isso geralmente é uma coisa ruim porque torna seu código mais complexo e, portanto, mais difícil de entender e testar. Naturalmente, quanto menor o escopo da variável, menor o problema.

O que você deseja realmente não é uma variável cujo valor possa ser modificado, mas uma constante temporária. Portanto, considere expressar isso em seu código, se o seu idioma permitir. Em Java você pode usar final e em C ++ você pode usar const . Na maioria das linguagens funcionais, esse é o comportamento padrão.

É verdade que as variáveis locais são o tipo menos prejudicial de estado mutável. As variáveis de membro podem causar muito mais problemas e as variáveis estáticas são ainda piores. Ainda acho importante expressar com a maior precisão possível em seu código o que é suposto fazer. E há uma enorme diferença entre uma variável que poderia ser modificada mais tarde e um mero nome para um resultado intermediário. Então, se o seu idioma permitir que você expresse essa diferença, basta fazê-lo.

    
por 05.01.2017 / 10:01
fonte
-1

Para mim, nenhuma delas é mais difícil de ler, contanto que você recue corretamente. Se o uso de uma variável intermediária tornasse as coisas mais claras, o padrão do construtor não existiria.

É claro que o papel principal do padrão de construtor não é descartar variáveis intermediárias, mas sim agrupar uma variável complexa. Mas descarta a variável intermediária no caminho.

Os problemas com variáveis intermediárias são os seguintes:

  1. Nomeie-os corretamente
  2. Quando eu ler seu código, verei uma variável local declarada aqui. Vou começar a pensar se é uma variável intermediária ou será usado em outro lugar.

No entanto, há um benefício interessante: você pode inspecionar os resultados intermediários.

Se você acha que poder olhar o estado da variável intermediária quando a depuração é interessante, use uma. Esta é praticamente a única razão pela qual eu uso variáveis intermediárias.

    
por 04.01.2017 / 11:46
fonte