language agnostic - ¿Cómo manejas enormes condiciones if?
language-agnostic if-statement (21)
@tweakt
No es mejor, pero lo que hice en el pasado:
boolean ok = cond1; ok & = cond2; ok & = cond3; ok & = cond4; ok & = cond5; ok & = cond6;
Que es lo mismo 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 la legibilidad, personalmente prefiero la propuesta de Mike Stone más arriba. 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 puedes hacer algo como:
do {
if (!cond1)
break;
if (!cond2)
break;
if (!cond3)
break;
...
DoSomething();
} while (false);
el tiempo (falso) es cursi. Ojalá los idiomas tuvieran un operador de scoping llamado "una vez" o algo de lo que pudieras salir fácilmente.
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 múltiples líneas, usar una declaración if anidada o simplemente aceptar que es fea y seguir adelante con mi vida.
¿Hay algún otro método que hayas encontrado que pueda ser útil para mí y para cualquier otra persona que tenga el mismo problema?
Ejemplo, todo en una línea:
if (var1 = true && var2 = true && var2 = true && var3 = true && var4 = true && var5 = true && var6 = true)
{
Ejemplo, multi-línea:
if (var1 = true && var2 = true && var2 = true
&& var3 = true && var4 = true && var5 = true
&& var6 = true)
{
Ejemplo anidado:
if (var1 = true && var2 = true && var2 = true && var3 = true)
{
if (var4 = true && var5 = true && var6 = true)
{
A menudo los dividiré en variables booleanas de componentes:
bool orderValid = orderDate < DateTime.Now && orderStatus != Status.Canceled;
bool custValid = customerBalance == 0 && customerName != "Mike";
if (orderValid && custValid)
{
...
Bueno, primero, ¿por qué no?
if (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 previene el cortocircuito en las pruebas booleanas, todas las pruebas se ejecutan incluso si la primera es falsa. No es un patrón recomendado a menos que sepa que siempre debe ejecutar todas las pruebas código antes de regresar - ¡Gracias a ptomato por detectar mi error!)
boolean ok = cond1;
ok & = cond2;
ok & = cond3;
ok & = cond4;
ok & = cond5;
ok & = cond6;
Que es lo mismo que: (no es lo mismo, ver la nota de arriba)
ok = (cond1 && cond2 && cond3 && cond4 && cond5 && cond6);
Como han mencionado otros, analizaría tus condicionales para ver si hay una manera de externalizarlo a otros métodos para aumentar la legibilidad.
Consejo de Steve Mcconell, de Code Complete : Usa 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 tabla [size] [weight_group]
Echa un vistazo a los patrones de implementación de 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.
Entonces, por ejemplo, si tiene un método que hace algo, pero hay ciertas condiciones en las que no debería hacer algo, en lugar de:
public void doSomething() {
if (condition1 && condition2 && condition3 && condition4) {
// do something
}
}
Puedes cambiarlo a:
public void doSomething() {
if (!condition1) {
return;
}
if (!condition2) {
return;
}
if (!condition3) {
return;
}
if (!condition4) {
return;
}
// do something
}
Es un poco más detallado, 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).
ALTAMENTE recomiendo ese libro por cierto.
En lenguajes reflectantes como PHP, puede usar variables variables:
$vars = array(''var1'', ''var2'', ... etc.);
foreach ($vars as $v)
if ($$v == true) {
// do something
break;
}
Estoy sorprendido de que nadie haya conseguido esto todavía. Hay una refactorización específicamente para este tipo de problema:
http://www.refactoring.com/catalog/decomposeConditional.html
Hay dos cuestiones que abordar aquí: legibilidad y comprensibilidad
La solución de "legibilidad" es un problema de estilo y, como tal, está abierto a la interpretación. Mi preferencia es 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
{ }
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 verificación compleja puede ser bastante difícil de analizar mentalmente mientras se escanea el código (especialmente si no eres el autor original). Considere crear un método auxiliar para abstraer parte de la complejidad:
/// <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 de prueba está oculta, pero el significado semántico se preserva para que los humanos comprendan a un nivel alto. 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 "Descomponer condicional", 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. no son nombres de variable aceptables. Deben tener un nombre que refleje la naturaleza subyacente de los datos que representan.
He visto a muchas personas y editores, ya sea sangrando cada condición en su declaración if con una sola pestaña, o haciendo coincidirla con la página abierta:
if (var1 == true
&& var2 == true
&& var3 == true
) {
/* do something.. */
}
Usualmente coloco el paréntesis cercano 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.
Intente 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 . Extracto del ejemplo 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 );
Ahora los detalles de todos esos predicados isHonorRoll y los cierres usados 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 );
}
};
McDowell,
Está en lo correcto al usar el operador ''&'' único que evalúan ambos lados de la expresión. 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.
Me gusta dividir 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
}
Me gusta dividirlos por nivel, así que formatearé tu ejemplo así:
if (var1 = true
&& var2 = true
&& var2 = true
&& var3 = true
&& var4 = true
&& var5 = true
&& var6 = true){
Es útil cuando tienes más anidamiento, así (obviamente las condiciones reales serían más interesantes que "= verdadero" para todo):
if ((var1 = true && var2 = true)
&& ((var2 = true && var3 = true)
&& (var4 = true && var5 = true))
&& (var6 = true)){
Primero, eliminaría todas las == true
partes == true
, eso lo haría 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 las leyes de De Morgan para simplificar un poco las expresiones booleanas.
Recurro a valores booleanos separados:
Bool cond1 == (var1 && var2);
Bool cond2 == (var3 && var4);
if ( cond1 && cond2 ) {}
Separa la condición en varios booleanos y luego usa un booleano maestro como 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 todavía:
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úrate de dar el nombre de tus variables que realmente indiquen intención en lugar de función. Esto ayudará enormemente al desarrollador a mantener su código ... ¡podría ser USTED!
Si haces esto:
if (var1 == true) {
if (var2 == true) {
if (var3 == true) {
...
}
}
}
Entonces también puede responder a casos donde algo no es verdad. Por ejemplo, si está validando una entrada, puede darle al usuario un consejo sobre cómo formatearla adecuadamente, o lo que sea.
Si lo estuviera haciendo en Perl, así es como podría ejecutar los controles.
{
last unless $var1;
last unless $var2;
last unless $var3;
last unless $var4;
last unless $var5;
last unless $var6;
... # Place Code Here
}
Si planea usar esto sobre una subrutina, reemplace cada instancia del last
con return
;
Si usted está programando en Python, es muy fácil con la función incorporada all()
aplicada sobre la lista de sus variables (aquí solo usaré los literales booleanos):
>>> 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 alguna función correspondiente en su idioma (C #? Java?). Si es así, ese es probablemente el enfoque más limpio.
if ( (condition_A)
&& (condition_B)
&& (condition_C)
&& (condition_D)
&& (condition_E)
&& (condition_F)
)
{
...
}
Opuesto 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();
}
Opuesto 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 las expresiones condicionales múltiples no usan el análisis explícito de expresión de dictado de paréntesis, en lugar de confiar en las reglas de precedencia del operador y menos paréntesis.
La alineación vertical en el mismo nivel de sangría entre llaves abiertas / cerradas {}, abrir cerrar paréntesis (), expresiones condicionales con paréntesis y operadores a la izquierda es una práctica muy útil, que ENFATIZA la legibilidad y la claridad del código en lugar de bloquear todo 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 una precedencia mayor que ||, pero | tiene prioridad que &&
Asi que, ...
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 sencilla para que los simples humanos lean y evalúen 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 de malo en el espacio horizontal (avances de línea), la alineación vertical o la evaluación explícita de la expresión de guía de paréntesis, todo lo cual MEJORA la legibilidad y la claridad