Composições de estilo AND / OR if

13

Como você denomina instruções complexas AND / OR complexas para obter a máxima legibilidade? Como você recua e onde coloca as quebras de linha? Minha situação particular é algo como o seguinte. É definitivamente melhor do que quebrar tudo em uma linha, mas ainda parece confuso.

if (
    (
        x == y
        && a != b
        && p.isGood() 
        && (
            i + u == b
            || q >= a
        )
    )
    || k.isSomething()
    || m > n
) {
    doSomething();
}
JoJo
fonte
1
A estrutura de indentação e parênteses / braçadeira é intencional ou parte do estilo?
Ed S.
Engraçado. Eu fiz essa mesma pergunta no SO há uma semana e ela foi fechada. Fico feliz em ver esta pergunta viva em algum lugar!
Eric Belair

Respostas:

6

Faça variáveis ​​booleanas para cada pequena etapa:

bool step1 = i + u == b || q >= a;
bool step2 = a != b && p.isGood() && group1;
bool step3 = group2 || k.isSomething() || m > n;
if (step3) { doSomething(); }

Naturalmente, isso é semelhante à resposta da lacrologia, exceto com nomes diferentes para cada etapa.

Se você citar step1, step2e step3de maneiras que façam um bom senso conceitual, isso deve ser de longe o mais legível. p.isGood()e k.isSomething()às vezes pode ser invocado em situações em que não estaria no seu código original; portanto, isso não seria uma opção se essas funções fossem caras ou se você estiver executando esse código em um loop muito restrito.

Por outro lado, você não precisa se preocupar com o impacto no desempenho que pode resultar na criação de novas variáveis; um bom compilador os otimizará.

Um exemplo com detecção de colisão de retângulo (que você provavelmente não usaria devido ao desempenho acima mencionado):

if((a.x + a.width >= b.x || b.x + b.width >= a.x)
 && (a.y + a.height >= b.y || b.y + b.width >= a.y)
)
{ collision(); }

Pode se tornar:

bool horizMatch = a.x + a.width >= b.x || b.x + b.width >= a.x;
bool vertMatch = a.y + a.height >= b.y || b.y + b.width >= a.y;
if(horizMatch && vertMatch) { collision(); }

Além disso, se você quiser deixar seu código como está, acho que também seria ótimo. Sinceramente, acho que seu código é bastante legível. Obviamente, não sei exatamente o que a b x y i u p k m nsão, mas, no que diz respeito à estrutura, parece bom para mim.

Rei Miyasaka
fonte
8

Normalmente, refiz meu código para ser mais modular se meus condicionais ficarem tão complicados.

JohnFx
fonte
Eu evitaria refratores nessa situação. Seria tolo testar essas pequenas funções isoladamente, e ter as definições penduradas fora da função torna o código menos aparente.
Rei Miyasaka
Depende de quão bem você refatorar. Eu diria que isso torna seu código muito MAIS auto-aparente ter nomes de funções significativos em vez de uma série de condicionais que se parece com um livro de álgebra lançado no seu código.
JohnFx
Visualmente, porém, você teria suas funções penduradas para fora e não seria imediatamente óbvio o que exatamente a função faz. É momentaneamente uma caixa preta até você rolar para cima. A menos que você esteja em um idioma que permita funções em funções, não acho que seja muito conveniente, seja para o leitor ou para o escritor, independentemente de quão bem você refratar. E se você estiver em um idioma que permita funções em funções, é provável que a sintaxe não seja diferente de declarar uma ligação ou variável, por exemplo, let x = a > bou let f a b = a > b.
Rei Miyasaka
Variáveis ​​funcionariam igualmente bem. Eu consideraria essa refatoração também.
JohnFx
Ah ok.
Rei Miyasaka
8

Eu faria algo mais parecido com este, neste nível de complexidade

bool doIt = x == y && a != b && p.isGood();
doIt &= ( i + u == b || q >= a);
doIt |= k.isSomething() || m > n;

if(doIt)
{
    doSomething();
}

é feio, mas é legível e tenho certeza de que o compilador saberá como refatorá-lo.

