Como se señaló en los comentarios de @benjamin-gruenbaum, esto se llama trampa booleana:
Digamos que tengo una función como esta
UpdateRow(var item, bool externalCall);
y en mi controlador, ese valor para externalCall
siempre será VERDADERO. ¿Cuál es la mejor manera de llamar a esta función? Yo suelo escribir
UpdateRow(item, true);
Pero me pregunto, ¿debería declarar un booleano, solo para indicar qué significa ese valor 'verdadero'? Puedes saberlo mirando la declaración de la función, pero obviamente es más rápido y claro si acabas de ver algo como
bool externalCall = true;
UpdateRow(item, externalCall);
PD: No estoy seguro de si esta pregunta realmente encaja aquí, si no es así, ¿dónde podría obtener más información sobre esto?
PD2: No etiqueté ningún idioma porque pensé que era un problema muy genérico. De todos modos, trabajo con c # y la respuesta aceptada funciona para c #
data CallType = ExternalCall | InternalCall
en haskell por ejemplo.Respuestas:
No siempre hay una solución perfecta, pero tiene muchas alternativas para elegir:
Use argumentos con nombre , si están disponibles en su idioma. Esto funciona muy bien y no tiene inconvenientes particulares. En algunos idiomas, cualquier argumento se puede pasar como un argumento con nombre, por ejemplo
updateRow(item, externalCall: true)
( C # ) oupdate_row(item, external_call=True)
(Python).Su sugerencia de usar una variable separada es una forma de simular argumentos con nombre, pero no tiene los beneficios de seguridad asociados (no hay garantía de que haya usado el nombre de variable correcto para ese argumento).
Use funciones distintas para su interfaz pública, con mejores nombres. Esta es otra forma de simular parámetros con nombre, colocando los valores de los parámetros en el nombre.
Esto es muy legible, pero genera muchas repeticiones para usted, que está escribiendo estas funciones. Tampoco puede lidiar bien con la explosión combinatoria cuando hay múltiples argumentos booleanos. Un inconveniente importante es que los clientes no pueden establecer este valor dinámicamente, sino que deben usar if / else para llamar a la función correcta.
Usa una enumeración . El problema con los booleanos es que se llaman "verdadero" y "falso". Entonces, en su lugar, introduzca un tipo con mejores nombres (por ejemplo
enum CallType { INTERNAL, EXTERNAL }
). Como beneficio adicional, esto aumenta la seguridad de tipo de su programa (si su idioma implementa enumeraciones como tipos distintos). El inconveniente de las enumeraciones es que agregan un tipo a su API visible públicamente. Para funciones puramente internas, esto no importa y las enumeraciones no tienen inconvenientes significativos.En idiomas sin enumeraciones, a veces se utilizan cadenas cortas . Esto funciona, e incluso puede ser mejor que los booleanos sin procesar, pero es muy susceptible a los errores tipográficos. La función debería afirmar inmediatamente que el argumento coincide con un conjunto de valores posibles.
Ninguna de estas soluciones tiene un impacto prohibitivo en el rendimiento. Los parámetros y enumeraciones con nombre se pueden resolver completamente en tiempo de compilación (para un lenguaje compilado). El uso de cadenas puede implicar una comparación de cadenas, pero el costo es insignificante para los literales de cadenas pequeñas y la mayoría de los tipos de aplicaciones.
fuente
La solución correcta es hacer lo que sugieres, pero empaquetarlo en una mini-fachada:
La legibilidad triunfa sobre la microoptimización. Puede permitirse la llamada de función adicional, ciertamente mejor de lo que puede permitirse el esfuerzo del desarrollador de tener que buscar la semántica de la bandera booleana incluso una vez.
fuente
true
hace). Valdría la pena incluso si cuesta tanto tiempo de CPU como ahorra tiempo de desarrollador, ya que el tiempo de CPU es mucho más barato.UpdateRow(item, true /*external call*/);
sería más limpio, en idiomas que permiten esa sintaxis de comentarios. Bloquear su código con una función adicional solo para evitar escribir un comentario no parece valer la pena para un caso simple. Quizás si hubiera muchos otros argumentos para esta función, y / o algún código circundante + complicado alrededor, comenzaría a atraer más. Pero creo que si está depurando, terminará verificando la función del contenedor para ver qué hace, y tendrá que recordar qué funciones son contenedores delgados alrededor de una API de biblioteca y cuáles tienen algo de lógica.¿Por qué tienes una función como esta?
¿En qué circunstancias lo llamaría con el argumento externalCall establecido en valores diferentes?
Si uno es de, digamos, una aplicación externa de cliente y el otro está dentro del mismo programa (es decir, diferentes módulos de código), entonces diría que debe tener dos métodos diferentes , uno para cada caso, posiblemente incluso definido en distintos Interfaces
Sin embargo, si estaba haciendo la llamada basándose en algún dato tomado de una fuente que no es del programa (por ejemplo, un archivo de configuración o una lectura de base de datos), entonces el método de paso booleano tendría más sentido.
fuente
Si bien estoy de acuerdo en que es ideal usar una función de lenguaje para imponer tanto la legibilidad como la seguridad del valor, también puede optar por un enfoque práctico : comentarios en tiempo de llamada. Me gusta:
o:
o (correctamente, como lo sugiere Frax):
fuente
UpdateRow(item, /* externalCall */ true )
. El comentario de oraciones completas es mucho más difícil de analizar, en realidad es en su mayoría ruido (especialmente la segunda variante, que también está muy unida al argumento).Puedes 'nombrar' tus bools. A continuación se muestra un ejemplo para un lenguaje OO (donde se puede expresar en la clase que proporciona
UpdateRow()
), sin embargo, el concepto en sí se puede aplicar en cualquier idioma:y en el sitio de la llamada:
Creo que el Artículo # 1 es mejor, pero no obliga al usuario a usar nuevas variables cuando usa la función. Si la seguridad de tipos es importante para usted, puede crear un
enum
tipo y hacer queUpdateRow()
acepte esto en lugar de unbool
:UpdateRow(var item, ExternalCallAvailability ec);
Puede modificar el nombre de la función de alguna manera que refleje mejor el significado del
bool
parámetro. No estoy muy seguro pero tal vez:UpdateRowWithExternalCall(var item, bool externalCall)
fuente
externalCall=false
, su nombre no tiene absolutamente ningún sentido. El resto de tu respuesta es buena.UpdateRowWithUsuallyExternalButPossiblyInternalCall
.Otra opción que aún no he leído aquí es: usar un IDE moderno.
Por ejemplo, IntelliJ IDEA imprime el nombre de la variable en el método al que está llamando si está pasando un literal como
true
onull
ousername + “@company.com
. Esto se hace en una fuente pequeña para que no ocupe demasiado espacio en su pantalla y se vea muy diferente del código real.Todavía no digo que sea una buena idea lanzar booleanos en todas partes. El argumento que dice que lee el código con mucha más frecuencia de la que escribe es a menudo muy fuerte, pero para este caso particular depende en gran medida de la tecnología que usted (y sus colegas) usen para respaldar su trabajo. Con un IDE es mucho menos problema que con, por ejemplo, vim.
Ejemplo modificado de parte de mi configuración de prueba:
fuente
¿2 días y nadie mencionó el polimorfismo?
Cuando soy un cliente que desea actualizar una fila con un elemento, no quiero pensar en el nombre de la base de datos, la URL del microservicio, el protocolo utilizado para comunicarse o si una llamada externa es o no va a necesitar ser usado para que esto suceda. Deja de presionarme estos detalles. Simplemente tome mi artículo y vaya a actualizar una fila en algún lugar ya.
Haga eso y se convierte en parte del problema de construcción. Eso se puede resolver de muchas maneras. Aquí hay uno:
Si estás viendo eso y pensando "Pero mi lenguaje ha llamado argumentos, no necesito esto". Bien, genial, ve a usarlos. Simplemente mantenga esta tontería fuera del cliente que llama que ni siquiera debería saber con qué está hablando.
Cabe señalar que esto es más que un problema semántico. También es un problema de diseño. Pase un booleano y se verá obligado a usar la ramificación para lidiar con él. No muy orientado a objetos. ¿Tienes dos o más booleanos para pasar? ¿Desea que su idioma tenga despacho múltiple? Mira lo que pueden hacer las colecciones cuando las anidas. La estructura de datos correcta puede hacer su vida mucho más fácil.
fuente
Si puede modificar la
updateRow
función, quizás pueda refactorizarla en dos funciones. Dado que la función toma un parámetro booleano, sospecho que se ve así:Eso puede ser un poco de olor a código. La función podría tener un comportamiento dramáticamente diferente según la
externalCall
variable establecida, en cuyo caso tiene dos responsabilidades diferentes. Refactorizarlo en dos funciones que solo tienen una responsabilidad puede mejorar la legibilidad:Ahora, en cualquier lugar donde llame a estas funciones, puede saber de un vistazo si se está llamando al servicio externo.
Este no es siempre el caso, por supuesto. Es situacional y una cuestión de gustos. Por lo general, vale la pena considerar la refactorización de una función que toma un parámetro booleano en dos funciones, pero no siempre es la mejor opción.
fuente
Si UpdateRow está en una base de código controlada, consideraría el patrón de estrategia:
Donde Solicitud representa algún tipo de DAO (una devolución de llamada en caso trivial).
Caso verdadero:
Caso falso:
Por lo general, la implementación del punto final se configuraría en un nivel superior de abstracción y simplemente la usaría aquí. Como un útil efecto secundario gratuito, esto me da la opción de implementar otra forma de acceder a datos remotos, sin ningún cambio en el código:
En general, dicha inversión de control asegura un menor acoplamiento entre el almacenamiento de datos y el modelo.
fuente