¿Es incorrecto usar un parámetro booleano para determinar el comportamiento?

194

He visto una práctica de vez en cuando que "se siente" mal, pero no puedo expresar lo que está mal al respecto. O tal vez es solo mi prejuicio. Aquí va:

Un desarrollador define un método con un valor booleano como uno de sus parámetros, y ese método llama a otro, y así sucesivamente, y eventualmente ese valor booleano se usa únicamente para determinar si se debe tomar una determinada acción. Esto podría usarse, por ejemplo, para permitir la acción solo si el usuario tiene ciertos derechos, o tal vez si estamos (o no) en modo de prueba o modo por lotes o modo en vivo, o tal vez solo cuando el sistema está en un cierto estado

Bueno, siempre hay otra forma de hacerlo, ya sea preguntando cuándo es el momento de tomar la acción (en lugar de pasar el parámetro), o teniendo múltiples versiones del método, o múltiples implementaciones de la clase, etc. Mi pregunta no es No es tanto cómo mejorar esto, sino más bien si realmente está mal o no (como sospecho), y si es así, qué tiene de malo.

Rayo
fuente
1
Esta es una cuestión de dónde pertenecen las decisiones. Mueva las decisiones en un lugar central en lugar de tenerlas esparcidas por todas partes. Esto mantendrá la complejidad más baja que tener un factor dos de rutas de código cada vez que tenga un if.
28
Martin Fowler tiene un artículo sobre esto: martinfowler.com/bliki/FlagArgument.html
Christoffer Hammarström
@ ChristofferHammarström Buen enlace. Incluiré eso en mi respuesta, ya que explica en muchos detalles la misma idea de mi explicación.
Alex
1
No siempre estoy de acuerdo con lo que Nick tiene que decir, pero en este caso estoy de acuerdo al 100%: no use parámetros booleanos .
Marjan Venema

Respuestas:

107

Sí, es probable que sea un olor a código, lo que llevaría a un código que no se puede mantener y que es difícil de entender y que tiene una menor probabilidad de ser reutilizado fácilmente.

Como otros carteles han señalado, el contexto lo es todo (no entre con mano dura si es único o si la práctica ha sido reconocida como una deuda técnica incurrida deliberadamente para ser re-factorizada más adelante), pero hablando en términos generales si se pasa un parámetro en una función que selecciona un comportamiento específico para ser ejecutado, luego se requiere un mayor refinamiento gradual; Romper esta función en funciones más pequeñas producirá funciones más altamente cohesivas.

Entonces, ¿qué es una función altamente cohesiva?

Es una función que hace una cosa y solo una cosa.

El problema con un parámetro pasado como lo describe, es que la función está haciendo más de dos cosas; puede o no verificar los derechos de acceso de los usuarios dependiendo del estado del parámetro booleano, luego, dependiendo de ese árbol de decisión, llevará a cabo una funcionalidad.

Sería mejor separar las preocupaciones de Control de acceso de las preocupaciones de Tarea, Acción o Comando.

Como ya ha notado, el entrelazamiento de estas preocupaciones parece estar apagado.

Entonces, la noción de Cohesividad nos ayuda a identificar que la función en cuestión no es altamente cohesiva y que podríamos refactorizar el código para producir un conjunto de funciones más cohesivas.

Entonces la pregunta podría ser reformulada; Dado que todos estamos de acuerdo en que es mejor evitar pasar parámetros de selección de comportamiento, ¿cómo podemos mejorar las cosas?

Me desharía del parámetro por completo. Tener la capacidad de desactivar el control de acceso incluso para las pruebas es un riesgo de seguridad potencial. Para fines de prueba, puede o la verificación de acceso para probar los escenarios de acceso permitido y acceso denegado.

Ref: Cohesión (informática)

