2010-03-19 11 views
78

Estoy leyendo el código de McConell Complete, y él discute el uso de variables booleanas para documentar su código. Por ejemplo, en lugar de:¿Deberían los programadores usar variables booleanas para "documentar" su código?

if((elementIndex < 0) || (MAX_ELEMENTS < elementIndex) || 
    (elementIndex == lastElementIndex)){ 
     ... 
} 

Sugiere:

finished = ((elementIndex < 0) || (MAX_ELEMENTS < elementIndex)); 
repeatedEntry = (elementIndex == lastElementIndex); 
if(finished || repeatedEntry){ 
    ... 
} 

Esto me parece, una buena práctica tan lógico, y muy auto-documentado. Sin embargo, dudo en comenzar a utilizar esta técnica con regularidad ya que casi nunca me he encontrado con ella; y tal vez sería confuso solo en virtud de ser raro. Sin embargo, mi experiencia no es muy amplia todavía, por lo que me interesa escuchar la opinión de los programadores sobre esta técnica, y me gustaría saber si alguien usa esta técnica con regularidad o si la ha visto a menudo al leer el código. ¿Es esta una convención/estilo/técnica que vale la pena adoptar? ¿Los otros programadores lo entenderán y lo apreciarán, o lo considerarán extraño?

+0

@ Pablo R - ¡Vaya, gracias. Comencé con la auto-documentación y me di cuenta de que no había ninguna etiqueta para eso, así que traté de cambiarlo a auto-documentación que ya existía. :) – froadie

+0

¿No es mejor comentar sobre lo que está sucediendo en la prueba? –

+3

Cuando sea apropiado, trato de hacer esto también, especialmente si tengo que verificar la (s) condición (es) en diferentes partes del método. Es mucho más descriptivo también. – geffchang

Respuesta

54

Dividir una expresión que está demasiado anidada y complicada en sub-expresiones simples asignadas a variables locales, y luego juntas de nuevo, es una técnica común y popular, independientemente de si las expresiones parciales y/o generales son booleanos o de cualquier otro tipo. Con nombres bien elegidos, una descomposición de buen gusto de este tipo puede aumentar la legibilidad, y un buen compilador no debería tener problemas para generar código que sea equivalente a la expresión original y complicada.

Algunos lenguajes que no tienen el concepto de "asignación" per se, como Haskell, incluso introducen construcciones especializadas que le permiten usar la técnica "dar un nombre a una subexpresión" (la cláusula where en Haskell) - - parece indicar cierta popularidad para la técnica en cuestión! -)

+6

si es más fácil Y más fácil de leer, digo que es un caso bastante claro de ganar-ganar :) – djcouchycouch

+0

Estoy de acuerdo. En muchos casos, es probable que se salga con un breve comentario, pero no veo cómo esto realmente podría * lastimar. * –

9

Intento hacerlo siempre que sea posible. Claro, estás usando una "línea adicional" de código, pero al mismo tiempo, estás describiendo por qué estás haciendo una comparación de dos valores.

En su ejemplo, miro el código, y me pregunto "¿está bien por qué la persona que ve el valor es menor que 0?" En el segundo, me estás diciendo claramente que algunos procesos han terminado cuando esto ocurre. Sin adivinar en el segundo cuál era tu intención.

El más grande para mí es cuando veo un método como: DoSomeMethod(true); ¿Por qué se establece automáticamente en verdadero? Es mucho más fácil de leer como

bool deleteOnCompletion = true; 

DoSomeMethod(deleteOnCompletion); 
+7

No me gustan los parámetros booleanos precisamente por esta razón. Terminas recibiendo llamadas como "createOrder (true, false, true, true, false)" y ¿qué significa eso? Prefiero usar enum, por lo que dices algo como "createOrder (Source.MAIL_ORDER, BackOrder.NO, CustomOrder.CUSTOM, PayType.CREDIT)". – Jay

+0

Pero si sigues el ejemplo de Kevin, es equivalente al tuyo. ¿Qué diferencia hace si la variable puede tomar 2 valores o más de 2? –

+2

Con Jay's puede tener la ventaja de ser definitivamente más claro en ciertos casos. Por ejemplo, en uso del PayType. Si fuera un booleano, el parámetro probablemente se llamaría isPayTypeCredit. No sabes lo que es la alternativa.Con una enumeración, puede ver claramente qué opciones tiene PayType: crédito, cheque, efectivo y seleccione la correcta. – kemiller2002

16

lo he utilizado, aunque normalmente envolver la lógica booleana en un método reutilizable (si se llama desde múltiples ubicaciones).

Ayuda a la legibilidad y cuando la lógica cambia, solo necesita cambiarse en un solo lugar.

Otros lo entenderán y no lo encontrarán extraño (excepto para aquellos que solo escriben mil funciones de línea, eso es).

+0

