Según ¿Está mal usar un parámetro booleano para determinar el comportamiento? , Sé la importancia de evitar el uso de parámetros booleanos para determinar un comportamiento, por ejemplo:
Versión original
public void setState(boolean flag){
if(flag){
a();
}else{
b();
}
c();
}
nueva versión:
public void setStateTrue(){
a();
c();
}
public void setStateFalse(){
b();
c();
}
Pero, ¿qué tal el caso de que el parámetro booleano se use para determinar valores en lugar de comportamientos? p.ej:
public void setHint(boolean isHintOn){
this.layer1.visible=isHintOn;
this.layer2.visible=!isHintOn;
this.layer3.visible=isHintOn;
}
Estoy tratando de eliminar el indicador isHintOn y crear 2 funciones separadas:
public void setHintOn(){
this.layer1.visible=true;
this.layer2.visible=false;
this.layer3.visible=true;
}
public void setHintOff(){
this.layer1.visible=false;
this.layer2.visible=true;
this.layer3.visible=false;
}
pero la versión modificada parece menos mantenible porque:
tiene más códigos que la versión original
no puede mostrar claramente que la visibilidad de layer2 es opuesta a la opción de sugerencia
cuando se agrega una nueva capa (por ejemplo: layer4), necesito agregar
this.layer4.visible=false;
y
this.layer4.visible=true;
en setHintOn () y setHintOff () por separado
Entonces, mi pregunta es, si el parámetro booleano se usa solo para determinar valores, pero no comportamientos (por ejemplo: no if-else en ese parámetro), ¿todavía se recomienda eliminar ese parámetro booleano?
setHint(boolean isHintOn)
como privada método, y añadir públicasetHintOn
ysetHintOff
métodos que llaman, respectivamente,setHint(true)
ysetHint(false)
.setHint(true|false)
. Potahto de papa. Al menos usa algo comosetHint
yunsetHint
.is
al principio.isValid
etc. Entonces, ¿por qué cambiar eso por dos palabras? Además, "más natural" está en el ojo del espectador. Si desea pronunciarlo como una oración en inglés, entonces para mí sería más natural tener "si la pista está activada" con "el" escondido.Respuestas:
El diseño de la API debe centrarse en lo que es más útil para un cliente de la API, desde el lado de la llamada .
Por ejemplo, si esta nueva API requiere que la persona que llama escriba regularmente código como este
entonces debería ser obvio que evitar el parámetro es peor que tener una API que permita a la persona que llama escribir
La versión anterior solo produce un problema que luego debe resolverse en el lado de la llamada (y probablemente más de una vez). Eso no aumenta la legibilidad ni la mantenibilidad.
El lado de la implementación , sin embargo, no debe dictar cómo las miradas de la API pública gusta. Si una función como
setHint
con un parámetro necesita menos código en la implementación, pero una API en términossetHintOn
/setHintOff
parece más fácil de usar para un cliente, se puede implementar de esta manera:Entonces, aunque la API pública no tiene un parámetro booleano, no hay una lógica duplicada aquí, por lo que solo hay un lugar para cambiar cuando llega un nuevo requisito (como en el ejemplo de la pregunta).
Esto también funciona al revés: si el
setState
método anterior necesita cambiar entre dos partes de código diferentes, esas partes de código pueden refactorizarse a dos métodos privados diferentes. Entonces, en mi humilde opinión, no tiene sentido buscar un criterio para decidir entre "un parámetro / un método" y "cero parámetros / dos métodos" mirando las partes internas. Mire, sin embargo, la forma en que le gustaría ver la API en el papel de un consumidor de la misma.En caso de duda, intente utilizar el "desarrollo impulsado por pruebas" (TDD), que lo obligará a pensar en la API pública y cómo usarla.
fuente
SetLoanFacility(bool enabled)
, porque después de haber otorgado un préstamo, puede que no sea fácil quitarlo de nuevo, y las dos opciones pueden involucrar una lógica completamente diferente, y querría pasar a separar Crear / Eliminar métodos.Toggle()
no es la función correcta que debe proporcionar. Ese es todo el punto; Si la persona que llama solo se preocupa por "cambiarlo" y no por "lo que termina como", entoncesToggle()
es la opción que evita la verificación y la toma de decisiones adicionales. No llamaría a eso un caso COMÚN, ni recomendaría ponerlo a disposición sin una buena razón, pero si el usuario necesita un cambio, entonces les daría un cambio.Martin Fowler cita a Kent Beck al recomendar
setOn()
setOff()
métodos separados , pero también dice que esto no debe considerarse inviolable:Otra recomendación es usar un valor enumerado o un tipo de marca para dar
true
yfalse
mejores nombres específicos del contexto. En su ejemplo,showHint
yhideHint
podría ser mejor.fuente
-[NSView setNeedsDisplay:]
método por el que se pasaYES
si una vista se debe volver a dibujar yNO
si no. Casi nunca necesita decirle que no lo haga, por lo que UIKit simplemente no tiene-[UIView setNeedsDisplay]
ningún parámetro. No tiene el-setDoesNotNeedDisplay
método correspondiente .bool isPremium
bandera en su ejemplo, pero usaría una enum (BookingType bookingType
) para parametrizar el mismo método, a menos que la lógica para cada reserva fuera bastante diferente. La "lógica enredada" a la que se refiere Fowler es a menudo deseable si uno quiere poder ver cuál es la diferencia entre los dos modos. Y si son radicalmente diferentes, expondría el método parametrizado externamente e implementaría los métodos separados internamente.Creo que estás mezclando dos cosas en tu publicación, la API y la implementación. En ambos casos, no creo que haya una regla fuerte que puedas usar todo el tiempo, pero debes considerar estas dos cosas de forma independiente (tanto como sea posible).
Comencemos con la API, ambos:
y:
son alternativas válidas según lo que se supone que debe ofrecer su objeto y cómo sus clientes utilizarán la API. Como señaló Doc, si sus usuarios ya tienen una variable booleana (desde un control de UI, una acción del usuario, una API externa, etc.), la primera opción tiene más sentido, de lo contrario, solo está forzando una declaración if adicional en el código del cliente . Sin embargo, si, por ejemplo, está cambiando la pista a verdadero al comenzar un proceso y a falso al final, la primera opción le ofrece algo como esto:
mientras que la segunda opción te da esto:
cuál IMO es mucho más legible, así que iré con la segunda opción en este caso. Obviamente, nada le impide ofrecer ambas opciones (o más, podría usar una enumeración como dijo Graham si eso tiene más sentido, por ejemplo).
El punto es que debe elegir su API en función de lo que se supone que debe hacer el objeto y de cómo los clientes lo van a usar, no de cómo lo va a implementar.
Luego debe elegir cómo implementar su API pública. Digamos que elegimos los métodos
setHintOn
ysetHintOff
como nuestra API pública y comparten esta lógica común como en su ejemplo. Puede abstraer fácilmente esta lógica a través de un método privado (código copiado de Doc):Por el contrario, supongamos que elegimos
setHint(boolean isHintOn)
nuestra API, pero invirtamos su ejemplo, debido a la razón por la cual la sugerencia Activada es completamente diferente a la desactivada. En este caso podríamos implementarlo de la siguiente manera:O incluso:
El punto es que, en ambos casos, primero elegimos nuestra API pública y luego adaptamos nuestra implementación para que se ajuste a la API elegida (y las restricciones que tenemos), y no al revés.
Por cierto, creo que lo mismo se aplica a la publicación que vinculó sobre el uso de un parámetro booleano para determinar el comportamiento, es decir, debe decidir en función de su caso de uso particular en lugar de alguna regla estricta (aunque en ese caso en particular generalmente lo correcto es hacer es romperlo en múltiples funciones).
fuente
begin
y /end
o sinónimos, solo para dejar explícitamente claro que eso es lo que hacen, e implicar que un el comienzo tiene que tener un final y viceversa.using
bloques en C #,with
administradores de contexto de declaración en Python, pasando el cuerpo de como un objeto lambda u invocable (por ejemplo, sintaxis de bloque Ruby), etc.Primero lo primero: el código no es menos fácil de mantener automáticamente, solo porque es un poco más largo. La claridad es lo que importa.
Ahora, si realmente solo está tratando con datos, entonces lo que tiene es un setter para una propiedad booleana. En ese caso, es posible que desee almacenar ese valor directamente y obtener las visibilidades de la capa, es decir
(Me tomé la libertad de dar nombres reales a las capas; si no tiene esto en su código original, comenzaría con eso)
Esto todavía te deja con la pregunta de si tener un
setHintVisibility(bool)
método. Personalmente, recomendaría reemplazarlo con un métodoshowHint()
yhideHint()
, ambos serán muy simples y no tendrá que cambiarlos cuando agregue capas. Sin embargo, no es un claro claro / incorrecto.Ahora, si llamar a la función realmente debería cambiar la visibilidad de esas capas, en realidad tiene un comportamiento. En ese caso, definitivamente recomendaría métodos separados.
fuente
showHint
vssetHintVisibility
). Mencioné la nueva capa solo porque OP estaba preocupada por eso. Además, sólo tenemos que añadir un método nuevo:isLayer4Visible
.showHint
yhideHint
simplemente establece elisHintVisible
atributo en verdadero / falso y eso no cambia.Los parámetros booleanos están bien en el segundo ejemplo. Como ya ha descubierto, los parámetros booleanos no son en sí mismos problemáticos. Está cambiando el comportamiento basado en una bandera, lo cual es problemático.
Sin embargo, el primer ejemplo es problemático porque el nombre indica un método de establecimiento, pero las implementaciones parecen ser algo diferente. Entonces tiene el antipatrón de cambio de comportamiento y un método engañosamente nombrado. Pero si el método en realidad es un configurador regular (sin cambio de comportamiento), entonces no hay problema con
setState(boolean)
. Tener dos métodos,setStateTrue()
ysetStateFalse()
simplemente complica innecesariamente las cosas sin ningún beneficio.fuente
Otra forma de resolver este problema es introducir un objeto para representar cada pista, y hacer que el objeto sea responsable de determinar los valores booleanos asociados con esa pista. De esta manera, puede agregar nuevas permutaciones en lugar de simplemente tener dos estados booleanos.
Por ejemplo, en Java, podría hacer:
Y luego su código de llamada se vería así:
Y su código de implementación se vería así:
Esto mantiene conciso el código de implementación y el código de la persona que llama, a cambio de definir un nuevo tipo de datos que mapee claramente las intenciones con nombres fuertemente tipados a los conjuntos de estados correspondientes. Creo que eso es mejor por todos lados.
fuente
Cuando tengo dudas sobre tales cosas. Me gusta imaginar cómo se vería el rastro de la pila.
Durante muchos años trabajé en un proyecto PHP que usaba la misma función como setter y getter . Si pasó nulo , devolvería el valor; de lo contrario, configúrelo. Fue horrible trabajar con él .
Aquí hay un ejemplo de seguimiento de pila:
No tenía idea del estado interno y esto hacía que la depuración fuera más difícil. Hay muchos otros efectos secundarios negativos, pero trate de ver si hay un valor en el nombre detallado de una función y claridad cuando las funciones realizan una sola acción.
Ahora imagine trabajar con el seguimiento de pila para una función que tiene un comportamiento A o B basado en un valor booleano.
Eso es confuso si me preguntas. Las mismas
setVisible
líneas producen dos trazados diferentes.Así que volvamos a tu pregunta. Intente imaginar cómo se verá el seguimiento de la pila, cómo se comunica a una persona lo que está sucediendo y pregúntese si está ayudando a una persona futura a depurar el código.
Aquí hay algunos consejos:
A veces, el código se ve excesivo bajo un microscopio, pero cuando regresa a la imagen más grande, un enfoque minimalista lo hará desaparecer. Si necesita que se destaque para poder mantenerlo. Agregar muchas funciones pequeñas puede parecer excesivamente detallado, pero mejora la capacidad de mantenimiento cuando se usa ampliamente en un contexto más amplio.
fuente
setVisible
seguimiento de la pila es que las llamadassetVisible(true)
parecen dar como resultado una llamada asetVisible(false)
(o al revés, dependiendo de cómo se produjo ese seguimiento).En casi todos los casos en los que pasa un
boolean
parámetro a un método como indicador para cambiar el comportamiento de algo, debe considerar una forma más explícita y segura de escribir para hacerlo.Si no hace nada más que usar un
Enum
que represente el estado, ha mejorado la comprensión de su código.Este ejemplo usa la
Node
clase deJavaFX
:siempre es mejor que, como se encuentra en muchos
JavaFX
objetos:pero creo
.setVisible()
y.setHidden()
es la mejor solución para la situación en la que la bandera es una,boolean
ya que es la más explícita y menos detallada.en el caso de algo con múltiples selecciones, esto es aún más importante hacerlo de esta manera.
EnumSet
existe solo por esta razón.fuente
Al decidir entre un enfoque para alguna interfaz que pasa un parámetro (booleano) frente a métodos sobrecargados sin dicho parámetro, mire a los clientes consumidores.
Si todos los usos pasarían valores constantes (por ejemplo, verdadero, falso), entonces eso argumenta a favor de las sobrecargas.
Si todos los usos pasaran un valor variable, entonces eso argumenta a favor del método con enfoque de parámetros.
Si ninguno de esos extremos se aplica, eso significa que hay una combinación de usos de los clientes, por lo que debe elegir si admite ambos formularios o hacer que una categoría de clientes se adapte al otro (lo que para ellos es un estilo más antinatural).
fuente
Hay dos consideraciones para diseñar:
No deben ser combinados.
Está perfectamente bien:
Como tal, cualquier argumento que intente equilibrar los costos / beneficios de un diseño de API con los costos / beneficios de un diseño de implementación es dudoso y debe examinarse cuidadosamente.
En el lado de la API
Como programador, generalmente favoreceré las API programables. El código es mucho más claro cuando puedo reenviar un valor que cuando necesito una declaración
if
/switch
en el valor para decidir qué función llamar.Esto último puede ser necesario si cada función espera argumentos diferentes.
En su caso, por lo tanto, un único método
setState(type value)
parece mejor.Sin embargo , no hay nada peor que sin nombre
true
,false
,2
, etc ... esos valores mágicos no tienen significado por sí mismos. Evite la obsesión primitiva y adopte la mecanografía fuerte.Por lo tanto, desde un POV API, quiero:
setState(State state)
.En el lado de la implementación
Recomiendo hacer lo que sea más fácil.
Si el método es simple, es mejor mantenerlo unido. Si el flujo de control es complicado, es mejor separarlo en varios métodos, cada uno de los cuales se ocupa de un sub-caso o un paso de la tubería.
Finalmente, considere la agrupación .
En su ejemplo (con espacio en blanco agregado para facilitar la lectura):
¿Por qué está
layer2
resistiendo la tendencia? ¿Es una característica o es un error?¿Podría ser posible tener 2 listas
[layer1, layer3]
y[layer2]
, con un nombre explícito que indique la razón por la que están agrupadas, y luego iterar sobre esas listas?Por ejemplo:
El código habla por sí mismo, está claro por qué hay dos grupos y su comportamiento es diferente.
fuente
Por separado de la pregunta
setOn() + setOff()
vsset(flag)
, consideraría cuidadosamente si un tipo booleano es mejor aquí. ¿Estás seguro de que nunca habrá una tercera opción?Puede valer la pena considerar una enumeración en lugar de un booleano. Además de permitir la extensibilidad, esto también hace que sea más difícil obtener el valor booleano de manera incorrecta, por ejemplo:
vs
Con la enumeración, será mucho más fácil extender cuando alguien decida que quiere una opción 'si es necesario':
vs
fuente
Sugeriría reevaluar este conocimiento.
En primer lugar, no veo la conclusión que está proponiendo en la pregunta SE que ha vinculado. Principalmente hablan de reenviar un parámetro a través de varios pasos de llamadas a métodos, donde se evalúa muy lejos en la cadena.
En su ejemplo, está evaluando el parámetro directamente en su método. A ese respecto, no difiere en absoluto de ningún otro tipo de parámetro.
En general, no hay absolutamente nada de malo en usar parámetros booleanos; y obviamente cualquier parámetro determinará un comportamiento, o ¿por qué lo tendrías en primer lugar?
fuente
Definiendo la pregunta
Su pregunta del título es "¿Está mal [...]?" - Pero, ¿qué quieres decir con "mal"?
Según un compilador C # o Java, no está mal. Estoy seguro de que eres consciente de eso y no es lo que estás preguntando. Aparte de eso, me temo que solo tenemos
n
programadores conn+1
opiniones diferentes. Esta respuesta presenta lo que el libro Clean Code tiene que decir sobre esto.Responder
Clean Code presenta un argumento sólido contra los argumentos de la función en general:
Un "lector" aquí puede ser el consumidor de API. También puede ser el próximo programador, que aún no sabe qué hace este código, que podría ser usted en un mes. Pasarán por 2 funciones por separado o por 1 función dos veces , una teniendo en cuenta
true
y otrafalse
en mente.En resumen, use la menor cantidad de argumentos posible .
El caso específico de un argumento de bandera se aborda más adelante directamente:
Para responder sus preguntas directamente:
De acuerdo con Clean Code , se recomienda eliminar ese parámetro.
Información adicional:
Su ejemplo es bastante simple, pero incluso allí puede ver la sencillez que se propaga a su código: las funciones sin parámetros solo realizan tareas simples, mientras que la otra función tiene que hacer aritmética booleana para alcanzar el mismo objetivo. Es una aritmética booleana trivial en este ejemplo simplificado, pero podría ser bastante complejo en una situación real.
He visto muchos argumentos aquí de que deberías hacerlo depender del usuario de la API, porque tener que hacer esto en muchos lugares sería estúpido:
Yo no estoy de acuerdo de que algo no óptima está pasando aquí. Tal vez sea demasiado obvio para ver, pero su primera oración menciona "la importancia de evitar [sic] usar parámetros booleanos para determinar un comportamiento" y esa es la base de toda la pregunta. No veo una razón para hacer que lo que es malo sea más fácil para el usuario de la API.
No sé si haces pruebas, en ese caso, también considera esto:
fuente