Estou escrevendo um programa em Java, onde em um momento preciso carregar uma senha para o meu keystore. Apenas por diversão, tentei manter minha senha em Java o mais curta possível, fazendo o seguinte:
//Some code
....
KeyManagerFactory keyManager = KeyManagerFactory.getInstance("SunX509");
Keystore keyStore = KeyStore.getInstance("JKS");
{
char[] password = getPassword();
keyStore.load(new FileInputStream(keyStoreLocation), password);
keyManager.init(keyStore, password);
}
...
//Some more code
Agora, eu sei que nesse caso isso é meio idiota. Há várias outras coisas que eu poderia ter feito, a maioria delas realmente melhor (eu não poderia usar uma variável).
No entanto, fiquei curioso para saber se houve um caso em que isso não foi tão burro. A única outra coisa em que consigo pensar é se você deseja reutilizar nomes de variáveis comuns como count
, ou temp
, mas boas convenções de nomenclatura e métodos curtos tornam improvável que isso seja útil.
Existe um caso em que usar apenas blocos para reduzir o escopo variável faz sentido?
Respostas:
Primeiro, falando com a mecânica subjacente:
No escopo C ++ == tempo de vida, os destruidores b / c são chamados na saída do escopo. Além disso, uma distinção importante em C / C ++ podemos declarar objetos locais. No tempo de execução do C / C ++, o compilador geralmente aloca um quadro de pilha para o método que for tão grande quanto necessário, com antecedência, em vez de alocar mais espaço de pilha na entrada de cada escopo (que declara variáveis). Portanto, o compilador está recolhendo ou achatando os escopos.
O compilador C / C ++ pode reutilizar o espaço de armazenamento da pilha para os locais que não entram em conflito durante a vida útil do uso (geralmente usa a análise do código real para determinar isso em vez do escopo, b / c que é mais preciso que o escopo!).
Menciono C / C ++ b / c A sintaxe do Java, ou seja, chaves e escopo, é pelo menos em parte derivada dessa família. E também porque o C ++ surgiu nos comentários da pergunta.
Por outro lado, não é possível ter objetos locais em Java: todos os objetos são objetos de heap e todos os locais / formais são variáveis de referência ou tipos primitivos (btw, o mesmo é verdadeiro para estática).
Além disso, no escopo e no tempo de vida de Java não são exatamente iguais: estar dentro / fora do escopo é um conceito principalmente de tempo de compilação que inclui acessibilidade e conflitos de nome; nada realmente acontece em Java na saída do escopo em relação à limpeza de variáveis. A coleta de lixo de Java determina (o ponto final da) vida útil dos objetos.
Além disso, o mecanismo de bytecode de Java (a saída do compilador Java) tende a promover variáveis de usuário declaradas em escopos limitados para o nível superior do método, porque não há recurso de escopo no nível de bytecode (isso é semelhante ao tratamento com C / C ++ do pilha). Na melhor das hipóteses, o compilador poderia reutilizar slots de variáveis locais, mas (ao contrário do C / C ++), apenas se o tipo for o mesmo.
(Porém, para garantir que o compilador JIT subjacente no tempo de execução possa reutilizar o mesmo espaço de armazenamento de pilha para dois locais de tipos diferentes (e com fendas diferentes), com análise suficiente do bytecode).
Falando com a vantagem do programador, eu tenderia a concordar com os outros que um método (privado) é uma construção melhor, mesmo se usado apenas uma vez.
Ainda assim, não há nada realmente errado em usá-lo para desconfigurar nomes.
Fiz isso em raras circunstâncias, quando criar métodos individuais é um fardo. Por exemplo, ao escrever um intérprete usando uma
switch
declaração grande em um loop, eu poderia (dependendo dos fatores) introduzir um bloco separado para cada umcase
para manter os casos individuais mais separados um do outro, em vez de tornar cada caso um método diferente (cada um que é invocado apenas uma vez).(Observe que, como código em blocos, os casos individuais têm acesso às instruções "break;" e "continue;" relacionadas ao loop envolvente, enquanto os métodos exigiriam o retorno de booleanos e o uso de condicionais do chamador para obter acesso a esse fluxo de controle afirmações.)
fonte
{ int x = 42; String s="blah"; } { long y = 0; }
, along
variável reutilizará os slots de ambas as variáveisx
es
com todos os compiladores comumente usados (javac
eecj
).Na minha opinião, seria mais claro colocar o bloco em seu próprio método. Se você deixou esse bloco, espero ver um comentário esclarecendo por que você está colocando o bloco lá em primeiro lugar. Nesse ponto, parece mais limpo ter a chamada de método
initializeKeyManager()
que mantém a variável de senha limitada ao escopo do método e mostra sua intenção com o bloco de código.fonte
Eles podem ser úteis no Rust, com regras rígidas de empréstimo. E geralmente, em linguagens funcionais, onde esse bloco retorna um valor. Mas em linguagens como Java? Embora a ideia pareça elegante, na prática raramente é usada, portanto a confusão de vê-la diminui a clareza potencial de limitar o escopo das variáveis.
Há um caso em que acho útil - em uma
switch
declaração! Às vezes, você deseja declarar uma variável emcase
:Isso, no entanto, falhará:
switch
declarações são estranhas - são declarações de controle, mas, devido à sua inovação, não introduzem escopos.Então - você pode apenas usar blocos para criar os escopos:
fonte
É geralmente considerado uma boa prática manter os métodos curtos. Reduzir o escopo de algumas variáveis pode ser um sinal de que seu método é bastante longo, suficiente para que você pense que o escopo das variáveis precisa ser reduzido. Nesse caso, provavelmente vale a pena criar um método separado para essa parte do código.
Os casos que eu consideraria problemáticos são quando essa parte do código usa mais do que alguns argumentos. Há situações em que você pode acabar com métodos pequenos, cada um com vários parâmetros (ou precisa de uma solução alternativa para simular o retorno de vários valores). A solução para esse problema específico é criar outra classe que mantenha as várias variáveis usadas e implementa todas as etapas que você tem em mente em seus métodos relativamente curtos: esse é um caso de uso típico para usar um padrão Builder .
Dito isto, todas essas diretrizes de codificação podem ser aplicadas vagamente às vezes. Às vezes, existem casos estranhos, nos quais o código pode ser mais legível se você o mantiver em um método um pouco mais longo, em vez de dividi-lo em pequenos sub-métodos (ou padrões de construtor). Normalmente, isso funciona para problemas de tamanho médio, bastante lineares e onde muita divisão realmente atrapalha a legibilidade (especialmente se você precisar pular entre muitos métodos para entender o que o código faz).
Pode fazer sentido, mas é muito raro.
Aqui está o seu código:
Você está criando um
FileInputStream
, mas nunca o fecha.Uma maneira de resolver isso é usar uma instrução try-with-resources . Ele escopo
InputStream
, e você também pode usar este bloco para reduzir o escopo de outras variáveis, se desejar (dentro do razoável, uma vez que essas linhas estão relacionadas):(It'a também muitas vezes melhor usar
getDefaultAlgorithm()
em vez deSunX509
, por sinal.)Além disso, embora o conselho para separar pedaços de código em métodos separados seja geralmente bom. Raramente vale a pena se for por apenas 3 linhas (se houver, é um caso em que a criação de um método separado dificulta a legibilidade).
fonte
Na minha humilde opinião, geralmente é algo a ser evitado em grande parte pelo código de produção, porque geralmente você não é tentado a fazê-lo, exceto em funções esporádicas que executam tarefas díspares. Costumo fazê-lo em algum código de sucata usado para testar as coisas, mas não sinto tentação de fazê-lo no código de produção, onde pensei antes sobre o que cada função deve fazer, pois a função naturalmente terá uma escopo limitado em relação ao seu estado local.
Eu realmente nunca vi exemplos de blocos anônimos sendo usados assim (sem envolver condicionais, um
try
bloco para uma transação, etc.) para reduzir o escopo de maneira significativa em uma função que não implorava a pergunta de por que não podia ser dividido ainda mais em funções mais simples, com escopos reduzidos, se realmente se beneficiasse praticamente do ponto de vista genuíno do SE dos blocos anônimos. Geralmente, o código eclético está fazendo um monte de coisas pouco relacionadas ou muito relacionadas, nas quais estamos mais tentados a buscar isso.Como exemplo, se você estiver tentando fazer isso para reutilizar uma variável denominada
count
, isso sugere que você está contando duas coisas díspares. Se o nome da variável for tão curto quantocount
, então faz sentido associá-lo ao contexto da função, que poderia estar contando apenas um tipo de coisa. Em seguida, você pode olhar instantaneamente o nome e / ou a documentação da função, vercount
e saber instantaneamente o que isso significa no contexto do que a função está fazendo sem analisar todo o código. Não costumo encontrar um bom argumento para uma função contar duas coisas diferentes, reutilizando o mesmo nome de variável de maneira a tornar escopos / blocos anônimos tão atraentes em comparação com as alternativas. Isso não sugere que todas as funções contem apenas uma coisa. EU'benefício de engenharia para uma função reutilizando o mesmo nome de variável para contar duas ou mais coisas e usando blocos anônimos para limitar o escopo de cada contagem individual. Se a função é simples e clara, não é o fim do mundo ter duas variáveis de contagem com nomes diferentes, sendo que a primeira possui possivelmente mais linhas de visibilidade do escopo do que o ideal. Tais funções geralmente não são a fonte de erros que carecem de blocos anônimos para reduzir ainda mais o escopo mínimo de suas variáveis locais.Não é uma sugestão para métodos supérfluos
Isso não é para sugerir que você crie métodos à força, apenas para reduzir o escopo. É indiscutivelmente tão ruim ou pior, e o que estou sugerindo não deve exigir uma necessidade de métodos "auxiliares" particulares, mais do que uma necessidade de escopos anônimos. Isso é pensar demais no código como está agora e como reduzir o escopo das variáveis do que pensar em como resolver conceitualmente o problema no nível da interface, de maneira que produza uma visibilidade curta e limpa dos estados da função local naturalmente sem aninhamento profundo de blocos e 6+ níveis de indentação. Concordo com Bruno que você pode dificultar a legibilidade do código colocando três linhas de código em uma função, mas isso começa com a suposição de que você está evoluindo as funções criadas com base na implementação existente, em vez de projetar as funções sem se envolver nas implementações. Se você fizer isso da última maneira, eu encontrei pouca necessidade de blocos anônimos que não servem para nada além de reduzir o escopo da variável em um determinado método, a menos que você esteja tentando zelosamente reduzir o escopo de uma variável em apenas algumas linhas de código inofensivo onde a introdução exótica desses blocos anônimos indiscutivelmente contribui com a sobrecarga intelectual que eles removem.
Tentando reduzir ainda mais os escopos mínimos
Se a redução de escopos de variáveis locais para o mínimo absoluto valeu a pena, deve haver uma ampla aceitação de código como este:
... como isso causa a visibilidade mínima do estado, nem mesmo criando variáveis para se referir a elas em primeiro lugar. Eu não quero parecer dogmático, mas acho que a solução pragmática é evitar bloqueios anônimos quando possível, assim como evitar a linha monstruosa de código acima e se eles parecem absolutamente necessários em um contexto de produção do perspectiva de correção e manutenção de invariantes dentro de uma função, definitivamente penso em como você organiza seu código em funções e cria suas interfaces vale a pena reexaminar. Naturalmente, se seu método tiver 400 linhas e o escopo de uma variável for visível para 300 linhas de código a mais do que o necessário, isso pode ser um problema de engenharia genuíno, mas não é necessariamente um problema a ser resolvido com blocos anônimos.
Se nada mais, a utilização de blocos anônimos em todo o lugar é exótica, não idiomática, e o código exótico corre o risco de ser odiado por outras pessoas, se não por você, anos depois.
A utilidade prática de reduzir o escopo
A utilidade final de reduzir o escopo das variáveis é permitir que você obtenha o gerenciamento de estado correto e mantenha-o correto, além de raciocinar facilmente sobre o que qualquer parte de uma base de código faz - para poder manter invariantes conceituais. Se o gerenciamento de estado local de uma única função é tão complexo que você precisa reduzir vigorosamente o escopo com um bloco anônimo de código que não deve ser finalizado e bom, novamente, isso é um sinal para mim de que a própria função precisa ser reexaminada . Se você tiver dificuldade em raciocinar sobre o gerenciamento de estado das variáveis em um escopo de função local, imagine a dificuldade de raciocinar sobre as variáveis privadas acessíveis a todos os métodos de uma classe inteira. Não podemos usar blocos anônimos para reduzir sua visibilidade. Para mim, ajuda começar com a aceitação de que as variáveis tenderão a ter um escopo um pouco mais amplo do que o ideal, em muitos idiomas, desde que não fique fora de controle a ponto de você ter dificuldade em manter invariantes. Não é algo para resolver tanto com bloqueios anônimos, como eu o vejo como aceitável do ponto de vista pragmático.
fonte
Eu acho que motivação é motivo suficiente para introduzir um bloqueio, sim.
Como Casey observou, o bloco é um "cheiro de código" que indica que é melhor extrair o bloco em uma função. Mas você não pode.
John Carmark escreveu um memorando sobre código embutido em 2007. Seu resumo final
Então pense , faça uma escolha com propósito e (opcional) deixe evidências descrevendo sua motivação para a escolha que você fez.
fonte
Minha opinião pode ser controversa, mas sim, acho que certamente há situações em que eu usaria esse tipo de estilo. No entanto, "apenas para reduzir o escopo da variável" não é um argumento válido, porque é isso que você já está fazendo. E fazer algo apenas para fazer isso não é uma razão válida. Observe também que essa explicação não tem sentido se sua equipe já tiver resolvido se esse tipo de sintaxe é convencional ou não.
Eu sou principalmente um programador de C #, mas ouvi dizer que, em Java, retornar uma senha
char[]
para permitir reduzir o tempo em que a senha permanecerá na memória. Neste exemplo, isso não ajudará, porque o coletor de lixo pode coletar a matriz a partir do momento em que não está em uso, portanto, não importa se a senha sair do escopo. Não estou discutindo se é viável limpar a matriz depois que você terminar com ela, mas, neste caso, se você quiser fazê-lo, o escopo da variável faz sentido:Isso é realmente semelhante à instrução try-with-resources , porque escopo o recurso e executa uma finalização após a conclusão. Novamente, observe que não estou argumentando para lidar com senhas dessa maneira, apenas que, se você decidir fazer isso, esse estilo é razoável.
A razão para isso é que a variável não é mais válida. Você o criou, usou e invalidou seu estado para não conter informações significativas. Não faz sentido usar a variável após esse bloco, portanto, é razoável fazer o escopo.
Outro exemplo em que consigo pensar é quando você tem duas variáveis com nome e significado semelhantes, mas trabalha com uma e depois com outra e deseja mantê-las separadas. Eu escrevi esse código em c #:
Você pode argumentar que eu posso simplesmente declarar
ILGenerator il;
na parte superior do método, mas também não quero reutilizar a variável para objetos diferentes (meio que abordagem funcional). Nesse caso, os blocos facilitam a separação dos trabalhos executados, tanto sintaticamente quanto visualmente. Também diz que, após o bloqueio, eu termineiil
e nada deveria acessá-lo.Um argumento contra este exemplo está usando métodos. Talvez sim, mas neste caso, o código não é tão longo, e separá-lo em diferentes métodos também precisaria passar todas as variáveis usadas no código.
fonte