De vez en cuando he visto alguna práctica que me parece equivocada, pero no puedo explicar qué tiene de malo. O quizá sean sólo prejuicios míos. Ahí va:
Un desarrollador define un método con un booleano como uno de sus parámetros, y ese método llama a otro, y así sucesivamente, y finalmente ese booleano se utiliza, únicamente para determinar si se debe o no realizar una determinada acción. Esto podría ser utilizado, por ejemplo, para permitir la acción sólo 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 sólo cuando el sistema está en un determinado estado.
Bueno, siempre hay otra forma de hacerlo, ya sea consultando cuando es el momento de realizar 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 tanto cómo mejorar esto, sino si realmente está mal (como sospecho), y si lo está, qué es lo que está mal.
Dejé de usar este patrón hace mucho tiempo, por una razón muy simple; coste de mantenimiento. Varias veces me encontré con que tenía alguna función digamos frobnicate(algo, forwards_flag)
que era llamada muchas veces en mi código, y necesitaba localizar todos los lugares en el código donde se pasaba el valor false
como valor de forwards_flag
. No puedes buscarlos fácilmente, así que esto se convierte en un dolor de cabeza para el mantenimiento. Y si necesita hacer una corrección de errores en cada uno de esos sitios, puede tener un problema desafortunado si se le escapa alguno.
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, sólo habría que buscar instancias de FrobnicateBackwards
. Aunque es posible que haya algún código que asigne esto a una variable de modo que haya que seguir unos hilos de control, en la práctica me parece que esto es lo suficientemente raro como para que esta alternativa funcione bien.
Sin embargo, hay otro problema con pasar la bandera de esta manera, al menos en principio. Esto es que algunos (sólo 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 utiliza la bandera) a las capas externas (que necesitan saber qué valor pasar en esta bandera). Para usar la terminología de Larry Constantine, este diseño puede tener un coupling demasiado fuerte entre el setter y el usuario de la bandera booleana. Franky aunque it's difícil decir con algún grado de certeza sobre esta cuestión sin saber más sobre la base de código.
Para abordar los ejemplos específicos que das, yo tendría cierto grado de preocupación en cada uno pero principalmente por razones de riesgo/corrección. Es decir, si su sistema necesita pasar banderas que indiquen en qué estado se encuentra el sistema, puede encontrarse con que tiene código que debería haber tenido esto en cuenta, pero no comprueba el parámetro (porque no se pasó a esta función). Entonces tienes un bug 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 muchos de los inconvenientes de una variable global. Creo que en muchos casos es mejor encapsular el conocimiento del estado del sistema (o las credenciales del usuario, o la identidad del sistema) dentro de un objeto que sea responsable de actuar correctamente basándose en esos datos. A continuación, se pasa una referencia a ese objeto en lugar de los datos en bruto. El concepto clave aquí es encapsulation.
Creo que Robert C Martins Clean code article afirma que debes eliminar los argumentos booleanos siempre que sea posible, ya que muestran que un método hace más de una cosa. Un método debe hacer una cosa y sólo una cosa, creo que es uno de sus lemas.
El contexto es importante. Estos métodos son bastante comunes en iOS. Por ejemplo, UINavigationController proporciona el método -pushViewController:animated:
, y el parámetro animated
es un BOOL. El método realiza esencialmente la misma función en ambos casos, pero anima la transición de un controlador de vista a otro si pasas YES, y no lo hace si pasas NO. Esto parece totalmente razonable; sería absurdo tener que proporcionar dos métodos en lugar de éste sólo para poder determinar si se utiliza o no la animación.
Puede que sea más fácil justificar este tipo de cosas en Objective-C, donde la sintaxis de nombres de métodos proporciona más contexto para cada parámetro que en lenguajes como C y Java. Sin embargo, creo que un método que toma un único parámetro podría fácilmente tomar un booleano y seguir teniendo sentido:
file.saveWithEncryption(false); // is there any doubt about the meaning of 'false' here?