Buscando la felicidad con Bots y Cognitive Services

Aviso: Es un artículo largo y no muy técnico. Te recomiendo coger un café y/o armarte de paciencia 🙂

Nota: Este artículo nace de un proyecto personal, presentado como charla en The Mindcamp. No hubiera sido posible construir esto sin todos mis amigos y colegas que me ayudaron cuando lo pedí. Aprovecho este canal para daros las gracias 🙂

Cuando Microsoft presentó el Bot Framework hace unos meses y además insistió bastante en IA y bots, se me ocurrió que podría hacer un mini proyecto para ver qué tal funciona y comprobar su uso.

El concepto de bots no es nada nuevo. Ya cuando era joven pasaba las tardes en el IRC respondiendo a bots de preguntas; e incluso alguno me hice para hacer chorradas con los usuarios de la sala de chat. Al presentar el Bot Framework, me vino el recuerdo de esa etapa y me puse a investigar a ver cuál es la visión de usar bots ahora y qué podemos hacer con ellos. Por un motivo o por otro quería hacer un bot y quería que fuese distinto. No quería el típico que te diga la hora o tengas frases prefijadas.

botsfreak

Bot Framework

Este artículo NO va a ser un artículo dedicado a analizar Bot Framework. Ya hay infinidad de artículos muy buenos en la MSDN y otros tantos tutoriales de “Hola Mundo”. Si quieres saber cómo funciona, te recomiendo que le eches un vistazo a la página de Bot Framework.

botframework

Para mí Bot Framework me supone un alivio a la hora de crear un bot, ya que simplifica lo que es el propio bot (básicamente una WebAPI que tiene un único endpoint a través del cual se reciben todos los mensajes): gestión de conversaciones, permite traducir automáticamente los mensajes y en especial, han cuidado mucho la integración con otras plataformas. Tiene un gestor muy sencillo en el cual con varios pasos guiados y muy sencillos te permite desplegar tu bot sobre distintas plataformas, como Skype, Telegram, Facebook, SMS, etc. Tal vez para mí esta sea una de las características más importantes, ya que me permite gestionar la publicación del bot sobre las distintas plataformas de una forma centralizada.

Una cosa a tener en cuenta es que no es una release. Bot Framework está en preview, con todo lo que ello implica. Funciona bien y han mejorado bastante respecto a hace unos meses, pero no omitas la palabra “preview” al pensar en ello.

Una vez sabiendo cómo podía publicar el bot, el siguiente paso era darle contenido. Según la plataforma puedes hacer que el bot sea proactivo desde el punto de vista del usuario o te ofrezca opciones. Por ejemplo, puedes hacer que a determinada hora el bot te abra una conversación y te diga algo. O puedes que te envíe imágenes o preguntas con opciones elegidas para que te muestre algún tipo de información. En cualquier caso mi objetivo es mucho más humilde y si el bot responde a algo relacionado con lo que el usuario le envía, me doy por satisfecho. No quiero que el bot reciba comandos, quiero que sea capaz de interpretar un texto normal escrito por un humano.

Como quiero un bot que responda, necesito dotarle de frases que responder. En un primer momento pensé en buscar frases célebres de WikiQuote o usar textos de subtítulos de alguna serie. Pero me resultó más fácil coger algún perfil de Twitter famoso y usarlo como corpus de frases que responder. Así pues con esto, la arquitectura de mi bot sería la siguiente:

  • Una web app que expone una web api para usar Bot Framework.
  • Un web job se encargaría de la ingestión de datos vía Twitter. Cada hora se conectaría a Twitter y extraería los tweets de los perfiles de usuario que he marcado.

Con esto tan simple ya tengo el bot con contenido funcionando. Además, me permite tener la web app en Free. Y sí, Bot Framework es gratuito. Al menos de momento.

Cognitive Services

Text Analyzer

El siguiente paso es encontrar una forma de dada una entrada de usuario, poder devolver la respuesta más apropiada dentro del corpus de mensajes. Aquí es importante tener un corpus amplio y variado. Para ello tenía que disponer de alguna forma de “entender” lo que quería decir el usuario y tener una forma de “entender” qué significan los tweets del corpus. La primera aproximación consistía en intentar hacer un análisis de la frase para extraer la estructura gramatical y trabajar con ello. Evidentemente es una tarea muy compleja y no es algo que se pueda hacer en un par de horas, así que descubrí una serie de APIs dentro de Microsoft Cognitive Services  que permiten extraer información del texto. En concreto me basé en Text Analyzer, que permite detectar el idioma y extraer las frases principales de un texto. Aunque bien es cierto que Cognitive Services proporciona APIs para extraer la estructura gramatical de una oración o frase, esta sólo está disponible en inglés. Como mi corpus son mensajes en español, tuve que descartarlo directamente.

Usar TextAnalizer (la API de Cognitive Services que se encarga de esto) es tan sencillo como hacer una petición HTTP a la propia API:


client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", _accountKey);
 client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

Primero se le pasa como header la clave de autenticación (que se obtiene una vez te registras en la API) y después lanzas una petición al endpoint deseado. En mi caso es el siguiente:


var uri = "text/analytics/v2.0/keyPhrases";
 response = await CallEndpoint(client, uri, byteData);
 var keyPhrases = JsonConvert.DeserializeObject<TextResponse<KeyPhraseResponse>>(response);

Donde el método CallEndPoint, simplemente hago la llamada con el formato correcto:


var result = string.Empty;

using (var content = new ByteArrayContent(byteData))
 {
 content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
 var response = await client.PostAsync(uri, content);

if (response.Headers.Contains("operation-location"))
 {
 var operationId = response.Headers.GetValues("operation-location").Single();
 var operationRequest = string.Format(operationUrl, operationId);

 do
 {
 // Request every minute
 await Task.Delay(60000);
response = await client.GetAsync(operationRequest);
 result = await response.Content.ReadAsStringAsync();
 } while (result.Contains("\"status\": \"succeded\""));
 }
 else
 {
 result = await response.Content.ReadAsStringAsync();
 }
 }

return result;

La estructura de datos de la API se organiza en base a un documento que incluye todos los datos, así que lo modelé y a la hora de hacer una petición, realmente envío el siguiente conjunto de bytes:


Document document = null;
 var request = new TextRequest() { Documents = new List<Document>() };
 for (int i = 1; i <= input.Count; i++)
 {
 document = new Document
 {
 Id = i.ToString(),
 Text = input[i - 1]
 };

request.Documents.Add(document);
 }

var serializedEntity = JsonConvert.SerializeObject(request);

byte[] byteData = Encoding.UTF8.GetBytes(serializedEntity);

Cada texto que envío debe estar identificado, porque después me es devuelto con el mismo identificador y con el resultado del análisis. Además, esto me sirve para aprovechar que puedo analizar varias frases en una misma llamada a la API en lugar de una por una, ya que hay un límite mensual de llamadas (tengas o no la suscripción gratuita). De esta forma aprovecho el webjob de ingesta de datos de Twitter para preanalizar las frases en bloques y así reducir la cantidad de llamadas efectuadas. Sólo se hace una llamada cuando se pregunta al bot, para obtener las frases principales de lo que ha escrito el usuario. Para hacerlas coincidir, miro cuáles coinciden con el corpus y elijo una al azar entre las seleccionadas.

L.U.I.S.

Ya tenía un sistema “para salir del paso” que me permitiese tener un bot que responda a algo parecido a lo que se le pregunta. Una vez hecho esto, le pedí a varios amigos que me ayudasen y le preguntasen cosas al bot. Esto fue crucial, porque pude ver qué se le preguntaba al bot y afinar un poco las respuestas. Gracias a ello, me permitió descubrir una serie de patrones de preguntas/frases que lanzan al bot y que yo no hacía. Ahora ya necesitaba algo más, necesitaba algo que me permitiese detectar patrones en las peticiones que recibía y reorientar la respuesta hacia esos patrones. Por ejemplo, si preguntas “¿Quien es Messi”? lo ideal es ser capaz de identificar que estás haciendo una pregunta, que preguntas quién es y en concreto, alguien en concreto. Si cambio la pregunta por “¿Quien es Cristiano Ronaldo?” es el mismo tipo de pregunta, pero el objeto sobre el que pregunto ha cambiado. La pregunta puede variar levemente, puede empezar en minúscula, sin el signo de interrogación inicial, sin el final, con / sin tildes… Además puede haber otras preguntas similares.

Para este tipo de casos se suele usar aprendizaje automático. La idea es sencilla: dado un conjunto inicial de elementos ya categorizados, seré capaz de ir detectando los nuevos elementos. Tal vez no los detecte a la primera, o no sean exactos, pero en ese caso puedo reanalizarlos de nuevo y detectarlos mejor iteración por iteración.

Cognitive Services nos proporciona L.U.I.S. (aka Language Understanding Intelligence System) que es justamente una herramienta de aprendizaje automático que está pensada para procesar modelos basados en comandos. Por ejemplo, dentro de mi modelo la pregunta “Quien és Messi?” es una entidad y le marco que “Messi” es una persona. A base de ponerle más preguntas y entrenarlo más, puedo ponerle preguntas similares y el sabrá decirme si es una pregunta de tipo “Quien és” y que “Messi” es la persona.

Aunque L.U.I.S. admite un conjunto limitado de entidades, me sirvió para obtener una serie de preguntas típicas que mis usuarios habían hecho. Además me permite mantener y entrenar a mi modelo si veo que algo no lo detecta. Si por ejemplo recibo la pregunta “quien es Federico Garcia Lorca” y no me detecta bien el nombre, puedo reindicarle “Federico García Lorca” es la persona y el modelo se vuelve a entrenar con esta información.

