Inverta a instrução "if" para reduzir o aninhamento

272

Quando executei o ReSharper no meu código, por exemplo:

    if (some condition)
    {
        Some code...            
    }

O ReSharper me deu o aviso acima (inverta a instrução "if" para reduzir o aninhamento) e sugeriu a seguinte correção:

   if (!some condition) return;
   Some code...

Eu gostaria de entender por que isso é melhor. Eu sempre pensei que usar "return" no meio de um método problemático, algo como "goto".

Lea Cohen
fonte
1
Acredito que a exceção checando e retornando no início seja boa, mas gostaria de alterar a condição para que você verifique a exceção diretamente, e não algo (por exemplo, se (alguma condição) retornar).
Br
36
Não, não fará nada pelo desempenho.
Seth Carnegie
3
Eu ficaria tentado a lançar uma ArgumentException se meu método estivesse passando dados incorretos.
asawyer
1
@asawyer Sim, há uma discussão paralela aqui sobre funções que perdoam demais as bobagens - ao contrário de usar uma falha de declaração. Escrever código sólido abriu meus olhos para isso. Nesse caso, isso seria algo parecido ASSERT( exampleParam > 0 ).
Greg Hendershott
4
As asserções são para estado interno, não para parâmetros. Você começaria validando os parâmetros, afirmando que seu estado interno está correto e, em seguida, executando a operação. Em uma construção de versão, você pode deixar de fora as asserções ou mapeá-las para um encerramento do componente.
Simon Richter

Respostas:

296

Um retorno no meio do método não é necessariamente ruim. Talvez seja melhor retornar imediatamente se tornar mais clara a intenção do código. Por exemplo:

double getPayAmount() {
    double result;
    if (_isDead) result = deadAmount();
    else {
        if (_isSeparated) result = separatedAmount();
        else {
            if (_isRetired) result = retiredAmount();
            else result = normalPayAmount();
        };
    }
     return result;
};

Nesse caso, se _isDeadfor verdade, podemos sair imediatamente do método. Talvez seja melhor estruturá-lo dessa maneira:

double getPayAmount() {
    if (_isDead)      return deadAmount();
    if (_isSeparated) return separatedAmount();
    if (_isRetired)   return retiredAmount();

    return normalPayAmount();
};   

Eu escolhi esse código do catálogo de refatoração . Essa refatoração específica é chamada: Substituir condicional aninhado por cláusulas de guarda.

piada
fonte
13
Este é um bom exemplo! O código refatorado se parece mais com uma declaração de caso.
Outros
16
Provavelmente apenas uma questão de gosto: sugiro alterar o segundo e o terceiro "se" para "senão se" para aumentar ainda mais a legibilidade. Se alguém ignorar a declaração "return", ficará claro que o caso a seguir será verificado apenas se o anterior falhar, ou seja, que a ordem das verificações é importante.
06/11/08
2
Em um exemplo simples, eu concordo que a segunda maneira é melhor, mas apenas porque é tão óbvio o que está acontecendo.
Andrew Bullock
Agora, como pegamos esse belo código que o jop escreveu e impedimos o visual studio de dividi-lo e colocar os retornos em linhas separadas? Realmente me irrita que reformate o código dessa maneira. Torna este código muito legível feio.
Mark T
@ Mark T Existem configurações no visual studio para impedir a quebra desse código.
Aaron Smith
333

Não é apenas estética , mas também reduz o nível máximo de aninhamento dentro do método. Isso geralmente é considerado uma vantagem porque facilita a compreensão dos métodos (e, de fato, muitas ferramentas de análise estática fornecem uma medida disso como um dos indicadores de qualidade do código).

Por outro lado, também faz com que seu método tenha vários pontos de saída, algo que outro grupo de pessoas acredita ser um não-não.

Pessoalmente, concordo com o ReSharper e o primeiro grupo (em um idioma que tem exceções, acho tolo discutir "vários pontos de saída"; quase tudo pode ser lançado, então existem vários pontos de saída possíveis em todos os métodos).

Em relação ao desempenho : ambas as versões devem ser equivalentes (se não no nível da IL, certamente depois que o tremor terminar com o código) em todos os idiomas. Teoricamente, isso depende do compilador, mas praticamente qualquer compilador amplamente usado hoje em dia é capaz de lidar com casos muito mais avançados de otimização de código que isso.

