Os blocos else aumentam a complexidade do código? [fechadas]

18

Aqui está um exemplo muito simplificado . Esta não é necessariamente uma pergunta específica do idioma, e peço que você ignore as muitas outras maneiras pelas quais a função pode ser escrita e as alterações que podem ser feitas nela. . A cor é de um tipo único

string CanLeaveWithoutUmbrella()
{
    if(sky.Color.Equals(Color.Blue))
    {
        return "Yes you can";
    }
    else
    {
        return "No you can't";
    }
}

Muitas pessoas que eu conheci, ReSharper, e esse cara (cujo comentário me lembrou que eu estava procurando isso por um tempo) recomendariam refatorar o código para remover o elsebloco deixando isso:

(Não me lembro do que a maioria disse, eu poderia não ter perguntado isso de outra forma)

string CanLeaveWithoutUmbrella()
{
    if(sky.Color.Equals(Color.Blue))
    {
        return "Yes you can";
    }
    return "No you can't";
}

Pergunta: Existe um aumento na complexidade introduzido por não incluir o elsebloco?

Estou com a impressão de que a elseintenção mais diretamente indica, afirmando o fato de que o código nos dois blocos está diretamente relacionado.

Além disso, acho que posso evitar erros sutis na lógica, especialmente após modificações no código posteriormente.

Pegue esta variação do meu exemplo simplificado (ignorando o fato do oroperador, pois este é um exemplo propositadamente simplificado):

bool CanLeaveWithoutUmbrella()
{
    if(sky.Color != Color.Blue)
    {
        return false;
    }
    return true;
}

Agora, alguém pode adicionar um novo ifbloco com base em uma condição após o primeiro exemplo, sem imediatamente reconhecer corretamente que a primeira condição está colocando uma restrição em sua própria condição.

Se um elsebloco estivesse presente, quem adicionasse a nova condição seria forçado a mover o conteúdo do elsebloco (e se, de alguma forma, passar por cima dele, a heurística mostrará que o código é inacessível, o que não ocorre no caso de um ifrestringir o outro) .

É claro que existem outras maneiras pelas quais o exemplo específico deve ser definido de qualquer maneira, todos os que impedem essa situação, mas é apenas um exemplo.

O comprimento do exemplo que eu dei pode distorcer o aspecto visual disso; portanto, suponha que o espaço ocupado entre parênteses seja relativamente insignificante para o restante do método.

Esqueci de mencionar um caso em que concordo com a omissão de um bloco else e é quando o uso é usado ifpara aplicar uma restrição que deve ser logicamente satisfeita para todo o código a seguir, como uma verificação nula (ou qualquer outra proteção) .

Selali Adobor
fonte
Na realidade, quando existe uma declaração "se" com um "retorno", ela pode ser vista como "bloqueio de guarda" em> 50% de todos os casos. Portanto, acho que aqui está seu equívoco de que um "bloco de guarda" é o caso excepcional, e o seu exemplo, o caso regular.
Doc Brown
2
@AssortedTrailmix: Concordo que um caso como o acima pode ser mais legível ao escrever a elsecláusula (a meu gosto, será ainda mais legível ao deixar de fora os colchetes desnecessários. Mas acho que o exemplo não é bem escolhido, porque no caso acima eu escreveria return sky.Color == Color.Blue(sem if / else) Um exemplo com um tipo de retorno diferente do que bool provavelmente tornaria isso mais claro.
Doc Brown
1
como apontado nos comentários acima, o exemplo sugerido é muito simplificado, convidando respostas especulativas. Quanto à parte da pergunta que começa com "Alguém agora pode adicionar um novo bloco se ..." , ela já foi solicitada e respondida várias vezes antes, consulte, por exemplo, abordagens para verificar várias condições? e várias perguntas vinculadas a ele.
gnat
1
Não vejo o que mais os blocos têm a ver com o princípio DRY. O DRY tem mais a ver com abstração e referências a códigos comumente usados ​​na forma de funções, métodos e objetos do que o que você está pedindo. Sua pergunta está relacionada à legibilidade do código e, possivelmente, convenções ou idiomas.
Shashank Gupta
2
Votação para reabrir. As boas respostas para essa pergunta não são particularmente baseadas em opiniões. Se o exemplo fornecido em uma pergunta for inadequado, melhorá-lo editando seria a coisa mais razoável a se fazer, não votar para fechar por um motivo que não é realmente verdadeiro.
Michael Shaw

Respostas:

27

Na minha opinião, o bloco else explícito é preferível. Quando eu vejo isso:

if (sky.Color != Blue) {
   ...
}  else {
   ...
}

Eu sei que estou lidando com opções mutuamente exclusivas. Não preciso ler o que há dentro dos blocos if para saber.

Quando eu vejo isso:

if (sky.Color != Blue) {
   ...
} 
return false;

Parece, à primeira vista, que ele retorna falso independentemente e tem um efeito colateral opcional: o céu não é azul.

A meu ver, a estrutura do segundo código não reflete o que o código realmente faz. Isso é ruim. Em vez disso, escolha a primeira opção que reflete a estrutura lógica. Não quero verificar a lógica de retorno / interrupção / continuação em cada bloco para conhecer o fluxo lógico.

Por que alguém prefere o outro é um mistério para mim, espero que alguém tome a posição oposta e me esclareça com a resposta.

Esqueci de mencionar um caso em que concordo com a omissão de um bloco else e é quando utilizo um bloco if para aplicar uma restrição que deve ser logicamente satisfeita para todo o código a seguir, como uma verificação nula (ou qualquer outra proteção) )

Eu diria que as condições de guarda são boas no caso de você ter deixado o caminho feliz. Ocorreu um erro ou falha que impede a execução comum de continuar. Quando isso acontece, você deve lançar uma exceção, retornar um código de erro ou o que for apropriado para o seu idioma.

Logo após a versão original desta resposta, código como este foi descoberto no meu projeto:

Foobar merge(Foobar left, Foobar right) {
   if(!left.isEmpty()) {
      if(!right.isEmpty()) {
          Foobar merged = // construct merged Foobar
          // missing return merged statement
      }
      return left;
   }
   return right;
}

Por não colocar o retorno dentro de outras partes, o fato de que faltava uma declaração de retorno foi ignorado. Se mais tivesse sido empregado, o código nem teria sido compilado. (Obviamente, a preocupação muito maior que tenho é que os testes escritos neste código foram muito ruins para não detectar esse problema.)

