Opinión: bool es sólo para true/false

Saludos a todos! Tanto a los que estéis trabajando, cómo aquellos que estando de vacaciones seais tan frikis que leais geeks.ms! 🙂

Hoy quiero hablar un poco sobre bool. Puede parecer un tipo de datos aburridote: a fin de cuentas sólo puede tener dos valores, pero precisamente ahí radica su gracia y de eso os quería contar. La idea del post es muy simple: bool es sólo para true/false.

Por ejemplo, en los arcanos tiempos en que un servidor usaba Visual C++ 6 para el desarrollo de aplicaciones windows, en las MFCs había un método muy divertido llamado UpdateData (que por lo que veo aún está). MFC tenía una cosa muy buena que era la posibilidad de realizar bindings entre variables de la clase que representaba la ventana (usualmente una clase derivada de CWnd) y los controles que contenía dicha ventana. Eso, ahora, puede parecer una chorrada pero por aquel entonces era una auténtica pasada.

El método UpdateData era el que se encargaba de realizar dicho binding. Se llamaba con un parámetro BOOL que si valía TRUE significaba que se pasaban los datos de los controles a las variables y si valía FALSE pues era al revés: se pasaban los datos de las variables a los controles. Eso lo acabo de leer ahora en la MSDN, pero cuanda usaba MFC lo tenía que leer a menudo: nunca me acordaba cual era el significado de TRUE y FALSE en este contexto… En el fondo el parámetro de dicha función no es true/false es “BindingDeControlesAVariables” o “BindingDeVariablesAControles”, es decir un enum con dos opciones.

Y a eso me refiero en este post: un enum con dos opciones no es un booleano, aunque en ambos casos tengamos sólo dos valores posibles. Establecer arbitrariamente un valor a true y otro a false sólo hace que la gente se confunda.

Que código creéis que es más legible?

Fichero.Abrir("foo.txt",true);

O bien:

Fichero.Abrir("foo.txt",ModoApertura.Lectura);

En el primer caso, el segundo parámetro es un bool que si vale true se abre el fichero para lectura y si es false, pues se abre para escritura. Pues vale, pero es una decisión arbitraria y cuando alguien deba usar Abrir probablemente deba consultar que hace exactamente este parámetro bool (y lo mismo si alguien revisa código).

Pero no sólo es por legibilidad de código… Podéis asegurar que dos opciones serán siempre dos opciones? Me explico, y esta vez, como nadie (ni mucho menos yo) está libre de pecado, con un ejemplo de cosecha propia.

En un framework que estamos desarrollando para permitir desarrollos de aplicaciones con ciertas características hay una propiedad de una clase (llamésmola Aplicacion) que indica si cuando se va a la pantalla anterior (las aplicaciones tienen navegación parecida a la de un browser) se debe destruir la vista de la cual se proviene o bien dicha vista se mantiene en memoria.

En su momento implementamos dicha propiedad con un bool (true = destruir la vista, false = no destruirla). Y estuvo bien… hasta cierto día.

Cierto día, nos vimos en la necesidad de que en ciertas aplicaciones la vista se debía destruir sólo si no contenía datos (en caso contrario se podía reaprovechar). A priori esto dependía de cada aplicación. Y ahí tuvimos el problema: como mapeamos esto en nuestra propiedad booleana? Porque ahora teníamos tres valores:

  1. No destruir la vista
  2. Destruir la vista siempre
  3. Destruir la vista sólo si no tiene datos

Nosotros solucionamos el problema añadiendo una segunda propiedad que fuese “que tipo de destrucción de vistas se quería”, y lo hicimos así, simplemente porque todas estas propiedades estaban serializadas en XML (en archivos que algunos ya estaban en producción), por lo que la propiedad original debía ser siendo bool. Total, ahora tenemos dos propiedades a establecer para un mismo comportamiento lo que es susceptible de errores y de confusión. Y todo por no haber usado en su momento un enum 🙂

Así pues, un consejo: cuando creeis propiedades bool, aseguraos de que realmente un bool es lo que necesitais y no un enum (aunque sea con dos opciones!).

Un saludo!!!!

