[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)

[C#] ¿Cómo funciona el Garbage Collector? ( III )

Siguiendo con la miniserie dedicada al Garbage Collector, hemos visto los siguientes puntos:

  • Primero sobre cómo se destruyen los objetos en C# y qué mecanismos e implicaciones hay.
  • Segundo, sobre cuándo se dispara el GC y qué efectos tiene sobre la memoria y el rendimiento.
  • Y en este tercero vamos a ver cómo podemos monitorizar el GC.

Notificaciones

El GC puede emitir notificaciones en estos casos:

  • Se ejecuta sobre una generación 2.
  • Pasan objetos de generación 1 a generación 2.

Por motivos de rendimiento no se registra el caso de objetos pequeños  Pero como hemos visto en el artículo anterior, si GC se dispara a menudo repercute en términos de rendimiento en nuestra aplicación. Y si además es porque tenemos objetos de generación 1/2 podemos añadir notificaciones a nuestra aplicación que nos permitirá ver cuándo se prepara y cuándo se ha ejecutado el GC. Veamos un ejemplo:

   1: GC.WaitForFullGCApproach();

Con este método paramos el hilo actual hasta que el GC esté preparado para lanzarse. Podemos hacer que sea un bloqueo indefinido o especificarle un tiempo en milisegundos de espera.

   1: GC.WaitForFullGCComplete();

Y con este otro método detenemos el hilo hasta que el GC completa su tarea en esa colección. Al igual que el método anterior podemos especificarle el tiempo máximo de espera en milisegundos.

Podemos crear un método que se encargue estar pendiente de estas notificaciones a través de un hilo:

   1: public static void WaitForFullGCProc()

   2: {

   3:     while (true)

   4:     {

   5:         while (true)

   6:         {

   7:             GCNotificationStatus s = GC.WaitForFullGCApproach();

   8:             if (s == GCNotificationStatus.Succeeded)

   9:             {

  10:                 Console.WriteLine("GC Notification raised.");

  11:             }

  12:             else if (s == GCNotificationStatus.Canceled)

  13:             {

  14:                 Console.WriteLine("GC Notification cancelled.");

  15:                 break;

  16:             }

  17:             else

  18:             {

  19:                 Console.WriteLine("GC Notification not applicable.");

  20:                 break;

  21:             }

  22:  

  23:             s = GC.WaitForFullGCComplete();

  24:             if (s == GCNotificationStatus.Succeeded)

  25:             {

  26:                 Console.WriteLine("GC Notifiction raised.");

  27:             }

  28:             else if (s == GCNotificationStatus.Canceled)

  29:             {

  30:                 Console.WriteLine("GC Notification cancelled.");

  31:                 break;

  32:             }

  33:             else

  34:             {

  35:                 Console.WriteLine("GC Notification not applicable.");

  36:                 break;

  37:             }

  38:         }

  39:  

  40:  

  41:         Thread.Sleep(500);

  42:  

  43:         if (finalExit)

  44:         {

  45:             break;

  46:         }

  47:     }

  48: }

Este método extremadamente feo y horroroso irá notificando las operaciones del GC y podremos ir viendo cuándo y en qué frecuencia se dispara. Es de notar que esto se llama sólo en caso de generación 1 y 2 y que además sólo en situaciones de bloqueo, por lo que si se llama muchas veces implica que quizá hay algo en nuestra aplicación que está consumiendo demasiados recursos.

Monitorización

Podemos configurar nuestra aplicación para que nos indique algunos datos sobre el consumo de memoria y CPU. A esto se le llama Application Resource Monitoring. Podemos activar la monitorización a través de la siguiente instrucción:

   1: AppDomain.MonitoringIsEnabled = true;

Una vez activada, no se puede desactivar. Aquí tenemos los contadores disponibles:

   1: AppDomain.MonitoringSurvivedProcessMemorySize

Con esto podemos ver cuánta es la memoria gestionada por el proceso que ha sobrevivido a una ejecución del GC.

   1: AppDomain.CurrentDomain.MonitoringSurvivedMemorySize

Y con esta lo mismo que la anterior pero referida al Application Domain. Tanto esta instrucción como la anterior toman en consideración la última aplicación del algoritmo en una fase bloqueante. Traduciendo, significa que se ha invocado a GC.Collect() de modo explícito o directamente lo ha hecho el hilo.

   1: AppDomain.CurrentDomain.MonitoringTotalAllocatedMemorySize

Con esta podemos ver cuántos bytes hemos alojado durante la vida de la aplicación. Hasta aquí todas las operaciones que permiten evaluar la memoria destinada en la aplicación y el proceso.

   1: AppDomain.CurrentDomain.MonitoringTotalProcessorTime

Y esta última el tiempo en el que la CPU está ejecutando el hilo de Application Domain. Veamos un ejemplo con el siguiente código, al que le pondremos estos contadores con el CustomObject de artículos anteriores:

   1: for (int i = 0; i < 10000; i++)

   2: {

   3:     var customObject = new CustomObject();

   4: }

Que produce la siguiente gráfica:

image

Vemos como la memoria total del proceso va en aumento hasta que entra el GC en su primera recolecta en la iteración 871. A partir de ahí, la memoria total crece pero el GC se encarga de ir recolectando. Si suprimimos esta variable para ver con un poco más de nitidez el resto, nos encontramos con esto:

image

Donde se aprecia los pequeños saltos que va optimizando el GC en las recolectas. Y un poco más en detalle:

image

Con lo que tenemos otro método para ver cuánta memoria consumimos y cómo el GC la va organizando. Si vemos que conforme la vida de nuestra aplicación la memoria aumenta y el coste de CPU también puede ser un síntoma de algo que no estamos haciendo bien.

Este es el último artículo de la “serie”. A modo de resumen hemos visto los siguientes puntos del Gargabe Collector:

  • En el primer artículo, hemos visto cómo se finaliza un objeto dentro de .NET Framework.
  • En el segundo, cómo se organizan los objetos internamente y el control algo “manual” del GC.
  • Y en este último sobre cómo podemos recibir notificaciones y medir la memoria empleada para averiguar si tenemos algún problema.

[C#] ¿Cómo funciona el Garbage Collector? ( II )

En el artículo anterior introduje brevemente qué mecanismos disponemos para liberar los recursos de los objetos y cómo actúa GC sobre ellos. Normalmente para un desarrollador GC es algo que está ahí pero no necesita trastear. Pero para aplicaciones más avanzadas, sistemas críticos o debug/testeo suele ser muy útil.

Liberando memoria

El GC tiene la función de liberar la memoria de los objetos. Esto puede ocurrir en los siguiente casos:

  • Limitaciones técnicas de la máquina: Tenemos demasiada memoria ocupada y alguien tiene que liberarla. Si es nuestra, el GC se encargará de ello.
  • Los objetos que tenemos en el Heap se acumulan y excede el máximo permitido. El GC se dispara y recolecta.
  • O directamente, porque lo invocamos explícitamente.

Veamos ejemplos de los dos últimos casos.

Para el primer ejemplo, simplemente tenemos el siguiente código:

   1: for (int i = 0; i < 100000; i++)

   2: {

   3:     var customObject = new CustomObject();

   4:  

   5:     System.Console

   6:         .Write(string.Format("Iteration {0}; Memory {1}n", i, GC.GetTotalMemory(false)));

   7: }

Donde tenemos un bucle que va instanciando los objetos. Al instanciar un objeto, se reserva la memoria necesaria para él y se crea la instancia. Ese objeto permanecerá en memoria hasta que el GC estime oportuno. Vamos a ver cómo a medida que se incrementa el número de iteraciones la memoria asignada por el GC irá aumentando hasta que decida liberarla.

En el segundo ejemplo, forzamos a través de GC.Collect() que cada vez que se crea el objeto, invocamos al GC para que recorra todo su grafo de objetos y elimine aquellos que no se van a usar:

   1: for (int i = 0; i < 100000; i++)

   2: {

   3:     var customObject = new CustomObject();

   4:     GC.Collect();

   5:     System.Console

   6:        .Write(string.Format("Iteration {0}; Memory {1}n", i, GC.GetTotalMemory(false)));

   7: }

Y como resultado para esta muestra obtenemos la siguiente gráfica:

image

 

Donde podemos observar como en el primer ejemplo el GC acumula memoria hasta que la libera según estima oportuno mientras que en el segundo caso la cantidad de memoria ocupada permanece estable. Evidentemente esta recolecta forzada no sale gratis, veamos ahora en escala de tiempo cuánto repercute. Como el coste por iteración es despreciable, a continuación muestro qué ocurre si invocamos el GC o no en bloques de iteraciones:

image

Podemos ver una diferencia de tiempo de orden exponencial. Es decir, debemos seguir las recomendaciones dadas de dejar que GC trabaje y nosotros no interactuar con él salvo que tengamos razones muy justificadas para ello.

Generaciones

Una vez que hemos visto cuándo se dispara y qué ocurre cuando se dispara, vamos a ver cómo esta distribuido. Aquí introduzco el tema de “Generaciones”, que es la estructura de distribución de objetos que dispone GC:

  • Generación 0: Contiene los objetos que menos duran, como variables temporales. Inicialmente todo objeto irá directamente a este nivel de generación.
  • Generación 1: Contiene objetos de corta duración y actúa de buffer entre objetos de corta y larga duración.
  • Generación 2: Contiene objetos de larga duración. Por ejemplo, variables estáticas a nivel de aplicación.

Todo objeto en función de su uso y otros factores, se van moviendo en las respectivas generaciones. Como ejemplo se puede ver cómo se obtiene la generación de un determinada instancia:

   1: GC.GetGeneration(customObject);

Esto devuelve 0,1 ó 2 en función de la generación de esa instancia. Además de el objeto en sí, podemos pasarle una WeakReference directamente.

También podemos conocer cuántas veces se ha invocado el GC en una generación concreta a través de este método:

   1: GC.CollectionCount(0);

Los objetos que no son reclamados al GC se llaman “supervivientes”. Si están en la generación 0 y no han sido reclamados, pasan a la 1. Si están en la 2, siguen en la 2. El GC además detecta estos casos como algo particular: si cada vez hay más supervivientes, balancea el algoritmo para lograr un equilibrio entre el consumo de memoria y el tiempo de ejecución del mismo.

El algoritmo sigue el siguiente proceso:

  • En una primera fase se busca y se crea una lista con todos los objetos vivos.
  • Después se actualizan las referencias de los objetos y se relocalizan.
  • Por último, una fase de compactación de memoria que agrupa el espacio disponible junto con el liberado por los objetos que ya no existen. La compactación siempre se hará salvo para aquellos casos en que los objetos sean demasiado grandes. Se puede recurrir a esta propiedad del GC para forzar que los objetos grandes siempre se compacten (sólo disponible a partir del 4.5.1).

Antes de que el GC se dispare, todos los hilos de ejecución de la aplicación se suspenden para poder activarse el hilo dedicado al GC. En ese momento el GC aplica el algoritmo y el proceso mencionado anteriormente. Cuando termina, el resto de hilos prosiguen en su ejecución:

When a thread triggers a Garbage Collection

[C#] ¿Cómo funciona el Garbage Collector? ( I )

Una de las principales diferencias respecto a C/C++ y similitudes con Java es la presencia del Garbage Collector (GC) que permite delegar en un proceso la gestión de los objetos que creamos. A priori nos olvidamos de tener que invocar de forma explícita el destructor de los objetos y liberar su memoria. Veamos qué ocurre realmente.

El GC aparece en la primera versión de .NET Framework. Su funcionamiento permite que hagamos cosas como estas:

   1: class CustomObject

   2: {

   3:     public byte[] ByteArray { get; set; }

   4:  

   5:     public CustomObject()

   6:     {

   7:         this.ByteArray = new byte[1024];

   8:     }

   9: }

Vemos que creamos un objeto array de una longitud determinada y al finalizar el programa podemos comprobar a través de herramientas que no tenemos fugas de memoria. Si analizamos el código CIL generado podemos ver que no aparece tampoco ninguna mención a destruir nada. El GC, a través del Framework se encarga todo. Todo totalmente transparente.

Para seguir hablando de GC es necesario introducir el concepto básico de memoria en Stack y en el Heap. Para tratar este tema aconsejo este artículo y su segunda parte que tratan sobre el diseño e implementación del stack. Como añadido, este no vendrá mal. El resumen sencillo y algo banal es que todo tipo que se instancia irá alojado en el Heap que es autogestionado. Es decir, hay alguien que se encarga de una vez instanciado un objeto que ya se ha usado, determinar qué hacer con él.

Finalizadores

La primera aproximación siguiendo un paradigma OO será que al igual que un objeto tiene constructor debe tener un destructor. Al igual que en C++, C# posee este método siguiendo la misma nomenclatura para cualquier tipo de objeto:

   1: class CustomObject

   2:         {

   3:             public byte[] ByteArray { get; set; }

   4:  

   5:             public CustomObject()

   6:             {

   7:                 System.Console.Write("Object created");

   8:             }

   9:  

  10:             ~CustomObject()

  11:             {

  12:                 System.Console.Write("Finalizer invoked");

  13:                 System.Console.Read();

  14:             }

  15:         }

Donde podremos definir qué ocurre una vez se “destruye” el objeto. Realmente no se destruye, puesto que en C# se llama “finalizador”. Se finaliza la vida del objeto pero este sigue estando vivo. Simplemente funciona así porque el GC tiene varias fases que veremos más adelante. Pero analicemos este hecho un poco más en profundidad. Si obtenemos el código IL generado observamos el siguiente método:

   1: .method family hidebysig virtual instance void 

   2:         Finalize() cil managed

   3: {

   4:   // Code size       31 (0x1f)

   5:   .maxstack  1

   6:   .try

   7:   {

   8:     IL_0000:  nop

   9:     IL_0001:  ldstr      "Finalizer invoked"

  10:     IL_0006:  call       void [mscorlib]System.Console::Write(string)

  11:     IL_000b:  nop

  12:     IL_000c:  call       int32 [mscorlib]System.Console::Read()

  13:     IL_0011:  pop

  14:     IL_0012:  nop

  15:     IL_0013:  leave.s    IL_001d

  16:   }  // end .try

  17:   finally

  18:   {

  19:     IL_0015:  ldarg.0

  20:     IL_0016:  call       instance void [mscorlib]System.Object::Finalize()

  21:     IL_001b:  nop

  22:     IL_001c:  endfinally

  23:   }  // end handler

  24:   IL_001d:  nop

  25:   IL_001e:  ret

  26: } // end of method CustomObject::Finalize

Para IL no existe el concepto de destructor. Existe y transforma lo que llamaríamos un destructor en un finalizador, en un método llamado Finalize() que alguien invocará. De hecho, si intentamos declarar lo siguiente:

   1: protected override void Finalize()

   2: {

   3:     //...

   4: }

Vemos como el propio compilador no te permite declarar ese método y te sugiere que declares un destructor.

¿Qué se debe escribir en un finalizador? Liberación de recursos (streams, conexiones, etc) y al ser crítico, un código que no deba fallar ni provocar excepciones. Y la siguiente pregunta es: ¿no estaba IDisposable para esto? En efecto, en la página de MSDN nos cuentan que IDisposable sirve para liberar los recursos que no se pueden gestionar porque GC, al no ser determinístico no sabemos cuándo podrá lanzarse. Y además como son recursos que no se pueden gestionar, GC los ignorará. Típicos ejemplos de esto son los streams. Y por eso se recomienda cada vez que abrimos un stream, insertarlo dentro de un using. Cuando finaliza el using se invoca de forma automática al método Dispose(). Algo se vio en un artículo anterior.

Un ejemplo sencillo de cómo funciona y cómo implementar correctamente Dispose lo tenemos en el siguiente fragmento de código:

   1: class DisposableObject : IDisposable

   2:         {

   3:             public void Dispose()

   4:             {

   5:                 this.Dispose(true);

   6:                 GC.SuppressFinalize(this);

   7:             }

   8:  

   9:             protected virtual void Dispose(bool disposing)

  10:             {

  11:                 if (disposing)

  12:                 {

  13:                     // Dispose on each object of this instance.

  14:                     // ...

  15:                 }

  16:             }

  17:  

  18:             ~DisposableObject()

  19:             {

  20:                 this.Dispose(false);

  21:             }

  22:         }

Al implementar la interfaz IDisposable tenemos que implementar el método Dispose(). El patrón ofrece una estructuración de las llamadas a Dispose() del objeto actual y los objetos que se integren, de modo que si invocamos Dispose() se invocará el Dispose() de cada uno de los objetos que hayamos ido añadiendo. Sin embargo si se invoca al finalizador a través del “destructor”, el GC seguirá su curso. Nótese la diferencia entre Dispose() y el finalizador:

– Dispose(): Primero invoca al método protegido para que se invoquen en cascada los Dispose de cada objeto y posteriormente, se indica al GC que no es necesario que invoque al finalizador en este instante puesto que ya hemos liberado los recursos. ¿Necesario? No. ¿Optimizado? Un poco, puesto que ahorramos llamadas al GC.

– ~(): Simplemente invoca al Dispose(false) que no debería invocar al Dispose de los objetos internos. Desde el punto de vista nuestro respecto al GC, no sabemos en qué estado están y ya es función del GC determinar si debe ir a por ellos o no.

Siguiendo con la pregunta de ¿qué diferencia hay entre un finalizador y IDisposable? La respuesta a nivel teórico es: ninguna. Ambos están pensados para liberar aquellos recursos que empleamos en los objetos. La única diferencia es que tenemos un mecanismo para controlar cuándo lo llamamos si empleamos IDisposable; porque el finalizador, al depender de GC no podemos esperar nada de él. No sabemos cuándo será llamado.

A modo de resumen tenemos lo siguiente:

  • En C# NO podemos destruir los objetos. Como mucho podemos indicar qué ocurre cuando tienen algún recurso asociado y liberarlo.
  • IDisposable no se invoca sólo. Hay que invocarlo. E invocarlo, no significa destruir el objeto.
  • ~() Es el método finalizador del objeto. Sólo es invocado a través del GC y cuando éste lo estima oportuno.