Robar
fuente
Rob, ¿darías alguna explicación de qué es Cohesión y cómo se aplica?
Ray
Ray, cuanto más pienso en esto, más pienso que deberías objetar el código basado en el agujero de seguridad que el booleano para activar el control de acceso introduce en la aplicación. La mejora de la calidad de la base del código será un buen efecto secundario;)
Rob
1
Muy buena explicación de cohesión y su aplicación. Esto realmente debería obtener más votos. Y también estoy de acuerdo con el problema de seguridad ... aunque si todos son métodos privados, es una vulnerabilidad potencial menor
Rayo
1
Gracias Ray Parece que será bastante fácil re-factorizar cuando el tiempo lo permita. Puede valer la pena dejar un comentario de TODO para resaltar el problema, logrando un equilibrio entre la autoridad técnica y la sensibilidad a la presión que a veces tenemos para hacer las cosas.
Rob
"
imposible de mantener
149

Dejé de usar este patrón hace mucho tiempo, por una razón muy simple; costo de mantenimiento. Varias veces descubrí que tenía alguna función frobnicate(something, forwards_flag)que se llamaba muchas veces en mi código, y que necesitaba ubicar todos los lugares en el código donde falsese pasaba el valor como valor de forwards_flag. No puede buscarlos fácilmente, por lo que esto se convierte en un dolor de cabeza de mantenimiento. Y si necesita hacer una corrección de errores en cada uno de esos sitios, puede tener un problema desafortunado si se pierde uno.

Pero este problema específico se soluciona fácilmente sin cambiar fundamentalmente el enfoque:

enum FrobnicationDirection {
  FrobnicateForwards,
  FrobnicateBackwards;
};

void frobnicate(Object what, FrobnicationDirection direction);

Con este código, uno solo necesitaría buscar instancias de FrobnicateBackwards. Si bien es posible que haya algún código que asigne esto a una variable, por lo que debe seguir algunos hilos de control, creo que en la práctica esto es lo suficientemente raro como para que esta alternativa funcione bien.

Sin embargo, hay otro problema al pasar la bandera de esta manera, al menos en principio. Esto es que algunos (solo algunos) sistemas que tienen este diseño pueden estar exponiendo demasiado conocimiento sobre los detalles de implementación de las partes profundamente anidadas del código (que usa la bandera) a las capas externas (que necesitan saber qué valor pasar en esta bandera) Para utilizar la terminología de Larry Constantine, este diseño puede tener un acoplamiento demasiado fuerte entre el colocador y el usuario de la bandera booleana. Franky, aunque es difícil de decir con cierto grado de certeza sobre esta pregunta sin saber más sobre el código base.

Para abordar los ejemplos específicos que da, me preocuparía un poco en cada uno, pero principalmente por razones de riesgo / corrección. Es decir, si su sistema necesita pasar banderas que indican en qué estado se encuentra el sistema, puede encontrar que tiene un código que debería haber tenido en cuenta esto pero no verifica el parámetro (porque no se pasó a esta función). Entonces tiene un error porque alguien omitió pasar el parámetro.

También vale la pena admitir que un indicador de estado del sistema que debe pasarse a casi todas las funciones es, de hecho, esencialmente una variable global. Se aplicarán muchas de las desventajas de una variable global. Creo que en muchos casos es una mejor práctica encapsular el conocimiento del estado del sistema (o las credenciales del usuario, o la identidad del sistema) dentro de un objeto que es responsable de actuar correctamente sobre la base de esos datos. Luego, pasa una referencia a ese objeto en lugar de los datos sin procesar. El concepto clave aquí es la encapsulación .

James Youngman
fuente
99
Realmente excelentes ejemplos concretos, así como una visión de la naturaleza de lo que estamos tratando y cómo nos afecta.
Ray
33
+1. Utilizo enumeraciones para esto tanto como sea posible. He visto funciones en las boolque se agregaron parámetros adicionales más tarde y las llamadas comienzan a verse DoSomething( myObject, false, false, true, false ). Es imposible descubrir qué significan los argumentos booleanos adicionales, mientras que con valores de enumeración con nombres significativos, es fácil.
Graeme Perrow
17
Oh hombre, finalmente un buen ejemplo de cómo Frobnicate hacia atrás. He estado buscando esto por siempre.
Alex Pritchard
38

Esto no es necesariamente incorrecto, pero puede representar un olor a código .

El escenario básico que debe evitarse con respecto a los parámetros booleanos es:

public void foo(boolean flag) {
    doThis();
    if (flag)
        doThat();
}

Luego, cuando llama, normalmente llama foo(false)y foo(true)depende del comportamiento exacto que desee.

Esto es realmente un problema porque es un caso de mala cohesión. Estás creando una dependencia entre métodos que no es realmente necesaria.

Lo que debe hacer en este caso es irse doThisy, doThatcomo métodos separados y públicos, hacer:

doThis();
doThat();

o

doThis();

De esa manera, deja la decisión correcta a la persona que llama (exactamente como si estuviera pasando un parámetro booleano) sin crear un acoplamiento.

Por supuesto, no todos los parámetros booleanos se usan de manera tan mala, pero definitivamente es un olor a código y tienes razón para sospechar si ves mucho en el código fuente.

Este es solo un ejemplo de cómo resolver este problema basado en los ejemplos que escribí. Hay otros casos en los que será necesario un enfoque diferente.

Hay un buen artículo de Martin Fowler que explica con más detalles la misma idea.

PD: si el método en foolugar de llamar a dos métodos simples tuvo una implementación más compleja, entonces todo lo que tiene que hacer es aplicar una pequeña refactorización de métodos de extracción para que el código resultante sea similar a la implementación de lo fooque escribí.

Alex
fuente
1
Gracias por llamar al término "olor a código". Sabía que olía mal, pero no pude entender cuál era el olor. Su ejemplo es bastante acertado con lo que estoy viendo.
Rayo
24
Hay muchas situaciones en las que if (flag) doThat()dentro foo()es legítimo. Impulsar la decisión de invocar doThat()a cada persona que llama obliga a la repetición que tendrá que ser eliminada si luego descubre algunos métodos, el flagcomportamiento también necesita llamar doTheOther(). Prefiero poner dependencias entre métodos en la misma clase que tener que buscar a todos los que llaman más tarde.
Blrfl
1
@Blrfl: Sí, creo que las refactorizaciones más sencillas serían la creación de métodos doOney doBoth(para el caso falso y verdadero, respectivamente) o el uso de un tipo de enumeración por separado, como lo sugiere James Youngman
hugomg
@missingno: usted todavía tiene el mismo problema de empujar código redundante a las personas que llaman para hacer la doOne()o doBoth()decisión. Las subrutinas / funciones / métodos tienen argumentos para que su comportamiento pueda variar. Usar una enumeración para una condición verdaderamente booleana se parece mucho a repetirse si el nombre del argumento ya explica lo que hace.
Blrfl
3
Si llamar a dos métodos uno después del otro o un solo método puede considerarse redundante, entonces también es redundante cómo escribir un bloque try-catch o tal vez un if if else. ¿Significa que escribirás una función para abstraerlos a todos? ¡No! Tenga cuidado, crear un método que lo único que hace es llamar a otros dos métodos no necesariamente representa una buena abstracción.
Alex
29

En primer lugar: la programación no es tanto una ciencia como un arte. Por lo tanto, rara vez hay una forma "incorrecta" y una "correcta" de programar. La mayoría de los estándares de codificación son meramente "preferencias" que algunos programadores consideran útiles; pero finalmente son bastante arbitrarios. Por lo tanto, nunca etiquetaría una elección de parámetro como "incorrecta" en sí misma, y ​​ciertamente no es algo tan genérico y útil como un parámetro booleano. El uso de un boolean(o un int, para el caso) para encapsular el estado es perfectamente justificable en muchos casos.