Winston Ewert
fonte
Isso resume meus pensamentos, fico feliz por não ser a única pessoa que pensa assim. Trabalho com programadores, ferramentas e IDEs preferindo a remoção do elsebloco (pode ser um incômodo quando sou forçado a fazê-lo apenas para me manter alinhado com as convenções de uma base de código). Também estou interessado em ver o contraponto aqui.
Selali Adobor 6/09/14
1
Eu varia nisso. Algumas vezes, o outro explícito não está ganhando muito, pois é improvável que acione os sutis erros lógicos mencionados pelo OP - é apenas ruído. Mas, na maioria das vezes, concordo e, ocasionalmente, farei algo como para } // elseonde o outro normalmente iria como um compromisso entre os dois.
Telastyn 6/09/14
3
@ Telastyn, mas o que você ganha ao pular o resto? Concordo que o benefício é muito pequeno em muitas situações, mas mesmo com o pequeno benefício, acho que vale a pena fazê-lo. Certamente, me parece bizarro colocar algo em um comentário quando poderia ser código.
Winston Ewert
2
Eu acho que você tem a mesma concepção errada do OP - "se" com "retorno" pode ser visto principalmente como um bloco de guarda, então você está discutindo aqui o caso excepcional, enquanto o caso mais frequente de blocos de guarda é IMHO mais legível sem o "outro".
Doc Brown
... então o que você escreveu não está errado, mas o IMHO não vale muitos votos positivos.
Doc Brown
23

A principal razão para remover o elsebloco que encontrei é o recuo excessivo. O curto-circuito dos elseblocos permite uma estrutura de código muito mais plana.

Muitas ifdeclarações são essencialmente guardas. Eles estão impedindo o cancelamento de referência de ponteiros nulos e outros "não faça isso!" erros. E eles levam ao término rápido da função atual. Por exemplo:

if (obj === NULL) {
    return NULL;
}
// further processing that now can assume obj attributes are set

Agora pode parecer que uma elsecláusula seria muito razoável aqui:

if (obj === NULL) {
    return NULL;
}
else if (obj.color != Blue) {
   // handle case that object is not blue
}

E, de fato, se é isso que você está fazendo, não é muito mais recuado que o exemplo acima. Mas isso raramente é o que você faz com estruturas de dados reais de vários níveis (uma condição que ocorre o tempo todo ao processar formatos comuns como JSON, HTML e XML). Portanto, se você deseja modificar o texto de todos os filhos de um determinado nó HTML, diga:

elements = tree.xpath(".//p");
if (elements === NULL) {
    return NULL;
}
p = elements[0]
if ((p.children === NULL) or (p.children.length == 0)) {
    return NULL;
}
for (c in p.children) {
    c.text = c.text.toUpperCase();
}
return p;

Os guardas não aumentam o recuo do código. Com uma elsecláusula, todo o trabalho real começa a se mover para a direita:

elements = tree.xpath(".//p");
if (elements === NULL) {
    return NULL;
}
else {
    p = elements[0]
    if ((p.children === NULL) or (p.children.length == 0)) {
        return NULL;
    }
    else {
        for (c in p.children) {
            c.text = c.text.toUpperCase();
        }
        return p;
    }
}

Isso está começando a ter um movimento significativo para a direita, e este é um exemplo bastante trivial, com apenas duas condições de guarda. Cada guarda adicional adiciona outro nível de recuo. Código real que eu escrevi processando XML ou consumindo feeds JSON detalhados pode facilmente empilhar 3, 4 ou mais condições de guarda. Se você sempre usa o else, acaba com um recuo de 4, 5 ou 6 níveis. Talvez mais. E isso definitivamente contribui para um senso de complexidade do código e mais tempo gasto para entender o que está alinhado com o que. O estilo rápido, plano e de saída antecipada elimina parte dessa "estrutura em excesso" e parece mais simples.

Adendo Os comentários a esta resposta me fizeram perceber que talvez não estivesse claro que o manuseio NULL / vazio não é o único motivo para condicionais de guarda. Nem perto! Embora este exemplo simples se concentre no manuseio NULL / vazio e não contenha outros motivos, pesquisar estruturas profundas como XML e ASTs em busca de conteúdo "relevante", por exemplo, geralmente possui uma longa série de testes específicos para eliminar nós irrelevantes e desinteressantes casos. Uma série de testes "se este nó não for relevante, retorne" causará o mesmo tipo de desvio à direita que os guardas NULL / vazios causarão. E, na prática, testes de relevância subsequentes são freqüentemente acoplados a proteções NULL / vazias para as estruturas de dados correspondentemente mais profundas pesquisadas - um golpe duplo para desvio à direita.

Jonathan Eunice
fonte
2
Esta é uma resposta detalhada e válida, mas vou acrescentar isso à pergunta (primeiro me escapou da cabeça); Há uma "exceção" em que sempre considero um elsebloco supérfluo, ao usar um ifbloco para aplicar uma restrição que deve ser logicamente satisfeita para todo o código a seguir, como uma verificação nula (ou qualquer outra proteção).
Selali Adobor 6/09/14
@AssortedTrailmix Acredito que se você excluir os casos em que é possível fazer rotineiramente uma saída antecipada da thencláusula (como declarações de guarda sucessivas), não haverá valor específico para evitar a elsecláusula em particular . De fato, isso reduziria a clareza e potencialmente seria perigoso (ao não declarar simplesmente a estrutura alternativa que existe / deveria estar lá).
Jonathan Eunice
Eu acho que é razoável usar condições de guarda para ejetar uma vez que você tenha deixado o caminho feliz. No entanto, também acho que, para o processamento de XML / JSON, se você validar a entrada em um esquema primeiro, receberá melhores mensagens de erro e código mais limpo.
Winston Ewert
Esta é uma explicação perfeita dos casos em que mais é evitado.
Chris Schiffhauer
2
Eu teria envolvido a API para não produzir NULLs tolos.
Winston Ewert
4

Quando eu vejo isso:

if(something){
    return lamp;
}
return table;

Eu vejo um idioma comum . Um padrão comum , que na minha cabeça se traduz em:

if(something){
    return lamp;
}
else return table;

Como esse é um idioma muito comum na programação, o programador que está acostumado a ver esse tipo de código tem uma 'tradução' implícita em sua cabeça sempre que o vê, que o 'converte' no significado apropriado.

Não preciso do explícito else, minha cabeça 'coloca lá' para mim porque reconheceu o padrão como um todo . Entendo o significado de todo o padrão comum, por isso não preciso de pequenos detalhes para esclarecê-lo.

Por exemplo, leia o seguinte texto:

insira a descrição da imagem aqui

Você leu isso?

Eu amo Paris na primavera

Se sim, você está errado. Leia o texto novamente. O que está realmente escrito é

