¿Son inaceptables los booleanos como argumentos de método? [cerrado]

123

Un colega mío afirma que los 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?

Thomas Koschel
fuente
77
Esta fue una lectura muy interesante, implementaré este método con más frecuencia.
Sara Chipps
hrm, he hecho esto antes, pero nunca me di cuenta de lo bueno que es un patrón de diseño. entonces la enumeración va en el archivo?
Shawn
Las enumeraciones ciertamente tienen más sentido desde un punto de vista semántico. En otra nota, sería interesante ver lo que algunos programadores han ideado para manejar la lógica difusa.
James P.
2
sólo hay que preguntar a la persona limón en tiempo de la aventura si esto es inaceptable
ajax333221

Respuestas:

131

Los booleanos representan opciones de "sí / no". Si desea representar un "sí / no", utilice un booleano, debe explicarse por sí mismo.

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

skaffman
fuente
3
Además, el nombre del método debe ser claro acerca de lo que hace el argumento sí o no, es decir, anular turnLightOn (bool), si se establece verdadero o sí, se encenderá la luz.
Simon
10
Aunque en este caso prefiero tener turnLightOn () y turnLightOff (), dependiendo de la situación.
skaffman
14
"turnLightOn (false)" significa "no encienda la luz"? Confusióng.
Jay Bazuzi
17
¿Qué tal setLightOn(bool).
Finbarr
10
Comentario tardío, pero @Jay Bazuzi: si su método se llama turnLightOn, y pasa falso, es mejor que no llame al método, pasar falso dice que no encienda la luz. Si la luz ya está encendida, no significa que la apague, significa que no la encienda ... Si tiene un método 'turnLight', una enumeración con 'On' y 'Off' tiene sentido, turnLight ( On), encienda la luz (Off). Estoy de acuerdo con skaffman aunque, preferiría dos métodos explícitos diferentes, turnLightOn () y turnLightOff (). (Por cierto: esto se explica en el libro "Código limpio" de Uncle Bobs)
Phill
32

Use 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. Aún así, la segunda opción es válida, pero innecesariamente complicada, en mi humilde opinión.

Jeremy Bourque
fuente
2
en su ejemplo prefiero tener dos métodos. lock.lock () lock.release () y lock.IsSet, pero todo depende de lo que tenga más sentido para el código consumidor.
Robert Paulson
3
Ese es un comentario justo, pero creo que también ilustra el punto más amplio de que hay muchas formas de modelar un problema dado. Debe usar el mejor modelo para sus circunstancias, y también debe usar las mejores herramientas que el entorno de programación proporciona para adaptarse a su modelo.
Jeremy Bourque
Estoy totalmente de acuerdo :), solo estaba comentando sobre el pseudocódigo particular que ofrece otra toma. Estoy de acuerdo con tu respuesta
Robert Paulson
14

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

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

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).

Usar una enumeración no aclara las cosas. No cambia nada, sigue siendo un argumento distintivo.

Pascal Thivent
fuente
77
¿No significa eso que una función que acepta una cadena ASCII de longitud N hace 128 ^ N cosas?
desvío
@delty ¿Es este un comentario serio? En caso afirmativo, ¿codifica un if en todos los valores posibles de una cadena a menudo? ¿Hay alguna comparación posible con el caso de un argumento booleano?
Pascal Thivent
Creo que son aceptables cuando se establece un valor booleano dentro de un objeto. Un ejemplo perfecto sería setVisible(boolean visible) { mVisible = visible; }. ¿Cuál sería una alternativa que sugerirías?
Brad
2
@Brad show () {mVisible = true} hide () {mVisible = false}
Oswaldo Acauan
@Oswaldo, aunque todavía es correcto, no creo que tenga sentido tener dos métodos diferentes para asignar un valor booleano a valores diferentes. No tienes un setIntToOne (), setIntToTwo () setIntToThree () ¿verdad? Es un poco más ambiguo cuando solo puede tener dos valores posibles, pero por razones de limpieza, use un booleano en ese caso.
Brad
13