¡Gracias por la respuesta! En cuanto a los métodos reutilizables, eso tiene otra razón válida (no relacionada) para tener en cuenta ... Así que supongo que mi pregunta realmente es si debería factorizar las expresiones booleanas de una sola vez, cuando no hay otra razón que la legibilidad. (Que por supuesto es una razón suficientemente grande por sí misma :)) Gracias por señalar eso. – froadie

+0

+1 para reducir los cambios de código necesarios cuando la lógica cambia, y burlarse de los programadores de miles de líneas de función. –

0

Creo que depende del estilo que prefiera su equipo. "Introducir la refactorización variable" podría ser útil, pero a veces no :)

Y no estoy de acuerdo con Kevin en su publicación anterior. Su ejemplo, supongo, utilizable en el caso, cuando se puede cambiar la variable introducida, pero introducirla solo para un booleano estático es inútil, porque tenemos el nombre del parámetro en una declaración de método, entonces ¿por qué duplicarlo en el código?

por ejemplo:

void DoSomeMethod(boolean needDelete) { ... } 

// useful 
boolean deleteOnCompletion = true; 
if (someCondition) { 
    deleteOnCompletion = false; 
} 
DoSomeMethod(deleteOnCompletion); 

// useless 
boolean shouldNotDelete = false; 
DoSomeMethod(shouldNotDelete); 
3

La única manera de que pudiera ver esto va mal es si el fragmento booleano no tiene un nombre que tenga sentido y un nombre se tomó de todos modos.

//No clue what the parts might mean. 
if(price>0 && (customer.IsAlive || IsDay(Thursday))) 

=> 

first_condition = price>0 
second_condition =customer.IsAlive || IsDay(Thursday) 

//I'm still not enlightened. 
if(first_condition && second_condition) 

Señalo esto porque es un lugar común para hacer reglas como "comentar todo el código", "uso booleanos nombrados por todas si-criterios con más de 3 partes" sólo para obtener comentarios que son semánticamente vacío de el siguiente tipo

i++; //increment i by adding 1 to i's previous value 
+2

Ha mal agrupado las condiciones en su ejemplo, ¿no? '&&' se une más fuerte que '||' en la mayoría de los idiomas que los usan (los scripts de shell son una excepción). –

+0

Parens agregado. Entonces, este sería otro ataque contra la división de variables nombradas, introduce la oportunidad de cambiar la agrupación de una manera no obvia. – MatthewMartin

2

al hacer esto

finished = ((elementIndex < 0) || (MAX_ELEMENTS < elementIndex)); 
repeatedEntry = (elementIndex == lastElementIndex); 
if(finished || repeatedEntry){ 
    ... 
} 

se quita el lógica de su cerebro y lo puso en el código. Ahora el programa sabe lo que usted quiso decir.
Cuando nombre algo le da representación física. Existe.
Puede manipularlo y reutilizarlo.

Incluso se puede definir todo el bloque como un predicado:

bool ElementBlahBlah? (elementIndex, lastElementIndex); 

y hacer más cosas (más tarde) en esa función.

+0

Y lo más importante, ¡el próximo desarrollador que vea el código sabrá lo que usted también quiso decir! Esta es una gran práctica, y lo hago todo el tiempo. –

+1

Además, ese próximo desarrollador puede ser usted mismo, mirando el código nuevamente después de unos meses (o incluso algunas semanas) y contento de que haya sido bien documentado. – Louis

2

Si la expresión es compleja, o bien la muevo a otra función que devuelve bool por ejemplo, isAnEveningInThePubAGoodIdea(dayOfWeek, sizeOfWorkLoad, amountOfSpareCash) o reconsidero el código para que no se requiera una expresión tan compleja.

1

Recuerde que de esta forma puede calcular más de lo necesario. Debido a las condiciones de salida del código, siempre se calculan ambos (sin cortocircuito).

Así que:

if((elementIndex < 0) || (MAX_ELEMENTS < elementIndex) || 
    (elementIndex == lastElementIndex)){ 
    ... 
} 

Después de la transformación se convierte en:

if((elementIndex < 0) || (MAX_ELEMENTS < elementIndex) | 
    (elementIndex == lastElementIndex)){ 
    ... 
} 

No es un problema en la mayoría de los casos, pero todavía en algunos puede significar un peor rendimiento u otros problemas, por ejemplo, cuando en la segunda expresión asumes que el 1er ha fallado.

+0

Un compilador resolverá eso durante la optimización. –

+0

Es cierto: recién me estaba dando cuenta de esto ahora, ya que volví a utilizar parte de mi código para refactorizar un par de sentencias if utilizando este método. Hubo una instrucción if aprovechando la evaluación de cortocircuitos con un && (la segunda parte arroja un NPE si la primera parte es falsa), y mi código estaba fallando cuando refactoré esto (porque * siempre * evaluaba ambos y los almacenaba en variables booleanas). Buen punto, gracias! Pero me preguntaba mientras probaba esto: ¿hay alguna forma de almacenar la * lógica * en una variable y hacer que demore la evaluación hasta la declaración real? – froadie