Jon
fonte
41
pontos de saída únicos? Quem precisa disso?
precisa
3
@ sq33G: Essa pergunta no SESE (e as respostas, é claro) é fantástica. Obrigado pelo link!
Jon
Continuo ouvindo nas respostas que algumas pessoas defendem pontos de saída únicos, mas nunca vi alguém defendendo isso, especialmente em idiomas como C #.
Thomas Bonini
1
@AndreasBonini: Ausência de prova não é prova de ausência. :-)
Jon
Sim, claro, eu só acho estranho que todo mundo sente a necessidade de dizer que algumas pessoas gostam de uma outra abordagem se essas pessoas não sentem a necessidade de dizer-se =)
Thomas Bonini
102

Este é um argumento um pouco religioso, mas concordo com o ReSharper que você deve preferir menos aninhamento. Acredito que isso supera os negativos de ter vários caminhos de retorno de uma função.

O principal motivo para ter menos aninhamento é melhorar a legibilidade e a manutenção do código . Lembre-se de que muitos outros desenvolvedores precisarão ler seu código no futuro, e código com menos recuo geralmente é muito mais fácil de ler.

As condições prévias são um ótimo exemplo de onde é bom retornar cedo no início da função. Por que a legibilidade do restante da função deve ser afetada pela presença de uma verificação de pré-condição?

Quanto aos aspectos negativos do retorno várias vezes de um método - os depuradores são bem poderosos agora, e é muito fácil descobrir exatamente onde e quando uma função específica está retornando.

Ter vários retornos em uma função não afetará o trabalho do programador de manutenção.

Legibilidade de código ruim.

LeopardSkinPillBoxHat
fonte
2
Vários retornos em uma função afetam o programador de manutenção. Ao depurar uma função, procurando o valor de retorno não autorizado, o mantenedor deve colocar pontos de interrupção em todos os locais que podem retornar.
007 EvilTeach
10
Eu colocaria um ponto de interrupção na chave de abertura. Se você percorrer a função, não apenas poderá ver as verificações sendo executadas em ordem, mas ficará muito claro qual a verificação da função em que foi executada.
John Dunagan
3
Concordo com John aqui ... Não vejo o problema em apenas percorrer toda a função.
Nailer 12/12/08
5
@Nailer Muito tarde, eu concordo especialmente, porque se a função for muito grande para passar, ela deve ser separada em várias funções de qualquer maneira!
Aidiakapi
1
Concordo com John também. Talvez seja um pensamento da velha escola colocar o ponto de interrupção no fundo, mas se você estiver curioso sobre o que uma função está retornando, você estará percorrendo a função para ver por que está retornando o que está retornando de qualquer maneira. Se você quiser apenas ver o que ele retorna, coloque-o no último colchete.
User441521 4/04
70

Como outros já mencionaram, não deve haver um impacto no desempenho, mas há outras considerações. Além dessas preocupações válidas, isso também pode abrir você para pegadinhas em algumas circunstâncias. Suponha que você estivesse lidando com um problema double:

public void myfunction(double exampleParam){
    if(exampleParam > 0){
        //Body will *not* be executed if Double.IsNan(exampleParam)
    }
}

Compare isso com a inversão aparentemente equivalente:

public void myfunction(double exampleParam){
    if(exampleParam <= 0)
        return;
    //Body *will* be executed if Double.IsNan(exampleParam)
}

Portanto, em certas circunstâncias, o que parece ser um invertido corretamente ifpode não ser.

Michael McGowan
fonte
4
Observe também que a correção rápida do novo compartilhador inverte exampleParam> 0 para exampleParam <0 not exampleParam <= 0, o que me chamou a atenção.
nickd
1
E é por isso que esse cheque deve ser antecipado e devolvido, já que o desenvolvedor acha que uma nan deve resultar em resgate.
User441521 4/04
51

A idéia de retornar apenas no final de uma função voltou nos dias anteriores aos idiomas terem suporte para exceções. Ele permitiu que os programas confiassem em poder colocar o código de limpeza no final de um método e, em seguida, tendo a certeza de que seria chamado e algum outro programador não ocultaria um retorno no método que fazia com que o código de limpeza fosse ignorado. . O código de limpeza ignorado pode resultar em vazamento de memória ou recurso.

No entanto, em um idioma que suporta exceções, ele não oferece tais garantias. Em um idioma que suporta exceções, a execução de qualquer instrução ou expressão pode causar um fluxo de controle que faz com que o método termine. Isso significa que a limpeza deve ser feita através do uso de finalmente ou de palavras-chave.

