¿Son las variables de bandera un mal absoluto? [cerrado]


47

¿Son malas las variables de bandera? ¿Son las siguientes clases de variables profundamente inmorales y es malo usarlas?

"variables booleanas o enteras a las que asigna un valor en ciertos lugares, luego, debajo, marca más adelante para hacer algo o no, como, por ejemplo, usar newItem = truealgunas líneas a continuación if (newItem ) then"


Recuerdo haber hecho un par de proyectos en los que descuidé totalmente el uso de banderas y terminé con una mejor arquitectura / código; sin embargo, es una práctica común en otros proyectos en los que trabajo, y cuando el código crece y se agregan banderas, también crece el espagueti en código de la OMI.

¿Diría que hay casos en los que usar banderas es una buena práctica o incluso necesario ?, o estaría de acuerdo en que usar banderas en el código son ... banderas rojas y deben evitarse / refactorizarse; yo, acabo de hacer funciones / métodos que verifican los estados en tiempo real.


99
Parece que MainMa y yo tenemos una definición diferente de "bandera". Estaba pensando en el preprocesador #ifdefs. ¿Sobre cuál preguntabas?
Karl Bielefeldt

Esta es una muy buena pregunta. Me lo he preguntado mucho, y en realidad me he encontrado diciendo "oh, bueno, usemos una bandera" demasiado.
Paul Richter

Los booleanos son banderas. (Así son los enteros, así son ...)
Thomas Eding

77
@KarlBielefeldt Creo que OP se refiere a variables booleanas o enteras a las que les asigna un valor en ciertos lugares, luego, debajo, marca más adelante para hacer algo o no, como, por ejemplo, usar newItem = truealgunas líneas a continuaciónif (newItem ) then
Tulains Córdova

1
Considere también la introducción que explica la refactorización de variables en este contexto. Mientras el método permanezca corto y tenga un número reducido de rutas, lo considero un uso válido.
Daniel B

Respuestas:


41

El problema que he visto al mantener el código que hace uso de banderas es que el número de estados crece rápidamente, y casi siempre hay estados no controlados. Un ejemplo de mi propia experiencia: estaba trabajando en un código que tenía estos tres indicadores

bool capturing, processing, sending;

Estos tres crearon ocho estados (en realidad, había otras dos banderas también). No todas las combinaciones de valores posibles estaban cubiertas por el código, y los usuarios estaban viendo errores:

if(capturing && sending){ // we must be processing as well
...
}

Resultó que había situaciones en las que la suposición en la declaración if anterior era falsa.

Las banderas tienden a agravarse con el tiempo y ocultan el estado real de una clase. Por eso deben evitarse.


3
+1, "debe evitarse". Agregaría algo sobre 'pero las banderas son necesarias en algunas situaciones' (algunas podrían decir 'un mal necesario')
Trevor Boyd Smith, el

2
@TrevorBoydSmith En mi experiencia no lo son, sólo requieren un poco más de la potencia media del cerebro que se uso para una bandera
dukeofgaming

En su examen, debería haber sido una sola enumeración que representa el estado, no 3 booleanos.
user949300

Puede que tenga un problema similar al que estoy enfrentando en este momento. Además de cubrir todos los estados posibles, dos aplicaciones pueden compartir el mismo indicador (por ejemplo, cargar datos de clientes). En este caso, solo un Uploader usará la bandera, la activará y buena suerte para encontrar el problema en el futuro.
Alan

38

Aquí hay un ejemplo cuando las banderas son útiles.

Tengo un código que genera contraseñas (usando un generador de números pseudoaleatorios criptográficamente seguro). La persona que llama del método elige si la contraseña debe contener letras mayúsculas, minúsculas, dígitos, símbolos básicos, símbolos extendidos, símbolos griegos, cirílicos y unicode.

Con banderas, llamar a este método es fácil:

var password = this.PasswordGenerator.Generate(
    CharacterSet.Digits | CharacterSet.LowercaseLetters | CharacterSet.UppercaseLetters);

e incluso se puede simplificar para:

var password = this.PasswordGenerator.Generate(CharacterSet.LettersAndDigits);

Sin banderas, ¿cuál sería la firma del método?

public byte[] Generate(
    bool uppercaseLetters, bool lowercaseLetters, bool digits, bool basicSymbols,
    bool extendedSymbols, bool greekLetters, bool cyrillicLetters, bool unicode);

llamado así:

// Very readable, isn't it?
// Tell me just by looking at this code what symbols do I want to be included?
var password = this.PasswordGenerator.Generate(
    true, true, true, false, false, false, false, false);

Como se señaló en los comentarios, otro enfoque sería utilizar una colección:

var password = this.PasswordGenerator.Generate(
    new []
    {
        CharacterSet.Digits,
        CharacterSet.LowercaseLetters,
        CharacterSet.UppercaseLetters,
    });

Esto es mucho más legible en comparación con el conjunto de truey false, pero aún tiene dos inconvenientes:

El principal inconveniente es que para permitir valores combinados, como CharacterSet.LettersAndDigitssi estuviera escribiendo algo así en el Generate()método:

if (set.Contains(CharacterSet.LowercaseLetters) ||
    set.Contains(CharacterSet.Letters) ||
    set.Contains(CharacterSet.LettersAndDigits) ||
    set.Contains(CharacterSet.Default) ||
    set.Contains(CharacterSet.All))
{
    // The password should contain lowercase letters.
}

posiblemente reescrito así:

var lowercaseGroups = new []
{
    CharacterSet.LowercaseLetters,
    CharacterSet.Letters,
    CharacterSet.LettersAndDigits,
    CharacterSet.Default,
    CharacterSet.All,
};

if (lowercaseGroups.Any(s => set.Contains(s)))
{
    // The password should contain lowercase letters.
}

Compare esto con lo que tiene usando banderas:

if (set & CharacterSet.LowercaseLetters == CharacterSet.LowercaseLetters)
{
    // The password should contain lowercase letters.
}

El segundo inconveniente menor es que no está claro cómo se comportaría el método si se llama así:

var password = this.PasswordGenerator.Generate(
    new []
    {
        CharacterSet.Digits,
        CharacterSet.LettersAndDigits, // So digits are requested two times.
    });

10
Creo que OP se refiere a variables booleanas o enteras a las que asigna un valor en ciertos lugares, luego, debajo, marca más adelante para hacer algo o no, como, por ejemplo, usar newItem = truealgunas líneas a continuaciónif (newItem ) then
Tulains Córdova

1
@MainMa Aparentemente hay un tercero: la versión con 8 argumentos booleanos es lo que pensé cuando leí "banderas" ...
Izkata

44
Lo siento, pero en mi humilde opinión, este es el caso perfecto para el encadenamiento de métodos ( en.wikipedia.org/wiki/Method_chaining ). Además, puede usar una matriz de parámetros (tiene que ser una matriz o mapa asociativo), donde cualquier entrada en esa matriz de parámetros omite utiliza el comportamiento del valor predeterminado para ese parámetro. Al final, la llamada a través de encadenamiento de métodos o matrices de parámetros puede ser tan sucinta y expresiva como las banderas de bits, además, no todos los idiomas tienen operadores de bits (en realidad me gustan las banderas binarias, pero usaría los métodos que acabo de mencionar).
dukeofgaming

3
No es muy POO, ¿verdad? Haría una interfaz ala: String myNewPassword = makePassword (randomComposeSupplier (new RandomLowerCaseSupplier (), new RandomUpperCaseSupplier (), new RandomNumberSupplier)); con String makePassword (proveedor <Character> charSupplier); y Proveedor <Personaje> randomComposeSupplier (Proveedor <Personaje> ... proveedores); Ahora puede reutilizar a sus proveedores para otras tareas, componerlos de la forma que desee y simplificar su método generatePassword para que use un estado mínimo.
Dibbeke

44
@Dibbeke Habla sobre un reino de sustantivos ...
Phil

15

Un gran bloque funcional es el olor, no las banderas. Si configura la bandera en la línea 5, solo verifique la bandera en la línea 354, entonces eso es malo. Si configura la bandera en la línea 8 y busca la bandera en la línea 10, está bien. Además, uno o dos indicadores por bloque de código están bien, 300 indicadores en una función son incorrectos.


10

Por lo general, las banderas se pueden reemplazar completamente por algún tipo de patrón de estrategia, con una implementación de estrategia para cada valor posible de la bandera. Esto hace que agregar un nuevo comportamiento sea mucho más fácil.

En situaciones críticas de rendimiento, el costo de la indirección puede aparecer y hacer que la deconstrucción se convierta en indicadores claros necesarios. Dicho esto, estoy teniendo problemas para recordar un solo caso en el que realmente tuve que hacer eso.


6

No, las banderas no son malas o un mal que debe ser refactorizado a toda costa.

Considere la llamada Pattern.compile (String regex, int flags) de Java . Esta es una máscara de bits tradicional y funciona. Eche un vistazo a las constantes en Java y cada vez que vea un grupo de 2 n , sabe que hay banderas allí.

En un mundo refactorizado ideal, se usaría un EnumSet donde las constantes son valores en una enumeración y como dice la documentación:

El rendimiento de espacio y tiempo de esta clase debería ser lo suficientemente bueno como para permitir su uso como una alternativa segura de tipo de alta calidad a los "indicadores de bits" tradicionales basados ​​en int.

En un mundo perfecto, esa llamada Pattern.compile se convierte Pattern.compile(String regex, EnumSet<PatternFlagEnum> flags).

Todo lo dicho, sigue siendo banderas. Es mucho más fácil trabajar con Pattern.compile("foo", Pattern.CASE_INSENSTIVE | Pattern.MULTILINE)lo que sería tener Pattern.compile("foo", new PatternFlags().caseInsenstive().multiline())u otro estilo de tratar de hacer lo que las banderas son realmente y para qué sirven.

Las banderas se ven a menudo cuando se trabaja con elementos de nivel de sistema. Al interactuar con algo en el nivel del sistema operativo, es probable que tenga un indicador en alguna parte, ya sea el valor de retorno de un proceso, o los permisos de un archivo, o los indicadores para abrir un socket. Intentar refactorizar estas instancias en una cacería de brujas contra un olor de código percibido probablemente terminará con un código peor que si uno usara y entendiera la bandera.

