Pregunta

Un colega mío afirma que booleanos como argumentos de método no son aceptables . Serán reemplazados por enumeraciones. Al principio no vi ningún beneficio, pero él me dio un ejemplo.

¿Qué es más fácil de entender?

file.writeData( data, true );

O

enum WriteMode {
  Append,
  Overwrite
};

file.writeData( data, Append );

Ahora lo tengo! ;-)
Este es definitivamente un ejemplo en el que una enumeración como segundo parámetro hace que el código sea mucho más legible.

Entonces, ¿cuál es tu opinión sobre este tema?

¿Fue útil?

Solución

Los booleanos representan " sí / no " elecciones Si desea representar un " sí / no " ;, luego use un valor booleano, debería explicarse por sí mismo.

Pero si es una opción entre dos opciones, ninguna de las cuales es claramente sí o no, entonces una enumeración a veces puede ser más legible.

Otros consejos

Las enumeraciones también permiten futuras modificaciones, donde ahora quieres una tercera opción (o más).

Utilice el que mejor modele su problema. En el ejemplo que da, la enumeración es una mejor opción. Sin embargo, habría otros momentos en que un booleano es mejor. Lo que tiene más sentido para ti:

lock.setIsLocked(True);

o

enum LockState { Locked, Unlocked };
lock.setLockState(Locked);

En este caso, podría elegir la opción booleana ya que creo que es bastante clara e inequívoca, y estoy bastante seguro de que mi bloqueo no tendrá más de dos estados. Sin embargo, la segunda opción es válida, pero innecesariamente complicada, IMHO.

Creo que casi lo ha respondido usted mismo, creo que el objetivo final es hacer que el código sea más legible, y en este caso, la enumeración hizo eso. OMI siempre es mejor mirar el objetivo final en lugar de las reglas generales, tal vez piense de ello más como una guía, es decir, las enumeraciones suelen ser más legibles en el código que los bools genéricos, ints, etc., pero siempre habrá excepciones a la regla.

Recuerde la pregunta que Adlai Stevenson le hizo al embajador Zorin en la ONU durante la misil cubano crisis ?

  

" Estás en el tribunal del mundo   Opinión en este momento, y puedes responder.    sí o no . Has negado que [los misiles]   Existe, y quiero saber si yo   te he entendido correctamente .... soy   preparado para esperar mi respuesta hasta   el infierno se congela, si ese es tu   decisión. "

Si el indicador que tiene en su método es de tal naturaleza que puede anclarlo en una decisión binaria , y esa decisión nunca se convertirá en un tres -decisión de camino o n-way, vaya por booleano. Indicaciones: su bandera se llama isXXX .

No lo convierta en booleano en caso de que se trate de un interruptor de modo . Siempre hay un modo más de lo que pensabas al escribir el método en primer lugar.

El dilema de un modo más tiene, por ejemplo, Unix encantado, donde los posibles modos de permiso que un archivo o directorio puede tener hoy en día dan como resultado extraños significados dobles de modos según el tipo de archivo, la propiedad, etc.

Para mí, ni usar booleano ni enumeración es un buen enfoque. Robert C. Martin captura esto muy claramente en su Consejo de código limpio # 12: eliminar los argumentos booleanos :

  

Los argumentos booleanos declaran en voz alta que la función hace más de una cosa. Son confusos y deben eliminarse.

Si un método hace más de una cosa, debería escribir dos métodos diferentes, por ejemplo, en su caso: file.append (data) y file.overwrite (data) .

El uso de una enumeración no aclara las cosas. No cambia nada, sigue siendo un argumento de bandera.

Hay dos razones por las que me he encontrado con que esto es algo malo:

  1. Porque algunas personas escribirán métodos como:

    ProcessBatch(true, false, false, true, false, false, true);
    

    Esto obviamente es malo porque es muy fácil mezclar parámetros, y no tienes idea al ver lo que estás especificando. Sin embargo, solo un bool no es tan malo.

  2. Debido a que controlar el flujo de programas mediante una simple rama sí / no puede significar que tiene dos funciones completamente diferentes que están agrupadas en una de una manera incómoda. Por ejemplo:

    public void Write(bool toOptical);
    

    Realmente, esto debería ser dos métodos

    public void WriteOptical();
    public void WriteMagnetic();
    

    porque el código en estos podría ser completamente diferente; Es posible que tengan que hacer todo tipo de manejo y validación de errores diferentes, o incluso que tengan que formatear los datos salientes de manera diferente. No puede saberlo simplemente utilizando Write () o incluso Write (Enum.Optical) (aunque, por supuesto, podría tener cualquiera de esos métodos simplemente llamando a métodos internos WriteOptical / Mag si quieres).