De qualquer forma, estou dizendo que acho que muitas pessoas citam a diretriz de 'único retorno no final de um método' sem entender por que isso sempre foi uma coisa boa a se fazer, e que reduzir o aninhamento para melhorar a legibilidade é provavelmente um objetivo melhor.

Scott Langham
fonte
6
Você acabou de descobrir por que as exceções são UglyAndEvil [tm] ... ;-) Exceções são gotos disfarçados e caros.
EricSchaefer 7/11/08
16
@ Eric, você deve ter sido exposto a códigos excepcionalmente ruins. É bastante óbvio quando usados ​​incorretamente, e geralmente permitem que você escreva códigos de qualidade superior.
Robert Paulson
Esta é a resposta que eu estava realmente procurando! Há ótimas respostas aqui, mas a maioria delas se concentra em exemplos, não na verdadeira razão pela qual essa recomendação nasceu.
Gustavo Mori
Essa é a melhor resposta, pois explica por que todo esse argumento surgiu em primeiro lugar.
Buttle Butkus
If you've got deep nesting, maybe your function is trying to do too many things.=> isso não é uma conseqüência correta da sua frase anterior. Porque, pouco antes de você dizer que pode refatorar o comportamento A com o código C para o comportamento A com o código D., o código D é mais limpo, concedido, mas "muitas coisas" se referem ao comportamento, que NÃO foi alterado. Portanto, você não faz nenhum sentido com esta conclusão.
precisa saber é o seguinte
30

Eu gostaria de acrescentar que existe um nome para os ifs invertidos - Cláusula Guard. Eu uso sempre que posso.

Eu odeio ler código onde existe, se no início, duas telas de código e nada mais. Apenas inverta se e retorne. Dessa forma, ninguém perderá tempo rolando.

http://c2.com/cgi/wiki?GuardClause

