O “else” deve ser usado em situações em que o fluxo de controle o torna redundante?


18

Às vezes me deparei com um código semelhante ao exemplo a seguir (o que essa função faz exatamente está fora do escopo desta pergunta):

function doSomething(value) {
  if (check1(value)) {
    return -1;
  }
  else if (check2(value)) {
    return value;
  }
  else {
    return false;
  }
}

Como você pode ver, if, else ife elseinstruções são utilizados em conjunto com o returncomunicado. Isso parece bastante intuitivo para um observador casual, mas acho que seria mais elegante (da perspectiva de um desenvolvedor de software) descartar o else-s e simplificar o código da seguinte forma:

function doSomething(value) {
  if (check1(value)) {
    return -1;
  }
  if (check2(value)) {
    return value;
  }
  return false;
}

Isso faz sentido, pois tudo o que segue uma returninstrução (no mesmo escopo) nunca será executado, tornando o código acima semanticamente igual ao primeiro exemplo.

Qual das opções acima se encaixa mais nas boas práticas de codificação? Existem desvantagens em qualquer método no que diz respeito à legibilidade do código?

Editar: Uma sugestão duplicada foi feita com esta pergunta fornecida como referência. Acredito que minha pergunta aborda um tópico diferente, pois não estou perguntando sobre evitar declarações duplicadas, como apresentado na outra pergunta. Ambas as questões buscam reduzir as repetições, embora de maneiras ligeiramente diferentes.


14
O problema elseé menor, o problema maior é que você obviamente está retornando dois tipos de dados de uma única função, o que torna a API da função imprevisível.
Andy

3
E_CLEARLY_NOTADUPE
kojiro

3
@ DavidPacker: pelo menos dois; pode ser três. -1é um número, falseé um booleano e valuenão é especificado aqui, portanto, pode ser qualquer tipo de objeto.
Yay295

1
@ Yay295 Eu estava esperando que na valueverdade fosse um número inteiro. Se pode ser qualquer coisa, então é ainda pior.
Andy

@DavidPacker Este é um ponto muito importante, especialmente quando levantado em relação a uma pergunta sobre boas práticas. Concordo com você e o Yay295, no entanto, os exemplos de código servem como exemplo para demonstrar uma técnica não relacionada de codificação. No entanto, lembro que esse é um assunto importante e não deve ser esquecido no código real.
Rd12

Respostas:


23

Eu gosto do que está sem elsee aqui está o porquê:

function doSomething(value) {
  //if (check1(value)) {
  //  return -1;
  //}
  if (check2(value)) {
    return value;
  }
  return false;
}

Porque fazer isso não quebrou nada que não deveria.

Odeie a interdependência em todas as suas formas (incluindo nomear uma função check2()). Isole tudo o que pode ser isolado. Às vezes você precisa, elsemas eu não vejo isso aqui.


Eu geralmente prefiro isso também, pela razão declarada aqui. No entanto, costumo colocar um comentário no final de cada ifbloco } //elsepara deixar claro, ao ler o código, que a única execução pretendida após o ifbloco de código é se a condição na ifinstrução for falsa. Sem algo assim, especialmente se o código dentro do ifbloco se tornar mais complexo, pode não estar claro para quem está mantendo o código que a intenção era nunca executar além do ifbloco quando a ifcondição for verdadeira.
Makyen

@ Makyen você levanta um bom ponto. Um que eu estava tentando ignorar. Também acredito que algo deve ser feito após o ifpara deixar claro que esta seção do código está concluída e a estrutura concluída. Para fazer isso, faço o que não fiz aqui (porque não queria me distrair do ponto principal do OP fazendo outras alterações). Faço a coisa mais simples possível que consegue tudo isso sem se distrair. Eu adiciono uma linha em branco.
candied_orange

27

Eu acho que depende da semântica do código. Se seus três casos dependem um do outro, indique-o explicitamente. Isso aumenta a legibilidade do código e facilita o entendimento para qualquer outra pessoa. Exemplo:

