Devo retornar de uma função antecipadamente ou usar uma instrução if? [fechadas]

304

Muitas vezes, escrevi esse tipo de função nos dois formatos, e fiquei pensando se um formato é preferido em relação a outro e por quê.

public void SomeFunction(bool someCondition)
{
    if (someCondition)
    {
        // Do Something
    }
}

ou

public void SomeFunction(bool someCondition)
{
    if (!someCondition)
        return;

    // Do Something
}

Normalmente, codigo com o primeiro, pois é assim que meu cérebro funciona durante a codificação, embora eu prefira o segundo, pois cuida de qualquer tratamento de erro imediatamente e acho mais fácil ler

Rachel
fonte
9
Estou um pouco atrasado para esta discussão, para não colocar isso em resposta; Também pensei nisso há dois anos: lecterror.com/articles/view/code-formatting-and-readability Acho o segundo mais fácil de ler, modificar, manter e depurar. Mas talvez isso é só me :)
dr Hannibal Lecter
8
Relacionado: De onde surgiu a noção de "apenas um retorno"?
precisa saber é
4
agora esta pergunta é um bom exemplo de pergunta baseada em opinião #
Rudolf Olah
2
e daí se não houver prova absoluta em uma ou outra direção? Quando argumentação suficiente é fornecida em uma e outra direção e há votação se a resposta estiver correta - isso a torna muito útil. Considero que perguntas finais como esta são prejudiciais ao valor deste site.
GSF
9
E eu gosto de perguntas e respostas baseadas em opinião. Eles me dizem o que a maioria prefere e isso permite que eu escreva o código para que outros possam ler.
Zygimantas

Respostas:

402

Eu prefiro o segundo estilo. Tire os casos inválidos primeiro, simplesmente saindo ou criando exceções, conforme apropriado, coloque uma linha em branco e adicione o corpo "real" do método. Acho mais fácil ler.

Mason Wheeler
fonte
7
Smalltalk chama essas "cláusulas de guarda". Pelo menos é o que Kent Beck os chama em Smalltalk Best Practice Patterns; Não sei se é linguagem comum.
Frank Shearar
154
Sair cedo permite que você retire coisas da sua pilha mental limitada. :)
Joren
50
Eu já ouvi isso conhecido como "o padrão do leão-de-chácara" - livre-se dos casos ruins antes que eles entrem pela porta.
RevBingo
14
Eu diria até que isso definitivamente é a ÚNICA coisa certa a se fazer.
Oliver Weiler
38
Além disso, você não adiciona recuos se se livrar dos casos de fronteira no início.
Doppelgreener
170

Definitivamente o último. O primeiro não parece ruim no momento, mas quando você obtém um código mais complexo, não consigo imaginar que alguém pensaria isso:

public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
    int retval = SUCCESS;
    if (someCondition)
    {
        if (name != null && name != "")
        {
            if (value != 0)
            {
                if (perms.allow(name)
                {
                    // Do Something
                }
                else
                {
                    reval = PERM_DENY;
                }
            }
            else
            {
                retval = BAD_VALUE;
            }
        }
        else
        {
            retval = BAD_NAME;
        }
    }
    else
    {
        retval = BAD_COND;
    }
    return retval;
}

é mais legível do que

public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
    if (!someCondition)
        return BAD_COND;

    if (name == null || name == "")
        return BAD_NAME;

    if (value == 0)
        return BAD_VALUE;

    if (!perms.allow(name))
        return PERM_DENY;

    // Do something
    return SUCCESS;
}

Admito plenamente que nunca entendi a vantagem de pontos de saída únicos.