Creo que casi respondiste esto tú 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, en mi opinión, siempre es mejor mirar el objetivo final en lugar de las reglas generales, tal vez piense más en ello como guía, es decir, las enumeraciones a menudo son más legibles en el código que los bools genéricos, ints, etc., pero siempre habrá excepciones a la regla.

Tim Jarvis
fuente
13

¿Recuerda la pregunta que Adlai Stevenson le hizo al embajador Zorin en la ONU durante la crisis de los misiles cubanos ?

"Estás en la corte de la opinión mundial en este momento, y puedes responder sí o no . Has negado que existan [los misiles] y quiero saber si te he entendido correctamente ... Estoy preparado para esperar por mi respuesta hasta que el infierno se congele, si esa es tu decisión ".

Si la bandera que tiene en su método es de tal naturaleza que puede precisarla como una decisión binaria , y esa decisión nunca se convertirá en una decisión de tres vías o de dos vías, vaya a boolean. Indicaciones: su bandera se llama isXXX .

No lo haga booleano en caso de que sea un cambio de modo . Siempre hay un modo más de lo que pensabas al escribir el método en primer lugar.

El dilema del modo uno más ha perseguido, por ejemplo, a Unix, donde los posibles modos de permiso que puede tener un archivo o directorio pueden dar como resultado un extraño doble sentido de modos dependiendo del tipo de archivo, propiedad, etc.

Thorsten79
fuente
13

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

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

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

    Obviamente, esto es malo porque es demasiado fácil mezclar parámetros, y no tienes idea al mirar lo que estás especificando. Sin embargo, solo un bool no es tan malo.

  2. Debido a que controlar el flujo del programa mediante una rama simple sí / no puede significar que tiene dos funciones completamente diferentes que se agrupan en una de una manera extraña. 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 tal vez incluso tengan que formatear los datos salientes de manera diferente. No puede decirlo simplemente usando Write()o incluso Write(Enum.Optical)(aunque, por supuesto, podría tener cualquiera de esos métodos, simplemente llame a los métodos internos WriteOptical / Mag si lo desea).

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

Sam Schutte
fuente
Muy buenos puntos! Dos parámetros booleanos en un método se ven terribles, de hecho (a menos que tenga suerte de tener parámetros con nombre, por supuesto).
Yarik
Sin embargo, esta respuesta podría beneficiarse de un reformateo. ;-)
Yarik
7

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

Borek Bernard
fuente
simplemente haga que el método sea muy descriptivo para que quede claro qué significa verdadero o sí.
Simon
6

Los booleanos pueden estar bien en lenguajes 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]
Chris Lundie
fuente
1
En mi humilde opinión, writeData () es un mal ejemplo del uso de un parámetro booleano, independientemente de si los parámetros con nombre son o no compatibles. ¡No importa cómo nombre el parámetro, el significado del valor Falso no es obvio!
Yarik
4

No estaría de acuerdo en que es una buena regla . Obviamente, Enum crea un mejor código explícito o detallado en algunos casos, pero, por regla general, parece exagerado.

Primero déjenme tomar su ejemplo: la responsabilidad (y 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 igual que el 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 diste es solo "mejor" porque estás pasando un CONST. Lo más probable 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 son VARIABLES. en cuyo caso mi segundo ejemplo (dar variables con buenos nombres) es mucho mejor y Enum le habría dado pocos beneficios.

csmba
fuente
1
Aunque estoy de acuerdo en que los parámetros booleanos son aceptables en muchos casos, en el caso de este ejemplo writeData (), el parámetro booleano como shouldAppend es muy inapropiado. La razón es simple: no es inmediatamente obvio cuál es el significado de Falso.
Yarik
4

Las enumeraciones tienen un beneficio definitivo, pero no debes simplemente reemplazar todas tus booleanas por enumeraciones. Hay muchos lugares donde verdadero / falso es en realidad la mejor manera de representar lo que está sucediendo.

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

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ía un argumento booleano.

Ejemplo de C #:

var worker = new BackgroundWorker { WorkerReportsProgress = true };

Ejemplo de rubí

validates_presence_of :name, :allow_nil => true

Ejemplo de Python

connect_to_database( persistent=true )

Lo único que se me ocurre cuando un argumento de método booleano es lo correcto es en Java, donde no tiene propiedades ni argumentos de palabras clave. Esta es una de las razones por las que odio Java :-(

Orion Edwards
fuente
4

Si bien es cierto que en muchos casos las enumeraciones son más legibles y más extensibles que los booleanos, una regla absoluta de que "los booleanos no son aceptables" es tonta. Es inflexible y contraproducente, no deja espacio para el juicio humano. Son un tipo incorporado fundamental en la mayoría de los idiomas porque son útiles; considere aplicarlo a otros tipos incorporados: decir, por ejemplo, "nunca usar un int como parámetro" sería una locura.

Esta regla es solo una cuestión de estilo, no de posibles errores o rendimiento en tiempo de ejecución. Una mejor regla sería "preferir enumeraciones a booleanos por razones de legibilidad".

Mira el marco .Net. Los booleanos se usan como parámetros en bastantes métodos. La API .Net no es perfecta, pero no creo que el uso de booleanos como parámetros sea un gran problema. La información sobre herramientas siempre le da el nombre del parámetro, y también puede crear 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 debería agregar que hay un caso en el que claramente debería refactorizar booleanos a una enumeración: cuando tiene dos o más booleanos en su clase o en los parámetros de su método, y no todos los estados son válidos (por ejemplo, no es válido tenerlos ambos se ponen verdaderos).

Por ejemplo, si su clase tiene propiedades como

public bool IsFoo
public bool IsBar

Y es un error tener ambas cosas verdaderas al mismo tiempo, lo que realmente tienes son tres estados válidos, mejor expresados ​​como:

enum FooBarType { IsFoo, IsBar, IsNeither };
Antonio
fuente
4

Algunas reglas a las que su compañero podría adherirse mejor 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 hoyo solo porque te gusta la forma este mes!
Alex Worden
fuente
3

Un booleano solo sería aceptable si no tiene la intención de ampliar la funcionalidad del marco. Se prefiere la enumeración porque puede extender la enumeración y no romper implementaciones anteriores de la llamada a la función.

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

David Basarab
fuente
2

Si el método hace una pregunta como:

KeepWritingData (DataAvailable());

dónde

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

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

Los argumentos del método booleano parecen tener un sentido absolutamente perfecto.

Jesse C. Slicer
fuente
Algún día necesitarás agregar "sigue escribiendo si tienes espacio libre", y luego irás de bool a enum de todos modos.
Ilya Ryzhenkov
Y luego tendrá un cambio radical, o una sobrecarga obsoleta, o puede ser algo así como KeppWritingDataEx :)
Ilya Ryzhenkov
1
@Ilya o tal vez no lo harás! Crear una posible situación en la que actualmente no exista ninguno no niega la solución.
Jesse C. Slicer
1
Jesse tiene razón. Planear un cambio como ese es una tontería. Haz lo que tenga sentido. En este caso, el booleano es intuitivo y claro. c2.com/xp/DoTheSimplestThingThatCouldPossiblyWork.html
Derek Park
@Derek, en este caso, el booleano ni siquiera es necesario, porque DataAvailable siempre devuelve verdadero :)
Ilya Ryzhenkov
2

Depende del método. Si el método hace algo que obviamente es 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 dónde es obvio el uso].

CommentService.SetApprovalStatus(commentId, false);

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

Greg Beech
fuente
2

Hace las cosas un poco más explícitas, pero comienza a extender masivamente la complejidad de sus interfaces, en una elección booleana pura, como agregar / sobrescribir, parece excesivo. Si necesita agregar una opción adicional (que no se me ocurre en este caso), siempre puede realizar una refactorización (dependiendo del idioma)

Jennifer
fuente
1
¿Qué pasa con pretender como una tercera opción posible? ;-))
Yarik
2

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 forma de especificar un valor predeterminado que no sea 0. Si no tiene un valor 0, ni siquiera puede probar la enumeración sin convertir a int, que sería mal estilo)

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

Si sus enumeraciones se publican de alguna manera en código externo y / o se guardan fuera del programa, considere numerarlas 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, 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 Enum.IsDefinedes que usa la reflexión y es más lento. También sufre un problema de versiones. Si necesita verificar el valor de 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 del control de 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

Puede hacer incluso más diversión con las [Flags]enumeraciones, y el código de validación para eso es ligeramente diferente.

También voy a señalar que para la portabilidad, debe utilizar la llamada ToString()en la enumeración, y el uso Enum.Parse()al leerlos de nuevo. Tanto ToString()y Enum.Parse()puedo manejar [Flags]enumeración es así, así que no hay razón para no usarlos. Eso sí, es otra trampa, porque ahora ni siquiera puedes cambiar el nombre de la enumeración sin posiblemente romper el código.

Entonces, a veces necesitas sopesar todo lo anterior cuando te preguntas ¿Puedo salirte con la suya?

Robert Paulson
fuente
1

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 en las que un booleano es todo lo que necesita. En ese caso, diría que usar una enumeración donde funcionaría un bool sería un ejemplo de uso de 7 palabras cuando 4 funcionarán.

Jurásico_C
fuente
0

Los booleanos tienen sentido cuando tienes una palanca obvia 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á pasando, por ejemplo, las escrituras en disco (sin búfer, con búfer de línea o síncrono) 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 que sean más detalladas para saber lo que hacen a primera vista.

Dicho esto, también puede usar Falso y Verdadero (booleano 0 y 1) y luego, si necesita más valores más adelante, expanda la función para admitir valores definidos por el usuario (digamos, 2 y 3), y sus viejos valores 0/1 se portará bien, por lo que su código no debería romperse.

Dan Udey
fuente
0

A veces es más simple modelar diferentes comportamientos con sobrecargas. Continuar con su ejemplo sería:

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

Este enfoque se degrada si tiene múltiples 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.

Las enumeraciones pueden, en algunos casos, hacer que el código sea más legible, aunque validar el valor exacto de la enumeración en algunos lenguajes (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 se hizo 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 manera que no rompan el código antiguo, aunque las bibliotecas antiguas pueden no admitir nuevos valores de enumeración, por lo que no se puede descartar por completo el control de versiones.


EDITAR

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

Enum.Parse(str, ignoreCase: true);
Drew Noakes
fuente
0

Si estoy de acuerdo con que las enumeraciones son una buena manera de hacerlo, en métodos en los que tiene 2 opciones (y solo dos opciones puede tener legibilidad sin enumeración).

p.ej

public void writeData(Stream data, boolean is_overwrite)

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

Haris Krajina
fuente
0

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

Un comentario en línea ayuda mucho a resolver el boolproblema inesperado . El ejemplo original es particularmente atroz: ¡imagínese tratando de nombrar la variable en la declinación de la función! Sería algo como

void writeData( DataObject data, bool use_append_mode );

Pero, por el bien de ejemplo, digamos que esa es la declaración. Luego, para un argumento booleano 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 */);
Robert Martin
fuente
-1

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.

CheeZe5
fuente
-1

El uso de enumeraciones en lugar de booleanos en su ejemplo ayuda a que el método llame más legible. Sin embargo, este es un sustituto de mi artículo de deseo favorito en C #, argumentos nombrados en 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 tener en cuenta 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 los métodos también.

MusiGenesis
fuente
Una idea interesante ¿Pero podría reordenar los parámetros? ¿Omitir parámetros? ¿Cómo sabría el compilador a qué sobrecarga estabas vinculado si fuera opcional? Además, ¿tendría que nombrar todos los parámetros de la lista?
Drew Noakes
-1

Valores booleanos true/ falsesolo. Por lo tanto, no está claro qué representa. Enumpuede tener nombre significativo, por ejemplo OVERWRITE, APPEND, etc. enumeraciones también lo son mejor.

fastcodejava
fuente