Supongo que solo depende. No haría una gran oferta al respecto excepto por el # 1.

Las enumeraciones son mejores, pero no llamaría parámetros booleanos como " inaceptable " ;. A veces es más fácil lanzar un pequeño booleano y seguir adelante (piense en métodos privados, etc.)

Los booleanos pueden estar bien en idiomas que tienen parámetros con nombre, como Python y Objective-C, ya que el nombre puede explicar lo que hace el parámetro:

file.writeData(data, overwrite=true)

o:

[file writeData:data overwrite:YES]

No estaría de acuerdo en que es una buena regla . Obviamente, Enum hace un mejor código explícito o detallado en algunos casos, pero como regla general, parece que está muy por encima del alcance.

Primero déjame tomar tu ejemplo: La responsabilidad (y la capacidad) de los programadores para escribir un buen código no se ve realmente comprometida por tener un parámetro booleano. En su ejemplo, el programador podría haber escrito solo un código detallado escribiendo:

dim append as boolean = true
file.writeData( data, append );

o prefiero más general

dim shouldAppend as boolean = true
file.writeData( data, shouldAppend );

Segundo: El ejemplo de Enum que dio es solo " mejor " Porque estás pasando un CONST. Lo más probable es que en la mayoría de las aplicaciones, al menos algunos, si no la mayoría de los parámetros de tiempo que se pasan a las funciones, sean VARIABLES. en cuyo caso, mi segundo ejemplo (dar variables con buenos nombres) es mucho mejor y Enum le habría dado pocos beneficios.

Las enumeraciones tienen un beneficio definido, pero no deberías simplemente reemplazar todos tus booleanos por enumeraciones. Hay muchos lugares donde verdadero / falso es en realidad la mejor manera de representar lo que está pasando.

Sin embargo, usarlos como argumentos de método es un poco sospechoso, simplemente porque no puedes ver sin investigar lo que se supone que deben hacer, ya que te permiten ver lo que significa verdadero / falso en realidad