Jason Viers
fonte
20
A vantagem de um único ponto de saída é que ... existe um único ponto de saída! Com o seu exemplo, há vários pontos que podem retornar. Com uma função mais complexa, isso pode se transformar em um ponto de busca e saída quando o formato do valor de retorno é alterado. Claro, há momentos em que forçar um único ponto de saída não faz sentido.
johnl
71
As grandes funções do @JohnL são o problema, não vários pontos de saída. A menos que você está trabalhando em um contexto em uma chamada de função adicional vai abrandar o seu código imensamente, é claro ...
Dan Rosenstark
4
@ Yar: Verdade, mas o ponto permanece. Não vou tentar converter ninguém, e estava apenas apontando a vantagem de ter o mínimo de pontos de saída possível (e o exemplo de Jason era meio que palhaço). Na próxima vez que você estiver tentando descobrir por que o SomeFunction às vezes retorna valores ímpares, talvez deseje adicionar uma chamada de registro antes do retorno. Muito mais fácil para depurar se houver apenas um dos bugs! :)
johnl
76
@ Jason Viers Como se um único return value;ajuda!?! Então é preciso caçar meia dúzia value = ..., com a desvantagem de que você nunca tem certeza de que o valor não será alterado entre essa tarefa e o retorno final. Pelo menos um retorno imediato é claro que nada mudará mais o resultado.
Sjoerd
5
@ Sjoed: Eu segundo Sjoerd aqui. Se você deseja registrar o resultado, pode efetuar logon no site do chamador. Se você deseja saber o motivo, precisa registrar em cada ponto de saída / atribuição, para que ambos sejam idênticos nesse aspecto.
Matthieu M.
32

Depende - em geral, não vou me esforçar para tentar mover um monte de código para interromper a função mais cedo - o compilador geralmente cuidará disso para mim. Dito isto, porém, se houver alguns parâmetros básicos no topo que eu preciso e não puder continuar de outra forma, vou me libertar mais cedo. Da mesma forma, se uma condição gerar um ifbloco gigante em função, também terei seu rompimento mais cedo.

Dito isso, se uma função exigir alguns dados quando for chamada, geralmente lançarei uma exceção (veja o exemplo) em vez de apenas retornar.

public int myFunction(string parameterOne, string parameterTwo) {
  // Can't work without a value
  if (string.IsNullOrEmpty(parameterOne)) {
    throw new ArgumentNullException("parameterOne");
  } 
  if (string.IsNullOrEmpty(parameterTwo)) {
    throw new ArgumentNullException("parameterTwo");
  }

  // ...      
  // Do some work
  // ...

  return value;
}
rjzii
fonte
9
E se o resultado final é sustentável, quem se importa com o estilo selecionado?
precisa
3
@ Jeff Siver - Portanto, por que isso tende a ser uma questão de estilo da "guerra santa", no final do dia, tudo se resume à preferência pessoal e ao que um guia de estilo interno diz.
rjzii
1
A chave aqui é que ele está lançando uma exceção em vez de retornar mais cedo. O valor de retorno não deve ser reutilizado para verificações de validade. E se você tivesse várias condições e quisesse informar o código usando o método por que ele falhou? De repente, você pode retornar dados comerciais reais, nada (resultado vazio) ou muitas seqüências, códigos, números, ... diferentes de um único método. Apenas para descrever por que falhou. Não, obrigado.
DanMan
e quanto à complexidade ciclomática, como sugere meu compilador? não é melhor não aninhar código se alguém puder ajudá-lo?
l
24

Eu prefiro o retorno antecipado.

Se você tem um ponto de entrada e um ponto de saída, sempre precisa rastrear todo o código em sua cabeça até o ponto de saída (você nunca sabe se algum outro pedaço de código abaixo faz outra coisa com o resultado, então você tem que rastreá-lo até o existir). Você faz isso, não importa qual ramo determina o resultado final. Isso é difícil de seguir.

Com uma entrada e várias, você retorna quando obtém o resultado e não se incomoda em rastreá-lo até o fim para ver que ninguém faz mais nada (porque não haverá mais nada desde que você retornou). É como dividir o corpo do método em mais etapas, cada uma com a possibilidade de retornar o resultado ou deixar a próxima etapa tentar a sorte.

user7197
fonte
13

Na programação C, em que é necessário limpar manualmente, há muito a ser dito para o retorno em um ponto. Mesmo que agora não haja necessidade de limpar algo, alguém pode editar sua função, alocar algo e precisar limpá-lo antes de retornar. Se isso acontecer, será um trabalho de pesadelo examinar todas as declarações de retorno.

Na programação C ++, você tem destruidores e até agora guardas de saída de escopo. Tudo isso precisa estar aqui para garantir que o código seja protegido contra exceções em primeiro lugar, para que o código seja bem protegido contra saída antecipada e, portanto, isso não tem desvantagem lógica e é puramente um problema de estilo.

Eu não tenho conhecimento suficiente sobre Java, se o código de bloco "finalmente" será chamado e se os finalizadores podem lidar com a situação de necessidade de garantir que algo aconteça.

C # Eu certamente não posso responder.