Las decisiones de codificación, en general, deben basarse principalmente en el rendimiento y la capacidad de mantenimiento. Si el rendimiento no está en juego (y no puedo imaginar cómo podría ser en sus ejemplos), entonces su siguiente consideración debería ser: ¿qué tan fácil será para mí (o un futuro redactor) mantenerlo? ¿Es intuitivo y comprensible? ¿Está aislado? Su ejemplo de las llamadas a funciones encadenadas de hecho parecen potencialmente frágil en este sentido: si decide cambiar su bIsUpa bIsDown, cuántos lugares distintos en el código tendrá que ser cambiado también? Además, ¿está aumentando su lista de paramater? Si su función tiene 17 parámetros, entonces la legibilidad es un problema, y ​​debe reconsiderar si está apreciando los beneficios de la arquitectura orientada a objetos.

kmote
fuente
44
Agradezco la advertencia en el primer párrafo. Estaba siendo intencionalmente provocativo al decir "incorrecto", y ciertamente reconozco que estamos tratando en el ámbito de las "mejores prácticas" y los principios de diseño, y que este tipo de cosas a menudo son situacionales, y uno debe sopesar múltiples factores
Ray
13
Su respuesta me recuerda una cita de la que no recuerdo la fuente: "si su función tiene 17 parámetros, probablemente le falte uno".
Joris Timmermans
Estoy muy de acuerdo con este, y aplico a la pregunta para decir que sí, a menudo es una mala idea pasar una bandera booleana, pero nunca es tan simple como malo / bueno ...
JohnB
15

Creo que el artículo del código Robert C Martins Clean establece que debe eliminar los argumentos booleanos siempre que sea posible, ya que muestran que un método hace más de una cosa. Un método debería hacer una cosa y solo una cosa, creo que es uno de sus lemas.

dreza
fuente
@dreza te refieres a la Ley Curlys .
MattDavey
Por supuesto, con experiencia debe saber cuándo ignorar tales argumentos.
gnasher729
8

Creo que lo más importante aquí es ser práctico.

Cuando el valor booleano determina el comportamiento completo, simplemente haga un segundo método.

Cuando el booleano solo determina un poco de comportamiento en el medio, es posible que desee mantenerlo en uno para reducir la duplicación de código. Siempre que sea posible, incluso podría dividir el método en tres: dos métodos de llamada para cualquier opción booleana y uno que haga la mayor parte del trabajo.

Por ejemplo:

private void FooInternal(bool flag)
{
  //do work
}

public void Foo1()
{
  FooInternal(true);
}

public void Foo2()
{
  FooInternal(false);
}

Por supuesto, en la práctica siempre tendrás un punto entre estos extremos. Por lo general, sigo con lo que parece correcto, pero prefiero equivocarme con menos duplicación de código.

Jorn
fuente
Solo uso argumentos booleanos para controlar el comportamiento en métodos privados (como se muestra aquí). Pero el problema: si algún dufus decide aumentar la visibilidad FooInternalen el futuro, ¿entonces qué?
ADTC
En realidad, iría por otra ruta. El código dentro de FooInternal debe dividirse en 4 partes: 2 funciones para manejar los casos booleanos de verdadero / falso, uno para el trabajo que ocurre antes y otro para el trabajo que sucede después. Entonces, el Foo1se convierte en: { doWork(); HandleTrueCase(); doMoreWork() }. Idealmente, las funciones doWorky doMoreWorkse dividen en (uno o más) fragmentos significativos de acciones discretas (es decir, como funciones separadas), no solo dos funciones en aras de la división.
jpaugh
7

Me gusta el enfoque de personalizar el comportamiento a través de métodos de creación que devuelven instancias inmutables. Así es como lo usa GuavaSplitter :

private static final Splitter MY_SPLITTER = Splitter.on(',')
       .trimResults()
       .omitEmptyStrings();