Eu amo Paris no a primavera

Existem duas palavras "as" no texto. Então, por que você perdeu um dos dois?

É porque seu cérebro viu um padrão comum e o traduziu imediatamente para seu significado conhecido. Não era necessário ler a coisa inteira detalhadamente para descobrir o significado, porque já reconhecia o padrão ao vê-lo. Por isso, ignorou o extra "the ".

A mesma coisa com o idioma acima. Programadores que leram muito código estão acostumados a ver esse padrão , de if(something) {return x;} return y;. Eles não precisam de explícitos elsepara entender o significado, porque o cérebro deles 'coloca isso para eles'. Eles o reconhecem como um padrão com um significado conhecido: "o que está depois da chave de fechamento será executado apenas como implícito elseno anterior if".

Então, na minha opinião, o elseé desnecessário. Mas basta usar o que parece mais legível.

Aviv Cohn
fonte
2

Eles adicionam desorganização visual no código; portanto, podem adicionar complexidade.

No entanto, o oposto também é verdadeiro, a refatoração excessiva para reduzir o comprimento do código também pode adicionar complexidade (não neste caso simples, é claro).

Concordo com a afirmação de que o elsebloco indica intenção. Mas minha conclusão é diferente, porque você está adicionando complexidade ao seu código para conseguir isso.

Não concordo com sua afirmação de que ela permite evitar erros sutis na lógica.

Vamos ver um exemplo, agora ele só deve retornar verdadeiro se o céu for azul e houver alta umidade, a umidade não for alta ou se o céu for vermelho. Mas então, você confunde uma chave e a coloca errada else:

bool CanLeaveWithoutUmbrella() {
    if(sky.Color == Color.Blue)
    {
        if (sky.Humidity == Humidity.High) 
        {
            return true;
        } 
    else if (sky.Humidity != Humidity.High)
    {
        return true;
    } else if (sky.Color == Color.Red) 
    {
            return true;
    }
    } else 
    {
       return false;
    }
}

Vimos todo esse tipo de erro bobo no código de produção e pode ser muito difícil de detectar.

Eu sugeriria o seguinte:

Acho isso mais fácil de ler, porque posso ver rapidamente que o padrão é false.

Além disso, a idéia de forçar a movimentação do conteúdo de um bloco para inserir uma nova cláusula é suscetível a erros. De alguma forma, isso se relaciona com o princípio aberto / fechado (o código deve estar aberto para extensão, mas fechado para modificação). Você está forçando a modificar o código existente (por exemplo, o codificador pode apresentar um erro no balanceamento de chaves).

Finalmente, você está levando as pessoas a responder de uma maneira predeterminada que você acha que é a certa. Por exemplo, você apresenta um exemplo muito simples, mas depois declara que as pessoas não devem levar isso em consideração. No entanto, a simplicidade do exemplo afeta toda a questão:

  • Se o caso for tão simples quanto o apresentado, minha resposta seria omitir quaisquer if elsecláusulas.

    return (sky.Color == Color.Blue);

  • Se o caso for um pouco mais complicado, com várias cláusulas, eu responderia:

    bool CanLeaveWithoutUmbrella () {

    if(sky.Color == Color.Blue && sky.Humidity == Humidity.High) {
        return true;
    } else if (sky.Humidity != Humidity.High) {
        return true;
    } else if (sky.Color == Color.Red) {
        return true;
    }
    
    return false;
    

    }

  • Se o caso for complicado e nem todas as cláusulas retornarem verdadeiras (talvez elas retornem um duplo), e eu quero introduzir algum comando de ponto de interrupção ou loggin, consideraria algo como:

    double CanLeaveWithoutUmbrella() {
    
        double risk = 0.0;
    
        if(sky.Color == Color.Blue && sky.Humidity == Humidity.High) {
            risk = 1.0;
        } else if (sky.Humidity != Humidity.High) {
            risk = 0.5;
        } else if (sky.Color == Color.Red) {
            risk = 0.8;
        }
    
        Log("value of risk:" + risk);
        return risk;
    

    }

  • Se não estamos falando de um método que retorna algo, mas de um bloco if-else que define alguma variável ou chama um método, então sim, incluiria uma elsecláusula final .

Portanto, a simplicidade do exemplo também é um fator a considerar.

FranMowinckel
fonte
Eu sinto que você está cometendo o mesmo erro que outros cometeram e transformando o exemplo um pouco demais, examinando um novo problema, mas preciso levar um segundo e examinar o processo de obter o seu exemplo, então, por enquanto, parece válido para mim. E uma observação lateral, isso não está relacionado ao princípio de abrir / fechar . O escopo é muito estreito para aplicar esse idioma. Mas é interessante observar que sua aplicação em um nível superior removeria todo esse aspecto do problema (removendo a necessidade de qualquer modificação da função).
Selali Adobor 6/09/14
1
Mas se não pudermos modificar esse exemplo simplificado adicionando cláusulas, nem modificar como ele está escrito, nem fazer nenhuma alteração, considere que essa questão se refere apenas àquelas linhas de código e não é mais uma questão geral. Nesse caso, você está completamente certo, não adiciona nenhuma complexidade (nem a remove) à cláusula else porque não havia complexidade para começar. No entanto, você não está sendo justo, porque é você quem sugere a ideia de que novas cláusulas possam ser adicionadas.
FranMowinckel
E, como observei, não estou afirmando que isso esteja relacionado ao conhecido princípio de abrir / fechar. Penso que a ideia de levar as pessoas a modificar o seu código, conforme sugerido, está sujeita a erros. Estou apenas tirando essa idéia desse princípio.
FranMowinckel
Aguarde as edições, você está pronto para algo de bom, eu estou digitando um comentário agora mesmo
Selali Adobor 6/14
1

Embora o caso if-else descrito tenha maior complexidade, na maioria das situações práticas (mas não em todas), isso não importa muito.

Anos atrás, quando eu estava trabalhando em um software usado para a indústria da aviação (tinha que passar por um processo de certificação), a sua foi uma pergunta que surgiu. Aconteceu que havia um custo financeiro para essa declaração 'else', pois aumentou a complexidade do código.

Aqui está o porquê.

A presença do 'else' criou um terceiro caso implícito que teve que ser avaliado - aquele do 'if' nem do 'else' sendo tomados. Você pode estar pensando (como eu estava na época) WTF?

A explicação foi assim ...

Do ponto de vista lógico, existem apenas as duas opções - o 'if' e o 'else'. No entanto, o que estávamos certificando era o arquivo de objeto que o compilador estava gerando. Portanto, quando havia um 'mais', era necessário fazer uma análise para confirmar que o código de ramificação gerado estava correto e que um terceiro caminho misterioso não apareceu.