if (x < 0) {
    return false;
} else if (x > 0) {
    return true;
} else {
    throw new IllegalArgumentException();
}

Aqui você está claramente dependendo do valor de xtodos os três casos. Se você omitir o último else, isso seria menos claro.

Se seus casos não dependem diretamente um do outro, omita-o:

if (x < 0) {
    return false;
}
if (y < 0) {
    return true;
}
return false;

É difícil mostrar isso em um exemplo simples, sem contexto, mas espero que você entenda meu ponto de vista.


6

Eu prefiro a segunda opção (separada ifscom no else ife early return), MAS é contanto que os blocos de código sejam curtos .

Se os blocos de código são longos, é melhor usar else if , porque, de outro modo, você não entenderia claramente os vários pontos de saída que o código possui.

Por exemplo:

function doSomething(value) {
  if (check1(value)) {
    // ...
    // imagine 200 lines of code
    // ...
    return -1;
  }
  if (check2(value)) {
    // ...
    // imagine 150 lines of code
    // ...
    return value;
  }
  return false;
}

Nesse caso, é melhor usar else if, armazenar o código de retorno em uma variável e ter apenas uma frase de retorno no final.

function doSomething(value) {
  retVal=0;
  if (check1(value)) {
    // ...
    // imagine 200 lines of code
    // ...
    retVal=-1;
  } else if (check2(value)) {
    // ...
    // imagne 150 lines of code
    retVal=value;
  }
  return retVal;
}

Mas, como alguém deve se esforçar para que as funções sejam curtas, eu diria que seja curta e saia mais cedo, como no seu primeiro trecho de código.


1
Concordo com a sua premissa, mas enquanto estivermos discutindo como deveria ser o código, poderíamos não apenas concordar que os blocos de código deviam ser curtos de qualquer maneira? Eu acho que é pior ter um longo bloco de código não dividido em funções do que usar outro se, se eu tivesse que escolher.
Kojiro

1
Uma discussão curiosa. returnestá dentro de 3 linhas ifou menos, else ifmesmo depois de 200 linhas de código. O estilo de retorno único que você introduz aqui é uma tradição de c e outros idiomas que não relatam erros com exceções. O objetivo era criar um único local para limpar recursos. Os idiomas de exceção usam um finallybloco para isso. Suponho que isso também crie um local para colocar um ponto de interrupção se o depurador não permitir que você coloque um nas funções que fecham a chave.
Candied_orange

4
Não gosto da tarefa retVal. Se você puder sair com segurança de uma função, faça-o imediatamente, sem atribuir o valor seguro a ser retornado a uma variável de uma única saída da função. Quando você usa o ponto de retorno único em uma função realmente longa, geralmente não confio em outros programadores e acabo procurando no restante da função por outras modificações no valor de retorno. Falhar rápido e voltar cedo são, entre outras, duas regras pelas quais eu vivo.
Andy

2
Na minha opinião, é ainda pior por longos quarteirões. Tento sair das funções o mais cedo possível, independentemente do contexto externo.
Andy

2
Estou com DavidPacker aqui. O estilo de retorno único nos obriga a estudar todos os pontos de atribuição, bem como o código após os pontos de atribuição. Estudar os pontos de saída de um estilo de retorno antecipado seria preferível a mim, longo ou curto. Enquanto a linguagem permitir um bloqueio final.
candied_orange

4

Eu uso os dois em diferentes circunstâncias. Em uma verificação de validação, eu omito o resto. Em um fluxo de controle, eu uso o else.

bool doWork(string name) {
  if(name.empty()) {
    return false;
  }

  //...do work
  return true;
}

vs

bool doWork(int value) {
  if(value % 2 == 0) {
    doWorkWithEvenNumber(value);
  }
  else {
    doWorkWithOddNumber(value);
  }
}