Veámoslo con una captura de ejemplo, extraída directamente del modelo que tengo construido:

luis

Dentro de las entidades que dispongo, es capaz de detectar que a las entradas “quien es cristiano ronaldo?” y “quien es el presidente?” se corresponden con la pregunta modelada como “quien es X”. Nos indica para la primera un 93% de probabilidad y un 95% para la segunda. La diferencia de colores en las palabras resaltadas es el tipo que busco, ya que tengo dos tipos de objetivos: personas y cosas. Es capaz de detectar que “cristiano ronaldo” es una persona y que “presidente” es una cosa. Y siempre, si considero que está mal o no está muy afinado, puedo cambiar esa asignación y volver a enviar la entrada pulsando “Submit”. En ese momento el modelo se entrenará de forma automática y será capaz de afinar la respuesta con la información que le hemos suministrado.

A nivel de código L.U.I.S tiene su propio paquete de NuGet, por lo que no es necesario hacer una llamada HTTP y preparar/procesar los datos a enviar/recibir.


bool _preview = true;
 LuisClient client = new LuisClient(_luisAppId, _luisSubscriptionKey, _preview);
 LuisResult res = await client.Predict(input);

Con estas tres simples líneas, invocamos a L.U.I.S. Nótese que lo que le enviamos es que nos prediga en base a una frase. Esa frase la lanzará contra el modelo que tiene ya entrenado y nos devolverá un resultado con la probabilidad de las entidades que él cree que son relacionadas con la frase que hemos enviado. Además, podrá detectar parte en las entidades (ej: “Messi”) si así le hemos entrenado previamente. Ya es decisión nuestra, en parte a la probabilidad de cada entidad en determinar qué hacer. Lo bueno es que si vemos que la probabilidad no nos encaja con lo que creemos que debería ser, siempre podemos volver a L.U.I.S y darle más información sobre el modelo para volver a entrenarlo.

Ahora ya sí, con un simple algoritmo podía lanzar peticiones a L.U.I.S, ver si encontraba algo que me pudiera valer y si no, seguía cotejando con las frases clave de los textos.

¿Cuál es el secreto de la felicidad?

Una pregunta arriesgada y profunda. Si supiera la respuesta o quisiera hacer creer que la se, posiblemente dejaría el gremio y me pondría a escribir libros y dar conferencias como buen gurú. Lamentablemente sólo sé que no sé nada, así que dejaré mis bestsellers para otro momento en mi vida. Pero si has llegado hasta aquí, al menos me reconocerás que el título tiene gancho.

Posiblemente, el hombre más feliz de Springfield
Posiblemente, el hombre más feliz de Springfield

Pero la pregunta, si bien no quiero saber cuál es el secreto, sí me interesa saber si mis usuarios son felices o no. Los textos que escribimos en redes sociales se usan en los típicos estudios de doctorandos desde hace varios años para analizar su contenido y entender qué significan. Tal vez no ahora, pero hace unos años era bastante común: saber si lo que escribo es algo feliz o triste. Se suele analizar teniendo en cuenta el contexto, las palabras que se emplean, expresiones no lingüísticas (como emoticonos) etc. Evidentemente desde un punto de vista comercial es bastante interesante de cara a categorizar a tus usuarios…

Una de las propiedades de la API de Text Analyzer es que permite extraer la felicidad de los textos. Realmente, para ser exactos, más que la felicidad en sí trata de analizar los textos para ver si tienen un sentimiento negativo o positivo. Usando esta API, del mismo modo que se hizo antes con las frases clave, puedo extraer de primera mano el sentimiento de mi corpus. Después lo usaré para ver el sentimiento del usuario cada vez que escriba un mensaje.

Preparados para el análisis…

Como la idea ahora ha sobrepasado la línea de “aplicación”, pues vamos a entrar en el análisis, necesito modificar el proyecto. Ahora he de decidir qué quiero analizar/medir y en base a qué, así que:

  • Voy a analizar el sentimiento de mis usuarios
  • Voy  a analizar cuáles son las palabras más usadas en mis usuarios
  • Voy a establecer una granularidad de tiempo hasta la hora del día.

Para ello necesitaré una estructura que permita estos análisis. Aunque tengo pocos datos, he decidido montar un cubo multidimensional que tiene las siguiente dimensiones:

  • Canal: Para saber de dónde vienen mis usuarios (Telegram, Skype, Facebook, SMS, etc)
  • Conversación: Los mensajes se agrupan en conversaciones (cada ventana de chat que se abre se considera una conversación)
  • Fecha: Fecha de cuándo se envío/respondió el mensaje, con una definición hasta la hora del día.
  • Fuente del mensaje: Para ver si es un mensaje que ha enviado el usuario o ha respondido el bot.
  • Tipo de mensaje: Para ver si es un mensaje o un comando.
  • Usuario: Los distintos usuarios que han usado el bot. Tengo la información que me suministra Bot Framework, que es prácticamente nada (Id y con suerte el nick/nombre) pero suficiente para poder separarlos después durante el análisis.
  • Palabra: Las palabras que se han usado en los mensajes.

Y finalmente, tendré dos tablas de hechos (donde junto las dimensiones con lo que quiero medir):

  • Conversación: Mido la felicidad de la conversaciones, cuántos mensajes tienen, cuántos usuarios participan…
  • Palabras: Mido cuántas palabras hay, cuál es la longitud de cada palabra…

Y para montar este modelo, he usado la aproximación de Kimball para montar un modelo en estrella. La idea detrás de un cubo multidimensional es que relajamos las formas normales típicas de los modelos relacionales. Así pues, en mi tabla de hechos tendré una fila por cada entrada que quiera medir. En el caso de conversaciones, la granularidad llega hasta la frase. Por lo tanto, cada vez que alguien escriba o el bot responda, se insertará una fila en esa tabla. Esta limitado así porque si quiero medir el sentimiento, lo mido en base a una frase completa. La siguiente imagen es un ejemplo del contenido de esta tabla:

Extracto de la tabla de hechos de conversaciones

En el caso de la tabla de Palabras, la granularidad alcanza hasta la palabra en sí. Es decir, si se escribe una frase con 50 palabras, en esa tabla se insertarán 50 filas con cada una de las palabras.

El modelo en estrella propone que cada columna de la tabla de hechos que no sea una medida apunte hacia una fila de una dimensión. De este modo, tenemos la tabla de hechos en el centro “rodeada” de las dimensiones que emplea. De ahí el nombre en estrella:

Extracto del modelo

Para poder montar este modelo, tengo dos retos fundamentales. El primero es que no quiero que la experiencia del bot se vea perjudicada. Ya bastante hace el tener que lanzar peticiones a una API externa por cada mensaje como para que además tenga que calcular e insertar las filas en el cubo. Aprovechando la estructura que tenía (webjobs gratuitos) y el patrón multitenant, monté dos webjobs y una cola:

  • Cada vez que hay una interacción con el bot, se manda la información a una cola de Azure.
  • Un webjob será el encargado de leer los datos de la cola y meterlos en una tabla de Staging dentro del cubo multidimensional. Esta tabla tiene datos casi “en bruto”.
  • Otro webjob se encarga de coger todo lo que hay en Staging y procesarlo para actualizar cada una de las dimensiones y finalmente, rellenar las tablas de hechos con la información suministrada.

Posiblemente la parte de la cola la podría haber ahorrado, pero al tener todo el entorno “bajo mínimo” quería minimizar el impacto en el bot y prefería darle a otro esa responsabilidad.

Ya casi está todo. Veamos un resumen:

  • Tenemos un bot que lee datos de Twitter
  • Al recibir una petición, consulta con L.U.I.S y TextAnalyzer la frase más acorde. Justo antes de mandar la respuesta al usuario, manda la información analítica a una cola
  • Por otro lado, los procesos de la parte analítica se encargan de coger los mensajes y rellenar las dimensiones y las tablas de hechos.

Todos los webjobs se ejecutan cada hora. Son independientes entre sí (pueden caerse los tres y el bot seguiría funcionando sin problema). Los mensajes no se borran de la cola hasta que no se insertan, por lo que si se cae incluso el job de la parte analítica, no perdería esa información. Quedaría ahí hasta que se pueda insertar de forma satisfactoria.

Además, lo tengo montado con Visual Studio Online con builds de integración y despliegue continuo. El despliegue sólo se hace si antes se ha hecho un backup automático de las bases de datos, por si rompo algo que al menos el bot deje de ir pero no pierda mis datos analíticos (que en el fondo es lo que importa en esta vida!)

Ver y entender, esa es la cuestión

De nada sirve que extraiga información, analíticas y luego no se pueda mostrar. Si no la puedo ver, es lo mismo que no tenerla.

Para visualizar los datos he usado PowerBI. Es una herramienta de Microsoft gratutita (la parte online no lo es) que permite generar reportes e informes en base a modelos de diversas fuentes. En mi caso particular, importé el cubo multidimensional en un modelo de PowerBI y le puse un formato correcto (nombre, tipos de datos, relaciones, etc). Esto permite que cualquiera que acceda al modelo pueda consultarlo con las dimensiones que antes he mencionado para extraer las medidas.

Así por ejemplo, se han visto cosas curiosas. He creado varios reportes para poder visualizar la información. El primero de ellos es una vista general:

generalreport