Compare isso com o caso em que existe apenas um 'se' e nenhum 'mais'. Nesse caso, existem apenas duas opções - o caminho 'se' e o caminho 'não-se'. Dois casos a avaliar são menos complexos que três.

Espero que isto ajude.

Sparky
fonte
em um arquivo de objeto, nenhum caso seria renderizado como jmps idêntico?
Winston Ewert
@WinstonEwert - Seria de esperar que eles fossem iguais. No entanto, o que é processado e o que se espera que seja processado são duas coisas diferentes, independentemente de quanto se sobrepõem.
Sparky
(Eu acho que SE comeu meu comentário ...) Esta é uma resposta muito interessante. Enquanto eu diria que o caso exposto é um pouco de nicho, mostra que algumas ferramentas encontrariam uma diferença entre os dois (mesmo a métrica de código mais comum baseada no fluxo que vejo, complexidade ciclomática, não medirá diferença.
Selali Adobor 6/09/14
1

O objetivo mais importante do código é ser compreensível como confirmado (em oposição a facilmente refatorado, o que é útil, mas menos importante). Um exemplo de Python um pouco mais complexo pode ser usado para ilustrar isso:

def value(key):
    if key == FROB:
        return FOO
    elif key == NIK:
        return BAR
    [...]
    else:
        return BAZ

Isso é bastante claro - é o equivalente a uma casedeclaração ou pesquisa de dicionário, a versão de nível superior de return foo == bar:

KEY_VALUES = {FROB: FOO, NIK: BAR, [...]}
DEFAULT_VALUE = BAZ

def value(key):
    return KEY_VALUES.get(key, default=DEFAULT_VALUE)

Isso é mais claro, porque, embora o número de tokens seja maior (32 vs 24 para o código original com dois pares de chave / valor), a semântica da função agora é explícita: é apenas uma pesquisa.

(Isso tem consequências para a refatoração - se você quiser ter um efeito colateral key == NIK, terá três opções:

  1. Reverta para o estilo if/ elif/ elsee insira uma única linha no key == NIKestojo.
  2. Salve o resultado da pesquisa em um valor e adicione uma ifinstrução valuepara o caso especial antes de retornar.
  3. Coloque uma ifdeclaração no chamador.

Agora vemos por que a função de pesquisa é uma poderosa simplificação: torna a terceira opção obviamente a solução mais simples, pois além de resultar em uma diferença menor do que a primeira opção , é a única que garante valueapenas uma coisa. )

Voltando ao código do OP, acho que isso pode servir como um guia para a complexidade das elseinstruções: o código com uma elsedeclaração explícita é objetivamente mais complexo, mas mais importante é se torna a semântica do código clara e simples. E isso deve ser respondido caso a caso, pois cada parte do código tem um significado diferente.

l0b0
fonte
Gosto da reescrita da tabela de pesquisa do caso de comutação simples. E eu sei que esse é um idioma preferido do Python. Na prática, no entanto, muitas vezes acho que condicionais de várias vias (aka caseou switchdeclarações) são menos homogêneos. Várias opções são apenas retornos de valor diretos, mas um ou dois casos precisam de processamento extra. E quando você descobre a estratégia de pesquisa não se adequar, você precisa reescrever em uma totalmente diferente if elif... elsesintaxe.
Jonathan Eunice
É por isso que acho que a pesquisa geralmente deve ser uma linha ou uma função, para que a lógica em torno do resultado da pesquisa seja separada da lógica de procurá-la em primeiro lugar.
L0b0
Essa pergunta traz uma visão de nível superior à questão que definitivamente deve ser mencionada e mantida em mente, mas eu sinto que uma restrição suficiente foi colocada no caso em que você pode se sentir livre para expandir sua própria postura
Selali Adobor,
1

Eu acho que depende do tipo de ifdeclaração que você está usando. Algumas ifinstruções podem ser vistas como expressões e outras apenas como instruções de fluxo de controle.

Seu exemplo parece uma expressão para mim. Nas linguagens do tipo C, o operador ternário ?:é muito parecido com uma ifdeclaração, mas é uma expressão e a outra parte não pode ser omitida. Faz sentido que um ramo else não possa ser omitido em uma expressão, porque a expressão sempre deve ter um valor.

Usando o operador ternário, seu exemplo seria assim:

bool CanLeaveWithoutUmbrella()
{
    return (sky.Color == Color.Blue)
               ? true
               : false;
}

Já que é uma expressão booleana, ela realmente deve ser simplificada até o fim

bool CanLeaveWithoutUmbrella()
{
    return (sky.Color == Color.Blue);
}

A inclusão de uma cláusula else explícita torna sua intenção mais clara. Os guardas podem fazer sentido em algumas situações, mas ao selecionar entre dois valores possíveis, nenhum dos quais é excepcional ou incomum, eles não ajudam.

Michael Shaw
fonte
+1, o OP está IMHO discutindo um caso patológico que deve ser melhor escrito da maneira que você mostrou acima. O caso muito mais frequente para "se" com "retorno" é, para minha experiência, o caso "guarda".
Doc Brown
Não sei por que isso continua acontecendo, mas as pessoas continuam ignorando o primeiro parágrafo da pergunta. De fato, todos vocês estão usando a linha exata de código que digo explicitamente para não usar. I ask that you ignore the many other ways the function can be written, and changes that can be made to it. (I know most people are itching to write return sky.Color == Color.Blue.)
Selali Adobor
Ao reler sua última pergunta, você parece entender mal a intenção da pergunta.
Selali Adobor
Você usou um exemplo que estava praticamente implorando para ser simplificado. Eu li e prestei atenção ao seu primeiro parágrafo, e é por isso que meu primeiro bloco de código está lá, em vez de pular diretamente para a melhor maneira de fazer esse exemplo específico. Se você acha que eu (ou qualquer outra pessoa) não entendi bem a intenção de sua pergunta, talvez você deva editá-la para deixar sua intenção clara.
Michael Shaw
Eu o editaria, mas não saberia como torná-lo mais claro, já que uso a linha exata de código que você faz e diz "não faça isso". Há infinitas maneiras que você poderia reescrever o código e remover a questão, não posso cobrir todos eles, ainda assim muitas pessoas têm entendido a minha declaração e respeitado isso ...
Selali Adobor
1

Outra coisa a se pensar nesse argumento são os hábitos que cada abordagem promove.

  • if / else renomeia uma amostra de codificação nos termos de ramificações. Como você diz, às vezes essa explicitação é boa. Realmente existem dois caminhos de execução possíveis e queremos destacar isso.

  • Em outros casos, há apenas um caminho de execução e todas essas coisas excepcionais com as quais os programadores devem se preocupar. Como você mencionou, as cláusulas de guarda são ótimas para isso.

  • Existe, é claro, o caso 3 ou mais (n), em que geralmente encorajamos o abandono da cadeia if / else e o uso de uma declaração case / switch ou polimorfismo.

O princípio orientador que tenho em mente aqui é que um método ou função deve ter apenas um propósito. Qualquer código com uma instrução if / else ou switch é apenas para ramificação. Portanto, deve ser absurdamente curto (4 linhas) e delegar apenas ao método correto ou produzir o resultado óbvio (como no seu exemplo). Quando seu código é tão curto, é difícil perder esses retornos múltiplos :) Assim como "goto considerado prejudicial", qualquer declaração de ramificação pode ser abusada; portanto, colocar algum pensamento crítico em outras declarações geralmente vale a pena. Como você mencionou, apenas ter um bloco else fornece um lugar para o código agrupar. Você e sua equipe terão que decidir como você se sente sobre isso.