MY_SPLITTER.split("one,,,,  two,three");

Los beneficios de esto son:

  • Lectura superior
  • Separación clara de la configuración frente a los métodos de acción.
  • Promueve la cohesión al obligarte a pensar en qué es el objeto, qué debería y qué no debería hacer. En este caso es un Splitter. Nunca pondrías someVaguelyStringRelatedOperation(List<Entity> myEntities)en una clase llamada Splitter, pero pensarías en ponerlo como un método estático en una StringUtilsclase.
  • Las instancias están preconfiguradas, por lo tanto, son fácilmente inyectables por dependencia. El cliente no necesita preocuparse sobre si pasar trueo falsepor un método para obtener el comportamiento correcto.
oksayt
fuente
1
Soy parcial a su solución como un gran amante de la guayaba y evangelista ... pero no puedo darle un +1, porque omite la parte que realmente estoy buscando, que es lo que está mal (o mal) por el otro lado. Creo que en realidad está implícito en algunas de sus explicaciones, por lo que tal vez si pudiera hacer eso explícito, respondería mejor a la pregunta.
Ray
¡Me gusta la idea de separar los métodos de configuración y acton!
Sher10ck
Enlaces a la biblioteca de guayaba se rompen
Josh Noe
4

Definitivamente un código de olor . Si no viola el Principio de Responsabilidad Única, entonces probablemente viola Tell, Don't Ask . Considerar:

Si resulta que no viola uno de esos dos principios, aún debe usar una enumeración. Las banderas booleanas son el equivalente booleano de los números mágicos . foo(false)tiene tanto sentido como bar(42). Las enumeraciones pueden ser útiles para el Patrón de estrategia y tienen la flexibilidad de permitirle agregar otra estrategia. (Solo recuerde nombrarlos apropiadamente ).

Su ejemplo particular me molesta especialmente. ¿Por qué se pasa esta bandera por tantos métodos? Esto suena como si necesita dividir su parámetro en subclases .

Eva
fuente
4

TL; DR: no use argumentos booleanos.

Vea a continuación por qué son malos y cómo reemplazarlos (en negrita).


Los argumentos booleanos son muy difíciles de leer y, por lo tanto, difíciles de mantener. El principal problema es que el propósito es generalmente claro cuando lee la firma del método donde se nombra el argumento. Sin embargo, nombrar un parámetro generalmente no se requiere en la mayoría de los idiomas. Por lo tanto, tendrá antipatrones como RSACryptoServiceProvider#encrypt(Byte[], Boolean)donde el parámetro booleano determina qué tipo de cifrado se utilizará en la función.

Entonces recibirá una llamada como:

rsaProvider.encrypt(data, true);

donde el lector tiene que buscar la firma del método simplemente para determinar qué demonios truepodría significar realmente. Por supuesto, pasar un número entero es igual de malo:

rsaProvider.encrypt(data, 1);

te diría lo mismo, o más bien: tan poco. Incluso si define constantes para el entero, los usuarios de la función pueden simplemente ignorarlas y seguir usando valores literales.

La mejor manera de resolver esto es usar una enumeración . Si tiene que pasar una enumeración RSAPaddingcon dos valores: OAEPo PKCS1_V1_5entonces inmediatamente podrá leer el código:

rsaProvider.encrypt(data, RSAPadding.OAEP);

Los booleanos solo pueden tener dos valores. Esto significa que si tiene una tercera opción, entonces tendría que refactorizar su firma. En general, esto no se puede realizar fácilmente si la compatibilidad con versiones anteriores es un problema, por lo que tendría que ampliar cualquier clase pública con otro método público. Esto es lo que finalmente hizo Microsoft cuando introdujeron RSACryptoServiceProvider#encrypt(Byte[], RSAEncryptionPadding)dónde usaron una enumeración (o al menos una clase que imitaba una enumeración) en lugar de un booleano.