Podemos ver la cantidad de usuarios que han interactuado, cantidad de conversaciones, felicidad media, mensajes, palabras y canal empleado. Lo bueno de PowerBI es que puedo hacer click sobre una parte del “circulito” de canal o fecha y me filtra todos los datos.

Sin embargo, la parte interesante comienza cuando creo el reporte de felicidad por usuario. Se percibe que los usuarios tienen un sentimiento positivo por la tarde. Durante la mañana están menos positivos, hasta que llega la tarde. A partir de las 20-21h (los datos son de verano) el sentimiento comienza a decrecer:

happinessreport

La gráfica superior izquierda muestra el sentimiento del usuario seleccionado por día y la cantidad de mensajes escritos. La de la parte superior derecha sirve para mostrar la relación entre el tamaño de los mensajes y el sentimiento. Los mensajes más cortos tienen un sentimiento más positivo que los mensajes más largos. Y la gráfica inferior, muestra el sentimiento por horas. Pueden variar los picos por usuario, pero la tendencia es la misma para todos mis usuarios.

El siguiente reporte que monté fue para analizar el resultado de las conversaciones. Si como usuario escribes un texto positivo y el bot te responde mal, tu siguiente respuesta será menos positiva. De un modo u otro, tu respuesta está condicionada por lo que te digan. Esto me resultó muy curioso porque no me lo imaginaba, ya que el contenido del bot era de tono humorístico y no esperaba que pudiese afectar a los usuarios. Sin embargo con los datos que tengo no es así. Es decir, que podría  manipular el tono de una conversación sin que el usuario se perciba de ello. Véase la siguiente gráfica:

conversationeffectreport

Se aprecia que una “respuesta” por parte del bot condiciona tu siguiente petición, ajustando tu sentimiento de forma inconsciente.

Conclusión

Hemos visto como podemos montar un bot y analizar cómo son los usuarios que tenemos. Que cada cual saque las aplicaciones que esto tiene y que se aplican hoy día 🙂

Si quieres más información, he dejado en mi GitHub una versión del bot lista para desplegar y probar. Sólo necesitarás:

  • Tener cuenta de desarrollador en Twitter.
  • Tener una suscripción de Azure.
  • Darlo de alta en Microsoft Bot Framework.
  • Dar de alta el servicio de L.U.I.S. en Cognitive Services.

Con estos datos, rellena los campos en la plantilla parametrizada de ARM. Luego la despliegas y ya lo tienes todo. No debes generar nada, los webjobs se encargarán cuando se ejecuten de generar y rellenar las bases de datos con lo que necesiten para funcionar. Pero por supuesto, hasta que no haya datos de Twitter, el bot no podrá responder nada.

Happy coding!

 