14 comentarios en “Opinión: bool es sólo para true/false”

  1. Buen post y buena reflexión Eduard.
    En mi caso prefiero utilizar enum en lugar de bool.
    Me encanta abusar del enum, incluso utilizar [Description(“”)] delante tirando de resources para en caso de error y necesidad de escribir en log, apuntar la descriptión de resources adecuada.
    Cada uno verá los bueno y lo malo de estas prácticas, pero en mi caso me apunto a enum sobre todo en comportamientos como los que has puesto de ejemplo.
    Otros comportamientos típicos de bool y no enum es el que sólo hay impepinablemente dos opciones, en cuyo caso y siempre y cuando esté muy muy claro, utilizaré bool. Si no está realmente claro, tiraré de enum para que no haya confusiones. Eso sin quitar el comentario XML que ayudará siempre como es evidente. 🙂

  2. Gracias Jorge por tu comentario 🙂

    Veo que coincidimos… 🙂
    El objetivo del post era por un lado que se vea la mejora de legibilidad de código al usar enums respecto a usar bools y también el problema en el que nos podemos encontrar cuando creemos al principio que sólo hay dos opciones, optamos por bool y luego nos aparece una tercera opción…

    Un saludo!!!

  3. ¿Que es más legible?

    Fichero.Abrir(“foo.txt”, true);
    Fichero.Abrir(“foo.txt”, ModoApertura.Lectura);
    Fichero.AbrirParaLecturar(“foo.txt”);

    Yo pasé de booleanos a enums hace muchos años hasta que me dí cuenta que rompía OCD y los problemas a los que conllevaba.

  4. El objetivo es hacer el código más comprehensible y mantenible. Los tipos enumerados muchas veces contribuyen a disminuir lo último.

    El prinicipio de única responsabilidad se tiene que aplicar a todo, no solo clases, sino también a métodos. Cuantos más parámetros tenga un método, más cosas hace, más complejo se convierte.

  5. Lo que más me gusta de estos debates abiertos es que en mi opinión, siempre se termina demostrando que nadie tiene la razón absoluta y sí te hace reflexionar y mucho sobre todo.

    @Hadi, tu comentario tiene en mi opinión su sentido, pero también lo que argumenta @Javier. Por cada enum, tendríamos entonces un método o función que lo sustituiría. Creo que esto no lo discute nadie.

    Leyendo tu segundo comentario Hadi, veo que tú mismo casi dices la respuesta: “El objetivo es hacer el código más comprehensible y mantenible. Los tipos enumerados muchas veces contribuyen a disminuir lo último”. Me quedo con el “muchas veces”.

    @Hadi, cierto es lo que comentas del principio de única responsabilidad, pero aquí volvemos al principio de todo. ¿Realmente así se hace el código más comprensible y mantenible?. En mi opinión, depende.
    Si bien está genial y tienes razón lo que argumentas, tampoco es todo principio de única responsabilidad llevado al extremo ni tampoco todo enums llevado al extremo.

    Por ejemplo:
    Para un true/false igual es cierto que un método o función con enum es bueno, pero mejor o más claro lo que comentas de una función para cada uno de los dos.
    Donde creo que todos coincidimos es que la función de tipo Fichero.Abrir(“foo.txt”, true) es más tediosa que las otras.
    Sin embargo, el uso de una función por cada elemento del enum, llevará a priori a aclarar el nombre de la función, pero pregunto:
    ¿Eso realmente lleva a un mejor mantenimiento?. Yo responderé con un “no siempre”, o un “depende”.
    El hecho de hacer esto así lleva en muchas ocasiones a duplicar el código de dentro del método y función y ahí podríamos llegar a hacer el código más engorroso y menos mantenible.
    Por otro lado, un enum de pongamos cinco ó más opciones quedaría más mantenible bajo mi punto de vista gestionada en una función de entrada que recibe ese enum y no con diez funciones una para cada opción del enum.

    En resumidas cuentas.
    Entiendo bien lo que se comentas, pero como en todo, hay que buscar el equilibrio porque ni todo es blanco ni todo es negro. Según el momento y nuestras necesidades.
    El OCP es interesante, pero ojo, su argumentación puede llevarnos a que un extraordinario guiso se nos queme (si se me permite la comparación). A veces lo que puede parecer simple lo dejamos peor.

    No obstante, en mi caso personal, encuentro más equilibrio, más mantenibilidad y más claridad, el uso general de enum del tipo [Fichero.Abrir(“foo.txt”, ModoApertura.SoloLectura);], eso sin olvidar que en otras circunstancias OCP es la solución, pero no siempre.

    Un saludo y muchas gracias a todos por aportar. Así también se aprende y mucho. 🙂

  6. @Jorge,

    Siempre creo que es más fácil leer y mantener 3 líneas de código que 10.

    También creo que es más fácil entender métodos bien descritos con el nombre que tener que descrifrar los parámetros, o como comentas recurrir a la documentación XML que como todos sabemos, se escribe (o mejor dicho, se genera) la primera vez y es lo que menos nos preocupa en las refactorizaciones.

    Hablas de tener que duplicar código. Nadie dice que un método no pueda llamar a otro para compartir partes comunes.

    Es más sencillo hacer pruebas unitarias sobre métodos que hacen un única cosa que otros que varian en función de condicionales.

    Además, es más sencillo cometer errores e introducir bugs cuando se tiene que tocar código existente.

    Todos estos factores para mi contribuyen a la mantenibilidad del código.

    Nadie dice que enum no sea mejor que bool, de hecho, dije que yo pasé de usar enums, pero también he visto mejores formas de hacerlo y comparto mi experiencia personal. Únicamente eso.

  7. Eduard, ni bool ni enums!

    – Un bool como parámetro de un método significa que ese método probablemente hace dos cosas: una por true y otra por false….

    – Una enumeración como parámetro de un método significa que tenés un switch y que definitivamente tu método tiene varios caminos.

    Entonces, si un método recibe un bool porque hace 2 cosas tenes que refactorizarlo y hacer 2 métodos. Los métodos resultantes van a ser mas sencillos y van a tener un parámetro menos. Por ejemplo, el método UpdateData es un ejemplo clarírismo de un método que hace dos cosas (y todo método debe hacer una cosa y solo una cosa). Mejor sería tener dos: UpdateControls y UpdateVAriables.

    Volviendo al tema de los enums, estos son peores que los booleanos! porque en los booleanos solo podes tener 2 valores mientras que en los enums (son enteros) podes recibir cualquier cosa. Hacé la prueba y corre Pex y vas a ver cómo mientras mas parámetros tenés más frágiles se vuelven tus métodos.

    Saludos groso

  8. @Hadi, Lucas, Jorge
    Muchas gracias por vuestras aportaciones!
    Me quedo con la frase de que el “SRP se debe aplicar a todo, no sólo a clases” (la misma idea que expone Lucas cuando habla de la fragilidad de los métodos). Parece un detalle, pero tiene muchas implicaciones…

    Gracias de nuevo!

  9. Sigo insistiendo que estas discusiones teórico-prácticas y amistosas se agradecen y mucho. Muchas gracias a todos por compartir las inquietudes. 🙂

    @Hadi, en mis desarrollos siempre trato de tener funciones aclarativas, con comentarios válidos, correctos y adecuados.

    Todos sabemos que refactorizar es necesario (y las herramientas de tu empresa son de las mejores en eso) pero ojo, el equilibrio entre la duplicidad de código y el mantenimiento puede traer quebraderos de cabeza. Digo esto porque como decía en mi primera intervención, ni todo es blanco ni todo negro.

    Evidentemente y es una realidad irrefutable creo, es lo que dices de “Es más sencillo hacer pruebas unitarias sobre métodos que hacen un única cosa que otros que varian en función de condicionales.”, aunque si se trata de enviar un valor enum, en todo momento podemos controlar con pruebas unitarias lo que devuelve, etc., pero a nivel de mantenimiento de código… depende, a veces enum es la mejor solución (es mi opinión).

    Sobre lo que comentas “Además, es más sencillo cometer errores e introducir bugs cuando se tiene que tocar código existente.” no entiendo bien a que te refieres. Si hay que mantener una aplicación, habrá que tocar código. ¿A qué te refieres exactamente?.

    @Lucas, entiendo que partiendo del principio de SRP, bool o enum no es lo que debe ser usado porque bool y enum rompe de raiz ese principio.
    Eso Lucas da para hacer una entrada en tu blog demostrando estos principios y más poniendo en ejecución la prueba que comentas.
    Cierto es que con bool solo tienes 2 opciones, mientras que con enum tienes x. Ahora bien, supongamos el caso inicial de tener 3 opciones y creamos un enum. Mañana creamos 1 valor más en enum (ya tendríamos 4) y la función que recibe enum sólo varía en que dentro de esa función agregamos en un switch su valor nuevo, cambia dos/tres cosas y el resto de la función es la misma. Como vemos, es poco cambio.
    Ahora nos vamos al otro medio, creamos una nueva función ya que el enum no existe y la cuarta nueva acción se crea como función.
    Dependiendo de quien consuma la clase, de las implicaciones que tengamos debajo o de otras muchas cosas, esta práctica haría por ejemplo que actualizáramos el servicio, que cambiáramos otras partes del código de la parte cliente, etc… vamos… depende de lo que haya dentro, mientras que de la otra forma, sólo cambiaríamos un sólo sitio.
    ¿Mantenibilidad?.
    Cuidado, que estoy hablando de un ejemplo teórico muy concreto, pero tampoco estoy diciendo que lo que estais argumentando tanto tú Lucas como Hadi me parezca mal, sólo digo que “depende” (soy muy pesado, lo sé, pero ni todo es blanco ni todo negro).

    @Eduard, el SRP (pongo el enlace de la Wiki para el que no sepa lo que es: http://en.wikipedia.org/wiki/Single_responsibility_principle) efectivamente se debería aplicar a todo, aunque aboga por el uso de una clase con una tarea única y concreta, pero todos sabemos que no todos los proyectos son de tipo “Hola Mundo” y a veces se empiezan a crear ciertas uniones entre diferentes partes que enmarañan las cosas.

    Lo ideal no es discutido, pero la práctica es más cabezona de lo que es la teoría y ahí es donde a veces un principio clave y en el que todos estamos de acuerdo, es más difícil de acoplar a nuestros desarrollos de lo que en principio parecía.

    Como bien dices… tiene muchas implicaciones y cada proyecto es un mundo y como me gusta decir a mí, cada proyecto es único y diferente. Bases parecidas y algunas iguales, pero en el fondo diferente.

    Incluso si empezáramos hoy un proyecto de nuevo que hicimos hace un año, lo haríamos diferente. ¿Porqué?. Pues por todo esto que estamos comentando. 🙂

    Me encantan estas discusiones que te hacen pensar y plantearte las cosas desde diferentes puntos de vista. Siempre puedes creer estar en el buen camino y gracias a otros abrir los ojos y cambiar de ruta.

  10. @Jorge,

    Mira que tu gusta escribir :).

    Empiezo por abajo. Cierto, no todos los ejemplos son Hola Mundo y justamente por eso se debe cumplir SRP ya que de lo contrario un cambio impacta muchas partes del sistema cuando no es estrictamente necesario. Pero por ello existen mecanismos, patrones y herramientas que permiten desacoplar sistemas para que esto no conlleve a problemas.

    Contestando a tu pregunta:

    ” Si hay que mantener una aplicación, habrá que tocar código. ¿A qué te refieres exactamente?.”

    Cuanto más código tenga que tocar en una aplicación, mayor probabilidad hay de que introduzca fallos de regresión. Si aislo funcionamiento correctamente y puedo extender y ampliar la funcionlidad de una aplicación sin tener que tocar muchas partes, hay menos probabilidad de que rompa algo. Nadie te asegura por ejemplo que un enumerado se toque solamente en 2 puntos, desde donde se llama y desde donde se procesa. Piensa por ejemplo en estados de un cliente o una factura. ¿Cuantas veces nos encontramos con varias instancias del enumerado por distintas partes del código? Y lo que comenta Lucas, yo lo he dicho en muchisimas ocasiones: si tienes un bool como parámetro, piensa si realmente tienes dos funciones ahí. Y extendiendolo a enum, pues se aumenta.

    No creo que haya dicho que todo es blanco o negro y hay que buscar una balance, pero sigo en el caso en cuestión, describir mejor las funciones, hacerlos que cumplan una única responsabilidad aunmenta la mantenibilidad y entendimiento del código.

    Por último, de nuevo aquí no se trata de compensar mantenibilidad con duplicdad de código. Soy el primero que cumple DRY (salvo en especificaciones).

    Un saludo

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *