É uma má prática usar return dentro de um método void?

92

Imagine o seguinte código:

void DoThis()
{
    if (!isValid) return;

    DoThat();
}

void DoThat() {
    Console.WriteLine("DoThat()");
}

É correto usar um return dentro de um método void? Tem alguma penalidade de desempenho? Ou seria melhor escrever um código como este:

void DoThis()
{
    if (isValid)
    {
        DoThat();
    }
}

fonte
1
E quanto a: void DoThis () {if (isValid) DoThat (); }
Dscoduc de
30
imagine o código? Por quê? É logo ali! :-D
STW de
Essa é uma boa pergunta, eu sempre acho que é uma boa prática usar return; para sair do método ou função. Especialmente em um método de mineração de dados LINQ com vários resultados IQueryable <T> e todos eles dependem uns dos outros. Se um deles não tiver resultado, alerte e saia.
Cheung,

Respostas:

175

Um retorno em um método void não é ruim, é uma prática comum inverter ifinstruções para reduzir o aninhamento .

E ter menos aninhamento em seus métodos melhora a legibilidade e a manutenção do código.

Na verdade, se você tiver um método void sem nenhuma instrução return, o compilador sempre gerará uma instrução ret no final dele.

Christian C. Salvadó
fonte
33

Há outro grande motivo para usar guardas (em oposição ao código aninhado): se outro programador adicionar código à sua função, ele estará trabalhando em um ambiente mais seguro.

Considerar:

void MyFunc(object obj)
{
    if (obj != null)
    {
        obj.DoSomething();
    }
}

versus:

void MyFunc(object obj)
{
    if (obj == null)
        return;

    obj.DoSomething();
}

Agora, imagine que outro programador adiciona a linha: obj.DoSomethingElse ();

void MyFunc(object obj)
{
    if (obj != null)
    {
        obj.DoSomething();
    }

    obj.DoSomethingElse();
}

void MyFunc(object obj)
{
    if (obj == null)
        return;

    obj.DoSomething();
    obj.DoSomethingElse();
}

Obviamente, este é um caso simplista, mas o programador adicionou um travamento ao programa na primeira instância (código aninhado). No segundo exemplo (saída antecipada com guardas), assim que você passar pelo guarda, seu código estará protegido contra o uso não intencional de uma referência nula.

Claro, um grande programador não comete erros como esse (frequentemente). Mas é melhor prevenir do que remediar - podemos escrever o código de uma forma que elimine totalmente essa fonte potencial de erros. O aninhamento adiciona complexidade, portanto, as práticas recomendadas recomendam refatorar o código para reduzir o aninhamento.

Jason Williams
fonte
Sim, mas por outro lado, várias camadas de aninhamento, com suas condições, tornam o código ainda mais sujeito a bugs, a lógica mais difícil de rastrear e - mais importante - mais difícil de depurar. Funções planas são menos maléficas, IMO.
Skrim de
19
Estou argumentando em favor de aninhamento reduzido! :-)
Jason Williams
Eu concordo com isto. Além disso, do ponto de vista do refatorador, é mais fácil e seguro refatorar o método se obj se tornar uma estrutura ou algo que você possa garantir que não será nulo.
Phil Cooper,
18

Prática ruim ??? De jeito nenhum. Na verdade, é sempre melhor lidar com as validações retornando do método o quanto antes se as validações falharem. Caso contrário, resultaria em uma grande quantidade de ifs & elses aninhados. O encerramento antecipado melhora a legibilidade do código.

Verifique também as respostas em uma pergunta semelhante: Devo usar a instrução return / continue em vez de if-else?

Usuário SO
fonte
8

Não é uma má prática (por todas as razões já mencionadas). No entanto, quanto mais retornos você tiver em um método, maior será a probabilidade de ele ser dividido em métodos lógicos menores.

Mike Hall
fonte
8

O primeiro exemplo está usando uma instrução de guarda. Da Wikipedia :

Na programação de computadores, um guarda é uma expressão booleana que deve ser avaliada como verdadeira se a execução do programa deve continuar no ramo em questão.

Acho que ter um monte de guardas no topo de um método é uma maneira perfeitamente compreensível de programar. Basicamente, ele está dizendo "não execute este método se algum deles for verdadeiro".

Então, em geral, seria assim:

