¿Cómo puedo hacer una llamada con un operador booleano? Trampa booleana

74

Como se señala en los comentarios de @ benjamin-gruenbaum, esto se llama la trampa booleana:

Di 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? Normalmente escribo

UpdateRow(item, true);

Pero me pregunto a mí mismo, ¿debería declarar un booleano, solo para indicar qué significa ese valor 'verdadero'? Puede saberlo observando la declaración de la función, pero obviamente es más rápido y claro si acaba 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 #

    
pregunta Mario Garcia 16.05.2018 - 15:09

9 respuestas

151

No siempre hay una solución perfecta, pero tienes muchas alternativas para elegir:

  • Use argumentos con nombre , si está disponible 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 # ) o update_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 los parámetros nombrados, al poner los valores del parámetro en el nombre.

    Esto es muy legible, pero conduce a un montón de repetitivo para usted, quien está escribiendo estas funciones. Tampoco puede manejar bien la explosión combinatoria cuando hay múltiples argumentos booleanos. Un inconveniente importante es que los clientes no pueden establecer este valor dinámicamente, pero deben usar si / else para llamar a la función correcta.

  • Utilice una enumeración . El problema con los booleanos es que se les llama "verdadero" y "falso". Entonces, en su lugar, introduzca un tipo con mejores nombres (por ejemplo, enum CallType { INTERNAL, EXTERNAL } ). Como beneficio adicional, esto incrementa el tipo de seguridad 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 usan 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 debe afirmar inmediatamente que el argumento coincide con un conjunto de valores posibles.

Ninguna de estas soluciones tiene un impacto de rendimiento prohibitivo. Los parámetros nombrados y las enumeraciones se pueden resolver completamente en el momento de la compilación (para un lenguaje compilado). El uso de cadenas puede implicar una comparación de cadenas, pero su costo es insignificante para los literales de cadenas pequeñas y la mayoría de los tipos de aplicaciones.

    
respondido por el amon 16.05.2018 - 15:28
37

La solución correcta es hacer lo que sugieres, pero empaquetarlo en una minifachada:

void updateRowExternally() {
  bool externalCall = true;
  UpdateRow(item, externalCall);
}

La legibilidad triunfa sobre la microoptimización. Puede pagar la llamada a la función adicional, sin duda mejor que el esfuerzo del desarrollador de tener que buscar la semántica de la bandera booleana incluso una vez.

    
respondido por el Kilian Foth 16.05.2018 - 15:12
24
  

Diga que tengo una función como UpdateRow (var item, bool externalCall);

¿Por qué tienes una función como esta?

¿Bajo qué circunstancias lo llamaría con el argumento externalCall establecido en valores diferentes?

Si uno es de, digamos, una aplicación cliente externa y el otro está dentro del mismo programa (es decir, diferentes módulos de código), entonces diría que debería tener dos métodos diferentes , uno para cada caso, posiblemente incluso definido en distintas interfaces.

Sin embargo, si hiciera la llamada basándose en algún datum tomado de una fuente que no es del programa (por ejemplo, un archivo de configuración o una base de datos leída), entonces el método de paso booleano haría más sentido.

    
respondido por el Phill W. 16.05.2018 - 15:51
18

Aunque estoy de acuerdo en que es ideal utilizar una función de idioma para garantizar la legibilidad y la seguridad de valor, también puede optar por un enfoque práctico : comentarios en tiempo de llamada. Me gusta:

UpdateRow(item, true /* row is an external call */);

o:

UpdateRow(item, true); // true means call is external

o (correctamente, como lo sugiere Frax):

UpdateRow(item, /* externalCall */true);
    