Piotr Perak
fonte
2
Exatamente. É mais uma refatoração. Ajuda a ler o código mais facilmente, como você mencionou.
rpattabi
'retorno' não é suficiente e horrível para uma cláusula de guarda. Eu faria: if (ex <= 0) jogue WrongParamValueEx ("[MethodName] Valor incorreto do parâmetro 1 de entrada {0} ... e você precisará capturar a exceção e gravar no log do aplicativo
JohnJohnGa
3
@John. Você está certo se isso for de fato um erro. Mas muitas vezes não é. E, em vez de verificar algo em qualquer lugar em que método é chamado de método, apenas verifica e retorna sem fazer nada.
Piotr Perak
3
Eu odiaria ler um método com duas telas de código, período, ifs ou não. lol
jpmc26
1
Pessoalmente, prefiro retornos antecipados exatamente por esse motivo (também: evitando o aninhamento). No entanto, isso não tem relação com a pergunta original sobre desempenho e provavelmente deve ser um comentário.
Patrick M
22

Isso não afeta apenas a estética, mas também evita o aninhamento de código.

Na verdade, ele pode funcionar como uma pré-condição para garantir que seus dados também sejam válidos.

Rion Williams
fonte
18

Isso é obviamente subjetivo, mas acho que melhora fortemente em dois pontos:

  • Agora é imediatamente óbvio que sua função não tem mais nada a fazer se for conditionmantida.
  • Mantém o nível de aninhamento baixo. Aninhar prejudica a legibilidade mais do que você imagina.
Deestan
fonte
15

Vários pontos de retorno eram um problema em C (e, em menor grau, em C ++) porque forçavam você a duplicar o código de limpeza antes de cada um dos pontos de retorno. Com coleta de lixo, o try| finallyconstrução e usingblocos, não há realmente nenhuma razão pela qual você deve ter medo deles.

Em última análise, tudo se resume ao que você e seus colegas acham mais fácil de ler.

Richard Poole
fonte
Essa não é a única razão. Há uma razão acadêmica referente ao pseudo-código que não tem nada a ver com considerações práticas, como limpar coisas. Essa razão acamédica tem a ver com o respeito às formas básicas de construções imperativas. Da mesma maneira que você não coloca um loop de saída no meio. Dessa forma, invariantes podem ser rigorosamente detectados e o comportamento pode ser comprovado. Ou a rescisão pode ser comprovada.
precisa saber é o seguinte
1
notícias: na verdade, encontrei uma razão muito prática pela qual os intervalos e retornos antecipados são ruins, e é uma conseqüência direta do que eu disse, análise estática. aqui está, documento do guia de uso do compilador Intel C ++: d3f8ykwhia686p.cloudfront.net/1live/intel/… . Frase-chave:a loop that is not vectorizable due to a second data-dependent exit
v.oddou
12

Em termos de desempenho, não haverá diferença perceptível entre as duas abordagens.

Mas a codificação é mais do que desempenho. Clareza e manutenção também são muito importantes. E, em casos como este em que não afeta o desempenho, é a única coisa que importa.

Existem escolas de pensamento concorrentes sobre qual abordagem é preferível.

Uma visão é a que outras pessoas mencionaram: a segunda abordagem reduz o nível de aninhamento, o que melhora a clareza do código. Isso é natural em um estilo imperativo: quando você não tem mais nada a fazer, é melhor voltar cedo.

Outra visão, da perspectiva de um estilo mais funcional, é que um método deve ter apenas um ponto de saída. Tudo em uma linguagem funcional é uma expressão. Portanto, as instruções if devem sempre ter cláusulas else. Caso contrário, a expressão if nem sempre terá um valor. Portanto, no estilo funcional, a primeira abordagem é mais natural.

Jeffrey Sax
fonte
11

As cláusulas de guarda ou pré-condições (como você provavelmente pode ver) verificam se uma determinada condição é atendida e, em seguida, interrompe o fluxo do programa. Eles são ótimos para lugares onde você realmente só está interessado em um resultado de uma ifdeclaração. Então, ao invés de dizer:

if (something) {
    // a lot of indented code
}

Você reverte a condição e quebra se essa condição revertida for atendida

if (!something) return false; // or another value to show your other code the function did not execute

// all the code from before, save a lot of tabs

returnnão está nem de longe tão sujo quanto goto. Ele permite que você passe um valor para mostrar ao restante do seu código que a função não pôde ser executada.

Você verá os melhores exemplos de onde isso pode ser aplicado em condições aninhadas:

if (something) {
    do-something();
    if (something-else) {
        do-another-thing();
    } else {
        do-something-else();
    }
}

vs:

if (!something) return;
do-something();

if (!something-else) return do-something-else();
do-another-thing();

Você encontrará poucas pessoas argumentando que o primeiro é mais limpo, mas é claro que é completamente subjetivo. Alguns programadores gostam de saber em que condições alguma coisa está operando por indentação, enquanto eu prefiro manter o fluxo do método linear.

Por um momento, não vou sugerir que precons mudem sua vida ou te deixem transar, mas você pode achar seu código um pouco mais fácil de ler.

Oli
fonte
6
Acho que sou um dos poucos então. Acho mais fácil ler a primeira versão. Os ifs aninhados tornam a árvore de decisão mais óbvia. Por outro lado, se houver algumas pré-condições, concordo que é melhor colocá-las todas no topo da função.
Outros do lado
@ Outerside: concordo completamente. os retornos escritos de maneira serial fazem com que seu cérebro precise serializar caminhos possíveis. Quando a árvore if pode ser mapeada diretamente em alguma lógica transicente, por exemplo, ela pode ser compilada no lisp. mas a moda de retorno em série exigiria um compilador muito mais difícil de fazer. O ponto aqui obviamente não tem nada a ver com esse cenário hipotético, tem a ver com análise de código, chances de otimização, provas de correção, provas de terminação e detecção de invariantes.
precisa saber é o seguinte
1
De maneira alguma alguém acha mais fácil ler código com mais ninhos. Quanto mais um bloco é algo, mais fácil é ler rapidamente. Não há como o primeiro exemplo aqui ser mais fácil de ler e ele só funciona em 2 ninhos quando a maioria dos códigos como esse no mundo real é mais profunda e, a cada ninho, é necessário mais poder cerebral para seguir.
User441521 4/04
1
Se o aninhamento for duas vezes mais profundo, quanto tempo você levaria para responder à pergunta: "Quando você faz outra coisa?" Acho que seria difícil responder sem caneta e papel. Mas você poderia respondê-lo com muita facilidade se tudo estivesse com uma cláusula de guarda.
David Storfer
9

Existem vários pontos positivos apresentados aqui, mas vários pontos de retorno também podem ser ilegíveis , se o método for muito longo. Dito isto, se você usar vários pontos de retorno, verifique se o seu método é curto; caso contrário, o bônus de legibilidade de vários pontos de retorno poderá ser perdido.

Jon Limjap
fonte
8

O desempenho é dividido em duas partes. Você tem desempenho quando o software está em produção, mas também deseja ter desempenho durante o desenvolvimento e a depuração. A última coisa que um desenvolvedor deseja é "esperar" por algo trivial. No final, compilar isso com a otimização ativada resultará em código semelhante. Portanto, é bom conhecer esses pequenos truques que valem a pena nos dois cenários.

O caso da pergunta é claro, o ReSharper está correto. Em vez de aninhar ifinstruções e criar novo escopo no código, você está definindo uma regra clara no início do seu método. Aumenta a legibilidade, será mais fácil de manter e reduz a quantidade de regras necessárias para descobrir para onde eles querem ir.

Ryan Ternier
fonte
7

Pessoalmente, prefiro apenas 1 ponto de saída. É fácil de realizar se você mantiver seus métodos curtos e direto ao ponto, além de fornecer um padrão previsível para a próxima pessoa que trabalha no seu código.

por exemplo.

 bool PerformDefaultOperation()
 {
      bool succeeded = false;

      DataStructure defaultParameters;
      if ((defaultParameters = this.GetApplicationDefaults()) != null)
      {
           succeeded = this.DoSomething(defaultParameters);
      }

      return succeeded;
 }

Isso também é muito útil se você quiser apenas verificar os valores de determinadas variáveis ​​locais dentro de uma função antes que ela saia. Tudo o que você precisa fazer é colocar um ponto de interrupção no retorno final e você tem a garantia de atingi-lo (a menos que uma exceção seja lançada).

ilitirit
fonte
4
bool PerformDefaultOperation () {DataStructure defaultParameters = this.GetApplicationDefaults (); ! retorno (DefaultParameters = NULL && this.DoSomething (DefaultParameters);} Há, fixa-lo para você :).
Tchen
1
Este exemplo é muito básico e perde o objetivo desse padrão. Faça cerca de 4 outras verificações dentro dessa função e diga-nos que é mais legível, porque não é.
precisa saber é o seguinte
5

Muitas boas razões para a aparência do código . Mas e os resultados ?

Vamos dar uma olhada em algum código C # e seu formulário compilado IL:


using System;

public class Test {
    public static void Main(string[] args) {
        if (args.Length == 0) return;
        if ((args.Length+2)/3 == 5) return;
        Console.WriteLine("hey!!!");
    }
}

Esse snippet simples pode ser compilado. Você pode abrir o arquivo .exe gerado com o ildasm e verificar qual é o resultado. Não vou postar toda a coisa do montador, mas vou descrever os resultados.

O código IL gerado faz o seguinte:

  1. Se a primeira condição for falsa, pule para o código onde está a segunda.
  2. Se for verdade, pula para a última instrução. (Nota: a última instrução é um retorno).
  3. Na segunda condição, o mesmo acontece depois que o resultado é calculado. Compare e: chegue ao Console.WriteLine se falso ou até o fim, se isso for verdadeiro.
  4. Imprima a mensagem e retorne.

Então, parece que o código vai pular para o final. E se fizermos um normal se com código aninhado?

using System;

public class Test {
    public static void Main(string[] args) {
        if (args.Length != 0 && (args.Length+2)/3 != 5) 
        {
            Console.WriteLine("hey!!!");
        }
    }
}

Os resultados são bastante semelhantes nas instruções de IL. A diferença é que antes havia saltos por condição: se false, vá para o próximo trecho de código, se true, vá para o fim. E agora o código IL flui melhor e tem 3 saltos (o compilador otimizou isso um pouco): 1. Primeiro salto: quando Length é 0 para uma parte em que o código salta novamente (terceiro salto) até o fim. 2. Segundo: no meio da segunda condição para evitar uma instrução. 3. Terceiro: se a segunda condição for falsa, pule para o final.

De qualquer forma, o contador de programas sempre pulará.

graffic
fonte
5
Esta é uma boa info - mas pessoalmente eu não poderia me importar menos se IL "flui melhor" porque as pessoas que estão indo gerenciar o código não vai ver qualquer IL
JohnIdol
1
Você realmente não pode prever como a otimização aprovada na próxima atualização do compilador C # funcionará em comparação com o que você vê hoje. Pessoalmente, eu não gastaria tempo tentando ajustar o código C # para produzir um resultado diferente na IL, a menos que a verificação mostre que você tem um problema de desempenho GRAVE em algum loop apertado em algum lugar e você ficou sem outras idéias . Mesmo assim, eu pensaria mais sobre outras opções. Na mesma linha, duvido que você tenha alguma idéia de que tipo de otimizações o compilador JIT está fazendo com o IL que é alimentado para cada plataforma ...
Craig
5

Em teoria, a inversão ifpode levar a um melhor desempenho se aumentar a taxa de acerto de previsão de ramificação. Na prática, acho muito difícil saber exatamente como a previsão de ramificação se comportará, especialmente após a compilação, para que eu não o faça no desenvolvimento do dia a dia, exceto se estiver escrevendo código de montagem.

Mais sobre previsão de ramificação aqui .

jfg956
fonte
4

Isso é simplesmente controverso. Não existe "acordo entre programadores" sobre a questão do retorno antecipado. É sempre subjetivo, tanto quanto eu sei.

É possível fazer um argumento de desempenho, já que é melhor ter condições escritas para que elas sejam verdadeiras com mais freqüência; também se pode argumentar que é mais claro. Por outro lado, cria testes aninhados.

Eu não acho que você receberá uma resposta conclusiva para esta pergunta.

descontrair
fonte
Não entendo seu argumento de desempenho. As condições são booleanas, portanto, seja qual for o resultado, sempre há dois resultados ... Não vejo por que reverter uma declaração (ou não) alteraria um resultado. (A menos que você está dizendo a adição de um "NÃO" à condição adiciona uma quantidade mensurável de processamento ...
Oli
2
A otimização funciona da seguinte maneira: se você garantir que o caso mais comum esteja no bloco if em vez do bloco else, a CPU geralmente já terá as instruções do bloco if carregadas em seu pipeline. Se a circunstância retorna false, a CPU precisa esvaziar seu pipeline e ...
Otherside
Eu também gosto de fazer o que os outros estão dizendo apenas para facilitar a leitura, odeio ter casos negativos no bloco "then" em vez de no "else". Faz sentido fazer isso mesmo sem saber ou considerar o que a CPU está fazendo
Andrew Bullock
3

Já existem muitas respostas perspicazes, mas ainda assim eu gostaria de direcionar para uma situação um pouco diferente: em vez da pré-condição, que deve ser colocada no topo de uma função, pense em uma inicialização passo a passo, onde você é necessário verificar se cada etapa é bem-sucedida e, em seguida, continuar com a próxima. Nesse caso, você não pode verificar tudo no topo.

Achei meu código realmente ilegível ao escrever um aplicativo host ASIO com o ASIOSDK de Steinberg, enquanto seguia o paradigma de aninhamento. Foi como oito níveis de profundidade, e não consigo ver uma falha de design lá, como mencionado por Andrew Bullock acima. Obviamente, eu poderia ter empacotado algum código interno em outra função e depois aninhado os níveis restantes para torná-lo mais legível, mas isso me parece bastante aleatório.

Ao substituir o aninhamento por cláusulas de guarda, eu até descobri um equívoco meu sobre uma parte do código de limpeza que deveria ter ocorrido muito mais cedo na função, em vez de no final. Com galhos aninhados, eu nunca teria visto isso, você poderia até dizer que eles levaram ao meu equívoco.

Portanto, essa pode ser outra situação em que os ifs invertidos podem contribuir para um código mais claro.

Ingo Schalk-Schupp
fonte
3

Evitar vários pontos de saída pode levar a ganhos de desempenho. Não tenho certeza sobre C #, mas em C ++ a otimização do valor de retorno nomeado (Copy Elision, ISO C ++ '03 12.8 / 15) depende de ter um único ponto de saída. Essa otimização evita que a cópia construa seu valor de retorno (no seu exemplo específico, isso não importa). Isso pode levar a ganhos consideráveis ​​de desempenho em loops apertados, pois você está salvando um construtor e um destruidor cada vez que a função é invocada.

Porém, para 99% dos casos, salvar as chamadas adicionais de construtor e destruidor não vale a perda de legibilidade ifintroduzida pelos blocos aninhados (como outros já apontaram).

shibumi
fonte
2
há. levei três longas leituras de dezenas de respostas em três perguntas, fazendo a mesma coisa (duas das quais estão congeladas), para finalmente encontrar alguém mencionando a NRVO. caramba ... obrigado.
precisa saber é o seguinte
2

É uma questão de opinião.

Minha abordagem normal seria evitar ifs de linha única e retornar no meio de um método.

Você não gostaria de linhas como as sugeridas em todos os lugares do seu método, mas há algo a ser dito para verificar várias suposições no topo do seu método, e apenas fazer o seu trabalho real se todas forem aprovadas.

Colin Pickard
fonte
2

Na minha opinião, o retorno antecipado é bom se você está retornando nulo (ou algum código de retorno inútil que você nunca vai verificar) e pode melhorar a legibilidade porque você evita o aninhamento e ao mesmo tempo explicita que sua função está concluída.

Se você realmente está retornando um returnValue - o aninhamento geralmente é a melhor maneira de fazê-lo, pois você retorna seu returnValue apenas em um local (no final - duh), e isso pode tornar seu código mais sustentável em muitos casos.

JohnIdol
fonte
1

Não tenho certeza, mas acho que o R # tenta evitar saltos distantes. Quando você possui o IF-ELSE, o compilador faz algo assim:

Condição false -> salto em distância para false_condition_label

true_condition_label: instrução1 ... instrução_n

false_condition_label: instrução1 ... instrução_n

bloco final

Se a condição for verdadeira, não há salto nem cache L1 de lançamento, mas o salto para false_condition_label pode estar muito longe e o processador deve lançar seu próprio cache. A sincronização do cache é cara. O R # tenta substituir saltos distantes em saltos curtos e, nesse caso, há uma probabilidade maior de que todas as instruções já estejam no cache.

Marcin Gołembiewski
fonte
0

Eu acho que depende do que você preferir, como mencionado, não existe um acordo geral. Para reduzir o aborrecimento, você pode reduzir esse tipo de aviso para "Dica"

Bluenuance
fonte
0

Minha ideia é que o retorno "no meio de uma função" não seja tão "subjetivo". O motivo é bem simples, pegue este código:

    função do_something (data) {

      if (! is_valid_data (data)) 
            retorna falso;


       do_something_that_take_an_hour (data);

       istance = new object_with_very_painful_constructor (data);

          if (istance is valid) {
               mensagem de erro( );
                Retorna ;

          }
       connect_to_database ();
       get_some_other_data ();
       Retorna;
    }

Talvez o primeiro "retorno" não seja tão intuitivo, mas é realmente muito bom. Existem muitas "idéias" sobre códigos limpos, que simplesmente precisam de mais prática para perder suas más idéias "subjetivas".

Joshi Spawnbrood
fonte
Com um idioma de exceção, você não tem esses problemas.
User441521 4/04
0

Existem várias vantagens nesse tipo de codificação, mas para mim a grande vitória é que, se você puder retornar rapidamente, poderá melhorar a velocidade do seu aplicativo. Ou seja, eu sei que, devido à pré-condição X, posso retornar rapidamente com um erro. Isso elimina os casos de erro primeiro e reduz a complexidade do seu código. Em muitos casos, como o pipeline da CPU agora pode ser mais limpo, ele pode interromper falhas ou interruptores no pipeline. Em segundo lugar, se você estiver em um loop, interromper ou retornar rapidamente pode economizar uma grande quantidade de CPU. Alguns programadores usam invariantes de loop para fazer esse tipo de saída rápida, mas com isso você pode interromper o pipeline da CPU e até criar um problema de busca de memória e significa que a CPU precisa carregar do cache externo. Mas basicamente acho que você deve fazer o que pretendia, que é o fim do loop ou a função não cria um caminho de código complexo apenas para implementar alguma noção abstrata de código correto. Se a única ferramenta que você tem é um martelo, tudo parece um prego.

David Allan Finch
fonte
Lendo a resposta do graffic, parece que o código aninhado foi otimizado pelo compilador de uma maneira que o código executado é mais rápido do que o uso de retornos múltiplos. No entanto, mesmo se vários retornos eram mais rápidos, esta otimização provavelmente não vai acelerar a sua aplicação tanto ... :)
hangy
1
Eu não concordo - a primeira lei é sobre como corrigir o algoritmo. Mas somos todos engenheiros, que consiste em resolver o problema correto com os recursos que temos e fazendo no orçamento disponível. Um aplicativo muito lento não é adequado para a finalidade.
David Allan Finch