O primeiro caso parece mais como se você estivesse verificando todos os pré-requisitos primeiro, tirando-os do caminho e depois progredindo para o código real que deseja executar. Como tal, o código interessante que você realmente deseja executar pertence diretamente ao escopo da função; é sobre o que realmente trata a função.
O segundo caso é mais parecido com os dois caminhos: um código válido a ser executado que é tão relevante para a função quanto o outro com base em alguma condição. Como tal, eles pertencem em níveis de escopo semelhantes entre si.


0

A única situação que eu já vi realmente importa é um código como este:

List<?> toList() {
    if (left != null && right != null) {
        Arrays.asList(merge(left, right));
    }
    if (left != null) {
        return Arrays.asList(left);
    }
    if (right != null) {
        return Arrays.asList(right);
    }
    return Arrays.asList();
}

Há claramente algo errado aqui. O problema é que não está claro se returndeve ser adicionado ou Arrays.asListremovido. Você não pode corrigir isso sem uma inspeção mais profunda dos métodos relacionados. Você pode evitar essa ambiguidade usando sempre blocos se-else totalmente exaustivos. Este:

List<?> toList() {
    if (left != null && right != null) {
        Arrays.asList(merge(left, right));
    } else if (left != null) {
        return Arrays.asList(left);
    } else if (right != null) {
        return Arrays.asList(right);
    } else {
        return Arrays.asList();
    }
}

não será compilado (em idiomas verificados estaticamente), a menos que você adicione retorno explícito primeiro if.

Alguns idiomas nem permitem o primeiro estilo. Eu tento usá-lo apenas em um contexto estritamente imperativo ou para pré-condições em métodos longos:

List<?> toList() {
    if (left == null || right == null) {
        throw new IllegalStateException()
    }
    // ...
    // long method
    // ...
}

-2

Ter três saídas da função assim é realmente confuso se você estiver tentando seguir o código e praticar mal.

Se você tiver um único ponto de saída para a função, os outros serão necessários.

var result;
if(check1(value)
{
    result = -1;
}
else if(check2(value))
{
    result = value;
}
else 
{
    result = 0;
}
return result;

De fato, vamos ainda mais longe.

var result = 0;
var c1 = check1(value);
var c2 = check2(value);

if(c1)
{
    result = -1;
}
else if(c2)
{
    result = value;
}

return result;

É justo que você possa argumentar sobre um único retorno antecipado da validação de entrada. Apenas evite agrupar todo o lote se sua lógica for um if.


3
Esse estilo vem de idiomas com gerenciamento explícito de recursos. Um único ponto foi necessário para liberar recursos alocados. Em idiomas com gerenciamento automático de recursos, um único ponto de retorno não é tão importante. Você deve se concentrar em reduzir o número e o escopo das variáveis.
Banthar

a: nenhum idioma é especificado na pergunta. b: Eu acho que você está errado nisso. existem muitas boas razões para o retorno único. que reduz a complexidade e melhora a legibilidade
Ewan

1
Às vezes reduz a complexidade, às vezes aumenta. Veja wiki.c2.com/?ArrowAntiPattern
Robert Johnson

Não, acho que sempre reduz a complexidade ciclomática, veja meu segundo exemplo. check2 sempre corre
Ewan

tendo o regresso antecipado é uma otimização que complica o código movendo algum código em uma condicional
Ewan

-3

Eu prefiro omitir a declaração else redundante. Sempre que o vejo (na revisão ou refatoração de código), pergunto-me se o autor entendeu o fluxo de controle e se pode haver um erro no código.

Se o autor acreditava que a declaração else era necessária e, portanto, não entendia as implicações do fluxo de controle da declaração de retorno, certamente essa falta de entendimento é a principal fonte de erros, nessa função e em geral.


3
Embora eu concorde com você, essa não é uma pesquisa de opinião. Faça um backup de suas reivindicações ou os eleitores não irão tratá-lo gentilmente.
candied_orange
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.