Las propiedades (especialmente con los inicializadores de objetos C # 3) o los argumentos de palabras clave (a la ruby ??o python) son una forma mucho mejor de ir a donde de otro modo usarías un argumento booleano.

Ejemplo de C #:

var worker = new BackgroundWorker { WorkerReportsProgress = true };

Ejemplo de Ruby

validates_presence_of :name, :allow_nil => true

Ejemplo de Python

connect_to_database( persistent=true )

Lo único que puedo pensar de dónde es correcto hacer un argumento de método booleano es en java, donde no tienes propiedades ni argumentos de palabras clave. Esta es una de las razones por las que odio Java :-(

Si bien es cierto que en muchos casos los enums son más legibles y más extensos que los booleanos, una regla absoluta de que " los booleanos no son aceptables " es tonto Es inflexible y contraproducente: no deja espacio para el juicio humano. Son un tipo fundamental integrado en la mayoría de los idiomas porque son útiles. Considere aplicarlo a otros tipos incorporados: decir, por ejemplo, "nunca use un int como parámetro " Sería una locura.

Esta regla es solo una cuestión de estilo, no de potencial para errores o rendimiento en tiempo de ejecución. Una regla mejor sería " preferir enums a booleans por razones de legibilidad " ;.

Mira el framework .Net. Los booleanos se utilizan como parámetros en bastantes métodos. La API de .Net no es perfecta, pero no creo que el uso de boolean como parámetros sea un gran problema. La información sobre herramientas siempre le da el nombre del parámetro, y usted también puede construir este tipo de orientación: complete sus comentarios XML sobre los parámetros del método, aparecerán en la información sobre herramientas.

También debo agregar que hay un caso en el que debe refactorizar claramente los booleanos a una enumeración, cuando tiene dos o más booleanos en su clase o en sus parámetros de método, y no todos los estados son válidos (por ejemplo, no es válido para que los dos sean verdaderos).

Por ejemplo, si su clase tiene propiedades como

public bool IsFoo
public bool IsBar

Y es un error que ambos sean verdaderos al mismo tiempo, lo que realmente tienes son tres estados válidos, mejor expresados ??como algo así como:

enum FooBarType { IsFoo, IsBar, IsNeither };

Algunas de las reglas a las que su colega podría estar mejor adhiriéndose son:

  • No seas dogmático con tu diseño.
  • Elija lo que mejor se adapte a los usuarios de su código.
  • ¡No intentes golpear clavijas en forma de estrella en cada agujero solo porque te gusta la forma este mes!

Un booleano solo sería aceptable si no pretende extender la funcionalidad del marco. Se prefiere la opción Enum porque puede extender la enumeración y no interrumpir las implementaciones anteriores de la llamada a la función.

La otra ventaja del Enum es que es más fácil de leer.

Si el método hace una pregunta como:

KeepWritingData (DataAvailable());

donde

bool DataAvailable()
{
    return true; //data is ALWAYS available!
}

void KeepWritingData (bool keepGoing)
{
   if (keepGoing)
   {
       ...
   }
}

Los argumentos de los métodos booleanos parecen tener un sentido absolutamente perfecto.

Depende del método. Si el método hace algo que es obviamente algo verdadero / falso, entonces está bien, por ejemplo. A continuación [aunque no estoy diciendo que este sea el mejor diseño para este método, es solo un ejemplo de donde el uso es obvio].

CommentService.SetApprovalStatus(commentId, false);

Sin embargo, en la mayoría de los casos, como el ejemplo que mencionas, es mejor usar una enumeración. Hay muchos ejemplos en el .NET Framework en el que no se sigue esta convención, pero eso se debe a que introdujeron esta guía de diseño bastante tarde en el ciclo.

Hace que las cosas sean un poco más explícitas, pero comienza a ampliar enormemente la complejidad de sus interfaces: en una elección booleana pura, como añadir / sobrescribir, parece una exageración. Si necesita agregar una opción adicional (que no puedo imaginar en este caso), siempre puede realizar un refactor (dependiendo del idioma)

Las enumeraciones ciertamente pueden hacer que el código sea más legible. Todavía hay algunas cosas a tener en cuenta (al menos en .net)

Debido a que el almacenamiento subyacente de una enumeración es un int, el valor predeterminado será cero, por lo que debe asegurarse de que 0 sea un valor predeterminado razonable. (Por ejemplo, las estructuras tienen todos los campos establecidos en cero cuando se crean, por lo que no hay manera de especificar un valor predeterminado distinto de 0. Si no tiene un valor de 0, ni siquiera puede probar la enumeración sin convertir a int, lo que sería mal estilo.)

Si sus enumeraciones son privadas para su código (nunca se expone públicamente), puede dejar de leer aquí.

Si sus enumeraciones se publican de alguna manera en un código externo y / o se guardan fuera del programa, considere numerarlos explícitamente. El compilador los numera automáticamente desde 0, pero si reorganiza sus enumeraciones sin darles valores, puede terminar con defectos.

Puedo escribir legalmente

WriteMode illegalButWorks = (WriteMode)1000000;
file.Write( data, illegalButWorks );

Para combatir esto, cualquier código que consuma una enumeración de la que no pueda estar seguro (por ejemplo, la API pública) debe verificar si la enumeración es válida. Lo haces a través de

if (!Enum.IsDefined(typeof(WriteMode), userValue))
    throw new ArgumentException("userValue");

La única advertencia de Enum.IsDefined es que usa la reflexión y es más lento. También sufre un problema de versiones. Si necesita verificar el valor de la enumeración a menudo, sería mejor lo siguiente:

public static bool CheckWriteModeEnumValue(WriteMode writeMode)
{
  switch( writeMode )
  {
    case WriteMode.Append:
    case WriteMode.OverWrite:
      break;
    default:
      Debug.Assert(false, "The WriteMode '" + writeMode + "' is not valid.");
      return false;
  }
  return true;
}

El problema con las versiones es que el código antiguo solo puede saber cómo manejar las 2 enumeraciones que tiene. Si agrega un tercer valor, Enum.IsDefined será verdadero, pero el código antiguo no necesariamente puede manejarlo. Whoops.

Hay aún más diversión que puedes hacer con las enumeración de [Flags] , y el código de validación es ligeramente diferente.

También señalaré que para la portabilidad, debe usar la llamada ToString () en la enumeración, y usar Enum.Parse () cuando los vuelva a leer. Tanto ToString () como Enum.Parse () pueden manejar enumeración de [Flags] , así que no hay razón para no usarlos. Tenga en cuenta que es otro escollo, porque ahora ni siquiera puede cambiar el nombre de la enumeración sin posiblemente romper el código.

Entonces, a veces necesitas ponderar todo lo anterior cuando te preguntas a ti mismo ¿Puedo salir con solo un bool?

En mi humilde opinión, parece que una enumeración sería la opción obvia para cualquier situación en la que sean posibles más de dos opciones. Pero definitivamente hay situaciones donde un booleano es todo lo que necesitas. En ese caso, diría que usar una enumeración donde funcionaría un bool sería un ejemplo de usar 7 palabras cuando 4 lo harán.

Los booleanos tienen sentido cuando tienes un interruptor obvio que solo puede ser una de dos cosas (es decir, el estado de una bombilla, encendida o apagada). Aparte de eso, es bueno escribirlo de tal manera que sea obvio lo que estás pasando, por ejemplo. Las escrituras en disco, sin búfer, con búfer de línea o sincrónicas, deben pasarse como tales. Incluso si no desea permitir escrituras síncronas ahora (y, por lo tanto, está limitado a dos opciones), vale la pena considerar hacerlas más detalladas con el fin de saber qué hacen a primera vista.

Dicho esto, también puede usar False and True (0 y 1 booleanos) y luego, si necesita más valores más adelante, expanda la función para admitir los valores definidos por el usuario (por ejemplo, 2 y 3), y su antiguo 0 Los valores de / 1 se trasladarán muy bien, por lo que su código no debería romperse.

A veces es más sencillo modelar un comportamiento diferente con sobrecargas. Para continuar desde tu ejemplo sería:

file.appendData( data );  
file.overwriteData( data );

Este enfoque se degrada si tiene varios parámetros, cada uno de los cuales permite un conjunto fijo de opciones. Por ejemplo, un método que abre un archivo puede tener varias permutaciones de modo de archivo (abrir / crear), acceso a archivos (lectura / escritura), modo de compartir (ninguno / lectura / escritura). El número total de configuraciones es igual a los productos cartesianos de las opciones individuales. Naturalmente, en tales casos, las sobrecargas múltiples no son apropiadas.

Enums puede, en algunos casos, hacer que el código sea más legible, aunque validar el valor exacto de enumeración en algunos idiomas (C # por ejemplo) puede ser difícil.

A menudo, un parámetro booleano se agrega a la lista de parámetros como una nueva sobrecarga. Un ejemplo en .NET es:

Enum.Parse(str);  
Enum.Parse(str, true); // ignore case

La última sobrecarga estuvo disponible en una versión posterior de .NET framework que la primera.

Si sabe que solo habrá dos opciones, un booleano podría estar bien. Las enumeraciones son extensibles de una manera que no romperá el código antiguo, aunque es posible que las bibliotecas antiguas no admitan los nuevos valores de enumeración, por lo que no se puede ignorar completamente la versión.


EDIT

En las versiones más nuevas de C # es posible usar argumentos con nombre que, IMO, pueden hacer que el código de llamada sea más claro de la misma manera que las enumeraciones. Usando el mismo ejemplo anterior:

Enum.Parse(str, ignoreCase: true);

Cuando estoy de acuerdo en que los Enums son una buena manera de ir, en métodos donde tienes 2 opciones (y solo dos opciones puedes tener legibilidad sin enumerar).

por ejemplo

public void writeData(Stream data, boolean is_overwrite)

Love the Enums, pero boolean también es útil.

Esta es una entrada tardía en una publicación antigua, y está tan abajo en la página que nadie la leerá nunca, pero como ya nadie la ha dicho ...

Un comentario en línea ayuda a resolver el problema inesperado bool . El ejemplo original es particularmente atroz: ¡imagina intentar nombrar la variable en la función de declearación! Sería algo así como

void writeData( DataObject data, bool use_append_mode );

Pero, por ejemplo, digamos que esa es la declaración. Luego, para un argumento booleano de otra manera inexplicable, puse el nombre de la variable en un comentario en línea. Comparar

file.writeData( data, true );

con

file.writeData( data, true /* use_append_mode */);

Realmente depende de la naturaleza exacta del argumento. Si no es un sí / no o verdadero / falso, una enumeración lo hace más legible. Pero con una enumeración, debe verificar el argumento o tener un comportamiento predeterminado aceptable, ya que se pueden pasar valores indefinidos del tipo subyacente.

El uso de enumeraciones en lugar de booleanos en su ejemplo ayuda a hacer que la llamada al método sea más legible. Sin embargo, este es un sustituto de mi artículo de deseo favorito en C #, llamado argumentos en las llamadas a métodos. Esta sintaxis:

var v = CallMethod(pData = data, pFileMode = WriteMode, pIsDirty = true);

sería perfectamente legible, y luego podría hacer lo que debería hacer un programador, que es elegir el tipo más apropiado para cada parámetro en el método sin importar cómo se ve en el IDE.

C # 3.0 permite argumentos con nombre en constructores. No sé por qué no pueden hacer esto con métodos también.

Valores booleanos true / false solamente. Así que no está claro lo que representa. Enum puede tener un nombre significativo, por ejemplo, OVERWRITE , APPEND , etc. Así que las enumeraciones son mejores.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top