Pergunta

É algo que me incomodou em todas as linguagens que usei, tenho uma instrução if, mas a parte condicional tem tantas verificações que tenho que dividi-la em várias linhas, usar uma instrução if aninhada ou apenas aceitar que é feia e seguir em frente com a minha vida.

Você descobriu algum outro método que possa ser útil para mim e para qualquer outra pessoa que tenha tido o mesmo problema?

Exemplo, tudo em uma linha:

if (var1 = true && var2 = true && var2 = true && var3 = true && var4 = true && var5 = true && var6 = true)
{

Exemplo, multilinha:

if (var1 = true && var2 = true && var2 = true
 && var3 = true && var4 = true && var5 = true
 && var6 = true)
{

Aninhado por exemplo:

if (var1 = true && var2 = true && var2 = true && var3 = true)
{
     if (var4 = true && var5 = true && var6 = true)
     {
Foi útil?

Solução

Separe a condição em vários booleanos e use um booleano mestre como condição.

bool isOpaque = object.Alpha == 1.0f;
bool isDrawable = object.CanDraw && object.Layer == currentLayer;
bool isHidden = hideList.Find(object);

bool isVisible = isOpaque && isDrawable && ! isHidden;

if(isVisible)
{
    // ...
}

Melhor ainda:

public bool IsVisible {
    get
    {
        bool isOpaque = object.Alpha == 1.0f;
        bool isDrawable = object.CanDraw && object.Layer == currentLayer;
        bool isHidden = hideList.Find(object);

        return isOpaque && isDrawable && ! isHidden;
    }
}

void Draw()
{
     if(IsVisible)
     {
         // ...
     }
}

Certifique-se de fornecer um nome às suas variáveis ​​que realmente indique a intenção e não a função.Isso ajudará muito o desenvolvedor a manter seu código...Poderia ser você!

Outras dicas

Estou surpreso que ninguém tenha conseguido esse ainda.Há uma refatoração específica para esse tipo de problema:

http://www.refactoring.com/catalog/decomposeConditional.html

Há duas questões a serem abordadas aqui:legibilidade e compreensibilidade

A solução de “legibilidade” é uma questão de estilo e, como tal, está aberta à interpretação.Minha preferência é esta:

if (var1 == true && // Explanation of the check
    var2 == true && // Explanation of the check
    var3 == true && // Explanation of the check
    var4 == true && // Explanation of the check
    var5 == true && // Explanation of the check
    var6 == true)   // Explanation of the check
    { }

ou isto:

if (var1 && // Explanation of the check
    var2 && // Explanation of the check
    var3 && // Explanation of the check
    var4 && // Explanation of the check
    var5 && // Explanation of the check
    var6)   // Explanation of the check
    { }

Dito isto, esse tipo de verificação complexa pode ser bastante difícil de analisar mentalmente durante a varredura do código (especialmente se você não for o autor original).Considere criar um método auxiliar para abstrair parte da complexidade:

/// <Summary>
/// Tests whether all the conditions are appropriately met
/// </Summary>
private bool AreAllConditionsMet (
    bool var1,
    bool var2,
    bool var3,
    bool var4,
    bool var5,
    bool var6)
{
    return (
        var1 && // Explanation of the check
        var2 && // Explanation of the check
        var3 && // Explanation of the check
        var4 && // Explanation of the check
        var5 && // Explanation of the check
        var6);  // Explanation of the check
}

private void SomeMethod()
{
    // Do some stuff (including declare the required variables)
    if (AreAllConditionsMet (var1, var2, var3, var4, var5, var6))
    {
        // Do something
    }
}

Agora, ao digitalizar visualmente o método "SomeMethod", a complexidade real da lógica de teste fica oculta, mas o significado semântico é preservado para que os humanos entendam em alto nível.Se o desenvolvedor realmente precisar entender os detalhes, o método AreAllConditionsMet poderá ser examinado.

Isso é formalmente conhecido como padrão de refatoração "Decompose Conditional", eu acho.Ferramentas como Resharper ou Refactor Pro!pode facilitar esse tipo de refatoração!

Em todos os casos, a chave para ter um código legível e compreensível é usar nomes de variáveis ​​realistas.Embora eu entenda que este é um exemplo inventado, "var1", "var2" etc. não nomes de variáveis ​​aceitáveis.Devem ter um nome que reflita a natureza subjacente dos dados que representam.

Freqüentemente, dividirei isso em variáveis ​​​​booleanas de componentes:

bool orderValid = orderDate < DateTime.Now && orderStatus != Status.Canceled;
bool custValid = customerBalance == 0 && customerName != "Mike";
if (orderValid && custValid)
{
...

Primeiro, eu removeria todos os == true partes, isso o tornaria 50% mais curto;)

Quando tenho condição grande procuro os motivos.Às vezes vejo que deveria usar polimorfismo, às vezes preciso adicionar algum objeto de estado.Basicamente, isso implica que é necessária uma refatoração (um cheiro de código).

Às vezes eu uso Leis de De-Morgan para simplificar um pouco as expressões booleanas.

Confira Padrões de Implementação por Kent Beck.Estou pensando em um padrão específico que pode ajudar nesta situação ...é chamado de "Guardas".Em vez de ter toneladas de condições, você pode dividi-las em uma guarda, o que deixa claro quais são as condições adversas de um método.

Por exemplo, se você tem um método que faz alguma coisa, mas há certas condições em que ele não deveria fazer alguma coisa, em vez de:

public void doSomething() {
    if (condition1 && condition2 && condition3 && condition4) {
        // do something
    }
}

Você poderia alterá-lo para:

public void doSomething() {
    if (!condition1) {
        return;
    }

    if (!condition2) {
        return;
    }

    if (!condition3) {
        return;
    }

    if (!condition4) {
        return;
    }

    // do something
}

É um pouco mais detalhado, mas muito mais legível, especialmente quando você começa a ter aninhamentos estranhos, o guarda pode ajudar (combinado com métodos de extração).

A propósito, recomendo ALTAMENTE esse livro.

Já vi muitas pessoas e editores recuando cada condição em sua instrução if com uma guia ou combinando-a com o parêntese aberto:

if (var1 == true
    && var2 == true
    && var3 == true
   ) {
    /* do something.. */
}

Normalmente coloco o parêntese próximo na mesma linha da última condição:

if (var1 == true
    && var2 == true
    && var3 == true) {
    /* do something.. */
}

Mas não acho que isso seja tão limpo.

O conselho de Steve McConell, de Código completo:Use uma tabela multidimensional.Cada variável serve como um índice para a tabela e a instrução IF se transforma em uma pesquisa de tabela.Por exemplo, if (size == 3 && Weight> 70) se traduz na decisão de entrada da tabela [tamanho] [Weight_group

Tente olhar para Functors e Predicados.O projeto Apache Commons possui um grande conjunto de objetos para permitir encapsular lógica condicional em objetos.Um exemplo de seu uso está disponível na O'reilly aqui.Trecho do exemplo de código:

import org.apache.commons.collections.ClosureUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.functors.NOPClosure;

Map predicateMap = new HashMap();

predicateMap.put( isHonorRoll, addToHonorRoll );
predicateMap.put( isProblem, flagForAttention );
predicateMap.put( null, ClosureUtils.nopClosure() );

Closure processStudents = 
    ClosureUtils.switchClosure( predicateMap );

CollectionUtils.forAllDo( allStudents, processStudents );

Agora, os detalhes de todos esses predicados isHonorRoll e os fechamentos usados ​​para avaliá-los:

import org.apache.commons.collections.Closure;
import org.apache.commons.collections.Predicate;

// Anonymous Predicate that decides if a student 
// has made the honor roll.
Predicate isHonorRoll = new Predicate() {
  public boolean evaluate(Object object) {
    Student s = (Student) object;

    return( ( s.getGrade().equals( "A" ) ) ||
            ( s.getGrade().equals( "B" ) && 
              s.getAttendance() == PERFECT ) );
  }
};

// Anonymous Predicate that decides if a student
// has a problem.
Predicate isProblem = new Predicate() {
  public boolean evaluate(Object object) {
    Student s = (Student) object;

    return ( ( s.getGrade().equals( "D" ) || 
               s.getGrade().equals( "F" ) ) ||
             s.getStatus() == SUSPENDED );
  }
};

// Anonymous Closure that adds a student to the 
// honor roll
Closure addToHonorRoll = new Closure() {
  public void execute(Object object) {
    Student s = (Student) object;

    // Add an award to student record
    s.addAward( "honor roll", 2005 );
    Database.saveStudent( s );
  }
};

// Anonymous Closure flags a student for attention
Closure flagForAttention = new Closure() {
  public void execute(Object object) {
    Student s = (Student) object;

    // Flag student for special attention
    s.addNote( "talk to student", 2005 );
    s.addNote( "meeting with parents", 2005 );
    Database.saveStudent( s );
  }
};

Bem, primeiro, por que não:

if (var1 && var2 && var2 && var3 && var4 && var5 && var6) {
...

Além disso, é muito difícil refatorar exemplos de código abstratos.Se você mostrasse um exemplo específico, seria mais fácil identificar um padrão melhor para se adequar ao problema.

Não é melhor, mas o que fiz no passado:(O método a seguir evita curto-circuito nos testes booleanos; todos os testes são executados mesmo que o primeiro seja falso.Não é um padrão recomendado, a menos que você saiba que precisa sempre executar todo o código antes de retornar - Obrigado ao ptomato por detectar meu erro!)

booleano ok = cond1;
ok &=cond2;
ok &= cond3;
ok &=cond4;
ok &=cond5;
ok &= cond6;

Que é o mesmo que: (não é o mesmo, veja a nota acima!)

ok = (cond1 && cond2 && cond3 && cond4 && cond5 && cond6);

Recorro a valores booleanos separados:

Bool cond1 == (var1 && var2);
Bool cond2 == (var3 && var4);

if ( cond1 && cond2 ) {}

Como outros mencionaram, eu analisaria suas condicionais para ver se há uma maneira de terceirizá-las para outros métodos para aumentar a legibilidade.

Em linguagens reflexivas como PHP, você pode usar variáveis-variáveis:

$vars = array('var1', 'var2', ... etc.);
foreach ($vars as $v)
    if ($$v == true) {
        // do something
        break;
    }

Gosto de dividi-los por nível, então formatava seu exemplo assim:

if (var1 = true
 && var2 = true
 && var2 = true
 && var3 = true
 && var4 = true
 && var5 = true
 && var6 = true){

É útil quando você tem mais aninhamentos, como este (obviamente as condições reais seriam mais interessantes que "= true" para tudo):

if ((var1 = true && var2 = true)
 && ((var2 = true && var3 = true)
  && (var4 = true && var5 = true))
 && (var6 = true)){

Se acontecer de você estar programando em Python, é muito fácil com o recurso integrado all() função aplicada sobre a lista de suas variáveis ​​(usarei apenas literais booleanos aqui):

>>> L = [True, True, True, False, True]
>>> all(L) # True, only if all elements of L are True.
False
>>> any(L) # True, if any elements of L are True.
True

Existe alguma função correspondente na sua linguagem (C#?Java?).Nesse caso, essa é provavelmente a abordagem mais limpa.

McDowell,

Você está certo ao usar o único operador '&' que ambos os lados da expressão são avaliados.No entanto, ao usar o operador '&&' (pelo menos em C#), a primeira expressão a retornar false é a última expressão avaliada.Isso faz com que colocar a avaliação antes da instrução FOR seja tão bom quanto qualquer outra maneira de fazê-lo.

@ajuste

Não é melhor, mas o que fiz no passado:

booleano ok = cond1;ok &=cond2;ok &= cond3;ok &=cond4;ok &=cond5;ok &= cond6;

Que é o mesmo que:

ok = (cond1 && cond2 && cond3 && cond4 && cond5 && cond6);

Na verdade, essas duas coisas não são iguais na maioria dos idiomas.A segunda expressão normalmente deixará de ser avaliada assim que uma das condições for falsa, o que pode ser uma grande melhoria de desempenho se a avaliação das condições for cara.

Para facilitar a leitura, pessoalmente prefiro a proposta de Mike Stone acima.É fácil comentar detalhadamente e preserva todas as vantagens computacionais de poder antecipar.Você também pode fazer a mesma técnica embutida em uma função se confundir a organização do seu código ao mover a avaliação condicional para longe de sua outra função.É um pouco extravagante, mas você sempre pode fazer algo como:

do {
    if (!cond1)
       break;
    if (!cond2)
       break;
    if (!cond3)
       break;
    ...
    DoSomething();
} while (false);

o while (falso) é meio cafona.Eu gostaria que os idiomas tivessem um operador de escopo chamado "uma vez" ou algo que você pudesse sair facilmente.

Se eu estivesse fazendo isso em Perl, é assim que poderia executar as verificações.

{
  last unless $var1;
  last unless $var2;
  last unless $var3;
  last unless $var4;
  last unless $var5;
  last unless $var6;

  ... # Place Code Here
}

Se você planeja usar isso em uma sub-rotina, substitua todas as instâncias de last com return;

Gosto de dividir cada condição em variáveis ​​descritivas.

bool isVar1Valid, isVar2Valid, isVar3Valid, isVar4Valid;
isVar1Valid = ( var1 == 1 )
isVar2Valid = ( var2.Count >= 2 )
isVar3Valid = ( var3 != null )
isVar4Valid = ( var4 != null && var4.IsEmpty() == false )
if ( isVar1Valid && isVar2Valid && isVar3Valid && isVar4Valid ) {
     //do code
}
    if (   (condition_A)
        && (condition_B)
        && (condition_C)
        && (condition_D)
        && (condition_E)
        && (condition_F)
       )
    {
       ...
    }

em oposição a

    if (condition_A) {
       if (condition_B) {
          if (condition_C) {
             if (condition_D) {
                if (condition_E) {
                   if (condition_F) {
                      ...
                   }
                }
             }
          }
       }
    }

e

    if (   (   (condition_A)
            && (condition_B)
           )
        || (   (condition_C)
            && (condition_D)
           )
        || (   (condition_E)
            && (condition_F)
           )
       )
    {
       do_this_same_thing();
    }

em oposição a

    if (condition_A && condition_B) {
       do_this_same_thing();
    }
    if (condition_C && (condition_D) {
       do_this_same_thing();
    }
    if (condition_E && condition_F) {
       do_this_same_thing();
    }

A maioria das ferramentas de análise estática para examinar código reclamará se múltiplas expressões condicionais não usarem parênteses explícitos para ditar a análise de expressão, em vez de confiar em regras de precedência de operadores e menos parênteses.

O alinhamento vertical no mesmo nível de recuo de colchetes de abertura/fechamento {}, parênteses de abertura e fechamento (), expressões condicionais com parênteses e operadores à esquerda é uma prática muito útil, que MELHORA muito a legibilidade e a clareza do código, em vez de congestionar tudo que pode ser preso em uma única linha, sem alinhamento vertical, espaços ou parênteses

As regras de precedência do operador são complicadas, por ex.&& tem maior precedência que ||, mas | tem precedência que &&

Então, ...

    if (expr_A & expr_B || expr_C | expr_D & expr_E || expr_E && expr_F & expr_G || expr_H {
    }

é uma expressão condicional múltipla realmente fácil para meros humanos lerem e avaliarem inadequadamente.

    if (   (  (expr_A)
            & (expr_B)
           )
        || (  (expr_C)
            | (  (expr_D)
               & (expr_E)
              )
           )
        || (   (expr_E)
            && (  (expr_F)
                & (expr_G)
               )
           )
        || (expr_H)
       )
    {
    }

Não há nada de errado com espaço horizontal (linefeeds), alinhamento vertical ou parênteses explícitos orientando a avaliação de expressões, os quais MELHORAM a legibilidade e a clareza

Se você fizer isto:

if (var1 == true) {
    if (var2 == true) {
        if (var3 == true) {
            ...
        }
    }
}

Então você também pode responder aos casos em que algo não é verdade.Por exemplo, se você estiver validando uma entrada, poderá dar ao usuário uma dica sobre como formatá-la corretamente ou algo assim.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top