O que a ferramenta está realmente reclamando é o fato de você ter um retorno / quebra / lançamento dentro do bloco if. No que diz respeito, você escreveu uma cláusula de guarda, mas estragou tudo. Você pode optar por deixar a ferramenta feliz ou "entreter" sua sugestão sem aceitá-la.

Steve Jackson
fonte
Eu nunca pensei sobre o fato de a ferramenta estar considerando a cláusula de se ser uma guarda assim que ela se encaixa no padrão de uma, provavelmente é o caso e explica o raciocínio de um "grupo de proponentes" por sua ausência
Selali Adobor
@AssortedTrailmix Certo, você está pensando elsesemanticamente, as ferramentas de análise de código geralmente procuram instruções estranhas, porque geralmente destacam um mal-entendido sobre o fluxo de controle. O elsedepois de um ifque retorna é bits desperdiçados. if(1 == 1)geralmente aciona o mesmo tipo de aviso.
Steve Jackson
1

Eu acho que a resposta é que depende. Seu exemplo de código (como você indica indiretamente) está simplesmente retornando a avaliação de uma condição e é melhor representado como você obviamente sabe, como uma única expressão.

Para determinar se a adição de uma condição else esclarece ou oculta o código, você precisa determinar o que o IF / ELSE representa. É uma expressão (como no seu exemplo)? Nesse caso, essa expressão deve ser extraída e usada. É uma condição de guarda? Nesse caso, o ELSE é desnecessário e enganoso, pois faz com que os dois ramos pareçam equivalentes. É um exemplo de polimorfismo processual? Extraia-o. Está definindo pré-condições? Então pode ser essencial.

Antes de decidir incluir ou eliminar o ELSE, decida o que ele representa, que informará se ele deve estar presente ou não.

jmoreno
fonte
0

A resposta é um pouco subjetiva. Um elsebloco pode ajudar na legibilidade, estabelecendo que um bloco de código seja executado exclusivamente em outro bloco de código, sem a necessidade de ler os dois blocos. Isso pode ser útil se, digamos, ambos os blocos forem relativamente longos. Existem casos, embora onde ter ifs sem elses possa ser mais legível. Compare o seguinte código com um número de if/elseblocos:

if(a == b) {
    if(c <= d) {
        if(e != f) {
            a.doSomeStuff();
            c.makeSomeThingsWith(b);
            f.undo(d, e);
            return 9;
        } else {
            e++;
            return 3;
        }
    } else {
        return 1;
    }
} else {
    return 0;
}

com:

if(a != b) {
    return 0;
}

if(c > d) {
    return 1;
}

if(e != f) {
    e++;
    return 3;
}

a.doSomeStuff();
c.makeSomeThingsWith(b);
f.undo(d, e);
return 9;

O segundo bloco de código altera as ifinstruções aninhadas em cláusulas de guarda. Isso reduz a indentação e torna algumas das condições de retorno mais claras ("se a != b, então retornaremos 0!")


Foi indicado nos comentários que pode haver alguns buracos no meu exemplo, por isso vou detalhar um pouco mais.

Poderíamos ter escrito o primeiro bloco de código aqui para torná-lo mais legível:

if(a != b) {
    return 0;
} else if(c > d) {
    return 1;
} else if(e != f) {
    e++;
    return 3;
} else {
    a.doSomeStuff();
    c.makeSomeThingsWith(b);
    f.undo(d, e);
    return 9;
}

Esse código é equivalente aos dois blocos acima, mas apresenta alguns desafios. A elsecláusula aqui conecta essas instruções if juntas. Na versão da cláusula de guarda acima, as cláusulas de guarda são independentes uma da outra. Adicionar um novo significa simplesmente escrever um novo ifentre o último ife o restante da lógica. Aqui, isso também pode ser feito, com apenas um pouco mais de carga cognitiva. A diferença, porém, é quando alguma lógica adicional precisa ocorrer antes dessa nova cláusula de guarda. Quando as declarações eram independentes, poderíamos fazer:

...
if(e != f) {
    e++;
    return 3;
}

g.doSomeLogicWith(a);

if(!g.isGood()) {
    return 4;
}

a.doSomeStuff();
...

Com a if/elsecorrente conectada , isso não é tão fácil de fazer. De fato, o caminho mais fácil a seguir seria quebrar a corrente para parecer mais com a versão da cláusula de guarda.

Mas realmente, isso faz sentido. Usar if/elsefracamente implica um relacionamento entre as partes. Poderíamos supor que isso a != besteja de alguma forma relacionado c > dna if/elseversão encadeada. Essa suposição seria enganosa, como podemos ver na versão da cláusula de guarda que elas não são, de fato, relacionadas. Isso significa que podemos fazer mais com cláusulas independentes, como reorganizá-las (talvez para otimizar o desempenho da consulta ou melhorar o fluxo lógico). Também podemos ignorá-los cognitivamente quando passamos por eles. Em uma if/elsecadeia, tenho menos certeza de que a relação de equivalência entre ae bnão aparecerá mais tarde.

O relacionamento implícito elsetambém é aparente no código de exemplo profundamente aninhado, mas de uma maneira diferente. As elseinstruções são separadas da condicional à qual estão anexadas e estão relacionadas em ordem inversa às condicionais (a primeira elseé anexada à última if, a última elseé anexada à primeira if). Isso cria uma dissonância cognitiva, na qual precisamos recuar no código, tentando alinhar a indentação em nossos cérebros. É como tentar combinar um par de parênteses. Fica ainda pior se o recuo estiver errado. Aparelhos opcionais também não ajudam.