Incluso puede ser más fácil usar un objeto completo o una interfaz como parámetro, en caso de que el parámetro en sí mismo deba parametrizarse. En el ejemplo anterior, el relleno OAEP en sí mismo podría parametrizarse con el valor hash para usarlo internamente. Tenga en cuenta que ahora hay 6 algoritmos hash SHA-2 y 4 algoritmos hash SHA-3, por lo que la cantidad de valores de enumeración puede explotar si solo usa una enumeración única en lugar de parámetros (esto es posiblemente lo próximo que Microsoft descubrirá) )


Los parámetros booleanos también pueden indicar que el método o clase no está bien diseñado. Al igual que con el ejemplo anterior: cualquier biblioteca criptográfica que no sea .NET no utiliza ningún indicador de relleno en la firma del método.


Casi todos los gurús del software que me gustan advierten contra los argumentos booleanos. Por ejemplo, Joshua Bloch advierte contra ellos en el muy apreciado libro "Effective Java". En general, simplemente no deben usarse. Se podría argumentar que podrían usarse si el caso es que hay un parámetro que es fácil de entender. Pero incluso entonces: Bit.set(boolean)probablemente se implementa mejor utilizando dos métodos : Bit.set()y Bit.unset().


Si no puede refactorizar directamente el código, puede definir constantes para al menos hacerlas más legibles:

const boolean ENCRYPT = true;
const boolean DECRYPT = false;

...

cipher.init(key, ENCRYPT);

es mucho más legible que:

cipher.init(key, true);

incluso si prefieres tener:

cipher.initForEncryption(key);
cipher.initForDecryption(key);

en lugar.

Maarten Bodewes
fuente
3

Me sorprende que nadie haya mencionado los parámetros con nombre .

El problema que veo con las banderas booleanas es que dañan la legibilidad. Por ejemplo, qué hace trueen

myObject.UpdateTimestamps(true);

¿hacer? No tengo idea. Pero que pasa:

myObject.UpdateTimestamps(saveChanges: true);

Ahora está claro qué debe hacer el parámetro que estamos pasando: le estamos diciendo a la función que guarde sus cambios. En este caso, si la clase no es pública, creo que el parámetro booleano está bien.


Por supuesto, no puede obligar a los usuarios de su clase a usar parámetros con nombre. Por esta razón, enumgeneralmente son preferibles uno o dos métodos separados, dependiendo de qué tan común sea su caso predeterminado. Esto es exactamente lo que hace .Net:

//Enum used
double a = Math.Round(b, MidpointRounding.AwayFromZero);

//Two separate methods used
IEnumerable<Stuff> ascendingList = c.OrderBy(o => o.Key);
IEnumerable<Stuff> descendingList = c.OrderByDescending(o => o.Key); 
BlueRaja - Danny Pflughoeft
fuente
1
La pregunta no es sobre qué es preferible a una bandera que determina el comportamiento, es si esa bandera huele, y si es así, por qué
Ray
2
@ Ray: no veo una diferencia entre esas dos preguntas. En un lenguaje donde puede imponer el uso de parámetros con nombre, o cuando puede estar seguro de que siempre se usarán parámetros con nombre (por ejemplo, métodos privados) , los parámetros booleanos están bien. Si el lenguaje (C #) no puede forzar los parámetros con nombre y la clase es parte de una API pública, o si el lenguaje no admite parámetros con nombre (C ++), entonces ese código myFunction(true)podría estar escrito, es un código- oler.
BlueRaja - Danny Pflughoeft
El enfoque del parámetro nombrado es aún más incorrecto. Sin un nombre, uno se verá obligado a leer el documento API. Con un nombre, crees que no es necesario: pero el parámetro podría ser un nombre incorrecto. Por ejemplo, podría haberse usado para guardar (todos) los cambios, pero más tarde la implicación cambió un poco para guardar solo los cambios grandes (por algún valor de grande).
Ingo
@Ingo no estoy de acuerdo. Ese es un problema de programación genérico; puedes definir fácilmente otro SaveBignombre. Se puede fastidiar cualquier código, este tipo de error no es específico de los parámetros con nombre.
Maarten Bodewes
1
@Ingo: Si estás rodeado de idiotas, entonces vas y buscas trabajo en otro lado. Ese tipo de cosas es para lo que tienes revisiones de código.
gnasher729
1

No puedo articular lo que está mal al respecto.

Si parece un olor a código, se siente como un olor a código y, bueno, huele a olor a código, probablemente sea un olor a código.

Lo que quieres hacer es:

1) Evite los métodos con efectos secundarios.

