miércoles, 25 de agosto de 2010

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!!!!



PD: Esto es (como siempre) un crosspost desde mi blog en geeks.ms!

sábado, 14 de agosto de 2010

Opinión: De los atributos y su uso…

Sí, ya sé: estamos en Agosto y lo que más seduce ahora mismo es darse un bañito en la playa y salir de copas a rebentar los mojitos del bar, así que los que podáis hacedlo sin dudar… Total, este post tampoco se largará a ninguna parte luego… :)

Los que no estéis de vacaciones o bien prefiráis leer geeks.ms en pleno Agosto (hay de todo en la viña del señor) a ver que os parece este post… es mi opinión sobre el uso que se da a los atributos y los “problemas” que a mi parecer conlleva dicho uso. Dado que es un post de opinión vuestros comentarios sobre vuestras opiniones serán muy bien recibidos (de hecho siempre lo son, simplemente en este post lo serán más si cabe).

He estado observando en determinados casos un uso de los atributos que no me termina de convencer… Para aclarar conceptos entiendo por atributos aquellas clases que derivan de System.Attribute y que usamos para decorar nuestras clases, métodos, propiedades…

Voy a centrar este artículo de opinión en Data Annotations, pero no es el único caso que tiene este “defecto”…

En mi opinión los atributos deberían representar sólamente metadatos, es decir mera información, sin comportamiento alguno definido… O dicho de otro modo no deberían tener ningún método, sólo propiedades.

Cojamos el siguiente código de ejemplo, que es lo más simple del mundo: una clase, con una sola propiedad decorada con [Required]. Dicho atributo indica que la propiedad que se decora con él debe tener un valor:

class Foo
{
[Required]
public string Bar { get; set; }
}





Bien, ahora dado el siguiente código:




Foo foo = new Foo();
foo.Bar = string.Empty;
bool b = ((RequiredAttribute)foo.GetType().
GetProperty("Bar").GetCustomAttributes(false)[0]).
IsValid(foo.Bar);





Cuál es el valor de b, en este punto?



La respuesta rápida es… depende de la implementación del método IsValid que está en RequiredAttribute. En este caso el valor de IsValid es false, porque string.Empty asume que no es un valor válido.



Y aquí es donde, a mi, me chirría todo un poco… Yo estoy marcando un valor como requerido (usando [Required]) pero no entiendo porque la implementación de Required decide que significa ser requerido dentro de mi aplicación. Es decir el mero de hecho de declararlo como requerido me obliga a una implementación concreta de que signfica “ser requerido”.



En mi lógica de negocio requerido puede significar que no valga null (siendo string.Empty un valor válido, cosa que no acepta el RequiredAttribute). Me diréis que me puedo crear mis propios atributos y tendréis toda la razón del mundo pero entonces si aplico a la propiedad Bar de la clase Foo un atributo mío, estoy cambiando los metadatos de dicha clase… que pasa si la clase es externa o bien quiero usarla en distintos proyectos (donde el concepto de requerido puede ser diferente)?



No se si sería mejor separar RequiredAttribute en dos clases: una que sea el atributo que marque el elemento como requerido y otro que sea “que significa que un elemento sea requerido (es decir el método IsValid)” (dejadme llamar AttributeHandler a estas clases por ponerles nombre). Obviamente el framework debería proporcionar un AttributeHandler asociado a cada atributo y debería darme un mecanismo para que yo pudiese indicar que en mi proyecto el AttributeHandler para la clase RequiredAttribute es la clase que yo quiera…



No sé si me entiende por donde voy… :)



Un (caluroso) saludo a todos!



PD: Un efecto colateral de tener atributos con cierto comportamiento es que estos empiezan a tener dependencias a terceros componentes (imaginaros un atributo tipo [Log]… que usa para generar los ficheros de log?). Y dado que los atributos los crea el CLR eso hace difícil inyectarles dependencias… Si el código que tiene el comportamiento (p.ej. el que hace log) estuviese en otras clases que no se creasen automáticamente por el CLR sería más fácil inyectarles dependencias).



PD2: Esto es un crosspost desde mi blog en geeks.ms (como todos)!