Esqueci de mencionar um caso em que concordo com a omissão de um bloco else e é quando utilizo um bloco if para aplicar uma restrição que deve ser logicamente satisfeita para todo o código a seguir, como uma verificação nula (ou qualquer outra proteção) )

Esta é uma boa concessão, mas realmente não invalida nenhuma resposta. Eu poderia dizer que no seu exemplo de código:

bool CanLeaveWithoutUmbrella()
{
    if(sky.Color != Color.Blue)
    {
        return false;
    }
    return true;
}

uma interpretação válida para escrever código como esse poderia ser que "só devemos sair com um guarda-chuva se o céu não estiver azul". Aqui há uma restrição de que o céu deve estar azul para que o código a seguir seja válido para execução. Eu conheci a definição de sua concessão.

E se eu disser que, se a cor do céu é preta, é uma noite clara, então não é necessário um guarda-chuva. (Existem maneiras mais simples de escrever isso, mas existem maneiras mais simples de escrever o exemplo inteiro.)

bool CanLeaveWithoutUmbrella()
{
    if(sky.Color == Color.Blue)
    {
        return true;
    }

    if(sky.Color == Color.Black)
    {
        return true;
    }

    return false;
}

Como no exemplo maior acima, eu posso simplesmente adicionar a nova cláusula sem muito pensamento necessário. Em um caso if/else, não posso ter tanta certeza de que não vou estragar nada, principalmente se a lógica for mais complicada.

Também apontarei que seu exemplo simples também não é tão simples. Um teste para um valor não binário não é o mesmo que o inverso desse teste com resultados invertidos.

cbojar
fonte
1
Minha edição da pergunta aborda esse caso. Quando uma restrição que deve ser logicamente satisfeita para todo o código a seguir, como uma verificação nula (ou quaisquer outros guardas), concordo que a omissão de um elsebloco é essencial para facilitar a leitura.
Selali Adobor
1
Sua primeira versão é difícil de entender, mas não acho que isso seja necessário usando if / else. Veja como eu o escreveria: gist.github.com/winstonewert/c9e0955bc22ce44930fb .
Winston Ewert
@WinstonEwert Veja edições para responder aos seus comentários.
Cbojar
Sua edição faz alguns pontos válidos. E certamente acho que as cláusulas de guarda têm um lugar. Mas, no caso geral, sou a favor de outras cláusulas.
Winston Ewert
0

Você simplificou demais o seu exemplo. Qualquer uma das alternativas pode ser mais legível, dependendo da situação maior: especificamente, depende se um dos dois ramos da condição é anormal . Deixe-me mostrar: em um caso em que um dos ramos é igualmente provável, ...

void DoOneOfTwoThings(bool which)
{
    if (which)
        DoThingOne();
    else
        DoThingTwo();
}

... é mais legível do que ...

void DoOneOfTwoThings(bool which)
{
    if (which) {
        DoThingOne();
        return;
    }
    DoThingTwo();
}

... porque o primeiro exibe estrutura paralela e o segundo não. Mas, quando a maior parte da função é dedicada a uma tarefa e você só precisa verificar algumas pré-condições primeiro, ...

void ProcessInputOfTypeOne(String input)
{
    if (InputIsReallyTypeTwo(input)) {
        ProcessInputOfTypeTwo(input);
        return;
    }
    ProcessInputDefinitelyOfTypeOne(input);

}

... é mais legível do que ...

void ProcessInputOfTypeOne(String input)
{
    if (InputIsReallyTypeTwo(input))
        ProcessInputOfTypeTwo(input);
    else
        ProcessInputDefinitelyOfTypeOne(input);
}

... porque deliberadamente não configurar uma estrutura paralela fornece uma pista para um futuro leitor de que obter informações "realmente do tipo dois" aqui é anormal . (Na vida real, ProcessInputDefinitelyOfTypeOnenão seria uma função separada, portanto a diferença seria ainda mais acentuada.)

zwol
fonte
Talvez seja melhor escolher um exemplo mais concreto para o segundo caso. Minha reação imediata a isso é: "não pode ser tão anormal se você prosseguiu e processou de qualquer maneira".
Winston Ewert
@WinstonEwert anormal é talvez o termo errado. Mas eu concordo com Zack, que se você tiver um padrão (case1) e uma exceção, ele deverá ser escrito como » if-> Faça o excepcional e deixe« como uma estratégia de retorno antecipado . Pense no código, no qual o padrão inclui mais verificações, seria mais rápido lidar com o caso excepcional primeiro.
Thomas Junk
Isso parece estar se encaixando no caso que eu estava falando when using an if block to apply a constraint that must be logically satisfied for all following code, such as a null-check (or any other guards). Logicamente, qualquer trabalho ProcessInputOfTypeOnedepende do fato de que !InputIsReallyTypeTwo(input), portanto, precisamos capturá-lo fora do nosso processamento normal. Normalmente ProcessInputOfTypeTwo(input);seria realmente o throw ICan'tProccessThatExceptionque tornaria a semelhança mais óbvia.
Selali Adobor
@WinstonEwert Eu poderia ter formulado isso melhor, sim. Eu estava pensando em uma situação que surge muito ao codificar tokenizadores manualmente: em CSS, por exemplo, a sequência de caracteres " u+" geralmente introduz um token "unicode-range", mas se o próximo caractere não for um dígito hexadecimal, então é necessário fazer backup e processar o 'u' como um identificador. Portanto, o loop principal do scanner despacha para "escanear um token de alcance unicode" de maneira um tanto imprecisa e começa com "... se estivermos nesse caso especial incomum, faça backup e chame a sub-rotina scan-an-identifier".
zwol 6/09/14
@AssortedTrailmix Mesmo que o exemplo em que eu estava pensando não tivesse sido de uma base de código herdada na qual exceções não podem ser usadas, essa não é uma condição de erro , é apenas que o código que chama ProcessInputOfTypeOneusa uma verificação imprecisa, mas rápida, que às vezes pega o tipo dois.
Zwol 6/09/14
0