void DoThis()
{
  if (guard1) return;
  if (guard2) return;
  ...
  if (guardN) return;

  DoThat();
}

Acho que é muito mais legível do que:

void DoThis()
{
  if (guard1 && guard2 && guard3)
  {
    DoThat();
  }
}
cdmckay
fonte
3

Não há penalidade de desempenho, no entanto, a segunda parte do código é mais legível e, portanto, mais fácil de manter.

Russell
fonte
Russell: eu discordo da sua opinião, mas você não deveria ter sido rejeitado por ela. 1 para equilibrar. A propósito, eu acredito que um teste booleano e retorno em uma única linha seguido por uma linha em branco é uma indicação clara do que está acontecendo. por exemplo, o primeiro exemplo de Rodrigo.
Paul Sasik
Eu discordo disso. Aumentar o aninhamento não melhora a legibilidade. A primeira parte do código está usando uma instrução "guarda", que é um padrão perfeitamente compreensível.
cdmckay
Eu também discordo. As cláusulas de proteção que escapam de uma função no início são geralmente consideradas uma coisa boa hoje em dia para ajudar o leitor a compreender a implementação.
Pete Hodgson
2

Nesse caso, seu segundo exemplo é um código melhor, mas isso não tem nada a ver com o retorno de uma função void, é simplesmente porque o segundo código é mais direto. Mas retornar de uma função vazia está perfeitamente bem.

Imagista
fonte
0

É perfeitamente normal e sem 'penalidade de desempenho', mas nunca escreva uma declaração 'se' sem colchetes.

Sempre

if( foo ){
    return;
}

É muito mais legível; e você nunca vai assumir acidentalmente que algumas partes do código estão dentro dessa instrução quando não estão.

Seda do meio-dia
fonte
2
legível é subjetivo. imho, qualquer coisa adicionada ao código que seja desnecessária torna-o menos legível ... (eu tenho que ler mais, e então me pergunto por que está lá e perco tempo tentando ter certeza de que não estou perdendo nada) ... mas esse é o meu opinião subjetiva
Charles Bretana
10
O melhor motivo para sempre incluir o aparelho é menos sobre legibilidade e mais sobre segurança. Sem as chaves, é muito fácil para alguém consertar posteriormente um bug que requer instruções adicionais como parte do if, não preste atenção o suficiente e adicione-as sem também adicionar as chaves. Sempre incluindo o aparelho ortodôntico, esse risco é eliminado.
Scott Dorman
2
Sedoso, pressione Enter antes de {. Isso o alinha {com o seu }na mesma coluna, o que ajuda muito na legibilidade (muito mais fácil de encontrar os colchetes abrir / fechar correspondentes).
Imagist de
1
@Imagist Vou deixar isso para preferência pessoal; e é feito da maneira que eu prefiro :)
Noon Silk
1
Se cada chave fechada for correspondida com uma chave aberta que é colocada no mesmo nível de recuo , então ifserá fácil distinguir visualmente quais instruções requerem chaves fechadas e, portanto, ter ifinstruções controlando uma única instrução será seguro. Empurrar a chave de volta para a linha com o ifsalva uma linha de espaço vertical em cada instrução múltipla if, mas exigirá o uso de uma linha de chave de fechamento desnecessária.
supercat de
0

Eu vou discordar de todos vocês, jovens whippersnappers neste aqui.

Usar o retorno no meio de um método, vazio ou não, é uma prática muito ruim, por razões que foram articuladas de forma bastante clara, quase quarenta anos atrás, pelo falecido Edsger W. Dijkstra, começando na conhecida "Declaração GOTO Considerada Nociva ", e continuando em" Programação Estruturada ", de Dahl, Dijkstra e Hoare.

A regra básica é que cada estrutura de controle, e cada módulo, deve ter exatamente uma entrada e uma saída. Um retorno explícito no meio do módulo quebra essa regra e torna muito mais difícil raciocinar sobre o estado do programa, o que por sua vez torna muito mais difícil dizer se o programa está correto ou não (o que é uma propriedade muito mais forte do que "se parece funcionar ou não").

"Declaração GOTO considerada Nociva" e "Programação Estruturada" deram início à revolução da "Programação Estruturada" nos anos 1970. Essas duas partes são os motivos pelos quais temos if-then-else, while-do e outras construções de controle explícitas hoje, e por que as instruções GOTO em linguagens de alto nível estão na lista de espécies ameaçadas. (Minha opinião pessoal é que eles precisam estar na lista de Espécies Extintas.)

