2008-08-08 19 views
28

Es algo que me molesta en todos los lenguajes que he usado, tengo una declaración if pero la parte condicional tiene tantos controles que tengo que dividirla en varias líneas, usar una declaración if anidada o simplemente aceptar que es fea y seguir con mi vida¿Cómo manejas enormes condiciones if?

¿Hay algún otro método que haya encontrado que pueda ser útil para mí y para cualquier otra persona que tenga el mismo problema?

ejemplo, en una sola línea:

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

ejemplo, de varias líneas:

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

Ejemplo-anidada:

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

Respuesta

61

independiente de la condición en varios booleanos y luego usar un valor lógico maestro como la condición.

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) 
{ 
    // ... 
} 

Mejor aún:

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) 
    { 
     // ... 
    } 
} 

Asegúrese de darle el nombre de variables que indican la intención podía comprender en lugar de la función. Esto ayudará enormemente al desarrollador a mantener su código ... ¡podría ser USTED!

+0

simple, fácil de hacer, y eficaz. –

5

primer lugar, me quito toda la == true piezas, que lo harían un 50% más corto;)

Cuando tengo una gran condición, busco las razones. A veces veo que debo usar polimorfismo, a veces necesito agregar algún objeto de estado. Básicamente, implica una refactorización necesaria (un olor a código).

A veces uso De-Morgan's laws para simplificar un poco las expresiones booleanas.

1

recurro a valores lógicos separados:

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

if (cond1 && cond2) {} 
3

que he visto a mucha gente y editores ya sea sangría cada condición en la sentencia if con una pestaña o emparejan para arriba con los paréntesis de apertura:

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

yo suelo poner los paréntesis de cierre en la misma línea que la última condición:

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

Pero no creo que esto sea tan limpio.

6

Voy a menudo a dividir estas arriba en variables booleanas componentes:

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

Bueno, para empezar, por qué no:

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

Además, es muy difícil refactorizar ejemplos de código abstracto. Si mostrara un ejemplo específico, sería más fácil identificar un patrón mejor para adaptarse al problema.

No es mejor, pero lo que hice en el pasado: (El siguiente método evita las pruebas booleanas de cortocircuito, todas las pruebas se ejecutan incluso si la primera es falsa. No es un patrón recomendado a menos que sepa que necesita para ejecutar siempre todo el código antes de volver - Gracias a ptomato para detectar mi error!)

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

¿Qué es la misma que: (no es lo mismo, ver más arriba nota!)

ok = (COND1 & & COND2 & & cond3 & & cond4 & & cond5 & & cond6) ;

+0

No es lo mismo si el operador '&&' está en cortocircuito. – ptomato

+0

Wow. Debería de haber sabido eso. Mi primera palma de la cara en una de mis respuestas ;-) –

0

me gusta descomponerlas por nivel, por lo que te había Ejemplo de formato de esta manera:

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

Es muy útil cuando se tiene más de anidación, así (obviamente, las condiciones reales sería más interesante que "= true" para todo):

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

salida Implementation Patterns por Kent Beck. Hay un patrón particular en el que estoy pensando que puede ayudar en esta situación ... se llama "Guardias". En lugar de tener toneladas de condiciones, puede dividirlas en un guardia, lo que deja en claro cuáles son las condiciones adversas en un método.

Así, por ejemplo, si usted tiene un método que hace algo, pero hay ciertas condiciones en las que no debe hacer algo, en lugar de:

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

Usted puede cambiarlo a:

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

    if (!condition2) { 
     return; 
    } 

    if (!condition3) { 
     return; 
    } 

    if (!condition4) { 
     return; 
    } 

    // do something 
} 

Es un poco más prolijo, pero mucho más legible, especialmente cuando comienzas a tener una anidación extraña, el guardia puede ayudar (combinado con métodos de extracción).

Recomiendo encarecidamente ese libro por cierto.

+0

'Retorno rápido' mata 'anti-patrón' de la punta de flecha también :) –

+0

No estoy de acuerdo con que estos Guardias lo hagan más fácil de leer. Un comentario sobre la condición "complicada" sería mejor. –

12

Estoy sorprendido de que nadie haya tenido esta todavía. Hay una refactorización específicamente para este tipo de problema:

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

+3

No me gusta Descomponer condicional, ya que contamina la estructura del código con funciones únicas que no son reutilizables.Prefiero tener una gran declaración IF con comentarios para cada "grupo" de cheques relacionados. –

7

Hay dos cuestiones a abordar aquí: legibilidad y comprensibilidad

La solución "legibilidad" es una cuestión de estilo y como tal es abierto a la interpretación . Mi preferencia es la siguiente:

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 
    { } 

o esto:

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 
    { } 

Dicho esto, este tipo de cheque complejo puede ser muy difícil de analizar mentalmente mientras escanea el código (especialmente si usted no es el autor original) Considerar la creación de un método de ayuda para abstraer parte de la complejidad de distancia:

/// <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 
    } 
} 

Ahora al escanear visualmente el método "SomeMethod", la complejidad real de la lógica prueba está oculto, pero el significado semántico se conserva para los seres humanos para entender en un alto nivel. Si el desarrollador realmente necesita comprender los detalles, se puede examinar el método AreAllConditionsMet.

Esto se conoce formalmente como el patrón de refactorización "Decompose Conditional", creo. ¡Herramientas como Resharper o Refactor Pro! puede hacer este tipo de refactorización fácil!