Sim, há um aumento na complexidade porque a cláusula else é redundante . Portanto, é melhor deixar de fora para manter o código enxuto e mesquinho. Está no mesmo sentido que omitir thisqualificadores redundantes (Java, C ++, C #) e omitir parênteses nas returninstruções, e assim por diante.

Um bom programador pensa "o que posso adicionar?" enquanto um grande programador pensa "o que posso remover?".

vidstige
fonte
1
Quando vejo a omissão do elsebloco, se é explicado em uma única linha (o que não é frequente), geralmente é com esse tipo de raciocínio, então +1 por apresentar o argumento "padrão" que vejo, mas não o vejo ache que é um raciocínio forte o suficiente. A good programmer things "what can I add?" while a great programmer things "what can I remove?".parece ser um ditado comum que muitas pessoas estendem demais sem consideração cuidadosa, e eu pessoalmente acho que esse é um desses casos.
Selali Adobor
Sim, lendo sua pergunta, imediatamente senti que você já havia se decidido, mas queria confirmação. Obviamente, essa resposta não é o que você queria, mas às vezes é importante considerar opiniões com as quais você não concorda. Talvez haja algo também isso?
vidstige
-1

Eu notei, mudando de idéia sobre este caso.

Quando essa pergunta foi feita há um ano, eu teria respondido algo como:

»Deve haver apenas um entrye um exitem um método« E com isso em mente, eu sugeriria refatorar o código para:

bool CanLeaveWithoutUmbrella()
{
    bool result

    if(sky.Color == Color.Blue)
    {
        result = true;
    }
    else
    {
        result = false;
    }

    return result;
}

Do ponto de vista da comunicação, é claro o que deve ser feito. Ifalgo é o caso, manipule o resultado de uma maneira , else manipule-o de outra maneira .

Mas isso envolve um desnecessário else . O elseexpressa o caso padrão . Então, em um segundo passo, eu poderia refatorá-lo para

bool CanLeaveWithoutUmbrella()
{
    bool result=false

    if(sky.Color == Color.Blue)
    {
        result = true;
    }
    return result;
}

Então surge a pergunta: por que usar uma variável temporária? O próximo passo da refatoração leva a:

bool CanLeaveWithoutUmbrella()
{

    if(sky.Color == Color.Blue)
    {
        return true;
    }
    return false;
}

Portanto, isso está claro no que faz. Eu daria um passo além:

bool CanLeaveWithoutUmbrella()
{

    if(sky.Color == Color.Blue) return true;
    return false;
}

Ou para os fracos de coração :

bool CanLeaveWithoutUmbrella()
{

    if(sky.Color == Color.Blue) { return true; }
    return false;
}

Você pode ler assim: »CanLeaveWithoutUmbrella retorna um bool . Se nada de especial acontecer, ele retornará false «Esta é a primeira leitura, de cima para baixo.

E então você "analisa" a condição »Se a condição for atendida, ela retornará verdadeira « Você poderia pular completamente a condicional e ter entendido o que essa função faz na caixa- padrão . Seus olhos e sua mente só precisam folhear algumas letras no lado esquerdo, sem ler a parte à direita.

Estou com a impressão de que o outro afirma mais diretamente a intenção, afirmando o fato de que o código nos dois blocos está diretamente relacionado.

Sim isso está certo. Mas essa informação é redundante . Você tem um caso padrão e uma "exceção" que é coberta pela ifdeclaração.

Ao lidar com estruturas mais complexas, eu escolheria outra estratégia, omitindo o ifirmão ou o feio switch.

Thomas Junk
fonte
A redundância de +1 mostra definitivamente um aumento não apenas na complexidade, mas também na confusão. Eu sei que sempre acabei tendo que verificar o código escrito assim exatamente por causa da redundância.
dietbuddha
1
Você pode remover os casos após o início So this is clear in what it does...e expandir nele? (Os casos anteriores também são de relevância questionável). Digo isso porque é o que a pergunta está fazendo, examinamos. Você declarou sua posição, omitindo o bloco else, e é na verdade a posição que precisa de mais explicações (e a que me levou a fazer essa pergunta). Da pergunta:I ask that you ignore the many other ways the function can be written, and changes that can be made to it. (I know most people are itching to write return sky.Color == Color.Blue.)
Selali Adobor
@AssortedTrailmix Sure! Sobre o que exatamente devo elaborar? Como sua pergunta inicial era »Os blocos else aumentam a complexidade do código?« E minha resposta é: sim. Nesse caso, pode ser omitido.
Thomas Junk
E não! Eu não entendo o voto negativo.
Thomas Junk
-1

Para resumir uma longa história, nesse caso, livrar-se da elsepalavra-chave é uma coisa boa. É totalmente redundante aqui e, dependendo de como você formata seu código, ele adiciona uma a três linhas completamente inúteis ao seu código. Considere o que está no ifbloco:

return true;

Portanto, se o ifbloco fosse inserido, todo o restante da função não é, por definição, executado. Mas se não for inserido, como não há mais ifinstruções, todo o restante da função é executado. Seria totalmente diferente se uma returndeclaração não estivesse no ifbloco; nesse caso, a presença ou ausência de um elsebloco teria um impacto, mas aqui, não teria um impacto.

Na sua pergunta, depois de inverter sua ifcondição e omitir else, você declarou o seguinte:

Agora, alguém pode adicionar um novo bloco if com base em uma condição após o primeiro exemplo, sem imediatamente reconhecer corretamente a primeira condição, colocando uma restrição em sua própria condição.

Eles precisam ler o código um pouco mais de perto então. Usamos linguagens de alto nível e uma organização clara do código para atenuar esses problemas, mas adicionar um elsebloco completamente redundante adiciona "ruído" e "confusão" ao código, e também não devemos inverter ou inverter novamente nossas ifcondições. Se eles não conseguem perceber a diferença, não sabem o que estão fazendo. Se eles podem dizer a diferença, eles sempre podem inverter as ifcondições originais .

Esqueci de mencionar um caso em que concordo com a omissão de um bloco else e é quando utilizo um bloco if para aplicar uma restrição que deve ser logicamente satisfeita para todo o código a seguir, como uma verificação nula (ou qualquer outra proteção) )

Isso é essencialmente o que está acontecendo aqui. Você está retornando do ifbloco, para que a ifavaliação da condição false seja uma restrição que deve ser logicamente satisfeita para todo o código a seguir.

Existe um aumento na complexidade introduzido por não incluir o bloco else?

Não, isso constituirá uma diminuição na complexidade do seu código e pode até constituir uma diminuição no código compilado, dependendo se certas otimizações super triviais ocorrerão ou não.

Panzercrisis
fonte
2
"Eles precisam ler o código um pouco mais de perto então." Existe um estilo de codificação para que os programadores possam prestar menos atenção aos detalhes detalhados e mais atenção ao que estão realmente fazendo. Se seu estilo faz com que você preste atenção desnecessariamente nesses detalhes, é um estilo ruim. "... linhas completamente inúteis ..." Elas não são inúteis - permitem ver rapidamente qual é a estrutura, sem perder tempo pensando sobre o que aconteceria se essa parte ou aquela parte fosse executada.
Michael Shaw
@ MichaelShaw Acho que a resposta de Aviv Cohn está no que estou falando.
Panzercrisis
-2