É importante notar que o modulador de fluxo de mensagem, o primeiro software militar que SEMPRE passou no teste de aceitação na primeira tentativa, sem desvios, renúncias ou palavreado "sim, mas", foi escrito em uma linguagem que nem tinha uma declaração GOTO.

Também vale a pena mencionar que Nicklaus Wirth mudou a semântica da instrução RETURN em Oberon-07, a versão mais recente da linguagem de programação Oberon, tornando-a uma peça final da declaração de um procedimento digitado (ou seja, função), em vez de um declaração executável no corpo da função. Sua explicação da mudança disse que ele fez isso precisamente porque o formulário anterior ERA uma violação do princípio de uma saída da Programação Estruturada.

John R. Strohm
fonte
2
@John: superamos a liminar de Dykstra sobre devoluções múltiplas quase na mesma época em que superamos Pascal (a maioria de nós, pelo menos).
John Saunders de
Casos em que múltiplos retornos são necessários são freqüentemente sinais de que um método está tentando fazer muito e devem ser reduzidos. Não vou tão longe quanto John com isso, e uma instrução de retorno como parte da validação de parâmetro pode ser uma exceção razoável, mas entendi de onde a ideia veio.
kyoryu
@nairdaen: Ainda há controvérsia sobre as exceções naquele trimestre. Minha orientação é esta: se o sistema em desenvolvimento PRECISA consertar o problema que causou a condição excepcional original, e eu não me importo de irritar o cara que terá que escrever esse código, lançarei uma exceção. Então gritaram em uma reunião, porque o cara não se preocupou em pegar a exceção e o aplicativo travou no teste, e eu explico POR QUE ele tem que consertar o problema e as coisas se acalmam novamente.
John R. Strohm
Há uma grande diferença entre declarações de guarda e gotos. O mal dos gotos é que eles podem pular em qualquer lugar, então pode ser muito confuso desvendar e lembrar. As instruções de proteção são exatamente o oposto - elas fornecem uma entrada bloqueada para um método, após a qual você sabe que está trabalhando em um ambiente "seguro", reduzindo o número de coisas que você deve considerar ao escrever o resto do código (por exemplo "Eu sei que este ponteiro nunca será nulo, então não preciso lidar com esse caso em todo o código").
Jason Williams
@Jason: A questão original não era especificamente sobre instruções de guarda, mas sobre instruções de retorno aleatório no meio de um método. O exemplo dado parecia ser um guarda. A questão principal é que, no site de devolução, você quer ser capaz de raciocinar sobre o que o método fez ou não, e retornos aleatórios tornam isso mais difícil, EXATAMENTE pelas mesmas razões que os GOTOs aleatórios tornam isso mais difícil. Veja: Dijkstra, "GOTO Statement Considered Harmful". Do lado da sintaxe, cdmckay deu, em outra resposta, sua sintaxe preferida para guardas; Não concordo com sua opinião sobre qual formulário é mais legível.
John R. Strohm
0

Ao usar protetores, certifique-se de seguir certas orientações para não confundir os leitores.

  • a função faz uma coisa
  • guardas são introduzidos apenas como a primeira lógica na função
  • a parte não aninhada contém a intenção central da função

Exemplo

// guards point you to the core intent
void Remove(RayCastResult rayHit){

  if(rayHit== RayCastResult.Empty)
    return
    ;
  rayHit.Collider.Parent.Remove();
}

// no guards needed: function split into multiple cases
int WonOrLostMoney(int flaw)=>
  flaw==0 ? 100 :
  flaw<10 ? 30 :
  flaw<20 ? 0 :
  -20
;
Orangle
fonte
-3

Lance uma exceção em vez de não retornar nada quando o objeto for nulo etc.

Seu método espera que o objeto não seja nulo e não seja o caso, então você deve lançar uma exceção e deixar o chamador tratar disso.

Mas o retorno antecipado não é uma má prática.

Dhananjay
fonte
1
A resposta não responde à pergunta. A pergunta é um método vazio, então nada está sendo retornado. Além disso, os métodos não têm parâmetros. Chego ao ponto de não retornar nulo se o tipo de retorno for um objeto, mas isso não se aplica a esta questão.
Luke Hammer