A linguagem D fornece protetores de saída de escopo internos adequados e, portanto, está bem preparada para saída antecipada e, portanto, não deve apresentar outro problema além do estilo.

Obviamente, as funções não devem demorar tanto tempo e, se você tiver uma declaração enorme de switch, seu código provavelmente também será mal considerado.

CashCow
fonte
1
O C ++ permite algo chamado otimização do valor de retorno que permite ao compilador omitir essencialmente a operação de cópia que normalmente ocorreria quando você retorna um valor. No entanto, sob várias condições, é difícil de executar e vários valores de retorno são um desses casos. Em outras palavras, o uso de vários valores de retorno em C ++ pode realmente tornar seu código mais lento. Isso certamente se aplica ao MSC e, considerando que seria bastante complicado (se não impossível) implementar o RVO com vários valores de retorno possíveis, provavelmente é um problema em todos os compiladores.
Eamon Nerbonne
1
Em C, basta usar gotoe, possivelmente, um retorno de dois pontos. Exemplo (formatação de código não é possível nos comentários):foo() { init(); if (bad) goto err; bar(); if (bad) goto err; baz(); return 0; err: cleanup(); return 1; }
mirabilos
1
Em vez de ir para um lugar, prefiro "Extrair método". Quando você acha que precisa implementar uma variável de valor de retorno ou ir para um esforço para garantir que seu código de limpeza seja sempre chamado, é um cheiro que você precisa dividir em várias funções. Isso permite que você simplifique seu código condicional complexo usando Cláusulas de Guarda e outras técnicas de retorno antecipado, garantindo ainda que seu código de limpeza sempre seja executado.
precisa saber é o seguinte
"alguém pode editar sua função" - essa é uma explicação ridícula para a tomada de decisões. Qualquer um pode fazer qualquer coisa no futuro. Isso não significa que você deve fazer algo específico hoje para impedir que alguém quebre as coisas no futuro.
Victor Yarema 23/10
Chamado tornando seu código sustentável. No mundo real, o código é escrito para fins comerciais e, às vezes, um desenvolvedor mais tarde precisa alterá-lo. Na época, eu escrevi essa resposta que eu tinha remendado CURL para introduzir caching e recordar a bagunça spaghetti que foi que realmente precisava de uma reescrita adequada no C ++ moderno
Cashcow
9

Retornos antecipados para ganhar. Eles podem parecer feios, mas muito menos feios do que ifembalagens grandes , especialmente se houver várias condições para verificar.

STW
fonte
9

Eu uso os dois.

Se DoSomethinghouver 3-5 linhas de código, o código ficará bonito usando o primeiro método de formatação.

Mas se tiver muito mais linhas do que isso, prefiro o segundo formato. Não gosto quando os colchetes de abertura e fechamento não estão na mesma tela.

azheglov
fonte
2
Lindo de jeito nenhum! Muito recuo!
JimmyKane
@ JimmyKane Há tanta indentação que pode acontecer em 3 a 5 linhas, especialmente quando você precisa de 2 (?) Linhas por nível, o resto é recuado: um para a estrutura de controle e início do bloco, outro para o final do bloco.
Deduplicator
8

Um motivo clássico para uma única entrada e uma saída é que, de outro modo, a semântica formal se torna indescritivelmente feia (caso contrário, o GOTO foi considerado prejudicial).

Em outras palavras, é mais fácil pensar em quando seu software sairá da rotina se você tiver apenas 1 retorno. O que também é um argumento contra exceções.

Normalmente, minimizo a abordagem de retorno antecipado.

Paul Nathan
fonte
mas para uma ferramenta de análise formal, você pode sintetizar uma função externa com a semântica de que a ferramenta precisa e manter o código legível por humanos.
Tim Williscroft
@Tim. Isso depende da quantidade de som que você deseja fazer e do que está analisando. Acho SESE razoavelmente legível se o codificador estiver sensato sobre as coisas.
Paul Nathan
Minha atitude em relação à análise é moldada pelas otimizações do projeto Self. 99% das chamadas de método dinâmico podem ser resolvidas e eliminadas estaticamente. então você pode analisar um código bastante linear. Como programador ativo, posso assumir com segurança que a maioria dos códigos nos quais trabalharei foi escrita por programadores comuns. Então eles não escreverão um código muito bom.
Tim Williscroft
@ Tim: justo o suficiente. Embora eu me sinta obrigado a apontar que as linguagens estáticas ainda têm muito jogo.
Paul Nathan
Um motivo que não se aplica a exceções. É por isso que a saída única é ótima em C, mas não compra nada em C ++.
Peterchen 11/11/10
7