respondido por el bishop 16.05.2018 - 22:02
10
  1. Puedes 'nombrar' a tus tontos. 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í puede aplicarse en cualquier idioma:

    class Table
    {
    public:
        static const bool WithExternalCall = true;
        static const bool WithoutExternalCall = false;
    

    y en el sitio de la llamada:

    UpdateRow(item, Table::WithExternalCall);
    
  2. Creo que el artículo # 1 es mejor, pero no obliga al usuario a usar nuevas variables cuando usa la función. Si el tipo de seguridad es importante para usted, puede crear un tipo enum y hacer que UpdateRow() acepte esto en lugar de un bool :

    UpdateRow(var item, ExternalCallAvailability ec);

  3. Puede modificar el nombre de la función de manera que refleje mejor el significado del parámetro bool . No estoy muy seguro, pero tal vez:

    UpdateRowWithExternalCall(var item, bool externalCall)

respondido por el simurg 16.05.2018 - 15:32
9

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 de las variables en el método al que está llamando si está pasando un literal como true o null o username + “@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 estoy diciendo que sea una buena idea lanzar booleanos en todas partes. El argumento que dice que usted lee el código con mucha más frecuencia de la que escribe es a menudo muy sólido, pero para este caso en particular, depende en gran medida de la tecnología que usted (y sus colegas) usen para respaldar su trabajo. Con un IDE es un problema mucho menor que con, por ejemplo, vim.

Ejemplo modificado de parte de mi configuración de prueba:

    
respondido por el Sebastiaan van den Broek 17.05.2018 - 05:33
3

¿2 días y no se menciona el polimorfismo?

target.UpdateRow(item);

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 es o no un Se necesitará usar una llamada externa para que esto ocurra. Deja de empujar estos detalles sobre mí. Solo toma mi artículo y ve a actualizar una fila en algún lugar ya.

Haz eso y se convierte en parte del problema de construcción. Eso se puede resolver de muchas maneras. Aquí hay uno:

Target xyzTargetFactory(TargetBuilder targetBuilder) {
    return targetBuilder
        .connectionString("some connection string")
        .table("table_name")
        .external()
        .build()
    ; 
}

Si estás viendo eso y pensando "Pero he nombrado argumentos, no necesito esto". Bien, genial, ve a usarlos. Simplemente mantenga este disparate 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 utilizar la bifurcación para tratar con él. No muy orientado a objetos. ¿Tienes dos o más booleanos para pasar? ¿Deseando que tu idioma tuviera múltiples despachos? Mire qué pueden hacer las colecciones cuando las anide. La estructura de datos correcta puede hacer tu vida mucho más fácil.

    
respondido por el candied_orange 18.05.2018 - 20:54
1

Si puedes modificar la función updateRow , quizás puedas refactorizarla en dos funciones. Dado que la función toma un parámetro booleano, sospecho que se ve así:

function updateRow(var item, bool externalCall) {
  Database.update(item);

  if (externalCall) {
    Service.call();
  }
}

Eso puede ser un poco de un olor de código. La función puede tener un comportamiento dramáticamente diferente según la configuración de la variable externalCall , en cuyo caso tiene dos responsabilidades diferentes. Refactorizarlo en dos funciones que solo tienen una responsabilidad puede mejorar la legibilidad:

function updateRowAndService(var item) {
  updateRow(item);
  Service.call();
}

function updateRow(var item) {
  Database.update(item);
}

Ahora, dondequiera que llame a estas funciones, puede saber de un vistazo si se está llamando al servicio externo.

Esto no siempre es el caso, por supuesto. Es situacional y una cuestión de gusto. 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.

    
respondido por el Kevin 16.05.2018 - 20:18
0

Si UpdateRow está en una base de código controlada, consideraría un patrón de estrategia:

public delegate void Request(string query);    
public void UpdateRow(Item item, Request request);

Donde la solicitud representa algún tipo de DAO (una devolución de llamada en un caso trivial).

Caso verdadero:

UpdateRow(item, query =>  queryDatabase(query) ); // perform remote call

Caso falso:

UpdateRow(item, query => readLocalCache(query) ); // skip remote call

Por lo general, la implementación del punto final se configuraría en un nivel más alto de abstracción y solo lo usaría aquí. Como un efecto secundario gratuito útil, esto me da la opción de implementar otra forma de acceder a datos remotos, sin ningún cambio en el código:

UpdateRow(item, query => {
  var data = readLocalCache(query);
  if (data == null) {
    data = queryDatabase(query);
  }
  return data;
} );

En general, dicha inversión de control asegura un acoplamiento más bajo entre el almacenamiento de datos y el modelo.

    
respondido por el Basilevs 18.05.2018 - 12:26

Lea otras preguntas en las etiquetas