2) Manejar los estados necesarios con una máquina de estado formal central (como esta ).

Babu
fuente
1

Estoy de acuerdo con todas las preocupaciones de usar los parámetros booleanos para no determinar el rendimiento para; mejorar, legibilidad, confiabilidad, reducir la complejidad, reducir los riesgos de una pobre encapsulación y cohesión y reducir el costo total de propiedad con capacidad de mantenimiento.

Comencé a diseñar hardware a mediados de los 70 que ahora llamamos SCADA (control de supervisión y adquisición de datos) y estos fueron hardware afinado con código de máquina en EPROM que ejecuta controles remotos macro y recopila datos de alta velocidad.

La lógica se llama máquinas Mealey & Moore , que ahora llamamos máquinas de estado finito . Esto también debe hacerse en las mismas reglas que anteriormente, a menos que sea una máquina en tiempo real con un tiempo de ejecución finito y luego se deben hacer accesos directos para cumplir el propósito.

Los datos son síncronos pero los comandos son asíncronos y la lógica de comandos sigue la lógica booleana sin memoria pero con comandos secuenciales basados ​​en la memoria del estado anterior, presente y deseado. Para que eso funcione en el lenguaje de máquina más eficiente (solo 64kB), se tuvo mucho cuidado al definir cada proceso de una manera heurística de IBM HIPO. Eso a veces significaba pasar variables booleanas y hacer ramas indexadas.

Pero ahora con mucha memoria y facilidad de OOK, la encapsulación es un ingrediente esencial hoy en día, pero una penalización cuando el código se contaba en bytes para el código de máquina SCADA en tiempo real.

Tony Stewart Sunnyskyguy EE75
fuente
0

No es necesariamente incorrecto, pero en su ejemplo concreto de la acción que depende de algún atributo del "usuario", pasaría por una referencia al usuario en lugar de una bandera.

Esto aclara y ayuda de varias maneras.

Cualquiera que lea la declaración de invocación se dará cuenta de que el resultado cambiará dependiendo del usuario.

En la función que finalmente se llama, puede implementar fácilmente reglas comerciales más complejas porque puede acceder a cualquiera de los atributos de los usuarios.

Si una función / método en la "cadena" hace algo diferente dependiendo de un atributo del usuario, es muy probable que se introduzca una dependencia similar en los atributos del usuario en algunos de los otros métodos de la "cadena".

James Anderson
fuente
0

La mayoría de las veces, consideraría esta mala codificación. Sin embargo, se me ocurren dos casos en los que esta podría ser una buena práctica. Como hay muchas respuestas que ya dicen por qué es malo, ofrezco dos veces cuando podría ser bueno:

La primera es si cada llamada en la secuencia tiene sentido por derecho propio. Tendría sentido si el código de llamada se pudiera cambiar de verdadero a falso o de falso a verdadero, o si el método llamado se pudiera cambiar para usar el parámetro booleano directamente en lugar de pasarlo. La probabilidad de diez de esas llamadas seguidas es pequeña, pero podría suceder, y si lo hiciera, sería una buena práctica de programación.

El segundo caso es un poco exagerado dado que estamos tratando con un valor booleano. Pero si el programa tiene múltiples hilos o eventos, pasar parámetros es la forma más simple de rastrear datos específicos de hilos / eventos. Por ejemplo, un programa puede recibir información de dos o más sockets. El código que se ejecuta para un socket puede necesitar generar mensajes de advertencia, y uno que se ejecuta para otro no. Entonces (más o menos) tiene sentido que un conjunto booleano en un nivel muy alto pase a través de muchas llamadas de método a los lugares donde se pueden generar mensajes de advertencia. Los datos no se pueden guardar (excepto con gran dificultad) en cualquier tipo de global porque múltiples hilos o eventos intercalados necesitarían cada uno su propio valor.

Sin duda, en este último caso, probablemente crearía una clase / estructura cuyo único contenido era un valor lógico y paso que todo en su lugar. Es casi seguro que necesitaré otros campos en poco tiempo, como por ejemplo dónde enviar los mensajes de advertencia.

RalphChapin
fuente
Una enumeración WARN, SILENT tendría más sentido, incluso cuando se usa una clase / estructura como la escribió (es decir, un contexto ). O, de hecho, simplemente configure el registro externamente, sin necesidad de pasar nada.
Maarten Bodewes
-1

El contexto es importante. Tales métodos son bastante comunes en iOS. Como solo un ejemplo de uso frecuente, UINavigationController proporciona el método -pushViewController:animated:y el animatedparámetro es un BOOL. El método realiza esencialmente la misma función de cualquier manera, pero anima la transición de un controlador de vista a otro si pasa SÍ, y no si pasa NO. Esto parece completamente razonable; Sería una tontería tener que proporcionar dos métodos en lugar de este solo para poder determinar si usar o no la animación.

Puede ser más fácil justificar este tipo de cosas en Objective-C, donde la sintaxis de denominación de métodos proporciona más contexto para cada parámetro que el que obtiene en lenguajes como C y Java. Sin embargo, creo que un método que toma un solo parámetro fácilmente podría tomar un valor booleano y seguir teniendo sentido:

file.saveWithEncryption(false);    // is there any doubt about the meaning of 'false' here?
Caleb
fuente
21
En realidad no tengo idea de lo que falsesignifica en el file.saveWithEncryptionejemplo. ¿Significa que se guardará sin cifrado? Si es así, ¿por qué el método tendría "con cifrado" justo en el nombre? Podría entender que tiene un método como save(boolean withEncryption), pero cuando veo file.save(false), no es del todo obvio de un vistazo que el parámetro indique que sería con o sin cifrado. Creo que, de hecho, este es el primer punto de James Youngman, sobre el uso de una enumeración.
Ray
66
Una hipótesis alternativa es que falsesignifica no sobrecargar ningún archivo existente del mismo nombre. Ejemplo contribuido, lo sé, pero para estar seguro, necesitará verificar la documentación (o código) para la función.
James Youngman
55
Un método llamado saveWithEncryptionque a veces no se guarda con cifrado es un error. Posiblemente debería ser file.encrypt().save(), o como Javas new EncryptingOutputStream(new FileOutputStream(...)).write(file).
Christoffer Hammarström
2
En realidad, el código que hace algo más de lo que dice no funciona y, por lo tanto, es un error. No vuela para hacer un método llamado saveWithEncryption(boolean)que a veces se guarda sin cifrado, al igual que no vuela para hacer un método saveWithoutEncryption(boolean)que a veces se guarda con cifrado.
Christoffer Hammarström
2
El método es malo, porque obviamente hay "dudas sobre el significado de 'falso' aquí". De todos modos, nunca escribiría un método como ese en primer lugar. Guardar y cifrar son acciones separadas, y un método debe hacer una cosa y hacerlo bien. Vea mis comentarios anteriores para obtener mejores ejemplos de cómo hacerlo.
Christoffer Hammarström