El problema se produce cuando la gente usa mal las banderas que las unen y crean un conjunto de banderas francas de todo tipo de banderas no relacionadas o cuando intentan usarlas donde no son banderas.


5

Supongo que estamos hablando de banderas dentro de las firmas de métodos.

Usar una sola bandera ya es bastante malo.

No significará nada para sus colegas la primera vez que lo vean. Tendrán que mirar el código fuente del método para establecer lo que hace. Probablemente estarás en la misma posición unos meses después, cuando olvides de qué se trataba tu método.

Pasar una bandera al método, normalmente significa que su método es responsable de varias cosas. Dentro del método, probablemente esté haciendo una simple verificación en las líneas de:

if (flag)
   DoFlagSet();
else
   DoFlagNotSet();

Esa es una mala separación de preocupaciones y normalmente puedes encontrar una forma de evitarla.

Normalmente tengo dos métodos separados:

public void DoFlagSet() 
{
}

public void DoFlagNotSet()
{
}

Esto tendrá más sentido con los nombres de métodos que son aplicables al problema que está resolviendo.

Pasar varias banderas es el doble de malo. Si realmente necesita pasar varios indicadores, entonces considere encapsularlos dentro de una clase. Incluso entonces, aún enfrentará el mismo problema, ya que su método probablemente esté haciendo varias cosas.


3

Las banderas y la mayoría de las variables temporales son un olor fuerte. Lo más probable es que puedan ser refactorizados y reemplazados con métodos de consulta.

Revisado:

Las banderas y las variables temporales al expresar el estado deben refactorizarse a los métodos de consulta. Los valores de estado (booleanos, ints y otras primitivas) casi siempre deben ocultarse como parte de los detalles de implementación.

Las banderas que se usan para el control, el enrutamiento y el flujo general del programa también pueden indicar la oportunidad de refactorizar secciones de las estructuras de control en estrategias o fábricas separadas, o lo que sea apropiado según la situación, que continúe utilizando los métodos de consulta.


2

Cuando hablamos de indicadores, debemos saber que se modificarán con el tiempo de ejecución del programa y que afectarán el comportamiento del programa en función de sus estados. Mientras tengamos un control claro sobre estas dos cosas, funcionarán muy bien.

Las banderas pueden funcionar muy bien si

  • Los ha definido en un ámbito apropiado. Por apropiado quiero decir que el alcance no debe contener ningún código que no los necesite / no los modifique. O al menos el código es seguro (por ejemplo, no se puede llamar directamente desde afuera)
  • Si es necesario manejar banderas desde el exterior y si hay muchas banderas, podemos codificar el manejador de banderas como una única forma de modificarlas de manera segura. Este controlador de banderas puede encapsular banderas y métodos para modificarlas. Luego se puede convertir en singleton y luego se puede compartir entre clases que necesitan acceso a banderas.
  • Y, por último, para la mantenibilidad, si hay demasiados indicadores:
    • No es necesario decir que deben seguir nombres razonables
    • Debe documentarse con los valores válidos (puede ser con enumeraciones)
    • Debe documentarse con CUAL CÓDIGO MODIFICARÁ cada uno de ellos, y también con CUÁL CONDICIÓN resultará en la asignación de un valor particular a la bandera.
    • QUÉ CÓDIGO LOS CONSUMIRÁ Y QUÉ COMPORTAMIENTO resultará para un valor particular

Si hay muchas banderas, un buen trabajo de diseño debe preceder, ya que las banderas comienzan a jugar un papel clave en el comportamiento del programa. Puede ir a los diagramas de estado para modelar. Dichos diagramas también funcionan como documentación y orientación visual al tratar con ellos.

Mientras estas cosas estén en su lugar, creo que no conducirá al desastre.


1

Supuse por la pregunta que el QA significaba variables de marca (globales), y no bits de un parámetro de función.

Hay situaciones en las que no tienes muchas otras posibilidades. Por ejemplo, sin un sistema operativo, debe evaluar las interrupciones. Si se produce una interrupción con mucha frecuencia y no tiene tiempo para realizar una evaluación prolongada en el ISR, no solo está permitido, sino que a veces, incluso las mejores prácticas solo establecen algunos indicadores globales en el ISR (debe pasar el menor tiempo posible) en el ISR), y para evaluar esas banderas en su bucle principal.


0

No creo que nada sea ​​un mal absoluto en la programación, nunca.

Hay otra situación en la que las banderas podrían estar en orden, que aún no se mencionaron aquí ...

Considere el uso de cierres en este fragmento de Javascript:

exports.isPostDraft = function ( post, draftTag ) {
  var isDraft = false;
  if (post.tags)
    post.tags.forEach(function(tag){ 
      if (tag === draftTag) isDraft = true;
    });
  return isDraft;
}

La función interna, que se pasa a "Array.forEach", no puede simplemente "devolver verdadero".

Por lo tanto, debe mantener el estado afuera con una bandera.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.