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

44

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

  

"las variables booleanas o enteras a las que asignas un valor en ciertos lugares y luego abajo, verificas para hacer algo o no, como, por ejemplo, usar newItem = true y luego algunas líneas debajo de 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 marcas, el código de espionaje IMHO también crece.

¿Diría que hay casos en los que el uso de banderas es una buena práctica o incluso necesario, o está de acuerdo en que el uso de banderas en el código son ... banderas rojas y debe evitarse / reformularse? En mi caso, simplemente me ocupo de hacer funciones / métodos que verifican estados en tiempo real.

    
pregunta dukeofgaming 31.10.2012 - 19:29

10 respuestas

38

El problema que he visto al mantener el código que hace uso de las banderas es que la cantidad 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 estas tres banderas

bool capturing, processing, sending;

Estos tres crearon ocho estados (en realidad, también había otras dos banderas). 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 era falsa.

Las banderas tienden a componerse con el tiempo y ocultan el estado real de una clase. Es por eso que deben evitarse.

    
respondido por el Ben 31.10.2012 - 20:04
37

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

Tengo un fragmento de código que genera contraseñas (utilizando un generador de números pseudoaleatorios criptográficamente seguro). La persona que llama al 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 true y false , pero todavía tiene dos inconvenientes:

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

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 de esta manera:

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 utilizando indicadores:

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

El segundo inconveniente, muy pequeño, es que no está claro cómo se comportaría el método si se llamara así:

var password = this.PasswordGenerator.Generate(
    new []
    {
        CharacterSet.Digits,
        CharacterSet.LettersAndDigits, // So digits are requested two times.
    });
    
respondido por el Arseni Mourzenko 31.10.2012 - 19:52
15

Un bloque de función enorme es el olor, no las banderas. Si establece la bandera en la línea 5, entonces solo busque la bandera en la línea 354, entonces eso es malo. Si configura la bandera en la línea 8 y verifica la bandera en la línea 10, está bien. Además, una o dos banderas por bloque de código está bien, 300 banderas en una función son malas.

    
respondido por el nbv4 01.11.2012 - 01:31
9

Por lo general, los indicadores pueden ser reemplazados completamente por algún tipo de patrón de estrategia, con una implementación de estrategia para cada valor posible del indicador. 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 podría salir a la superficie y hacer necesaria la deconstrucción en indicadores claros. Dicho esto, estoy teniendo problemas para recordar un solo caso en el que realmente tuve que hacer eso.

    
respondido por el back2dos 31.10.2012 - 19:55
6

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

Considere los Pattern.compile (Strex regex, int flags) call. Esta es una máscara de bits tradicional y funciona. Echa un vistazo a las constantes en java y donde veas un montón de 2 n sabes que hay banderas que están ahí.

En un mundo refactorizado ideal, uno usaría en cambio un EnumSet donde las constantes son valores en cambio en una enumeración y como se lee en 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 de alta calidad, segura para tipos, a las tradicionales.   "banderas de bits" basadas en int.

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

Todo lo dicho, sus banderas quietas. Es mucho más fácil trabajar con Pattern.compile("foo", Pattern.CASE_INSENSTIVE | Pattern.MULTILINE) de lo que sería tener Pattern.compile("foo", new PatternFlags().caseInsenstive().multiline()) o algún otro estilo de intentar hacer lo que realmente son buenas para las banderas.

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

El problema se produce cuando las personas hacen mal uso de las banderas al juntarlas y crean un conjunto frankenflag de todo tipo de banderas no relacionadas o cuando intentan usarlas donde no son banderas.

    
respondido por el user40980 31.10.2012 - 21:56
5

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

Usar una sola bandera es suficientemente 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á en la misma posición unos meses después, cuando olvide de qué se trató su método.

Al pasar un indicador al método, normalmente significa que su método es responsable de varias cosas. Dentro del método, probablemente esté haciendo una comprobación simple en las líneas de:

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

Es una separación pobre de preocupaciones y normalmente puedes encontrar una manera de evitarlo.

Normalmente tengo dos métodos separados:

public void DoFlagSet() 
{
}

public void DoFlagNotSet()
{
}

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

Pasar varias banderas es el doble de malo. Si realmente necesita pasar varias marcas, entonces considere encapsularlas dentro de una clase. Incluso entonces, seguirás enfrentando el mismo problema, ya que es probable que tu método esté haciendo varias cosas.

    
respondido por el CodeART 31.10.2012 - 20:06
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:

Los indicadores y las variables temporales cuando expresan el estado, deben ser refactorizadas a los métodos de consulta. Los valores del estado (booleanos, ints y otros primativos) deben ocultarse casi siempre como parte de los detalles de la implementación.

Los indicadores que se usan para el control, enrutamiento y 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 que sea apropiado según la situación, que continúen utilizando los métodos de consulta. .

    
respondido por el JustinC 31.10.2012 - 20:09
2

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

Las banderas pueden funcionar muy bien si

  • Los ha definido en un ámbito apropiado. Me refiero a que el alcance no debe contener ningún código que no necesite / no deba modificarlo. O al menos el código es seguro (por ejemplo, no puede llamarse directamente desde afuera)
  • Si hay necesidad de manejar banderas desde el exterior y si hay muchas banderas, podemos codificar el controlador de bandera como una forma única de modificar las banderas de manera segura. Este controlador de bandera puede encapsular banderas y métodos para modificarlos. Luego se puede hacer singleton y luego compartirse entre las clases que necesitan acceso a las banderas.
  • Y, por último, para el mantenimiento, si hay demasiadas banderas:
    • No es necesario decir que deben seguir una nomenclatura sensata
    • Se debe documentar cuáles son los valores válidos (puede ser con las enumeraciones)
    • Debe documentarse con QUÉ CÓDIGO MODIFICARÁ cada uno de ellos, y también con QUÉ CONDICIÓN dará como resultado la asignación de un valor particular a la bandera.
    • QUÉ CÓDIGO LOS CONSUMIRÁ Y QUE CONDUCTA resultará para un valor particular

Si hay muchas banderas, el trabajo de buen diseño debe preceder a las banderas y luego comenzar a jugar un papel clave en el comportamiento del programa. Puedes ir a diagramas de estado para modelar. Dichos diagramas también funcionan como documentación y guía visual al tratarlos.

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

    
respondido por el Mahesha999 01.11.2012 - 10:02
1

Supuse de la pregunta que el QA era variables de bandera (globales) de significado, 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 tienes que evaluar las interrupciones. Si una interrupción se produce 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 se recomienda establecer solo banderas globales en el ISR (debe pasar el menor tiempo posible) en el ISR), y para evaluar esas banderas en su bucle principal.

    
respondido por el vsz 01.11.2012 - 12:18
0

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

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

Considere el uso de cierres en este fragmento de código 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, necesitas mantener el estado afuera con una bandera.

    
respondido por el firstdoit 07.11.2012 - 22:36

Lea otras preguntas en las etiquetas