Pessoalmente, prefiro fazer verificações de condições de aprovação / reprovação no início. Isso permite que eu agrupe a maioria das falhas mais comuns na parte superior da função com o restante da lógica a seguir.

nathan
fonte
6

Depende.

Retorno antecipado, se houver alguma condição óbvia de beco sem saída para verificar imediatamente, o que tornaria inútil a execução do restante da função. *

Defina Retval + retorno único se a função for mais complexa e, caso contrário, poderia ter vários pontos de saída (problema de legibilidade).

* Isso geralmente pode indicar um problema de design. Se você achar que muitos dos seus métodos precisam verificar algum estado externo / paramater ou algo semelhante antes de executar o restante do código, isso provavelmente é algo que deve ser tratado pelo chamador.

Bobby Tables
fonte
6
Quando escrevo códigos que podem ser compartilhados, meu mantra é "Não assuma nada. Não confie em ninguém". Você sempre deve validar suas entradas e qualquer estado externo do qual dependa. É melhor lançar uma exceção do que possivelmente corromper algo porque alguém lhe deu dados incorretos.
TMN
@ TMN: Bom ponto.
Bobby Tables
2
O ponto principal aqui é que no OO você lança uma exceção , não retorna . Vários retornos podem ser ruins, vários lançamentos de exceção não são necessariamente um cheiro de código.
Michael K
2
@ MichaelK: Um método deve ser uma exceção se a pós-condição não puder ser atendida. Em alguns casos, um método deve sair mais cedo porque a pós-condição foi atingida antes mesmo do início da função. Por exemplo, se alguém chamar um método "Definir rótulo de controle" para alterar o rótulo de um controle, o rótulo Fredda janela já estará Frede definir o nome do controle para seu estado atual forçaria um redesenho (o que, embora potencialmente útil em alguns casos, seria irritante no caso), seria perfeitamente razoável que o método set-name saia antecipadamente se os nomes antigo e novo corresponderem.
Supercat
3

Use um If

No livro de Don Knuth sobre os GOTO, li-o dar uma razão para sempre ter a condição mais provável em primeiro lugar em uma declaração if. Partindo do pressuposto de que essa ainda é uma idéia razoável (e não por pura consideração pela velocidade da época). Eu diria que retornos antecipados não são uma boa prática de programação, especialmente considerando o fato de que eles costumam ser usados ​​para tratamento de erros, a menos que seu código tenha mais probabilidade de falhar do que não falhar :-)

Se você seguir o conselho acima, precisará colocar esse retorno na parte inferior da função e, em seguida, é melhor nem chamá-lo de retorno lá, basta definir o código de erro e, em seguida, retornar duas linhas. Conseguindo assim a 1 entrada 1 saída ideal.

Delphi Specific ...

Sou de opinião que essa é uma boa prática de programação para programadores Delphi, embora eu não tenha nenhuma prova. Antes do D2009, não temos uma maneira atômica de retornar um valor, temos exit;e / result := foo;ou poderíamos simplesmente lançar exceções.

Se você tivesse que substituir

if (true) {
 return foo;
} 

para

if true then 
begin
  result := foo; 
  exit; 
end;

você pode ficar cansado de ver isso no topo de todas as suas funções e preferir

if false then 
begin
  result := bar;

   ... 
end
else
   result := foo;

e apenas evite exitcompletamente.

Peter Turner
fonte
2
Com Delphis mais recentes, pode ser abreviado para if true then Exit(foo);eu uso muitas vezes a técnica de primeira inicializar resulta nilou FALSE, respectivamente, em seguida, verificar todas as condições de erro e apenas Exit;se for atendida. O caso de sucesso resulté então (normalmente) definido em algum lugar no final do método.
JensG # 21/14
Sim, eu gosto desse novo recurso, embora pareça que é apenas um prazer apaziguar os programadores Java, depois você sabe que eles nos deixarão definir nossas variáveis ​​dentro dos procedimentos.
22614 Peter Turner
E programadores em C #. Sim, para ser honesto, eu consideraria isso realmente útil, pois reduz o número de linhas entre a declaração e o uso (o IIRC ainda tem alguma métrica para isso, esqueci o nome).
JensG
2

Eu concordo com a seguinte declaração:

Pessoalmente, sou fã de cláusulas de guarda (o segundo exemplo), pois reduz o recuo da função. Algumas pessoas não gostam deles porque isso resulta em vários pontos de retorno da função, mas acho que é mais claro com eles.

Retirado desta pergunta no stackoverflow .

Toto
fonte
+1 Para cláusulas de guarda. Eu também prefiro um guarda orientado positivamente, por exemplo: if (condition = false) return ao invés de if (!
Condition
1

Eu uso retornos antecipados quase exclusivamente nos dias de hoje, ao extremo. Eu escrevo isso

self = [super init];

if (self != nil)
{
    // your code here
}

return self;

Como

self = [super init];
if (!self)
    return;

// your code here

return self;

mas isso realmente não importa. Se você tiver mais de um ou dois níveis de aninhamento em suas funções, eles precisam ser eliminados.

Dan Rosenstark
fonte
Concordo. Recuo é o que causa problemas na leitura. Quanto menos recuo, melhor. Seu exemplo simples tem o mesmo nível de indentação, mas esse primeiro exemplo certamente aumentará para mais indentações que requerem mais poder cerebral.
User441521 4/04
1

Eu preferiria escrever:

if(someCondition)
{
    SomeFunction();
}
Josh K
fonte
2
eww. Isso é pré-validação? Ou está em um método de validação dedicado DoSomeFunctionIfSomeCondition?
STW
Como isso mantém suas preocupações separadas?
justkt
2
Você está violando o encapsulamento tornando a implementação da função (sua lógica de dependência) externa.
TMN
1
Se isso for executado em um método público e SomeFunction () for um método privado na mesma classe, pode estar ok. Mas se alguém estiver chamando SomeFunction (), você terá que duplicar as verificações lá. Acho melhor fazer com que cada método cuide do que precisa para fazer seu trabalho, ninguém mais deve saber disso.
Por Wiklander
2
Esse é definitivamente o estilo que Robert C. Martin propõe em "Código Limpo". Uma função deve fazer apenas uma coisa. No entanto, em "Padrões de implementação", Kent Beck sugere, das duas opções propostas no OP, a segunda é melhor.
Scott Whitlock
1

Como você, eu costumo escrever o primeiro, mas prefiro o último. Se eu tiver muitas verificações aninhadas, geralmente refatoro para o segundo método.

Não gosto de como a manipulação de erros é afastada da verificação.

if not error A
  if not error B
    if not error C
      // do something
    else handle error C
  else handle error B
else handle error A

Eu prefiro isso:

if error A
  handle error A; return
if error B
  handle error B; return
if error C
  handle error C; return

// do something
sacudida
fonte
0

As condições no topo são chamadas "pré-condições". Ao colocar if(!precond) return;, você visualiza todas as pré-condições.

Usar o bloco "if-else" grande pode aumentar a sobrecarga do recuo (esqueci a citação sobre recuos de três níveis).

Ming-Tang
fonte
2
O que? Você não pode fazer um retorno antecipado em Java? C #, VB (.NET e 6) e, aparentemente, Java (o que eu assumi, mas tive que procurar uma vez que não uso a linguagem há 15 anos), todos permitem retornos antecipados. portanto, não acuse 'idiomas de tipo forte' como não tendo o recurso. stackoverflow.com/questions/884429/…
ps2goat 2/16
-1

Eu prefiro manter as declarações se pequenas.

Então, escolhendo entre:

if condition:
   line1
   line2
    ...
   line-n

e

if not condition: return

line1
line2
 ...
line-n

Eu escolheria o que você descreveu como "retorno antecipado".

Lembre-se, eu não me importo com retornos antecipados ou o que seja, eu realmente gosto de simplificar o código, encurtar os corpos das declarações if etc.

Aninhados se e para e enquanto são horríveis , evite-os a todo custo.

hasen
fonte
-2

Como outros dizem, isso depende. Para pequenas funções que retornam valores, posso codificar retornos antecipados. Mas, para funções importantes, eu sempre gosto de ter um lugar no código onde sei que posso colocar algo que será executado antes que ele retorne.

Mike Dunlavey
fonte
-2

Eu pratico fail-fast no nível da função. Mantém o código consistente e limpo (para mim e para aqueles com quem trabalhei). Por isso, eu sempre volto cedo.

Para algumas condições frequentemente verificadas, você pode implementar aspectos dessas verificações se usar o AOP.

Steven Evers
fonte