En todos los casos, la clave para tener un código legible y comprensible es usar nombres de variables realistas. Si bien entiendo que este es un ejemplo artificial, "var1", "var2", etc. son no nombres de variables aceptables. Deben tener un nombre que refleje la naturaleza subyacente de los datos que representan.

0

Si quieres pasar a ser la programación en Python, es un juego de niños con la incorporada en el all() función aplicada sobre la lista de sus variables (Voy a utilizar literales booleanos aquí):

>>> 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 

¿Hay cualquier función correspondiente en su idioma (C#? Java?). Si es así, ese es probablemente el enfoque más limpio.

-2

Si hace esto:

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

, entonces también puede responder a los casos en que hay algo que no es cierto. Por ejemplo, si está validando una entrada, puede darle al usuario un consejo sobre cómo formatearla adecuadamente, o lo que sea.

+3

Esta es quizás la peor solución a esta pregunta. –

+1

Barra de desplazamiento horizontal - activar !!! 11eleven –

0

McDowell,

Tiene razón en que cuando se utiliza el single '&' operador que ambos lados de la expresión a evaluar. Sin embargo, cuando se utiliza el operador '& &' (al menos en C#), la primera expresión que devuelve falso es la última expresión evaluada. Esto hace que poner la evaluación antes de la declaración FOR sea tan buena como cualquier otra forma de hacerlo.

1

Como han mencionado otros, analizaría sus condicionales para ver si hay una manera de poder subcontratarlo a otros métodos para aumentar la legibilidad.

0

@tweakt

no es mejor, pero lo que he hecho en el pasado:

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

Qué es la misma que:

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

En realidad, estas dos cosas no son lo mismo en la mayoría de los idiomas. La segunda expresión típicamente dejará de evaluarse tan pronto como una de las condiciones sea falsa, lo que puede representar una gran mejora en el rendimiento si evaluar las condiciones es costoso.

Para legibilidad, personalmente prefiero la propuesta de Mike Stone anterior. Es fácil comentar con detalle y preservar todas las ventajas computacionales de poder salir antes. También puede hacer la misma técnica en línea en una función si confundiría la organización de su código para alejar la evaluación condicional de su otra función. Es un poco cursi, pero siempre se puede hacer algo como:

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

el tiempo (falso) es un poco cursi. Ojalá los idiomas tuvieran un operador de scoping llamado "una vez" o algo de lo que pudieras salir fácilmente.

+0

Eso no es mejor que el código original. –

2

Intenta mirar Functors y Predicates. El proyecto Apache Commons tiene un gran conjunto de objetos que le permiten encapsular la lógica condicional en objetos. Ejemplo de su uso está disponible en O'reilly here. Fragmento de código de ejemplo:

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); 

Ahora los detalles de todos los predicados isHonorRoll y los cierres utilizados para evaluarlos:

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); 
    } 
}; 
+0

¡Agradable! Uno para descargar y jugar con creo. – toolkit

0

me gusta romper cada condición en variables descriptivas.

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 
} 
+0

¿Cuál es el objetivo de crear bools individuales? Además, compara cada 'varX' con' true' y lo asigna al bool 'isVarXValid', que es esencialmente solo * longhand * para' isVar1Valid = var', que es redundante. Ya tienes bools para empezar, entonces ¿por qué no simplemente 'if (var1 && var2 && var3 && var4)' – dreamlax

+0

@dreamlax Estás en lo correcto. Usé un pobre ejemplo. – wusher

2

consejos de Steve McConnell, de Code Complete: utilizar una tabla multidimensional. Cada variable sirve como un índice para la tabla, y la instrucción if se convierte en una tabla de búsqueda. Por ejemplo, si (== tamaño 3 & & peso> 70) se traduce en la decisión de entrada de la tabla [tamaño] [weight_group]

0

Si lo hacía en Perl, esta es la forma en que podría funcionar los controles.

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

    ... # Place Code Here 
} 

Si usted planea usar esto sobre una subrutina reemplazar cada instancia de last con return;

1

En lenguajes reflexivos como PHP, puede utilizar variables variables:

$vars = array('var1', 'var2', ... etc.); 
foreach ($vars as $v) 
    if ($$v == true) { 
     // do something 
     break; 
    } 
0
if ( (condition_A) 
     && (condition_B) 
     && (condition_C) 
     && (condition_D) 
     && (condition_E) 
     && (condition_F) 
     ) 
    { 
     ... 
    } 

en contraposición a

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

y

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

en contraposición 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(); 
    } 

La mayoría de las herramientas de análisis estático para examinar el código se quejarán si varias expresiones condicionales no usan análisis de expresión de dictado de paréntesis explícito, en lugar de basarse en reglas de precedencia del operador y menos paréntesis.

Alineación vertical en el mismo nivel de indentación de llaves abiertas/cerradas {}, abrir cerrar paréntesis(), expresiones condicionales con paréntesis y operadores a la izquierda es una práctica muy útil, que MEJORA en gran medida la legibilidad y claridad del código se opone al bloqueo de todo lo que posiblemente se pueda atascar en una sola línea, sin alineación vertical, espacios o paréntesis

Las reglas de precedencia del operador son complicadas, por ejemplo & & tiene mayor prioridad que ||, pero | tiene precedencia que & &

Así, ...

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

es una expresión condicional múltiple muy fácil para los simples humanos para leer y evaluar de manera incorrecta.

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

No hay nada malo con el espacio horizontal (saltos de línea), la alineación vertical, o un paréntesis explícita guiar la evaluación de expresiones, todo lo cual mejora la legibilidad y claridad

Cuestiones relacionadas