Por outro lado, se eu me encontrar na situação de escrever uma declaração IF, repenso a solução, porque CERTINOS há uma maneira de simplificá-la ou, pelo menos, abstrair algumas dessas condições (por exemplo: talvez x == y && a != b && p.isGood()realmente apenas significa this->isPolygon()e eu posso fazer esse método;

Lacrymology
fonte
4

Estou ficando menos obcecado com o alinhamento vertical ao longo do tempo, mas minha forma geral com expressões de várias linhas é ...

if (   (   (expr1 == expr2)
        || (expr3 == expr4)
        || (expr5 == expr6)
       )
    && (   (expr7 == expr8)
        || (expr9 == expra)
       )
   )
{
  blah;
}

Pontos chave...

  • Os parênteses próximos alinham-se verticalmente com os parênteses abertos, assim como com os chavetas.
  • As subexpressões que se encaixam em uma linha estão dentro de uma linha e são alinhadas verticalmente à esquerda. Onde isso ajuda na legibilidade, os operadores infix nessas peças de linha única também são alinhados verticalmente.
  • As chaves de fechamento criam naturalmente linhas quase vazias, ajudando a agrupar visualmente as coisas.

Às vezes, formato +e / *ou outros operadores como esse também. Algumas expressões complexas assumem a forma de soma do produto ou produto da soma (que pode se referir a "somas" e "produtos" booleanos), portanto é provavelmente bastante comum que um estilo consistente valha a pena.

Tenha cuidado com isso, no entanto. Muitas vezes, é melhor refatorar (mover partes da expressão para uma função ou calcular e armazenar partes intermediárias em uma variável) em vez de usar o recuo para tentar tornar uma expressão de excesso de complexidade mais legível.

Se você prefere empilhar seus parênteses no lado direito, eu não odeio , mas acho que não é tão ruim. Levando longe demais, você corre o risco de que um erro possa deixar o recuo deturpando o que os parênteses fazem.

if (   (   (expr1 == expr2)
        || (expr3 == expr4)
        || (expr5 == expr6))

    && (   (expr7 == expr8)
        || (expr9 == expra)))
{
  blah;
}
Steve314
fonte
+1 Gosto do seu estilo. Ele respondeu minha pergunta diretamente, mas acho que Rei Miyasaka acertou a raiz do problema. Se eu tiver preguiça de usar o método de Rei, usarei seu estilo.
JoJo
Uau, isto é realmente bom.
Rei Miyasaka
1

http://www.codinghorror.com/blog/2006/01/flattening-arrow-code.html

Concordo com a resposta de JohnFx e com a de Lacrymology. Eu construí um monte de funções (de preferência estáticas) que atingem pequenos objetivos e depois os desenvolvemos de maneira inteligente.

Então, que tal algo assim? Observe que essa não é a solução perfeita, mas funciona. Existem maneiras de limpar isso ainda mais, mas são necessárias informações mais específicas. Nota: esse código deve ser executado tão rápido quanto o compilador é inteligente.

// Currently based on members or global vars
// (which is often a bad idea too)
function doSomethingCondirionally()
{
  if (k.isSomething() || m > n)
  {
    doSomething();
    return;
  }

  // Else ... 
  if (x != y) return;
  if (a == b) return;
  if (!p.isGood()) return;

  // Final, positive check
  if (i + u == b || q >= a)
  {
    doSomething();
  }
}
Trabalho
fonte
Se ter apenas um ponto de saída de uma função é algo que você valoriza (por exemplo, se você estiver em um idioma funcional), essa pode não ser a melhor opção, ou mesmo uma disponível. Dito isto, sim, é isso que costumo fazer.
Rei Miyasaka
E se for uma linguagem interpretada como Javascript?
JoJo
@ Rei Miyasaka, quanto eu valorizo ​​depende de um idioma. Embora eu goste da família de idiomas LISP, não precisei usar um no trabalho. Se eu tiver que redimensionar a função de outra pessoa, mas não tocar em nenhum outro código (geralmente uma realidade), faria algo como o descrito acima. Se eu puder escrever / reescrever essa lógica desde o início, minha abordagem será diferente, mas não posso escrever esse código sem ter um exemplo específico do que o autor está tentando fazer aqui.
Job
1
@Rei Miyasaka, essa pessoa pode ser um gênio ou cheio de porcaria. Não sei tudo, mas ficaria curioso para saber a defesa dessa pessoa do ponto de saída única. Há alguma discussão sobre isso aqui e no SO, e a impressão que tive foi que essa abordagem tem sido popular entre os acadêmicos nos anos 80, talvez, mas não importa mais e pode de fato dificultar a legibilidade. Obviamente, se você estiver fazendo tudo no estilo funcional do LINQ, esse problema nem surgirá.
Job
2
@Job @Steve Acho que é uma diretriz mais importante em idiomas que exigem desalocação explícita. Obviamente, não para todas as funções, mas provavelmente é um hábito que os programadores iniciantes foram incentivados a manter, a fim de evitar esquecer de liberar recursos.
Rei Miyasaka
1

Pelo que vale, fiquei surpreso ao ver que seu exemplo se parece muito com os complicados predicados que escrevi. Concordo com os outros que um predicado complicado não é o melhor para manutenção ou legibilidade, mas ocasionalmente eles surgem.

Deixe-me enfatizar que você fez esta parte correta: && a != b NUNCA coloque o conector lógico no final de uma linha, é muito fácil perder visualmente. Outro local em que você nunca deve colocar um operador no final da linha está na concatenação de cadeias, em idiomas com esse operador.

Faça isso:

String a = b
   + "something"
   + c
   ;

Não faça isso:

String a = b +
   "something" +
   c;
Bruce Ediger
fonte
Você tem alguma lógica ou estudos que apoiam sua afirmação para os conectores lógicos ou é apenas sua preferência declarada como fato? Eu já ouvi muito isso em vários lugares, e nunca entendi (ao contrário dos condicionais yoda, que têm uma razão válida [se equivocada]).
Caleb Huitt - cjhuitt
@Caleb - A matemática tem sido escrita dessa maneira há séculos. Ao ignorar o código, focamos no lado esquerdo de cada linha. Uma linha que começa com um operador é obviamente uma continuação da linha anterior, e não uma nova instrução indentada incorretamente.
precisa
Eu também gosto de prefixar operadores matemáticos. Eu percebi que maneira de tarde na minha carreira de programação :)
JoJo
0

Se o condicional é tão complicado, geralmente é uma indicação de que ele deve ser dividido em partes. Talvez uma cláusula possa ser atribuída a uma variável intermediária. Talvez uma cláusula possa ser transformada em um método auxiliar. Geralmente, prefiro não ter tantos ands e ors em uma linha.

Zhehao Mao
fonte
0

Você pode dividir o código em várias instruções, facilitando a compreensão. Mas um ninja de verdade faria algo assim. :-)

if
(
    (
        x == y
    &&
        a != b
    &&
        p.isGood()
    &&
        (
            i + u == b
        ||
            q >= a
        )
    )
||
    k.isSomething()
||
    m > n
)
{
    doSomething();
}
Daniel Lubarov
fonte
5
Sou fã de espaços em branco, mas isso é excessivamente preenchido com linhas quase vazias para o meu gosto.
Steve314