No exemplo específico que você deu, ele faz.

  • Isso força um revisor a ler o código novamente para garantir que não seja um erro.
  • Ele adiciona complexidade ciclomática (desnecessária) porque adiciona um nó ao gráfico de fluxo.

Eu acho que não poderia ficar mais simples do que isso:

bool CanLeaveWithoutUmbrella()
{
    return (sky.Color == Color.Blue);
}
Tulains Córdova
fonte
6
Abordo especificamente o fato de que este exemplo muito simplificado pode ser reescrito para remover a ifdeclaração. De fato, desencorajo explicitamente uma simplificação adicional usando o código exato que você usou. Além disso, a complexidade ciclomática não é aumentada pelo elsebloco.
Selali Adobor
-4

Eu sempre tento escrever meu código de forma que a intenção seja a mais clara possível. Seu exemplo está faltando algum contexto, então vou modificá-lo um pouco:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        if(this.color == Color.Blue)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}

Nossa intenção parece ser que possamos sair sem um guarda-chuva quando o céu estiver azul. No entanto, essa simples intenção é perdida em um mar de códigos. Existem várias maneiras de facilitar a compreensão.

Em primeiro lugar, há sua pergunta sobre o elseramo. Eu não me importo de qualquer maneira, mas tendem a deixar de fora o else. Entendo o argumento de ser explícito, mas minha opinião é que as ifinstruções devem agrupar ramos 'opcionais', como return trueesse. Eu não chamaria o return falseramo de 'opcional', pois está agindo como nosso padrão. De qualquer forma, vejo isso como um problema muito menor e muito menos importante do que os seguintes:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        if(this.color == Color.Blue)
        {
            return true;
        }
        return false;
    }
}

Uma questão muito mais importante é que suas filiais estão fazendo muito! Ramos, como tudo, devem ser "o mais simples possível, mas não mais". Nesse caso, você usou uma ifinstrução, que se ramifica entre blocos de código (coleções de instruções) quando tudo o que você realmente precisa se ramificar são expressões (valores). Isso fica claro, pois a) seus blocos de código contêm apenas instruções únicas eb) são a mesma instrução ( return). Podemos usar o operador ternário ?:para restringir o ramo apenas à parte diferente:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        return (this.color == Color.Blue)
            ? true
            : false;
    }
}

Agora chegamos à redundância óbvia de que nosso resultado é idêntico ao this.color == Color.Blue . Sei que sua pergunta nos pede para ignorar isso, mas acho realmente importante ressaltar que essa é uma maneira processual de primeira ordem: você está quebrando os valores de entrada e construindo alguns valores de saída. Tudo bem, mas se possível, é muito mais poderoso pensar em termos de relacionamentos ou transformações entre a entrada e a saída. Nesse caso, obviamente, o relacionamento é o mesmo, mas muitas vezes vejo código processual complicado como esse, que obscurece um relacionamento simples. Vamos em frente e faça esta simplificação:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        return (this.color == Color.Blue);
    }
}

A próxima coisa que gostaria de salientar é que sua API contém um duplo negativo: é possível para CanLeaveWithoutUmbrella que seja false. Obviamente, isso simplifica o NeedUmbrellaser true, então vamos fazer o seguinte:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool NeedUmbrella()
    {
        return (this.color != Color.Blue);
    }
}

Agora podemos começar a pensar no seu estilo de codificação. Parece que você está usando uma linguagem orientada a objetos, mas usando ifinstruções para ramificar entre blocos de código, você acabou escrevendo código processual .

No código orientado a objetos, todos os blocos de código são métodos e todas as ramificações são feitas por meio de despacho dinâmico , por exemplo. usando classes ou protótipos. É discutível se isso simplifica as coisas, mas deve tornar seu código mais consistente com o restante do idioma:

class Sky {
    bool NeedUmbrella()
    {
        return true;
    }
}

class BlueSky extends Sky {
    bool NeedUmbrella()
    {
        return false;
    }
}

Observe que o uso de operadores ternários para ramificar entre expressões é comum em funções programação .

Warbo
fonte
O primeiro parágrafo da resposta parece estar no caminho certo para responder à pergunta, mas diverge imediatamente para o tópico exato que peço explicitamente que se ignore esse exemplo muito simples . A partir da pergunta: I ask that you ignore the many other ways the function can be written, and changes that can be made to it. (I know most people are itching to write return sky.Color == Color.Blue.). Na verdade, você usa a linha exata de código que mencionei. Além disso, embora essa parte da pergunta esteja fora do tópico, eu diria que você está indo longe demais em sua inferência. Você mudou radicalmente qual é o código.
Selali Adobor
@AssortedTrailmix Isso é puramente uma questão de estilo. Faz sentido se preocupar com o estilo e faz sentido ignorá-lo (focando apenas nos resultados). Eu não acho que faz sentido se preocupar com return false;vs, else { return false; }embora não se preocupe com polaridade de ramificação, despacho dinâmico, ternários, etc. Se você estiver escrevendo código de procedimento em uma linguagem OO, é necessária uma mudança radical;)
Warbo
Você pode dizer algo assim em geral, mas não é aplicável quando você está respondendo a uma pergunta com parâmetros bem definidos (especialmente quando, sem respeitar esses parâmetros, você pode "abstrair" toda a pergunta). Eu diria também que a última frase está completamente errada. OOP é sobre "abstração processual", não "obliteração processual". Eu diria que o fato de você ter passado de uma linha para um número infinito de classes (uma para cada cor) mostra o porquê. Além disso, ainda estou imaginando qual despacho dinâmico deve ter a ver com uma if/elsedeclaração e o que é polaridade de ramificação.
Selali Adobor
Eu nunca disse que a solução OO é melhor (na verdade, eu prefiro programação funcional), apenas disse que é mais consistente com a linguagem. Por "polaridade", quis dizer qual coisa é "verdadeira" e qual é "falsa" (troquei com "NeedUmbrella"). No projeto OO, todo o fluxo de controle é determinado por despacho dinâmico. Por exemplo, Smalltalk não permite que qualquer outra coisa en.wikipedia.org/wiki/Smalltalk#Control_structures (ver também c2.com/cgi/wiki?HeInventedTheTerm )
Warbo
2
Eu teria votado positivo até chegar ao último parágrafo (sobre OO), que ganhou um voto negativo! Esse é exatamente o tipo de uso excessivo e impensado das construções OO que produz código ravioli , que é tão ilegível quanto o espaguete.
Zwol 6/09/14