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();
}
}
Respostas:
Um retorno em um método void não é ruim, é uma prática comum inverter
if
instruçõ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.
fonte
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.
fonte
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?
fonte
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.
fonte
O primeiro exemplo está usando uma instrução de guarda. Da Wikipedia :
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(); } }
fonte
Não há penalidade de desempenho, no entanto, a segunda parte do código é mais legível e, portanto, mais fácil de manter.
fonte
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.
fonte
É 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.
fonte
{
. 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).if
será fácil distinguir visualmente quais instruções requerem chaves fechadas e, portanto, terif
instruções controlando uma única instrução será seguro. Empurrar a chave de volta para a linha com oif
salva uma linha de espaço vertical em cada instrução múltiplaif
, mas exigirá o uso de uma linha de chave de fechamento desnecessária.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.
fonte
Ao usar protetores, certifique-se de seguir certas orientações para não confundir os leitores.
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 ;
fonte
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.
fonte