+2

Realmente dudo que el compilador resuelva eso. Si la segunda llamada no es efectiva, generalmente se debe a la invocación de alguna función, y el compilador AFAIK no está intentando determinar si una función llamada no tiene efectos secundarios. –

5

la muestra proporcionada:

finished = ((elementIndex < 0) || (MAX_ELEMENTS < elementIndex)); 
repeatedEntry = (elementIndex == lastElementIndex); 
if(finished || repeatedEntry){ 
    ... 
} 

Puede también ser reescrito para utilizar métodos, que mejoran la legibilidad y preservar la lógica booleana (como Konrad señaló):

if (IsFinished(elementIndex) || IsRepeatedEntry(elementIndex, lastElementIndex)){ 
    ... 
} 

... 

private bool IsFinished(int elementIndex) { 
    return ((elementIndex < 0) || (MAX_ELEMENTS < elementIndex)); 
} 

private bool IsRepeatedEntry(int elementIndex, int lastElementIndex) { 
    return (elementIndex == lastElementIndex); 
} 

Viene a un precio , por supuesto, que son dos métodos adicionales. Si haces esto mucho, puede hacer que tu código sea más legible, pero tus clases menos transparentes. Pero, de nuevo, también puedes mover los métodos extra a clases auxiliares.

+0

Si tiene mucho ruido de código con C#, también puede aprovechar las clases parciales y mover el ruido al parcial, y si las personas están interesadas en lo que IsFinished está verificando, es fácil ir directamente a ellas. –

2

Creo que es mejor crear funciones/métodos en lugar de variables temporales. De esta forma, la legibilidad aumenta también porque los métodos se acortan. El libro Refactoring de Martin Fowler tiene buenos consejos para mejorar la calidad del código.Las refactorizaciones relacionadas con su ejemplo particular se denominan "Reemplazar temperatura con consulta" y "Método de extracción".

+2

¿Estás diciendo que al saturar el espacio de clases con muchas funciones únicas, estás aumentando la legibilidad? Por favor explique. – Zano

+0

Siempre es una compensación. La legibilidad de la función original mejorará. Si la función original es corta, puede que no valga la pena. – mkj

+0

También "abarrotar el espacio de clases" es algo que creo depende del lenguaje utilizado y de la partición del código. – mkj

2

Personalmente, creo que esta es una gran práctica. Su impacto en la ejecución del código es mínimo, pero la claridad que puede proporcionar, si se usa correctamente, es invaluable cuando se trata de mantener el código más tarde.

0

En mi experiencia, a menudo volví a algunos guiones antiguos y me preguntaba '¿en qué demonios estaba pensando entonces?'. Por ejemplo:

Math.p = function Math_p(a) { 
    var r = 1, b = [], m = Math; 
    a = m.js.copy(arguments); 
    while (a.length) { 
     b = b.concat(a.shift()); 
    } 
    while (b.length) { 
     r *= b.shift(); 
    } 
    return r; 
}; 

que no es tan intuitivo como:

/** 
* An extension to the Math object that accepts Arrays or Numbers 
* as an argument and returns the product of all numbers. 
* @param(Array) a A Number or an Array of numbers. 
* @return(Number) Returns the product of all numbers. 
*/ 
Math.product = function Math_product(a) { 
    var product = 1, numbers = []; 
    a = argumentsToArray(arguments); 
    while (a.length) { 
     numbers = numbers.concat(a.shift()); 
    } 
    while (numbers.length) { 
     product *= numbers.shift(); 
    } 
    return product; 
}; 
+0

-1. Esto tiene poco que ver con la pregunta original, para ser honesto. La pregunta es sobre una cosa diferente, muy específica, no sobre cómo escribir un buen código en general. –

+0

Y sí, bienvenido a StackOverflow.com! –

0

rara vez crear variables independientes. Lo que hago cuando las pruebas se complican es anidar las IF y agregar comentarios. Al igual que

boolean processElement=false; 
if (elementIndex < 0) // Do we have a valid element? 
{ 
    processElement=true; 
} 
else if (elementIndex==lastElementIndex) // Is it the one we want? 
{ 
    processElement=true; 
} 
if (processElement) 
... 

El defecto admitido en esta técnica es que el próximo programador que viene puede cambiar la lógica, pero no se molestan en actualizar los comentarios. Supongo que es un problema general, pero he tenido muchas veces que he visto un comentario que dice "validar la identificación del cliente" y la siguiente línea está examinando el número de parte o algo así y me pregunto dónde está el cliente. Identificación viene en

1

si el método requiere una notificación de éxito:. (ejemplos en C#) me gusta usar la

bool success = false; 

para empezar. el código es un falure hasta que cambie a:

success = true; 

luego, al final:

return success; 
Cuestiones relacionadas