[C#] Efectos de AsParallel()

Durante la pasada DotNetConference  y después de la charla sobre Async Best Practices de Lluis Franco vino Rodrigo con una serie de curiosidades de los efectos de AsParallel() sobre una colección. Y de esas curiosidades, nace este post.

El ejemplo que comentamos es sencillo. Se trata de calcular cuántos números primos hay de 1 hasta N. Para ello he usado una función sencilla para calcular si un número es primo o no:


static bool IsPrime(int candidate)
{
	if ((candidate & 1) == 0)
	{
		if (candidate == 2)
		{
			return true;
		}
		else
		{
			return false;
		}
	}
	for (int i = 3; (i * i) <= candidate; i += 2)
	{
		if ((candidate % i) == 0)
		{
			return false;
		}
	}
	return candidate != 1;
}

Y he creado una lista de enteros hasta N:


var list = Enumerable.Range(1, 10000000);

Quedando la consulta sobre LINQ de la siguiente manera:


var onlyPrimes = list.Where(i => IsPrime(i))
		     .Count();

Evidentemente esto tiene un coste. Para pocos elementos es prácticamente despreciable, pero si generamos una cierta cantidad de elementos a partir de 1 millón vemos como este se dispara:

image

Una solución para prevenir este tipo de casos es usar AsParallel(). Este método permite distribuir el contenido de un IEnumerable para que se habilite el procesamiento en paralelo. Es decir, cambiando la sentencia LINQ original por esta obtendremos del tiempo actual de 7,3 segundos a 2,5 para el último caso:

var onlyPrimesParallel = list
	.AsParallel()
	.Where(i => IsPrime(i))
	.Count();

Y juntando los dos tiempos, la gráfica quedaría tal que:

image

¿Qué efectos produce AsParallel?

Para verlo, usaremos una extensión de Visual Studio que se llama Concurrency Visualizer. Es parecido a los ya conocidos reportes de rendimiento, diagnósticos y análisis sólo que está destinado a mostrar cómo se distribuyen los hilos sobre la memoria y CPU de la máquina. Podemos asociarlo al proyecto en ejecución o asignarle cualquier programa que esté en ejecución.

En el primer caso, que es secuencial, esperamos ver que el hilo de la aplicación se ejecuta en mayor medida sobre uno de los núcleos de nuestro sistema:

image

Mientras que si vemos el resultado de la opción en ejecución en paralelo obtenemos lo siguiente:

image

Se puede apreciar fácilmente que hay mucha carga y más procesos que están distribuidos a lo largo de los núcleos.

¿Debemos usar AsParallel?

Bien, antes de responder la pregunta hay que aclarar una serie de puntos. AsParallel no es gratuito. La paralelización no es gratuita. Es costosa. Y Mucho. Para una CPU que no paralaleliza no hay mayor problema, porque entra un proceso y este ocupará el tiempo de la CPU sin importar nada más. Cuando termine devolverá su trabajo y la CPU podrá seguir con otra cosa. Cuando se paraleliza, al coste de las operaciones que se estén haciendo, hay que añadirle el peaje de paralelización. Este peaje es el coste que tiene para el núcleo y el sistema sincronizarse con sus semejantes. Veamos más información del uso de los núcleos en el caso paralelo:

image

El cambio de contexto es el “peaje” que comenté anteriormente. Supone el proceso que se debe llevar para que los núcleos de la CPU se sincronicen para ejecutar la tarea de forma paralela. Esto implica que en cada cambio de contexto, los registros de los procesadores son guardados y cargados, el kernel del sistema operativo se ejecutará, la TLB (Translation Lookaside Buffer) se recargará y las etapas de instrucciones del procesador finalizará. Es decir, se fuerza prácticamente a que ejecute nuevo código pero con registros ya cargados para que continúe por donde lo dejó la otra etapa paralela. Y le podemos añadir por si fuera poco, que puede ser todavía más costoso para el núcleo si la caché no es válida para el hilo actual. El escenario ideal sería que se paralelice en la justa medida para que todos los núcleos puedan reusar la caché que ya tienen.

Aparecen tres métricas en el reporte:

  • Cross Core Context Switches:  Número de veces que un hilo ha cambiado de un núcleo lógico a otro.
  • Total Context Switches: Número de veces que se cambia el contexto (de ejecución a sincronización, etc).
  • Percent of Content Switches: Las dos métricas anteriores divididas (CrossCore / Total Context). Cuando más alto sea el porcentaje, mayor es la actividad del núcleo y del hilo en particular.

Ahora ya podemos responder a la pregunta. Sabemos que es costoso, porque implica un coste asociado por hilo al paralelizar. Así que:

¿Debemos usarlo? Depende. Hay que medir qué aporta y que no aporta. En la segunda gráfica se aprecia que el coste es algo superior al secuencial, pero a medida que crece la lista el coste del paralelo tiene una pendiente con menor inclinación que la secuencial. Otro punto es ver qué hacemos con la paralelización. Si el resultado implica recoger los resultados del proceso, será más caro –como es el ejemplo actual- Si sólo queremos aplicar una operación (cómo hacer una petición HTTP) será más barato. En cualquier caso, hay que medir y probar.

¿Puedo colocar AsParallel donde quiera? Sí, por supuesto. Pero evidentemente eso no significa que funcione como crees que debería funcionar. En la sentencia LINQ actual, AsParallel() está justo antes del WHERE() porque lo que queremos es que distribuya los N elementos de la lista en los núcleos de la CPU y que una vez distribuidos, ejecute el WHERE(). Esto ha sido porque hemos asumido que lo que ejecuta el WHERE() es atómico y costoso. Si por el contrario colocamos AsParallel() después, provocaremos esto:

image

Sí, efectivamente usa todos los núcleos disponibles. También tarda casi más de 3 segundos que la forma secuencial. Lo que hemos provocado aquí es:

  • La ejecución de calcular un número primo es puramente secuencial. Es decir, tenemos una lista de N elementos y para cada uno de ellos vamos a determinar es primo o no. Puramente secuencial.
  • Cuando tengamos todo el resultado, que habrá devuelvo una cantidad X<N de elementos (aquellos que sean primos) aplicamos el AsParallel(). Y la siguiente operación es un COUNT() de forma paralela, por lo que el sistema va a distribuir los X elementos entre los núcleos para poder contarlos y finalmente sumarlos para obtener el resultado final. ¿Es paralelo? Por supuesto, pero de forma incorrecta porque hemos hecho el cálculo de los primos en secuencial y le hemos añadido el sobrecoste de la paralelización para obtener el COUNT. Y además se puede apreciar como el número de cambios de contexto es bastante mayor que la versión paralela correcta, por lo que en lugar de mejorar el rendimiento, lo hemos empeorado con creces respecto a la versión secuencial.

BONUS: DNX

Probando el código de AsParallel() con DNX 4.5.1 (el DNX Core que tengo instalado no soporta AsParallel por el momento) he obtenido un tiempo máximo de 2,2 segundos (0,3 menor que la media –que no máximo-) del ejemplo anterior. Cuanto menos curioso…

Puedes encontrar el código que he usado en GitHub

[Evento] DotNetConference 2016: Un breve y personal resumen

Siguiendo la estela del año pasado, este año hemos tenido una nueva edición de la DotNetConference. Es un evento organizado por Microsoft y orientado para todas las comunidades técnicas y desarrolladores que trabajamos con tecnologías Microsoft. Se volvió a batir la afluencia: 1700 personas asistieron de forma presencial y otras 5000 de forma online siguiendo la retransmisión vía streaming.

Tal vez la primera gran diferencia respecto al año pasado es que sólo ha durado un día. En la pasada edición la duración se extendió a dos días, por lo que hubo más espacio y más tiempo entre las charlas. Este año todas las charlas han estado comprimidas en el mismo día, lo cual ha sido un poco caótico porque había que elegir entre ir a la charla o hacer algo de networking. Sin embargo, hay otra pequeña diferencia que sí hemos tenido este año:

Sí señor, Satya Nadella (CEO de Microsoft) pudo asistir al evento e impartir una keynote. Un gran salto de calidad y potencia al evento, porque no todos pueden presumir de tener como ponentes al CEO de una de las mayores empresas de software del mundo. Aquí se ha demostrado el gran trabajo de organización efectuado por el DX Team de Microsoft Ibérica. Supongo que esta noche podrán dormir tranquilos y descansar, porque desde luego se lo tienen merecido después de todo el esfuerzo que han hecho.

Respecto al contenido del evento, otro año más no he asistido a la primera keynote. Tampoco he podido asistir a todas las charlas que me gustaría. Desgraciadamente muchas coincidían en la misma hora pero en distinta sala, así que la elección no fue fácil.

  • Some dirty, quick and well-known tricks to hack your bad .NET WebApps: Chema Alonso impartió esta divertida y práctica charla sobre pequeñas cosas a tener en cuenta a la hora de desarrollar una aplicación web. Nos demostró, con ejemplos reales y en directo que una mala configuración de servidor (tan simple como dejar los valores por defecto) puede suponer que un atacante que tenga el suficiente tiempo libre nos dé alguna sorpresa. Chema terminó la charla con una frase lapidaria: “Si no revisas los detalles de tu aplicación web no te preocupes, alguien lo hará”
  • Entity Framework Core 1.0: Unai Zorilla nos vuelve a hacer una demostración que es posible hacer una charla picando código y sin necesidad de IntelliSense. Explicó la motivación que hay detrás de Entity Framework 7 Entity Framework Core 1.0, tanto para aplicaciones Full .NET como para DotNet Core. Hizo hincapié en algunas de las nuevas características que incluye EF 1.0 (que ahora mismo está en RC1) muy interesantes y potentes. Aunque en mi opinión, una gran potencia conlleva una gran responsabilidad. Como último apunte, EF Core 1.0 ha sido reescrito desde cero y no soporta todavía toda la funcionalidad que tiene EF6. Así que entre esos motivos y que todavía está en RC1, no se recomienda su uso en sistemas de producción.

  • El arte del logging en el Cloud: Luis Guerrero vuelve a marcar una charla espectacular. Contenido y explicación directa. A través de una aplicación que analiza los problemas que puedan aparecer en diversos sitios webs, emplea AppInsights para analizar y obtener la información de lo que va ocurriendo. Porque como él mismo dijo, está muy bien tener un sistema de logging. Pero es mucho más importante tener la capacidad de explotarlo para poder analizar y saber lo que ocurre. Tener un sistema de logging y no poder leerlo o directamente no leerlo, es lo mismo que no tenerlo.
  • Wearables con C# y .NET: Microsoft Band 2, Apple Watch y Google Wear. Josué Yerai nos explica las diferencias entre Microsoft Band, el Apple Watch y cualquier Android Wear. Algo muy curioso porque todas tienen su propio SDK y aunque parezca sorprendente, la única que dispone de SDK para todas las plataformas es Microsoft Band. A través de un ejemplo mostró cómo funciona realmente las aplicaciones de los wearables y qué podemos hacer con cada uno de ellos.

Mención especial (y no por temas corporativos) me gustaría hacer a mis compañeros de Plain Concepts. Organizaron un concurso a través de un cuestionario de cinco “simples” preguntas de programación en C# que causó furor. No creo que sea por el premio (una Microsoft Band 2 al que acertase las cinco preguntas) sino por el hecho de que es algo que motiva y “pica” para poder investigar y obtener la respuesta correcta. La afluencia al stand de gente rellenando el cuestionario y entregándolo fue constante a lo largo del día, por lo cual considero que fue todo un éxito por la iniciativa de todos los implicados.

Por último me gustaría volver a agradecer a todos la organización, ponentes y patrocinadores la realización del evento. No es sencillo montar un evento de este tipo ni para tanta gente. Y en el fondo, lo importante es que es un evento de comunidad. Una comunidad que está viva y muy activa. Y es algo que en mi opinión, motiva bastante para poder seguir investigando y aprendiendo nuevas tecnologías. Así que: ¡enhorabuena!

[Deploy] Trasformaciones de config (II): App.config

Siguiendo con la temática del artículo anterior en el que se mostró cómo crear configuraciones y como aplicar los valores en el web.config para cada una de ellas, en esta ocasión dedicaré el asunto a App.config

¿Por qué lo visto anteriormente no es válido? Técnicamente es válido, pero el problema es que por defecto los app.config no están pensados para soportar transformaciones y los proyectos en los que se encuentran, tradicionalmente de consola o aplicaciones de escritorio, no tienen en cuenta este detalle.

Si se crea un proyecto de consola o aplicación de escritorio en general se aprecia que el app.config no dispone de versión para Debug y Release, pese a que dichas configuraciones sí existen en el proyecto o solución. Por lo tanto, lo primero que se debe hacer es habilitar tantos app.config como configuraciones se disponen.

Como resumen breve, en el post anterior se comenta lo siguiente:

  • Por un lado, las distintas versiones de web.config son en realidad desde el punto de vista del proyecto ficheros dependientes de otro.
  • Por otro lado, para poder habilitar las transformaciones es necesario añadir el import correspondiente.

Crear los app.config

Respecto al primer punto la principal diferencia es que debemos crear los ficheros de forma manual, puesto que no tenemos menú disponible para ello. Así pues, si se dispone de la misma configuración que el artículo anterior de cuatro entornos (dev, qa, pre y pro) se creará en primer lugar los app.config equivalentes:

Además cada fichero de configuración se ha creado con una estructura básica por defecto que deberemos preparar. Mientras que la parte original permanece así:

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

</configuration>

Se debe añadir el namespace de transformaciones:

<?xml version="1.0" encoding="utf-8" ?>

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">

</configuration>

Por lo tanto la estructura de ficheros que queda en el proyecto sería algo muy parecido a esto:

image

Para que los ficheros pemanezcan jerarquizados en base al App.config original, se debe modificar el csproj indicando que el resto de ficheros que hemos creado por entorno tienen son dependientes del App.config “padre”:

<ItemGroup>

  <None Include="App.config" />

  <None Include="App.Debug.config">

    <DependentUpon>App.config</DependentUpon>

  </None>

  <None Include="App.PRE.config">

    <DependentUpon>App.config</DependentUpon>

  </None>

  <None Include="App.QA.config">

    <DependentUpon>App.config</DependentUpon>

  </None>

  <None Include="App.Release.config">

    <DependentUpon>App.config</DependentUpon>

  </None>

</ItemGroup>

De este modo ya se dispone de un app.config jerárquico al igual que ocurre en un proyecto web:

image

Trasformación de app.config

Ahora se debe importar la capacidad de transformar el XML. Fácilmente se puede lograr añadiendo la siguiente línea en el csproj:

<Import Project="$(MSBuildExtensionsPath)MicrosoftVisualStudiov12.0WebMicrosoft.Web.Publishing.targets" />

Ahora podemos modificar el web.config y añadir algo en función del entorno. A modo de ejemplo mantengo el del artículo anterior, por lo que el web.config original quedaría de la siguiente forma:

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

  <appSettings>

    <add key="environmentName" value="none"/>

  </appSettings>

    <startup> 

        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />

    </startup>

</configuration>

Y el de QA así:

<?xml version="1.0" encoding="utf-8" ?>

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">

  <appSettings>

    <add key="environmentName" value="QA"

         xdt:Transform="Replace"

         xdt:Locator="Match(key)"/>

  </appSettings>

</configuration>

Si compilamos en QA, se aprecia que no ha aplicado ningún tipo de transfomación y mantiene el web.config original. Aquí tenemos dos problemas:

  • El proyecto de consola no entiende de transformaciones, somos nosotros quienes las estamos introduciendo casi “a calzador”.
  • No disponemos de publicación condicionada. En un proyecto web se puede establecer multitud de parámetros que se tienen en cuenta a la hora de publicar, entro ellos la configuración del entorno. Aquí directamente se ofrece la posiblidad de publicar con la única opción de indicar la ruta donde se copiará el resultado de la compilación que en esta opción será siempre Release.

Por lo tanto, lo que se debe hacer es habilitar que tenga en cuenta en la compilación el resto de entornos personalizados que se han ido creando. Para ello se necesita añadir al csproj la siguiente opción post-compilación:

<Target Name="AfterBuild">

  <TransformXml 

    Source="@(AppConfigWithTargetPath)" 

    Transform="App.$(ConfigurationName).config" 

    Destination="@(AppConfigWithTargetPath->'$(OutDir)%(TargetPath)')" />

</Target>

Se añade TransformXML gracias al soporte del Import de Web.Transformation y se transformará todo fichero App.{Entorno}.config. La variable $(ConfigurationName) es una macro predefinida de msbuild que indica cuál es el nombre de la configuración que actualmente está seleccionada. Como se ha seguido el patrón de generar la configuración por entorno (app.debug.config, app.qa.config, etc) podrá encontrar el fichero y de este modo aplicará la transformación. Es decir, gracias a esta tarea de post-compilación se comprobará el efecto de la transformación al compilar el proyecto independientemente o no de la publicación del mismo, justo al contrario que en un proyecto web. Finalmente el app.config para QA quedaría del siguiente modo:

<?xml version="1.0" encoding="utf-8"?>

<configuration>

  <appSettings>

    <add key="environmentName" value="QA"/>

  </appSettings>

    <startup> 

        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />

    </startup>

</configuration>

 

Opciones de terceros

Existen estas opciones en lugar de aplicar el DIY:

  • Configuration Transform: Es una extensión de VS(2010,2012,2013 por ahora) que añade la funcionalidad que aquí se ha aplicado de forma manual.
  • SlowCheetah: No he llegado a usarlo pero parece ser que está descontinuado. Hace un año o dos era una buena solución para la gestión de XML en configuraciones.

Fuente:

Basado en esta respuesta de Stackoverflow y experiencias varias

[Deploy] Transformaciones de .config (I): Web.config

Cuando se desarrolla una aplicación sea del tipo que sea, siempre se incluye un fichero .config donde aparecen datos de la configuración (settings, cadenas de conexión, configuración de dlls, versiones específicas de dlls, etc). Si estamos en una aplicación web tendremos un fichero web.config y si estamos en otro tipo de aplicación se llamará app.config.

Creando entornos

Normalmente en web se trabajan con múltiples entornos al desplegar. Podemos disponer de un entorno de desarrollo, de un entorno de QA/test, de un entorno de preproducción y finalmente de un entorno de producción. Normalmente a cada entorno se corresponde una configuración de despliegue distinta, porque tendrán settings o cadenas de conexión distintas.

Cuando creamos un proyecto web, automáticamente nos genera un web.config. Sin embargo podemos apreciar que nos genera dos ficheros: uno para Debug y otro para Release:

image

Lo cual en el csproj se traduce de la siguiene forma:

<Content Include="Web.Debug.config">

  <DependentUpon>Web.config</DependentUpon>

</Content>

<Content Include="Web.Release.config">

  <DependentUpon>Web.config</DependentUpon>

</Content>

Y para generar esos ficheros, la plantilla de VS se basa en la configuración de la solución que exista. Por defecto se crean dos configuraciones: debug y release para cada uno de los proyectos integrados en la solución:

image

Lo cual nos permite crear rápidamente tantas configuraciones como necesitemos. Además, podemos crearlas basándonas en otras ya existentes:

image

Y una vez creadas, nos situamos encima de Web.Config y ya Visual Studio nos sugiere que podemos añadir nuevas configuraciones:

image

image

A partir de este momento, cuando se publique la aplicación se aplicará la transformación de web.config a la configuración seleccionada. Es decir, los efectos del web.config sólo tienen validez una vez se ha desplegado la aplicación, por lo que no es posible probarlo si desde el propio Visual Studio cambiamos la configuración al ejecutar/probar.

Transformaciones

Si disponemos de un web.config base,compilamos y desplegamos con una solución determinada (Debug, Release o cualquier otra que se haya creado de forma previa) este se transformará. ¿Cómo? Si se presta atención al csproject del proyecto, se importa una extensión de msbuild:

<Import

Project="$(MSBuildExtensionsPath32)MicrosoftVisualStudiov10.0WebApplicationsMicrosoft.WebApplication.targets" 

Condition="false" />

 

Esta extensión permite que después de la compilación, se aplique el web.config original y después se apliquen las transformaciones que se hubieran definido en la configuración específica de cada entorno.

Pero ¿qué es una transformación? Es una alteración del web.config base para insertar, eliminar o modificar parte de la estructura del XML. Véase en detalle una configuración de Release por ejemplo:

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">

  <system.web>

    <compilation xdt:Transform="RemoveAttributes(debug)" />

  </system.web>

</configuration>

Véase el uso del namespace de XML-Document-Transform. Este habilitará las instrucciones necesarias para las trasformaciones. Las más comunes suelen ser las relativas a la substitución de un atributo XML, añadir un nuevo nodo o reemplazarlo por otro. Los atributos que se usarán serán los siguientes:

  • Transform: Indica la naturaleza de la operación: Replace, Insert, InsertIfNotExists, etc.
  • Locator: Si necesitamos localizar el nodo para una sustitución, se indicará aquí el nombre del atributo que se usará a tal efecto.

Por ejemplo, si se tiene una sección de appsettings en el web.config base tal que:

<appSettings>

  <add key="environmentName" value="none" />

</appSettings>

En un hipotético web.QA.config sería del siguiente modo:

<appSettings>

  <add key="environmentName" value="QA" 

    xdt:Transform="Replace" 

    xdt:Locator="Match(key)" />

</appSettings>

En el cual indicamos en Transform que se desea aplicar un Replace en aquel nodo cuyo atributo Key sea “environmentName”. Así al publicar la aplicación en con la configuración de QA, se aplicarará esa transformación.

También se puede reemplazar un nodo completo, en cuyo caso no se indicaría el atributo locator. Mientras que en un web.config base no aparece este nodo, podemos insertarlo en cuaquier transformación:

<system.webServer >

    <applicationInitialization xdt:Transform="Insert" >

      <add initializationPage="/Static/QAAdvice"/>

    </applicationInitialization>

</system.webServer>

O incluso insertar un nodo completo:

...

<system.diagnostics xdt:Transform="Insert">

  <trace>

    <listeners>

      <add type="Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitorTraceListener, Microsoft.WindowsAzure.Diagnostics, Version=2.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" name="AzureDiagnostics">

        <filter type="System.Diagnostics.EventTypeFilter" initializeData="Warning"/>

      </add>

    </listeners>

  </trace>

</system.diagnostics>

configuration>

De este modo se puede configurar cada entorno y configuración y que se apliquen los cambios correspondientes.

Si quiere conocer más a fondo las transformaciones de XML para proyectos web, consulte este enlace.

[Evento] Freakend 2015

El fin de semana pasado tuvo lugar el Freakend 2015. Es una reunión en una casa rural en la que nos juntamos diferentes personas pero con un mismo objetivo: nuestra pasión por los videojuegos.

FREAKEND_02_200

48 “frikis” de los videojuegos entre los que contaban desarrolladores, artistas, productores y aficionados como un servidor entre los que su dedicación profesional no son los videojuegos en estos momentos. La idea como siempre es organizar charlas en base a la experiencia de cada uno. Todo el fin de semana estuvo repleto de charlas muy interesantes y de diversos ámbitos: desde la experiencia de hacer un kickstarter hasta cómo programar juegos con C# y GameBoy. Tanto era el interés que cada uno de nosotros venía de una ciudad distinta o incluso de algún país extranjero para poder asistir.

Tuvimos con la presencia de lo que para mí son grandes leyendas del desarrollo de videojuegos en España, como José Rauly de Mercury Stream o Unai Landa (Digital Legends y ex-Pyro), Sergi Vargas, Jesús Martínez y una lista de enorme calidad en la que me demostró de nuevo lo mucho, muchísimo que tengo que aprender todavía. También se apuntó a la fiesta David Bonilla y nos contó de primera mano su experiencia con Otogami, el famoso buscador de juegos online que te encuentra siempre el mejor precio.

¿Lo mejor del evento? Sin duda alguna, la gente y el hecho de poder compartir experiencias. Si hay algo que adore de este tipo de eventos es que me permite salir de la burbuja de mi trabajo diario y conocer profesionales de otras tecnologías y ámbitos que nos permiten intercambiar conocimientos y opiniones. Se nota y se percibe cuando hay comunidad, pasión y ganas por hacer las cosas bien hechas. Y pese a ser muchos de distintos ámbitos, tuve esa fantástica sensación. Después de cada charla hubo numerosos debates comentando aspectos de la misma y relacionados, con lo que se pudieron compartir numerosas opiniones muy valiosas. También como ocurre en estos casos, al acabar las charlas del día nos juntamos varios en corillos comentando cualquier cosa: desde el estado de la industria, aspectos profesionales, técnicos o directamente qué juegos nos han gustado más.

Como punto original, tuvimos a los GameYourHear que además de asistir al evento nos deleitaron a las 3 de la mañana con unas cuantas canciones en riguroso directo de videojuegos.

WP_20150307_02_50_29_Pro

Y como siempre estuvimos bien alimentados en base a lo que necesita el cuerpo:

WP_20150307_21_57_49_ProWP_20150306_20_42_00_Pro

Por último (y no menos importante) quiero agradecer a Diego y Jorge todo su esfuerzo y empeño para lograr coordinar tanta gente para un evento que ha salido redondo. Sé que han terminado muy cansados y agotados, con toda la razón del mundo. Pero no me cansaré de repetirlo: GRACIAS por hacer cosas como estas.

P.D: Como suele ser habitual, tuvimos algún invitado especial que no se perdió el evento:

WP_20150308_14_32_18_Pro

[Evento] DotNetConference 2015

El pasado fin de semana tuvo lugar en la universidad de Alcalá de Henares la DotNet Spain Conference 2015. Fue un evento espectacular y que se echaba en falta.

Desgraciadamente no pude asistir a la keynote ni a las primeras charlas, así que voy a comentar sobre lo que sí tuve ocasión. También desgraciadamente no me pude desdoblar para asistir a más de una charla o taller a la vez, porque había varías a la misma hora y en distintos tracks muy interesantes. Vaya por delante mi agradecimiento a todas las personas implicadas en la organización de tal magno evento en el que hubo 1000 asistentes y otros tantos virtuales siguiendo las charlas y talleres de forma online. Se echaba en falta un evento donde la comunidad .NET podamos juntarnos y compartir nuestras experiencias, ya que si por alguien lo dudada existimos Sonrisa

Tengo mi aplicación en el cloud y no escala: ¿Qué hago? En esta charla Quique Martínez nos cuenta experiencias reales de clientes que han querido llevar sus aplicaciones on-premise a la nube y los problemas que han tenido. Muy útil para conocer las implicaciones de una decisión que no se debe tomar a la ligera.

Complex Event Processing con Event Store: Marçal Serrate nos cuenta cómo modelar y usar un EventStore de una forma muy curiosa. Mostró un ejemplo usando la api de GitHub, en el que extrajo estadísticas de los lenguajes más usados en función de los commits. Después usó Event Store para analizar los comentarios y mostrar estadísticas de en qué lenguajes se incluyen más palabras mal sonantes. Muy curioso.

Sube tu Universal App al Cloud: Aquí asistí a medias, pero Adrían Fernández nos cuenta cómo desarrollar una universal app de ingredientes de hamburguesas y subir la api correspondiente a Azure.

MongoDB en Azure para programadores de .NET: Aquí Francesc Jaumot nos cuenta una introducción sobre qué es Mongo Db y cómo integrarlo dentro de una aplicación .NET.

Async best practices: Lluis Franco y Alex Casquete explican en profundidad la evolución de asincronía en todas las versiones del framework: desde eventos y delegados hasta el async/await. Estuvo bastante bien porque contaron algunos temas sobre la parte interna de la gestión de tareas dentro del framework.

Code Smells: Fernando Escolar nos ilustra sobre aquellos fragmentos de código que nos persiguen durante la noche y lo más profundos de los sueños cual orcos buscando oro y plata. Código que todos hemos escrito alguna vez y no está bien y cómo corregirlo haciendo que quede más elegante y funcional.

Por qué deberías plantearte F# para tu próximo proyecto: Una charla introductoria sobre F# y lenguajes funcionales que impartió Alex Casquete. Promete, porque ya hay una comunidad creciente de usuarios en F# en Madrid y Barcelona que en un futuro no muy lejano nos contarán cosas muy molonas…

Let’s Fight !!! Arduino vs Netduino vs .Net Gadgeteer vs Galileo: La verdad es que asistir a un evento de este calibre y no asistir a una charla de Bruno debería ser motivo de castigo. Nos explicó las múltiples alternativas de IoT, tanto de .NET como de C y las comparó en base a su experiencia. Muy interesante porque es un mundo que apasiona a la vez que asusta.

Gestión masiva de datos en la era IoT: Cuando empezamos a capturar datos con nuestros sensores de IoT, ¿qué hacemos con ello? Luis Guerrero nos propone usar Azure Stream Analytics con una charla interactiva en la que mediante Twitter fuimos viendo el resultado y análisis de los Twitts que cada uno de nosotros mandó.

Algunas que me hubiera gustado ir son CloudFirst, Visual Studio Online, Effective C#, el taller de Machine Learning, Release Management, Galileo y el IoT, Cloud Hardcore Debugging… Lo interesante de este tipo de eventos es que permite explorar áreas y conocimientos que en tu día a día es prácticamente imposible que alcances. Ya de por sí es interesante conocer áreas nuevas, pero todavía más es encontrarme con gente de la comunidad, antiguos y nuevos MSP’s e incluso algún amigo y compañero de clase en la universidad.

Por último, de nuevo me gustaría agradecer a TODA la organización su esfuerzo y dedicación para lograr un evento muy interesante y que me ha traído a la memoria aquellos eventos grandes de hace años donde todos nos reuníamos. Espero que el año que viene podamos disfrutar de una nueva edición. Hasta entonces, simplemente ¡mil gracias!

[Reflexión] Rover Curiosity y pruebas

Hace ya varios años que tomé la decisión de descartar cualquier temática no técnica para los blogs y dejar a un lado las ideas o pensamientos que pudiera tener, inclusive aquellos relacionados con las propias materias técnicas. Sin embargo, hay ocasiones que a veces la parte técnica no es mostrar qué fragmento de código, qué framework o qué tecnología he usado para resolver un problema. Una idea, un matiz o una sugerencia pueden dar con la pista y abrir un nuevo mundo de posibilidades.

Ayer en La 2 (sí, veo La 2, ¿qué pasa?) emitieron un documental sobre la construcción del Rover Curiosity que desde el punto de vista ingenieril es muy pero que muy interesante:

Considero que la formación recibida como ingeniero informático ha sido satisfactoria; aunque como todo en la vida, puede ser mejorable en algún campo más que en otro. Recuerdo todavía casi como si fuera ayer, las clases en las que nos enseñaban metodologías de gestión de proyecto y un poquito de tareas más o menos relacionadas pero sin entrar en profundizar (algo de pruebas, algo de gestión de personal, algo de estimación, algo de arquitectura, etc) lo cual me debería llevar a tener una cierta visión del Mundo Real para poder sobrevivir en él.

Sin embargo, tengo muchísimas lagunas, de las que poco a poco (bien aprendiendo despacito o generalmente mediante palos) voy consiguiendo rellenar. Hoy voy a hablar de pruebas. En el documental podrás ver algunas de las pruebas a las que someten al Rover Curiosity y a algunos de sus elementos. En la carrera me enseñaron poco de pruebas y es algo que debería ser desde mi punto de vista algo fundamental en cualquier ingeniería.

P & P: Hacer Pruebas es de Pobres

Probar debería ser lo más normal del mundo. Metodologías como TDD y BDD se centran precisamente en probar lo que hace desde distintos puntos de vista. ¿Quién no ha oído hablar de TDD? ¿Quién no ha visto una charla/evento sobre TDD? ¿Quién aplica TDD? Ah amigo…

La ingeniería del software es una (¿ingeniería?) muy reciente. Los fundamentos matemáticos de la computación moderna (sin desmerecer a pioneros como Ada, Pascal, Baggage e incluso el misterioso mecanismo de Anticitera) nace a partir de 1920-30, de la mano de científicos como Von Neumann o Turing. A partir de ahí, el ritmo de desarrollo de la ciencia de la computación va cobrando un camino vertiginoso hasta nuestros días en los cuales la tecnología avanza a un ritmo que difícilmente podemos seguir. Nos hemos acostumbrado a servicios como Facebook, Youtube, Gmail/Hotmail, la nube… todo el mundo los conoce y parece que siempre han estado ahí. Parece, porque realmente alguno ni siquiera tiene 10 años.

Este rápido avance provoca que las disciplinas tengan que adaptarse casi a paso cambiado. Otras ingenierías como la civil, industrial o química llevan décadas, siglos e incluso milenios de desarrollo con lo cual sus procesos están asentados, depurados y aceptados. Nosotros todavía estamos discutiendo qué metodología es mejor o más adecuada porque, sencillamente es un mundo cambiante al cual tenemos que adaptarnos y proponer constantemente alternativas y soluciones para poder sobrevivir.

Todo lo que usamos a diario ha sido sometido a multitud de pruebas. Desde nuestra casa, el coche (con sus famosas pruebas de airbag), carreteras, puentes… Hasta el teclado sobre el que escribo este texto ha sido sometido a durísimas pruebas para estimar cuánto tiempo debe durar y proporcionar un funcionamiento correcto bajo una serie de condiciones.

¿Ocurre lo mismo con el software? Desgraciadamente, no. Las excusas son variopintas y todos, YO el primero, las hemos pronunciado alguna vez:

  • No tengo tiempo para hacer pruebas.
  • Hacer pruebas no sirve para nada.
  • No sé cómo probar esto.
  • Paso de hacer pruebas; esto funciona de lujo.
  • Hay pruebas, pero no funcionan.
  • etc…

http://thecodinglove.com/post/80285273617/when-i-dont-have-time-for-writing-tests

Justificadas o injustificadas, son excusas. Y son excusas porque hay solución a cada una de ellas:

  • “No hay tiempo para hacer pruebas” : El 95% de los clientes van a preferir una entrega que les funcione en un alto porcentaje de la funcionalidad contratada antes que una bomba nuclear inestable en sus manos.
  • “No sirve para nada” : Respirar tampoco, fíjate.
  • “No sé como probar esto”: A esta la respondo más abajo.
  • “Paso de hacer pruebas; esto funciona de lujo” : Explotará. Y lo sabes. ¿El motivo? Salvo sorpresa, somos humanos, no máquinas autómatas y cometemos errores. Es normal, cotidiano y predecible. Pero podemos intentar que sea menos cotidiano cada vez.
  • “Hay pruebas, pero no funcionan” : Simple. Si no sirven, se quitan. Y si no, se adaptan. Respondo más abajo también.

Podemos agrupar las excusas en tres tipos:

  • Timing: No se puede dedicar tiempo pese al riesgo que conlleva. Y si conocemos y aceptamos los riesgos, adelante.
  • Pereza: Poco podemos hacer aquí…
  • Conocimientos: Esta es la buena y la más importante.

TDD es una metodología con una curva de aprendizaje inicial muy dura, porque exige un cambio de paradigma mental a algo que no estamos acostumbrados. Normalmente estamos (mal) acostumbrados a programar, arrancar y probar. Si falla, vemos qué falla y vamos a corregirlo hasta que quede listo. Sin embargo no hace falta aplicar TDD/BDD/{ponga usted su favorita} para hacer pruebas. ¿Cuánto tiempo se pierde en parar, cambiar y arrancar? ¿Cuántos pasos hay que dar para volver a ejecutar la prueba? ¡Automaticemos todo esto en la medida de posible!

Probar antes ejecutar la aplicación te puede ayudar con las siguientes capacidades:

  • Adquirir un mayor conocimiento de lo que estás haciendo.
  • Te permite centrarte en una sola cosa y descartar el resto de tareas relacionadas.
  • Te proporciona una visión muy clara de la funcionalidad.
  • Averiguar, probar y acotar comportamientos que no estaban contemplados en un principio.
  • Mejorar el código descubriendo las dependencias, acoplamientos, etc que estaban ocultos.
  • Descubrir funciones o métodos que deberían/no deberían existir
  • etc…

Precisamente una de las bazas de hacer pruebas es el refactor una vez se ha probado para que cada vez el código sea más claro, limpio y conciso respetando la funcionalidad. Y bueno, eso es simplemente (simplificando mucho, claro) teniendo una prueba que represente la validación de un requisito. Podemos cambiar el código, o lo que haga pero sabemos que el test va a recibir una entrada X y devolverá una salida Y. Si eso no ocurre, es que algo hemos roto. Se trata de seguridad. De tener la seguridad empírica, con el paso del tiempo, que el desarrollo y su avance no rompe nada de lo que funcionaba anteriormente.

Be water, my friend

Nadie nace sabiendo y parece que lo olvidamos demasiado a menudo. Reconocer la ignorancia de algo es la primera piedra de conocimiento de ese algo, porque es el momento en que nos planteamos en el subconsciente una necesidad de aprendizaje que anteriormente no se nos presentó.

Probar es algo que supone esfuerzo, especialmente al principio. Y al final también. Las primeras pruebas son feas, mal hechas, desorganizadas, acopladas… y poco a poco, a medida que se van haciendo se mejora. Porque el único método de aprender a hacer pruebas es haciéndolas. Podrás leer muchos libros, consultar o tener mucha referencia a recursos, pero hasta que no te sientas frente al lienzo en blanco no aprenderás a hacer pruebas. Y con la experiencia y la multitud de recursos disponibles (libros, github, blogs) se irá refinando el proceso cada vez.

En un campo en que la tecnología cambia cada 6-18 meses y los requisitos también, no nos queda otra que adaptarnos y cambiar continuamente. Si cambian los requisitos, cambian los tests que deben validarlos. Es una exigencia continua de un flujo continuo…

Para la mayoría de proyectos en los que he trabajado en mi vida profesional, me tomo los despliegues y entregas con demasiado estrés. Intento que todo lo que pueda hacer en función de mis conocimientos esté bien: build etiquetada, todos los tests de todos los tipos pasando, despliegue controlado por si hay que hacer rollback en cualquier momento, bakcups de todo… Son momentos críticos para cualquier sistema porque implica que durante ese tiempo, lo que sea que se esté desplegando no está disponible y hay que asegurar que el despliegue no rompa nada de la versión antigua. Porque en última instancia, podemos hacer rollback y estaremos en una situación de estrés por solventar la incidencia en el menor tiempo posible… pero al menos efectivamente, se puede solucionar.

A veces pienso en la cantidad de pruebas que hacen los ingenieros de NASA, ESA, EADS como el del Rover Curiosity y la presión que deben sentir (proyectos de 2.000 millones de € que dependen de un cable de 8€, por ejemplo) para que todas las pruebas y toda la casuística posible quede demostrada y ejecutada, intentando no dejar nada al azar. Porque una vez el robot ha sido lanzado y está en Marte, no hay vuelta atrás ni solución si ocurre algún problema*. Y eso, como ingeniero no hace más que producirme la más absoluta admiración.

Hay casos en los que por mucho que se sigan las mejores prácticas, por mucho que queramos habrá una parte en la que no tenemos control sobre lo que ocurre. Son cosas inevitables. Pero todo aquello que podamos hacer a nivel de pruebas para garantizar la estabilidad, seguridad y calidad de nuestro trabajo, hagámoslo. Afortunadamente disponemos de la tecnología para que en la mayoría de casos podamos replicar los entornos de producción/despliegue de forma bastante fidedigna, lo que permitiría reducir el riesgo de un despliegue. Este tipo de capacidades no las tiene la industria aeroespacial ni muchas otras, por lo que deberíamos usar las herramientas disponibles para minimizar riesgos y asegurarnos los menores problemas posibles. Porque, a fin de cuentas es una inversión a largo plazo que siempre sale muy, pero que muy rentable.

* Algunos gazapos de la industria aeroespacial derivados de NO hacer las pruebas suficientes:

  • Ariane 5: No se probó el sistema de cálculo de la aceleración hasta el día del lanzamiento. Este sistema venía derivado del Ariane 4 y funcionaba perfectamente, pero no se probó sobre un Ariane 5. Resultado: el piloto automático no interpretó los datos correctamente ya que se produjo una excepción de punto flotante, obligando a los ingenieros de control a autodestruir el cohete ya que el desvío de trayectoria podría haber provocado una auténtica catástrofe.
  • Mars Climate Orbiter: Parte del software de tierra hablaba en millas mientras que otra parte en metros. Resultado: se enviaron datos incorrectos a la nave, no ajustó el ángulo y distancia correcta para situarse en la órbita de Marte. Quedó carbonizada al tomar contacto con la atmósfera marciana.

[ASP.NET Identity] I: Introducción

La irrupción de ASP.NET ha supuesto una ruptura y un giro hacia una nueva modalidad de evolución del producto. Disponemos del código fuente de ASP (al menos en su totalidad) y un ciclo de versiones mucho más dinámico y no anclada a una versión del framework dedicado. En esta serie de artículos voy a dedicarlo a analizar la parte de Identity puesto que supone grandes cambios respecto a las versiones anteriores de Membership.

Como novedades más destacadas tenemos:

  • Aplicable a todo ASP.NET (Webforms, MVC, WebApi, SignalR, etc)
  • Mucho menos acoplado
  • Por lo tanto, más fácil de testear.
  • Soporte de roles, de claims, oauth, WAAD,etc.
  • OWIN
  • Basado en NuGet

Todo esto ha supuesto básicamente romper y hacer de nuevo la parte de Membership, quedando como resultado este diagrama de dependencias:

A través de este esquema vemos un par de cosas básicas:

  • La implementación de Identity está basada en la implementación de Microsoft.OWIN.
  • Para dar soporte a toda la generación, conexión y gestión de datos hace uso de EntityFramework.

Estas dos últimas características unidas a las anteriores suponen una ruptura del paradigma respecto a Membership. Mientras que antes teníamos que “colocar con calzador” nuestras entidades de usuario a Membership para poder trabajar con ella, ahora podemos usar nuestras entidades y poder trabajarlas sin tener que estar tan acopladas a un sistema de autenticación u otro.

Es decir, mientras antes teníamos que usar WebSecurity para todo ahora tenemos un UserManager que dispone de toda la funcionalidad. El concepto de manager acude a nosotros como la parte encargada de gestionar la funcionalidad completa y nos permitirá hacer todo lo posible con el usuario (login, creación, borrado, asignar roles, claims, obtener datos, etc). Para ello hará uso de una UserStore que será quien por debajo implemente toda la funcionalidad necesaria. Estamos separando el manager que usaremos para la gestión de quién se encarga de ejecutar las tareas:

   1: public class UserManager<TUser> : IDisposable where TUser : IUser

   2: {

   3:        public UserManager(IUserStore<TUser> store);

   4:        ...

   5: }

Aquí vemos que nuestro Manager depende de un TUser que implementa la interfaz IUser. Vamos por partes…

IUser

Nuestro TUser será un usuario. Y este usuario no es mas que cómo define Identity la entidad de usuario, basada únicamente en una clave o propiedad única e identificativa y un nombre de usuario. Tenemos dos posibles implementaciones:

  • La primera, la que incluye por defecto que es IUser donde la clave es un string.
  • O podemos extender IUser con una clave del tipo que queramos:
   1: public class MyUser : IUser<int>

   2:     {

   3:         public int Id { get; set; }

   4:         public string UserName { get; set; }

   5:     }

Podemos ir completando nuestro usuario con otra serie de interfaces, como por ejemplo IdentityUserLogin, IdentityUserRole, IdentityUserClaim… todas aquellas que sean necesarias para poder ir construyendo un usuario y una identidad hecha a medida para nuestra aplicación:

image

IUserStore

Lo siguiente que nos pide el Manager es un UserStore. Este será el encargado de proporcionar la funcionalidad que puede usar el manager. Y si vemos, el UserStore se basa en cumplir una serie de interfaces que son Stores que suministran funcionalidad. La más básica de todas es una IUserStore:

   1: public interface IUserStore<TUser, in TKey> : IDisposable where TUser : class, IUser<TKey>

   2:    {

   3:        Task CreateAsync(TUser user);

   4:        Task DeleteAsync(TUser user);

   5:        Task<TUser> FindByIdAsync(TKey userId);

   6:        Task<TUser> FindByNameAsync(string userName);

   7:        Task UpdateAsync(TUser user);

   8:    }

Al igual que el caso anterior, podemos optar por usar la UserStore suministrada por defecto o usar una nuestra. Y del mismo modo que en el caso anterior, vamos añadiendo interfaces a nuestra UserStore para adoptar la funcionalidad deseada:

   1: public class UserStore<TUser> : UserStore<TUser, IdentityRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>,

   2:  IUserStore<TUser>, IUserStore<TUser, string>, 

   3: IDisposable where TUser : IdentityUser

   4: {

   5: ...

   6: }

image

Así pues, tenemos el siguiente esquema final:

image

Donde primero, definimos nuestra entidad de dominio (por ejemplo, MyUser). Este será usado por una Store (por ahora la UserStore) y esta Store, por el UserManager.

¿Qué ventajas aporta este modelo frente al antiguo? Evidentemente, todo mucho más desacoplado y testeable. Tenemos un control absoluto de las entidades y la funcionalidad que queremos dar a nuestra identidad.

[C#] ¿Cómo funcionan los atributos?

Siguiendo en la línea de mis últimos artículos en los que desgrano un poco el framework de .NET para ver qué hace por dentro, ahora le toca el turno a los atributos.

¿Qué es un atributo?

Un atributo no es más que una clase que sirve para decorar un tipo. Esta decoración puede ser de tipo informativa o funcional. Por ejemplo, podemos decorar una propiedad con el atributo Description para indicar más información a nivel de metadatos y los que trabajamos en ASP.MVC tenemos el archiconocido atributo Authorize que indica si el usuario está autenticado o no para permitir entrar en la acción seleccionada o redirigirlo a una página de error.

¿Cómo crear un atributo?

Muy sencillo, heredamos de Attribute (o si partimos de otro atributo ya creado) y sobreescribimos los métodos que queramos:

   1: public class MyAttribute : Attribute

   2:         {

   3:             public override bool IsDefaultAttribute()

   4:             {

   5:                 return base.IsDefaultAttribute();

   6:             }

   7:  

   8:             public override bool Match(object obj)

   9:             {

  10:                 return base.Match(obj);

  11:             }

  12:         }

Como lo que nos interesa es reusar uno que ya existe, vamos a heredar de Description para añadirle un campo más:

   1: [AttributeUsage(System.AttributeTargets.Property,

   2:                    AllowMultiple = false,

   3:                    Inherited = false)]

   4:         public class CustomDescriptionAttribute : DescriptionAttribute

   5:         {

   6:             private readonly string shortDescription;

   7:  

   8:             public CustomDescriptionAttribute(string shortDescription, string longDescription)

   9:                 : base(longDescription)

  10:             {

  11:                 this.shortDescription = shortDescription;

  12:             }

  13:         }

Con AttributeUsage podemos definir cómo queremos que se comporte ese atributo y para qué queremos que sea: para cualquier tipo de objeto, sólo para clases, métodos, propiedades, etc.:

  • AttributeTarget: Indica cuál es el destino del atributo.
  • AllowMultiple: Indica si para un mismo tipo, podemos colocar el atributo más de una vez.
  • Inherited: Indica si este atributo se heredará en las clases que hereden del atributo actual.

Una vez lo tenemos creado, lo asociamos a lo que queramos o hayamos definido previamente. Sé que el ejemplo no es el más adecuado, pero me gustaría usarlo sólo para ver cómo funciona:

   1: public class Person

   2:        {

   3:            [CustomDescription("First name", 

   4:                "A given name or the name that occurs first in a given name.")]

   5:            public string Name { get; set; }

   6:        }

Con este atributo podemos decorar una propiedad para indicarle una descripción corta y una larga. Esto podría ser útil si queremos exportar esta entidad a un fichero o un documento donde muestre una información más detallada del campo que se está mostrando.

¿Y qué hace por dentro?

Bien, vamos por partes. Evidentemente aquí no hay magia, si ponemos un breakpoint o una salida por consola vemos que el atributo no hace nada. Pero antes de eso vamos a ver que el código que ha generado en CIL:

   1: .class auto ansi nested public beforefieldinit CustomDescriptionAttribute

   2:         extends [System]System.ComponentModel.DescriptionAttribute

Primero vemos que muestra una herencia de una clase normal y que no hay nada del otro mundo. Un objeto cualquiera, vamos. Sigamos:

   1: .custom instance void [mscorlib]System.AttributeUsageAttribute::.ctor(valuetype [mscorlib]System.AttributeTargets) = ( 01 00 80 00 00 00 02 00 54 02 0D 41 6C 6C 6F 77   // ........T..Allow

   2:                                                                                                                            4D 75 6C 74 69 70 6C 65 00 54 02 09 49 6E 68 65   // Multiple.T..Inhe

   3:                                                                                                                            72 69 74 65 64 00 )                               // rited.

Aquí tenemos la línea que indica el AttributeUsage mencionado anteriormente. Quedemos con esto porque es bastante importante… Pero mientras, sigamos:

   1: .field private initonly string shortDescription

   2:     .method public hidebysig specialname rtspecialname 

   3:             instance void  .ctor(string shortDescription,

   4:                                  string longDescription) cil managed

   5:     {

   6:       // Code size       18 (0x12)

   7:       .maxstack  8

   8:       IL_0000:  ldarg.0

   9:       IL_0001:  ldarg.2

  10:       IL_0002:  call       instance void [System]System.ComponentModel.DescriptionAttribute::.ctor(string)

  11:       IL_0007:  nop

  12:       IL_0008:  nop

  13:       IL_0009:  ldarg.0

  14:       IL_000a:  ldarg.1

  15:       IL_000b:  stfld      string Blog.AttributeToCil.Program/CustomDescriptionAttribute::shortDescription

  16:       IL_0010:  nop

  17:       IL_0011:  ret

  18:     } // end of method CustomDescriptionAttribute::.ctor

Aquí tenemos el resto de la clase. No hay diferencia alguna con cualquier otro tipo de clase en MSIL. Una vez tenemos esto, vamos a ver cómo queda la propiedad Name de la clase Person a la que le añadimos el atributo:

   1: .property instance string Name()

   2:    {

   3:      .custom instance void Blog.AttributeToCil.Program/CustomDescriptionAttribute::.ctor(string,

   4:                                                                                          string) = ( 01 00 0A 46 69 72 73 74 20 6E 61 6D 65 3B 41 20   // ...First name;A 

   5:                                                                                                      67 69 76 65 6E 20 6E 61 6D 65 20 6F 72 20 74 68   // given name or th

   6:                                                                                                      65 20 6E 61 6D 65 20 74 68 61 74 20 6F 63 63 75   // e name that occu

   7:                                                                                                      72 73 20 66 69 72 73 74 20 69 6E 20 61 20 67 69   // rs first in a gi

   8:                                                                                                      76 65 6E 20 6E 61 6D 65 2E 00 00 )                // ven name...

   9:      .get instance string Blog.AttributeToCil.Program/Person::get_Name()

  10:      .set instance void Blog.AttributeToCil.Program/Person::set_Name(string)

  11:    } // end of property Person::Name

Vemos el get/set y además, una referencia a una instancia del atributo que hemos creado. El mismo tipo de referencia que había en el caso anterior con el AttributeUsage. En CIL, .custom se usa para indicar CustomAttributes que es todo aquello que hereda de System.Attribute. Y CIL obtiene la información del atributo y la coloca además en el propio código generado, en el metadata asociado. La forma de instanciar un objeto de este tipo es por su nombre y un array de bytes en el que previamente se indican el tipo de objeto que irá en el constructor:

   1: .ctor(string,

   2:   string) = ( 01 00 0A 46 69 72 73 74 20 6E 61 6D 65 3B 41 20   // ...First name;A 

   3:               67 69 76 65 6E 20 6E 61 6D 65 20 6F 72 20 74 68   // given name or th

   4:               65 20 6E 61 6D 65 20 74 68 61 74 20 6F 63 63 75   // e name that occu

   5:               72 73 20 66 69 72 73 74 20 69 6E 20 61 20 67 69   // rs first in a gi

   6:               76 65 6E 20 6E 61 6D 65 2E 00 00 )                // ven name...

Con esto vemos que por un lado cada tipo mantiene una referencia a una instancia del tipo del atributo. Pero no la instancia en sí misma. La instancia se crea en cuanto se consulta la información del atributo. Si no hay nadie que la consulte, no se crea.

¿Cómo obtengo la información del atributo?

Por Reflection. Tenemos dos vías (en función del atributo que busquemos):

  • Obtener directamente el atributo empleando Reflection y navegando en los campos, métodos, etc para buscarlo.
  • Usar Attribute.GetCustomAttributes que nos permitirá dado un assembly, obtener los tipos de atributos que hemos creado para ello.

Afortunadamente hace un par de días publicaron una actualización que permite navegar por el código fuente de .NET de una forma más amena. Podemos encontrar como la clase Attribute y ver cómo obtiene los atributos por reflexión.

Por ejemplo, con el siguiente código podemos obtener el valor de CustomDescription que hemos creado anteriormente. Básicamente consiste en obtener la primera propiedad de la clase Person y preguntarle por sus CustomAttributes:

   1: var customDescription = person.GetType().GetProperties().First().GetCustomAttributes(true);

   2:  

* Si quieres saber más sobre atributos, puedes consultar la definición en el ECMA (sección II.22; página 205 y siguientes)