Então, eu estou programando há alguns anos e recentemente comecei a usar o ReSharper mais. Uma coisa que o ReSharper sempre me sugere é "inverter 'se' para reduzir o aninhamento".
Digamos que eu tenho esse código:
foreach (someObject in someObjectList)
{
if(someObject != null)
{
someOtherObject = someObject.SomeProperty;
}
}
E o ReSharper sugerirá que eu faça isso:
foreach (someObject in someObjectList)
{
if(someObject == null) continue;
someOtherObject = someObject.SomeProperty;
}
Parece que o ReSharper SEMPRE sugere que eu inverta os IFs, não importa quanto aninhamento esteja acontecendo. Esse problema é que eu meio que gosto de aninhar em pelo menos algumas situações. Para mim, parece mais fácil ler e descobrir o que está acontecendo em certos lugares. Nem sempre é esse o caso, mas às vezes me sinto mais confortável ao aninhar.
Minha pergunta é: além da preferência pessoal ou legibilidade, existe um motivo para reduzir o aninhamento? Existe uma diferença de desempenho ou outra coisa que eu possa não estar ciente?
fonte
Respostas:
Depende. No seu exemplo, ter a
if
declaração não invertida não é um problema, porque o corpo daif
é muito curto. No entanto, você geralmente deseja inverter as declarações quando os corpos crescerem.Somos apenas humanos e é difícil manter várias coisas ao mesmo tempo em nossas cabeças.
Quando o corpo de uma declaração é grande, a inversão não apenas reduz o aninhamento, mas também informa ao desenvolvedor que eles podem esquecer tudo o que está acima da declaração e se concentrar apenas no restante. É muito provável que você use as instruções invertidas para se livrar de valores errados o mais rápido possível. Depois que você souber que eles estão fora do jogo, a única coisa que resta são valores que realmente podem ser processados.
fonte
break
,continue
e múltiplosreturn
não são estruturados.break
/continue
/return
ter uma vantagem real sobre umelse
bloco. Com saídas antecipadas, existem 2 quarteirões : a saída e a estadia lá; comelse
existem 3 blocos : if, else, e o que vier depois (que é usado independentemente do ramo utilizado). Eu prefiro ter menos blocos.Não evite negativos. Os seres humanos sugam como analisá-los, mesmo quando a CPU os ama.
No entanto, respeite os idiomas. Nós, seres humanos, somos criaturas de hábitos, por isso preferimos caminhos bem gastos a caminhos diretos cheios de ervas daninhas.
foo != null
, infelizmente, tornou-se um idioma. Eu mal percebo mais as partes separadas. Eu olho para isso e penso: "oh, é seguro destacarfoo
agora".Se você quiser derrubar isso (oh, um dia, por favor), precisará de um argumento realmente forte. Você precisa de algo que permita que meus olhos deslizem sem esforço e entendam em um instante.
No entanto, o que você nos deu foi o seguinte:
O que nem é estruturalmente o mesmo!
Isso não está fazendo o que meu cérebro preguiçoso esperava. Eu pensei que poderia continuar adicionando código independente, mas não posso. Isso me faz pensar em coisas que não quero pensar agora. Você quebrou meu idioma, mas não me deu um melhor. Tudo porque você queria me proteger daquele negativo que queimou, é um desagradável ego em minha alma.
Desculpe, talvez o ReSharper esteja certo ao sugerir isso 90% das vezes, mas em verificações nulas, acho que você encontrou um caso de esquina onde é melhor ignorá-lo. As ferramentas simplesmente não são boas para ensinar o que é um código legível. Pergunte a um humano. Faça uma revisão por pares. Mas não siga cegamente a ferramenta.
fonte
if (foo != null){ foo.something() }
me permite continuar adicionando código sem me importar sefoo
era nulo. Isso não acontece. Mesmo que eu não precise fazer isso agora, não me sinto motivado a estragar o futuro dizendo "bem, eles podem refatorá-lo se precisarem". Feh. O software é macio apenas se for fácil mudar.É o velho argumento sobre se uma pausa é pior do que o aninhamento.
As pessoas parecem aceitar retorno antecipado para verificações de parâmetros, mas isso é desaprovado na maior parte de um método.
Pessoalmente, evito continuar, quebrar, ir etc., mesmo que isso signifique mais aninhamento. Mas na maioria dos casos, você pode evitar os dois.
Obviamente, o aninhamento dificulta as coisas quando você tem muitas camadas
O pior é quando você tem ambos
fonte
foreach (subObject in someObjectList.where(i=>i != null))
não sei por que nunca pensei em algo assim.O problema
continue
é que, se você adicionar mais linhas ao código, a lógica ficará complicada e provavelmente conterá erros. por exemploVocê quer chamar
doSomeOtherFunction()
para todos someObject, ou apenas se não nulo? Com o código original, você tem a opção e é mais clara. Com acontinue
pessoa pode esquecer "ah, sim, lá atrás, pulamos nulos" e você nem tem a opção de chamá-la para todos os alguns objetos, a menos que você faça essa chamada no início do método, o que pode não ser possível ou correto por outras razões.Sim, muitos aninhamentos são feios e possivelmente confusos, mas nesse caso eu usaria o estilo tradicional.
Pergunta de bônus: Por que existem nulos nessa lista para começar? Talvez seja melhor nunca adicionar um nulo em primeiro lugar; nesse caso, a lógica de processamento é muito mais simples.
fonte
return
s porque eles formam uma "pausa limpa", mas a IMObreak
econtinue
causa confusão e, na maioria dos casos, é melhor evitar.A ideia é o retorno antecipado. No início
return
de um ciclo torna-se cedocontinue
.Muitas respostas boas para esta pergunta: Devo retornar de uma função mais cedo ou usar uma instrução if?
Observe que, embora a questão seja encerrada com base em opiniões, mais de 430 votos são a favor do retorno antecipado, ~ 50 votos são "depende" e 8 são contra o retorno antecipado. Portanto, há um consenso excepcionalmente forte (ou isso, ou sou mau em contar, o que é plausível).
Pessoalmente, se o corpo do loop estiver sujeito a continuação antecipada e tempo suficiente para importar (3-4 linhas), eu tendem a extrair o corpo do loop em uma função em que eu uso o retorno antecipado em vez de continuar antecipadamente.
fonte
Essa técnica é útil para certificar pré-condições quando a maior parte do seu método ocorre quando essas condições são atendidas.
Freqüentemente invertemos
ifs
meu trabalho e empregamos outro fantasma. Costumava ser bastante frequente que, ao revisar um PR, eu pudesse apontar como meu colega poderia remover três níveis de aninhamento! Isso acontece com menos frequência, pois lançamos nossos ifs.Aqui está um exemplo de brinquedo que pode ser implementado
Código como este, onde há UM caso interessante (onde o resto são simplesmente retornos ou pequenos blocos de instrução) são comuns. É trivial reescrever o item acima como
Aqui, nosso código significativo, as 10 linhas não incluídas, não é recuado três vezes na função. Se você tem o primeiro estilo, ou tenta refatorar o bloco longo em um método ou tolera o recuo. É aqui que a economia entra em cena. Em um local, essa é uma economia trivial, mas em muitos métodos em várias classes, você economiza a necessidade de gerar uma função extra para tornar o código mais limpo e você não tem tantos níveis hediondos em vários níveis recuo .
Em resposta ao exemplo de código do OP, concordo que esse é um colapso excessivo. Especialmente porque em 1 TB, no estilo que uso, a inversão faz com que o código aumente de tamanho.
fonte
As sugestões de novos compartilhadores são muitas vezes úteis, mas devem sempre ser avaliadas criticamente. Ele procura alguns padrões de código predefinidos e sugere transformações, mas não pode levar em consideração todos os fatores que tornam o código mais ou menos legível para humanos.
Sendo todas as outras coisas iguais, menos aninhamento é preferível, e é por isso que o ReSharper sugere uma transformação que reduz o aninhamento. Mas todas as outras coisas não são iguais: nesse caso, a transformação requer a introdução de a
continue
, o que significa que o código resultante é realmente mais difícil de seguir.Em alguns casos, trocar um nível de aninhamento por um
continue
poderia realmente ser uma melhoria, mas isso depende da semântica do código em questão, que está além do entendimento das regras mecânicas do ReSharpers.No seu caso, a sugestão do ReSharper tornaria o código pior. Então apenas ignore. Ou melhor: não use a transformação sugerida, mas pense um pouco sobre como o código pode ser aprimorado. Observe que você pode estar atribuindo a mesma variável várias vezes, mas apenas a última atribuição terá efeito, pois a anterior seria substituída. Isso faz parecer um bug. A sugestão ReSharpers não corrige o bug, mas torna um pouco mais óbvio que alguma lógica dúbia está acontecendo.
fonte
Eu acho que o ponto mais importante aqui é que o objetivo do loop determina a melhor maneira de organizar o fluxo dentro do loop. Se você estiver filtrando alguns elementos antes de percorrer o restante, a
continue
saída antecipada faz sentido. No entanto, se você estiver executando diferentes tipos de processamento em um loop, pode fazer mais sentido tornar issoif
realmente óbvio, como:Observe que no Java Streams ou em outros idiomas funcionais, é possível separar a filtragem do loop usando:
Aliás, essa é uma das coisas que eu amo sobre o invertido if (
unless
) do Perl aplicado a instruções únicas, o que permite o seguinte tipo de código, que eu acho muito fácil de ler:fonte