coding style - realiza - ¿Son los booleanos como argumentos del método inaceptables?
qué son los operadores booleanos y cuáles son (26)
¿Recuerda la pregunta que le hizo Adlai Stevenson al embajador Zorin en la ONU durante la crisis de los misiles cubanos ?
"Estás en el tribunal de la opinión mundial en este momento, y puedes contestar sí o no . Has negado que [los misiles] existan, y quiero saber si te he entendido correctamente ... Estoy dispuesto a esperar. por mi respuesta hasta que el infierno se congele, si esa es tu decisión ".
Si el indicador que tiene en su método es de tal naturaleza que puede definirlo como una decisión binaria , y esa decisión nunca se convertirá en una decisión de tres vías o n-way, elija boolean. Indicaciones: su bandera se llama isXXX .
No lo haga booleano en el caso de algo que sea un cambio de modo . Siempre hay un modo más de lo que pensaba al escribir el método en primer lugar.
El dilema del modo más uno tiene, por ejemplo, un Unix embrujado, donde los posibles modos de permiso que un archivo o directorio pueden tener hoy en día dan como resultado un extraño doble significado de modos dependiendo del tipo de archivo, propiedad, etc.
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 donde 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?
A veces es simplemente más simple modelar diferentes comportamientos con sobrecargas. Para continuar desde su ejemplo sería:
file.appendData( data );
file.overwriteData( data );
Este enfoque se degrada si tiene múltiples parámetros, cada uno 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.
Los mensajes pueden, en algunos casos, hacer que el código sea más legible, aunque validar el valor de enum exacto 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 del .NET framework que la primera.
Si sabes que solo habrá dos opciones, un booleano podría estar bien. Las entradas son extensibles de una manera que no romperá el código anterior, aunque las bibliotecas antiguas pueden no ser compatibles con los nuevos valores enum, por lo que las versiones no se pueden descartar por completo.
EDITAR
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 enums. Usando el mismo ejemplo que arriba:
Enum.Parse(str, ignoreCase: true);
Algunas reglas que su colega podría cumplir 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 con forma de estrella en cada agujero simplemente porque te gusta la forma este mes!
Booleanos solo valora true
/ false
. Entonces no está claro lo que representa. Enum
puede tener un nombre significativo, por ejemplo OVERWRITE
, APPEND
, etc. Así que las enumeraciones son mejores.
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 lo hizo, IMO siempre es mejor mirar el objetivo final en lugar de reglas generales, tal vez pensar en ello más como una pauta, es decir, las enumeraciones son a menudo más legibles en código que los bools genéricos, ints, etc., pero siempre habrá excepciones a la regla.
Depende del método. Si el método hace algo que obviamente es una cosa verdadera / falsa, está bien, por ejemplo, debajo [aunque no estoy diciendo que este es 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 en el ejemplo que mencionas, es mejor usar una enumeración. Hay muchos ejemplos en el .NET Framework en sí mismo donde no se sigue esta convención, pero es porque introdujeron esta guía de diseño bastante tarde en el ciclo.
Donde sí estoy de acuerdo en que los Enums son una buena forma de hacerlo, en métodos donde tienes 2 opciones (y solo dos opciones puedes tener legibilidad sin enum).
p.ej
public void writeData(Stream data, boolean is_overwrite)
Amo los Enums, pero boolean también es útil.
El uso de enumeraciones en lugar de booleanos en su ejemplo ayuda a que la llamada al método sea más legible. Sin embargo, este es un sustituto de mi artículo preferido en C #, llamado argumentos en llamadas a métodos. Esta sintaxis:
var v = CallMethod(pData = data, pFileMode = WriteMode, pIsDirty = true);
sería perfectamente legible, y entonces podría hacer lo que un programador debería hacer, 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 también con métodos.
En mi humilde opinión, parece que una enumeración sería la elección obvia para cualquier situación en la que sean posibles más de dos opciones. Pero definitivamente hay situaciones donde un Boolean 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ía.
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 hace mucho por resolver el problema bool
inesperado. El ejemplo original es particularmente atroz: imagina intentar nombrar la variable en la decleación de la función. Sería algo así como
void writeData( DataObject data, bool use_append_mode );
Pero, por el bien del ejemplo, digamos que esa es la declaración. Luego, para un argumento booleano inexplicado, coloco el nombre de la variable en un comentario en línea. Comparar
file.writeData( data, true );
con
file.writeData( data, true /* use_append_mode */);
Hace que las cosas sean un poco más explícitas, pero comienza a extender de forma masiva la complejidad de tus interfaces; en una opción booleana pura como anexar / sobrescribir, parece exagerado. Si necesita agregar una opción adicional (que no puedo pensar en este caso), siempre puede realizar un refactor (dependiendo del idioma)
Hay dos razones por las que me he encontrado con que esto es algo malo:
Porque algunas personas escribirán métodos como:
ProcessBatch(true, false, false, true, false, false, true);
Esto es obviamente malo porque es muy fácil mezclar parámetros, y no tienes idea al mirar lo que estás especificando. Sin embargo, un bool no es tan malo.
Debido a que el control del flujo de programas mediante una simple rama de sí / no puede significar que tiene dos funciones completamente diferentes que están envueltas en una sola 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 puede 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 decir eso simplemente usando
Write()
o inclusoWrite(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 un gran trato al respecto, excepto por el # 1.
Los booleanos pueden estar bien en los lenguajes que tienen parámetros nombrados, 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]
Los booleanos representan elecciones "sí / no". Si quiere representar un "sí / no", entonces use un booleano, debería ser autoexplicativo.
Pero si se trata de una elección entre dos opciones, ninguna de las cuales es claramente sí o no, entonces una enumeración a veces puede ser más legible.
Los booleanos tienen sentido cuando tienes un alternar 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á pasando, por ejemplo, las grabaciones de disco, sin búfer, con búfer de línea o sincrónico, deben transmitirse 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 prolijas a los efectos de saber lo que hacen a primera vista.
Dicho esto, también puede usar False and True (booleano 0 y 1) y luego, si necesita más valores más adelante, amplíe la función para admitir los valores definidos por el usuario (por ejemplo, 2 y 3) y sus valores anteriores de 0/1. funcionará muy bien, por lo que su código no debería romperse.
Los enum ciertamente pueden hacer que el código sea más legible. Todavía hay algunas cosas de las que hay que tener cuidado (en .net al menos)
Como 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 configurados en cero cuando se crean, por lo que no hay forma de especificar un valor predeterminado distinto de 0. Si no tiene un valor 0, ni siquiera puede probar la enumeración sin conversión a int, que sería mal estilo.)
Si las enumeraciones son privadas para su código (nunca expuestas públicamente), puede dejar de leer aquí.
Si sus enumeraciones se publican de alguna forma a 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, una 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 reflexión y es más lenta. También sufre un problema de control de versiones. Si necesita verificar el valor enum a menudo, sería mejor que 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 de las versiones es que el código anterior solo puede saber cómo manejar las 2 enumeraciones que tiene. Si agrega un tercer valor, Enum.IsDefined será verdadero, pero el código anterior no puede manejarlo necesariamente. Whoops.
Hay incluso más diversión que puedes hacer con [Flags]
enums, y el código de validación para eso 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. ToString()
y Enum.Parse()
pueden manejar [Flags]
enum como bueno, entonces no hay razón para no usarlos. Eso sí, es otro escollo, porque ahora ni siquiera puedes cambiar el nombre de la enumeración sin posiblemente descifrar el código.
Entonces, a veces tienes que sopesar todo lo anterior cuando te preguntas ¿Puedo salir con un bool?
Los enum tienen un beneficio definitivo, pero no deberías reemplazar todos tus booleanos con enumeraciones. Hay muchos lugares donde verdadero / falso es en realidad la mejor manera de representar lo que está sucediendo.
Sin embargo, utilizarlos como argumentos de método es un poco sospechoso, simplemente porque no se puede ver sin investigar lo que se supone que deben hacer, ya que le permiten ver lo que realmente significa lo verdadero / lo 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 donde, de lo contrario, usaría un argumento booleano.
C # ejemplo:
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 donde 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 :-(
Los enumerados también permiten modificaciones futuras, donde ahora desea una tercera opción (o más).
Los enums 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.)
No estoy de acuerdo con que sea una buena regla . Obviamente, Enum crea un mejor código explícito o detallado en algunos casos, pero, por regla general, parece demasiado extenso.
Primero permítanme tomar su ejemplo: la responsabilidad (y la capacidad) de los programadores de escribir un buen código no está realmente en peligro al tener un parámetro booleano. En su ejemplo, el programador podría haber escrito como 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á 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 son VARIABLES. en cuyo caso mi segundo ejemplo (dando variables con buenos nombres) es mucho mejor y Enum le habría dado pocos beneficios.
Para mí, ni usar booleano ni enumeración es un buen enfoque. Robert C. Martin capta esto muy claramente en su Clean Code Tip # 12: Elimine Boolean Arguments :
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, debe 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 de bandera.
Realmente depende de la naturaleza exacta del argumento. Si no es un sí / no o verdadero / falso, entonces 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 los valores indefinidos del tipo subyacente.
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 "booleanos no son aceptables" es tonta. Es inflexible y contraproducente; no deja lugar para el juicio humano. Son un tipo integrado fundamental en la mayoría de los lenguajes 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 regla mejor sería "preferir enums a booleanos por razones de legibilidad".
Mira el framework .Net. Los booleanos se usan como parámetros en bastantes métodos. La .Net API 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 generar este tipo de orientación: complete sus comentarios XML sobre los parámetros del método, que aparecerán en la información sobre herramientas.
También debo agregar que hay un caso en el que debe refactorizar claramente 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 (p. Ej., No es válido tenerlos). ambos son verdaderos).
Por ejemplo, si tu clase tiene propiedades como
public bool IsFoo
public bool IsBar
Y es un error tener ambos verdaderos al mismo tiempo, lo que en realidad tiene son tres estados válidos, mejor expresado como algo como:
enum FooBarType { IsFoo, IsBar, IsNeither };
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.
Un booleano solo sería aceptable si no tiene la intención de ampliar la funcionalidad del marco. Se prefiere Enum porque puede extender la enumeración y no interrumpir las implementaciones previas de la llamada a función.
La otra ventaja del Enum es que es más fácil de leer.
Usa el que mejor modele tu problema. En el ejemplo que das, la enumeración es una mejor opción. Sin embargo, habría otras veces cuando un boolean es mejor. Lo cual 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 y no ambigua, 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.