Archivo

Archive for the ‘Desarrollo’ Category

Deuda técnica–qué es y cómo se detecta?

Cuántos de vosotros habéis oído de productos que, a pesar de arrancar brillantemente, vieron su desarrollo retrasado o totalmente detenido por problemas que no hacían más que aumentar con el tiempo a cada nueva versión?

debt
Hace unos días me hicieron llegar un excelente artículo de Enrique Dans acerca de la deuda técnica y cómo dicha deuda provocó la caída de los sistemas de British Airways que muchos miles de pasajeros padecieron. Y aunque estoy de acuerdo con el artículo, me pareció que el autor cargaba excesivamente las tintas contra la parte final del proceso: el mantenimiento del sistema una vez puesto en marcha, prácticamente igualando la deuda técnica al tiempo que transcurre desde que un sistema arranca y no es mantenido apropiadamente en producción.

La deuda técnica se define como los recursos (tiempo y coste económico) necesarios para resolver problemas en sistemas tanto en su fase de desarrollo como una vez puestos en producción. Con el tiempo, si dichos defectos de diseño o mantenimiento no se solucionan, no hacen más que incrementar los recursos – de ahí el término “deuda” que, como los tipos de interés, no hace más que aumentar con el tiempo – necesarios para resolver el problema.

El artículo de Enrique Dans analiza la generación de deuda técnica incrementada por un negligente mantenimiento de un sistema que, por otro lado, no aparentaba problemas de diseño. En lugar de emplear recursos de la empresa de forma constante para mantener los sistemas, se empleó personal externo para ello. Dicho personal externo, a medida que es reemplazado por otro personal externo sin el entrenamiento pertinente, no hacía más que empeorar los procesos – hasta que alguien tumbó el sistema. Es lo que yo considero “deuda técnica de IT”. Pero el sistema podría haber sido perfectamente funcional.

Pero mi concepto de deuda técnica viene de mucho más atrás. La deuda técnica empieza el día que se empieza el desarrollo del sistema.

La historia va así: alguien tiene una idea, posiblemente una excelente idea para un nuevo producto, un proyecto que podría ser incluso revolucionario. Manos a la obra, empieza el desarrollo, inicialmente con unas expectativas concretas, un punto concreto al que llegar para el lanzamiento. Aparentemente todo va según lo planeado, el desarrollo avanza según lo previsto: salimos en fechas.

Poco a poco, el desarrollo se va ralentizando, pierde ese empuje inicial, empiezan a aparecer problemas a cada paso que bloquean todo el desarrollo hasta que se resuelven. Al principio las paradas son relativamente esporádicas y se resuelven rápidamente, pero según avanzan las semanas cada problema se vuelve más “raro”, más complejo, requiere de más recursos, y cuando se soluciona uno, aparece otro por otro sitio. Es el síntoma obvio de un problema de deuda técnica: cada nuevo problema es más caro de resolver que el anterior, porque los problemas iniciales de diseño no se resolvieron en su momento.

Si cuando apareció el tercer problema, se hubiese realizado un análisis de los tres como un conjunto (y no simplemente parchear cada uno de ellos individualmente que es, tristemente, lo que los estándares de la industria dictan hoy en día) y solucionar el defecto de diseño inicial que los provocó, no habrían aparecido los subsiguientes problemas (y el desarrollo sería más rápido desde ese momento, sin innumerables paradas). Sí, arreglar el error de diseño al aparecer el primer problema hubiese requerido una parada de desarrollo (y las subsecuentes modificaciones de los módulos afectados), pero en las fases iniciales el número de módulos suele ser reducido y el impacto, limitado.

Pero si el desarrollo prosigue y se sigue tratando cada problema como una entidad individual, y se siguen desarrollando módulos sobre una base defectuosa (la deuda aumenta), cuando alguien determine que el problema no es puntual sino congénito, el coste de repararlo será masivo: implicará tanto aplicar la solución al problema de diseño más el refactoring de ahora una inmensa cantidad de módulos hechos a medida de ese defecto de diseño.

Imaginemos un problema del diseño base que se puede resolver (incluyendo testing) en cuatro días. Para completar los datos, asumamos que nuestros programadores ganan 60 euros diarios. Subsanar el defecto de diseño inicial costaría unos irrisorios 240 euros (60 euros multiplicado por 4 días).  Imaginemos que durante la fase inicial (el “tercer” fallo del que hablábamos antes) nos ponemos en el peor de los casos y el cambio en la base implica “breaking changes” que lo hacen incompatible con las implementaciones de los módulos implicados (hasta ahora, tres). Adaptar o reescribir esos tres módulos (y sus dependencias) implica7 días por módulo:

(4 días x 1 módulo x 60€) +
(7 días x 3 módulos x 60€)
= 1500€

Obviamente, puede que no se pueda hacer ningún trabajo en los módulos afectados mientras se realiza la corrección de la base, así que el costo de un número de programadores potencialmente “esperando” también aumentaría el costo, pero ya tenemos una cifra, en euros, que es lo que nos interesa. De los 240 euros iniciales ya nos hemos puesto en 1500.

Supongamos que nuestro director opina que esos 1500 euros, más un número potencial de programadores parados no es justificable y exige continuar con el diseño actual (al famoso grito de “eso ya lo arreglamos en dos patás más adelante”). El defecto de diseño persiste, se siguen escribiendo módulos dependientes, los problemas y errores ya están ralentizando el desarrollo (seguid multiplicando euros por horas). Ya está costando dinero (30? 40? 50 mil euros?), y eso que ni siquiera estamos arreglando el problema de base, porque los módulos dependientes llevan cerca de un 30% de código necesario para soportar el defecto de diseño inicial: estamos pagando dinero por escribir código “tirita” o “parche” y ni siquiera estamos en producción. La cifra ya es monumental.

Cuando llega el problema número quince, con docenas de módulos y dependencias afectados y el proyecto costando un dineral (por horas perdidas en ir “parcheando” defectos), llega el momento de la verdad y alguien, más arriba del director, decide que es momento de parar y solucionar el problema… Veamos ahora a cuanto asciende… Supongamos ahora quince módulos directamente dependientes, también con 7 días de refactoring, pero ahora con cada módulo soportando a su vez otros cinco elementos (35 en total), con un promedio de 5 días de media: algunos módulos se pueden aprovechar, otros toca reescribirlos o requieren una reescritura considerable:

(4 días x 1 módulo x 60€) +
(7 días x 15 módulos x 60€) +
(5 días x 35 módulos x 60€)
= 17040€

De los 240 euros iniciales nos hemos puesto en 17000 sólo para resolver el problema y sin contar el precio del desarrollo ya realizado sobre la base defectuosa (esos 30, 40, 50 mil euros). Y a este nivel del problema, lo más probable es que los programadores sí estén parados durante la reestructuración. Cuánto hemos gastado por no solucionar el problema al principio? Reparar el problema congénito sigue constando 240 euros, pero su deuda técnica ha sido masiva, potencialmente letal para el proyecto o incluso la empresa.

Ahora, ese alguien por encima del director, tendrá que decidir, no ya si invertir 17000 euros en reparar algo que no debía existir en primer lugar, sino en admitir el gasto de los 30, 40 o 50 mil euros empleados sobre una base defectuosa y que, más que posiblemente, también requieran más inversión. Es cuando empiezan a rodar cabezas.

De repente, los 240, o incluso los 1500 euros, parecen un precio de risa…

Aplicaciones internacionales con ASP.NET

Me sigue chocando seguir viendo soluciones de lo más artesanal para internacionalizar páginas web hospedadas con IIS, cuando la plataforma nos las da de serie y con un mínimo esfuerzo por parte del programador.

He visto usar parámetros en las URLs, pasarlos de módulo a módulo, y construir complejas funcionalidades para arrastrar el parámetro de página en página… Realmente no hay que hacer nada de eso, simplemente dejar que el navegador nos ayude y .NET (e IIS) lo aprovechen.

Todo empieza en el navegador

Es posible que no lo sepáis, pero los navegadores incluyen el idioma deseado en cada petición que realizan al servidor. Las peticiones HTTP (y HTTPS, por supuesto) incluyen una cabecera donde se incluye información útil para que el servidor pueda realizar su trabajo: desde la página que se está solicitando, un método (GET, POST) si soporta compresión o no, y, lo que nos interesa, el idioma esperado para la respuesta. La cabecera en cuestión se llama “Accept-Language” y da la lista de idiomas configurados por el usuario.

Cread (o usad una página web ya existente) en vuestro servidor – en este ejemplo usaremos una página WebForms clásica “TestPage.aspx”, y abridla con vuestro navegador favorito. Usando las herramientas de desarrollo de Chrome, por ejemplo, en la parte de visualización de red, encontraréis algo como esto, y la entrada en la que estamos particularmente interesados es “Accept-Language”:

AcceptLanguageHeader

En este ejemplo, Chrome está solicitando la página en inglés, y ofrece dos opciones: la “específica” (“en-US”, inglés de Estados Unidos) y la genérica (“en”, Inglés).

Obviamente, esa información está al alcance de una línea de código en el servidor. En nuestra página TestPage.aspx podemos incluir lo siguiente para comprobarlo:

protected void Page_Load(object sender, EventArgs e)
{
  var requestedCulture = Request.Headers["Accept-Language"];
  System.Diagnostics.Debug.WriteLine("Culture:" + requestedCulture);
}

En la ventana de Depuración veréis lo siguiente:

AcceptLanguageHeader_Server

Pero qué ocurre cuando configuramos Chrome para que solicite páginas en francés como idioma preferido e inglés como secundario? Podemos configurarlo navegando la url “chrome://settings” y abriendo “Ajustes avanzados”. Configuraremos los idiomas así:

ChromeLangSettings

y refrescamos la página: en el servidor veremos lo siguiente, con la nueva opción (“fr”) como primera preferencia:

AcceptLanguageHeader_Server_2

Y ahora, antes de que os pongáis a leer la cabecera “a pelo” en todas vuestras páginas, seguid leyendo… porque no tenéis que hacerlo!!!

Una página para experimentar

Vamos a añadir una simple página (también WebForms, aunque es exactamente lo mismo en Razor, o WebAPI, o MVC) para experimentar un poco. El código de la página es el siguiente (por simplicidad he eliminado el código posterior (“code behind”):

<%@ Page Language="C#" Inherits="System.Web.UI.Page" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Welcome</title>
</head>
<body>
    <h1>Welcome to my web!!</h1>
    <p>Today is <% =DateTime.Today.ToLongDateString() %></p>
</body>
</html>

Y vamos a navegarla con Chrome configurado para pedir contenidos en francés. El resultado es, como podéis esperar, una página en… inglés?

Intl_Nav_1

El primer problema es obvio: el ordenador que hospeda IIS corre un sistema operativo en español, así que las fechas salen en el idioma de Cervantes. Vamos mal… pero aunque parezca increíble, todo el proceso empieza con una sola línea en el archivo web.config. Cambiad la sección <system.web> de esto:

  <system.web>
    <compilation debug="true" targetFramework="4.5.2"/>
    <httpRuntime targetFramework="4.5.2"/>
  </system.web>

a esto:

  <system.web>
    <compilation debug="true" targetFramework="4.5.2"/>
    <httpRuntime targetFramework="4.5.2"/>
    <globalization culture="auto" uiCulture="auto" enableClientBasedCulture="true"/>
  </system.web>

Con una simple directiva IIS aplicará la región solicitada por el navegador a todas (si, todas!) las peticiones que gestione. Simplemente arrancad de nuevo, navegad la página otra vez y veréis el cambio:

Intl_Nav_2

La fecha aparece perfectamente formateada en francés… sin escribir ni una sola línea de código.

Al hacer la cultura de interface de usuario (uiCulture) automática y permitir a IIS que aplique la cultura requerida por el usuario (enableClientBasedCulture) lo que ocurre es que IIS, en las primeras fases de la cola de procesos que implica responder a una solicitud web, lee la cabecera Accept-Language, la analiza y busca la mejor cultura (CultureInfo) disponible y la aplica de manera automática al resto del proceso. Cuando en la página escribimos DateTime.Today.ToLongDateString(), la cultura está correctamente configurada para generar la fecha en el idioma correcto. Y tenemos acceso a dicha cultura automática por medio de la propiedad CurrentUICulture de CultureInfo. Si cambiamos nuestra página de pruebas a lo siguiente:

protected void Page_Load(object sender, EventArgs e)
{
  var requestedCulture = CultureInfo.CurrentUICulture;
  System.Diagnostics.Debug.WriteLine("Culture:" + requestedCulture.NativeName);
}

al navegarla con Chrome el resultado es el siguiente:

AcceptLanguageHeader_Server_3

Ya, pero, y el resto de los textos?

Si, hasta ahora hemos traducido una parte extremadamente pequeña de nuestra página. Ahora viene lo realmente importante, nuestros textos.

Y .NET ha facilitado la operación de obtener textos traducidos de forma automática desde el primer día, empleando archivos de recursos. Los archivos de recursos son colecciones de elementos dependientes de idioma (habitualmente cadenas de texto, pero también imágenes) gestionados por .NET. Para empezar a traducir nuestra página web vamos a empezar trasladando lo textos actuales a un archivo de recursos.

Empezaremos añadiendo un archivo con los textos en inglés (ya que nuestra web está actualmente en inglés). En Visual Studio cread una carpeta “Resources” y dentro de ella cread un nuevo archivo de recursos. Llamadlo “WebStrings.resx”:

NewResource

(Nota: si usáis WebForms hay una forma más específica de añadir archivos .resx y de gestionarlos desde la página. El método que estoy documentando es genérico y válido tanto para páginas web como para aplicaciones nativas).

Una vez creado el archivo, abridlo en VisualStudio:

StringEditor_1

Importante: antes de empezar a añadir los textos, cambiad (en la parte superior) el modificador de acceso para los recursos de “Interno” a “Público”.

Ahora añadid los diversos textos necesarios en la página:

StringEditor_2

Las cadenas que estamos añadiendo se convierten en miembros de una clase que podemos emplear en cualquier parte de nuestro código. Y lo mejor, al ser un tipo generado automáticamente, disponemos de Intellisense a la hora de trabajar con él:

UsingStrings_Intellisense

En nuestro ejemplo, y dado que la aplicación se llama “IntlWeb”, el namespace para nuestras cadenas es IntlWeb.Resources.WebStrings. Una vez reemplazados todos los textos, la página queda así:

<%@ Page Language="C#" Inherits="System.Web.UI.Page" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title><% =IntlWeb.Resources.WebStrings.Page_Title %></title>
</head>
<body>
    <h1><% =IntlWeb.Resources.WebStrings.Body_WelcomeMessage %></h1>
    <p><% =IntlWeb.Resources.WebStrings.Body_TodayIs %> <% =DateTime.Today.ToLongDateString() %></p>
</body>
</html>

Obviamente, nuestra página sigue mostrando mensajes en inglés. Ahora tenemos que añadir los textos en francés. Para ello (y tendréis que disculparme, no hablo francés así que los textos pueden no estar del todo correctos) duplicad desde VisualStudio el archivo WebStrings.resx y ponedle como nombre WebStrings.fr.resx. El nombre del archivo es extremadamente importante: el código del idioma debe ir entre el nombre y la extensión, y puede ser un idioma general (“.es.”) o un código completo cuando necesitamos más “finura” a la hora de ofrecer textos (“.es-ES.”). Una vez copiado, abridlo y cambiad los textos:

StringEditor_3

Si navegamos la página desde Chrome, el resultado será el siguiente:

Intl_Nav_3

Como no podría ser de otro modo, añadamos también textos en español. Como podréis imaginar el nombre del archivo será WebStrings.es.resx:

StringEditor_4

Y desde Chrome volved a abrir la configuración y cambiad los idiomas colocando el Español el primero de la lista (no es necesario eliminar el idioma francés):

ChromeLangSettings_2

Aceptad los ajustes, volved a la página y simplemente refrescadla:

Intl_Nav_4

Cuando una aplicación pide una de las cadenas de recursos guardadas en un archivo .resx, .NET busca primero el archivo con la cultura que más se aproxime y luego los va evaluando por prioridad descendente. Si, por ejemplo, la cultura pedida por el usuario es “Alemán de Austria” (“de-AU”) buscará primero un archivo con la extensión “de-AU.resx”, Si no lo encuentra continuará con la versión “Alemán” (".de.resx") y finalmente leerá los contenidos de la versión genérica (“.resx”). Procurad por ello emplear en lo posible archivos de idiomas lo más generales posibles: si empleáis “.es-MX.resx”, un usuario desde España verá los textos en el idioma predeterminado al no encontrar los recursos ni específicos (“es-ES.resx”) ni generales para el idioma (“.es.resx”).

Ya, pero… y el JavaScript?

Pues es una estupenda pregunta. Si bien localizar textos en el servidor está perfectamente soportado por .NET (e IIS) de “fábrica”, las páginas emplean cada vez más librerías de cliente escritas en JavaScript. Pero aunque dicha funcionalidad no exista en el servidor, no significa que no podamos añadirla muy fácilmente.

Volviendo a nuestro ejemplo, imaginemos que nuestra página usa código JavaScript para realizar alguna operación en el cliente. Nuestro archivo se llama “App.js” y actualmente usa textos en inglés, junto con controles en la misma página. En este ejemplo vamos a traducir (también!) los controles de la página desde JavaScript en lugar de utilizar los recursos del servidor como hemos hecho hasta ahora – traducción totalmente en el lado del cliente.

El código de la página es el siguiente:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <script src="App.js"></script>
</head>
<body>
  <button id="cmd1">Command 1</button>
  <button id="cmd2">Command 2</button>
  <button id="cmd3">Command 3</button>
</body>
</html>

y el archivo JavaScript (“App.js”) contiene lo siguiente:

window.onload = function ()
{
  var cmd1 = document.getElementById("cmd1");
  var cmd2 = document.getElementById("cmd2");
  var cmd3 = document.getElementById("cmd3");

  cmd1.addEventListener("click", function ()
  {
    alert("You clicked on the first button");
  });
  cmd2.addEventListener("click", function ()
  {
    alert("You clicked on the second button");
  });
  cmd3.addEventListener("click", function ()
  {
    alert("You clicked on the third button");
  });
};

Como podréis imaginar, no es que haga mucho, pero la idea es simple: cuando el usuario pulsa sobre uno de los botones, se muestra un texto informando de la importantísima operación que se ha llevado a cabo:

JavaScript_1

El tema es: cómo traducimos las cadenas incluidas en el programa JavaScript? Si bien IIS no tiene soporte directo para traducir javascript, nosotros podemos añadirlo.

Lo primero que vamos a hacer es algo parecido a lo que hicimos con nuestra página: convertir las cadenas de texto en variables que guardaremos en sendos archivos javascript, empleando un nombre que nos permita identificarlos por idioma. El primero que escribiremos será el archivo con los textos genéricos, en nuestro caso, en inglés. Para identificar que el archivo contiene los textos de nuestra aplicación, lo llamaremos “App.strings.js”:

// App.strings.js
var strings =
  {
    cmd1Caption: "Button 1",
    cmd2Caption: "Button 2",
    cmd3Caption: "Button 3",
    cmd1Clicked: "You clicked on the first button",
    cmd2Clicked: "You clicked on the second button",
    cmd3Clicked: "You clicked on the third button",
  };

También modificaremos nuestro programa para usar las cadenas del archivo en lugar de las predeterminadas:

window.onload = function ()
{
  var cmd1 = document.getElementById("cmd1");
  var cmd2 = document.getElementById("cmd2");
  var cmd3 = document.getElementById("cmd3");

  cmd1.innerText = strings.cmd1Caption;
  cmd2.innerText = strings.cmd2Caption;
  cmd3.innerText = strings.cmd3Caption;

  cmd1.addEventListener("click", function ()
  {
    alert(strings.cmd1Clicked);
  });
  cmd2.addEventListener("click", function ()
  {
    alert(strings.cmd2Clicked);
  });
  cmd3.addEventListener("click", function ()
  {
    alert(strings.cmd3Clicked);
  });
};

Y finalmente, en la página añadiremos la referencia al archivo de cadenas (App.strings.js) además de nuestro código (App.js) en la cabecera de la página:

<head>
  <meta charset="utf-8" />
  <script src="App.strings.js"></script>
  <script src="App.js"></script>
</head>

Hasta ahora todo muy bien, pero nuestra página sigue funcionando únicamente en inglés y, de hecho, si la ejecutamos ahora veremos exactamente lo mismo que antes. Cómo conseguir ahora que los textos (o, mejor dicho, el archivo App.strings.js) se traduzca? Ahora es cuando IIS nuevamente vuelve a echarnos una mano. Vamos a añadir un “handler” al servidor que, aprovechando que IIS nos informará del idioma que desea el usuario, se encargue de devolvernos el archivo .strings.js correcto.

Cuando el handler reciba una petición para un archivo cuyo nombre termine en “.strings.js”, usará la propiedad CurrentUICulture para construir diversos nombres de archivo. Si por ejemplo el navegador solicita el archivo “app.strings.js” y el idioma es “de-AU”, primero buscará un archivo “app.de-AU.strings.js”, a continuación buscará “app.de.strings.js” y finalmente probará con “app.strings.js”.

using System.Globalization;
using System.IO;
using System.Web;

namespace IntlWeb
{
  public class JSLocalizer : IHttpHandler
  {
    private  const string Extension = ".strings.js";

    public bool IsReusable => true;

    public void ProcessRequest(HttpContext context)
    {
      var culture = CultureInfo.CurrentUICulture;
      var basePath = context.Request.AppRelativeCurrentExecutionFilePath;

      basePath = basePath.Remove(basePath.Length - Extension.Length);
      var physPath = context.Server.MapPath(basePath);

      // <filename>.ll-RR.strings.js
      var fileName = $"{physPath}.{culture.Name}{Extension}";
      if (File.Exists(fileName) == false)
      {
        // <filename>.ll.strings.js
        fileName = $"{physPath}.{culture.TwoLetterISOLanguageName}{Extension}";
        if (File.Exists(fileName) == false)
        {
          // <filename>.strings.js
          fileName = $"{physPath}{Extension}";
          if (File.Exists(fileName) == false)
          {
            context.Response.StatusCode = 404// File not found
            return;
          }
        }
      }

      var response = context.Response;
      response.ContentType = "text/javascript";
      response.Charset = System.Text.Encoding.UTF8.WebName;
      response.ContentEncoding = System.Text.Encoding.UTF8;
      response.Expires = 24 * 60;   // 1 day user caching

      response.WriteFile(fileName);
    }
  }
}

Solo queda registrarlo en el archivo web.config para que gestione las peticiones de cualquier archivo “*.strings.js”:

  <system.webServer>
    <handlers>
      <add name="Script String Localizer" verb="*" path="*.strings.js" type="IntlWeb.JSLocalizer" />
    </handlers>
  </system.webServer>

Finalmente, sólo nos queda probarlo. Vamos a añadir un archivo con los textos en español a nuestra aplicación JavaScript, copiando el archivo App.strings.js en uno que se llame App.es.strings.js. Cambiemos los textos y pidamos la página, a ver qué pasa:

// App.es.strings.js
var strings =
  {
    cmd1Caption: "Botón 1",
    cmd2Caption: "Botón 2",
    cmd3Caption: "Botón 3",
    cmd1Clicked: "Ha pulsado en el primer botón",
    cmd2Clicked: "Ha pulsado en el segundo botón",
    cmd3Clicked: "Ha pulsado en el tercer botón",
  };

Cuando el navegador solicita el archivo App.strings.js con los ajustes del navegador para solicitar contenido en castellano, la petición acabará en nuestro handler, que conoce el idioma esperado por el usuario (via la propiedad CultureInfo.CurrentUICulture). Primero busca un archivo App.es-ES.strings.js. Al no encontrarlo, busca la versión App.es.strings.js, que sí encuentra y devuelve al cliente. El resultado es:

Intl_Nav_5

Tanto el contenido de los botones como el mensaje mostrado por Javascript aparecen en castellano. Al igual que con los archivos .resx, ahora en el servidor tendremos archivos Javascript con versiones de los textos para cada idioma. El handler se encargará de devolver la mejor versión posible.

Este (largo!) artículo ha intentado describir de forma rápida las principales herramientas que permiten traducir de forma sencilla tanto aplicaciones como servicios web hospedados en IIS como aplicaciones de usuario escritas en JavaScript. Y recordad que todo esto empieza con una simple línea de configuración en web.config, que no está nada mal. Aprovechadla!

Padeces “NuGet-itis”?

Cuando tú, programador, haces click en “Nuevo Projecto” en Visual Studio y el asistente termina, qué es lo siguiente que abres? Si la respuesta es agregar incluso más componentes de NuGet (o similares) puede que padezcas NuGet-itis.

Lo siento, es que hoy me he mosqueado. Estaba haciendo unas pruebas, necesitaba implementar un par de APIs básicos para hacer experimentos. Así que nada, abrí VisualStudio y creé un nuevo “proyecto Web/Web API”. Hasta ahí todo bien hasta que di a “Ok” y apareció ésta monstruosidad en mi entorno de desarrollo:

SimpleWebAPI-1

Lo triste no eran sólo las dependencias… lo espectacular era lo que había como “contenido” de mi nuevo servicio web:

SimpleWebAPI-2

Y eso que era un “Web API en blanco”, para añadir un par de cositas y probar unos servicios que tengo para probar. Es simplemente ridículo.

La idea de OWIN (para empezar) es un runtime “minúsculo” (ejem!) para el desarrollo de aplicaciones web con la mínima carga en el servidor. Obviamente, el que hizo la “plantilla” de Web API no leyó las especificaciones. Bootstrap? JQuery? Para un Web API? Pero qué locura es ésta? Y caí en cuenta… “Es que ahora, TODO va así”.

Recordé de repente a un compañero, con la tarea de utilizar una serie de archivos que contenían argumentarios para centros de llamadas, con contenidos codificados en BASE64. Se pasó tres días (literalmente) en buscar una implementación de BASE64 para Java, ignorando la que yo le escribí en media hora que hacía sobradamente el trabajo. O programadores que según empiezan un proyecto abren la consola de componentes para añadir “lo que necesitan”, aunque ello implique arrastrar (en la mayoría de los casos) enormes cantidades de código que ni siquiera necesitan (como el caso de mi proyecto “Web API”).

El problema de depender de NuGet (o similares) para realizar hasta el más simple de los proyectos es que en muchos casos las dependencias que arrastra determinan en qué forma hay que realizar el resto del proyecto. Me pongo en el ejemplo de varios tutoriales que muestran cómo añadir autentificación a una aplicación web: sólo funcionan si estás haciendo una aplicación web “desde cero”. Pero, qué pasa cuando ya tienes esa aplicación web hecha y simplemente quieres añadir la autentificación? Simplemente no puedes (os recomiendo fervorosamente que intentéis hacerlo siguiendo las instrucciones del tutorial). O eso, o te toca reescribir prácticamente la totalidad de tu aplicación – cosa que en muchos casos es incluso más caro que hacerte la autentificación a mano desde cero.

Entity Framework, OWIN, MVC, MVVM, todos son frameworks para conseguir hacer cosas que en muchos casos te “obligan” a desarrollar en cierta forma. Si tenías una base hecha, simplemente no puede ser portada directamente al nuevo entorno. O el mero hecho de añadir Entity Framework, que puede suponer un verdadero quebradero de cabeza de integrar con tu infraestructura de datos actual – o el error de asumir que tienes que usarlo absolutamente para todo, y es cuando acabas retorciendo el framework para que “tambien sirva” para lo que tenías antes, sin pensar que, simplemente, no te hace falta.

El problema finalmente reside en que muchos programadores modernos sólo saben usar este tipo de frameworks – y tristemente porque muchos leyeron el tutorial equivocado.. Les quitas Entity Framework o cualquier “sabor” de MVC o MVVM y simplemente están perdidos. Si, Entity Framework es absolutamente increíble, pero en su contexto – no sirve para todo. Al igual que muchos otros. Y el problema se agrava cuando hay múltiples opciones para un framework: no se sabe cual escoger, porque “todos hacen lo mismo” y todos son “maravillosos”.

Otro ejemplo, un “curso” de la MVA (Microsoft Virtual Academy) acerca de programación multiplataforma con Xamarin. Lo siento, pero eso no es un curso, eso es un tutorial de cómo pegar trozos de componentes – siendo generoso. Hace falta realmente Prism para hacer un “Hello World” en Android? No. Ni de lejos. El problema es que hacer el 90% del tutorial dejó de consistir en programación universal de aplicaciones para Android, iOS y Windows Phone para convertirse en cómo configurar una aplicación MVVM  – de una sola pantalla. Una sola! Queréis un excelente libro al respecto? No dudéis en descargar el ebook gratuito Creating Mobile Apps with Xamarin.Forms, Preview Edition. Ni MVC, ni NuGet ni nada – simplemente cómo se programa – porque el autor del libro asume que lo que haga la aplicación es cosa tuya. Y que sabes hacerlo.

Parece que hoy, todo lo que no se pueda escribir en doce (o mejor cinco?) líneas de código es “arcaico” o está mal hecho o “pues anda que no te has complicado la vida”. Ni mucho menos. Los componentes ayudan, pero cuando se limitan a una función concreta dentro de nuestro proyecto y, sobre todo, se adaptan a nuestra forma de trabajar. Si un framework, una librería, un componente define un cambio estructural en un proyecto, se debe buscar otra alternativa.

Programamos máquinas. Cuanto más liviano sea el proyecto, el ejecutable, el servicio web, mejor será el rendimiento de nuestra solución. No todo viene envasado en un componente de NuGet. A veces (la mayoría) toca mancharse las manos y hacer las cosas por uno mismo. Y lo mejor del caso, es que muchas veces esa es la forma correcta de hacerlo.

Categorías:Desarrollo, Opinión

C#–Cómo escribir código realmente rápido

Vuelvo tras una larga ausencia con un tema con el que llevo luchando el último año: optimizar código para mejorar la respuesta a los usuarios tanto a nivel de servidor como de aplicación usando incluso menos recursos que antes. Y si piensas en distribuir tu aplicación en la nube, incluso puede que ahorres dinero.

Cualquiera que me conozca sabe que mi obsesión es cómo exprimir las máquinas al máximo. Y por “exprimir” no significa “tenerlas echando humo” haciendo cosas, sino más bien al contrario: que un solo servidor pueda ejecutar las mismas funcionalidades pero con un consumo mínimo de CPU y memoria. Y en C#, mínimos cambios en los programas pueden acelerarlos de forma más que notable.

No hay que pensar en segundos… toca hacerlo en milisegundos

Es lo que veo muchísimas veces hablando con otros programadores que no ven ningún problema en su código: prácticamente hagas lo que hagas hoy en día con un ordenador, si sólo lo haces una vez, es virtualmente instantáneo. Escriben código, ejecutan el programa de prueba y violà! el resultado en “menos de un segundo”. El problema es que cuando eso mismo hay que hacerlo decenas de miles de veces por minuto (o segundo!) la cosa se complica: lo que antes tardaba “menos de un segundo” ahora tarda horas. Y es donde las micro-optimizaciones o los “refactorings” complejos (que veremos como parte final de este artículo) muestran sus resultados: limando no segundos, sino milisegundos a cada operación se obtienen beneficios simplemente espectaculares en el rendimiento de nuestros programas.

Importante: hay cosas que no se pueden optimizar: hay que rehacerlas

Y no se nos deben caer los anillos por ello. Puede que algo simplemente sea lento. Muy lento. Excesivamente lento. Y es muy probable que no tenga por qué serlo y la “optimización” no sea la respuesta, sino más bien un rediseño. Y no pasa nada: es lo que he hecho durante el último año. Si estás leyendo esto pensando en que vas a encontrar una pauta de programación que apliques “en dos líneas” a tu código y que de repente funcione deprisa, deja de leer ya: si, algo ganarás, pero no mucho. Pero sí hay infinidad de cosas a saber para mejorar de forma más que notable la velocidad y el rendimiento de nuestros actuales programas sin tener que tirarlos enteros y empezarlos de cero.

El truco? Optimizar bloque por bloque, función por función, y no pensando en que “quede más bonito”, sino que quede “más rápido” (lamentablemente en muchos casos no es compatible lo uno con lo otro, pero recuerda: comenta tu código). Y siempre: usa el Profiler de Visual Studio!

Pero vamos a un ejemplo real…

new() y el recolector de basura

O “garbage collector” como muchos lo conocemos. Nos permite olvidarnos de la gestión de memoria, de acordarnos de liberar los objetos que ya no usamos, pero con un precio: reservar memoria es “caro”, pero es que la gestión de la recogida de basura es carísima. Y eso que cada generación de la máquina virtual .NET mejora particularmente en eso, en la reserva y la limpieza de la memoria.

Por supuesto no estoy diciendo que crear objetos con new() sea malo, en absoluto. El problema, particularmente con C# y otros lenguajes gestionados, es que hay montones de new()’s implícitos en infinidad de operaciones que podemos evitar. Menos reservas de memoria, más velocidad. Menos objetos que gestionar, mucha más velocidad todavía. El otro problema es asumir que el coste de hacer new() es cero: nada más lejos de la realidad (creo que sólo hay otra operación más cara que reservar memoria: lanzar una excepción, pero eso es otra historia).

Un ejemplo simple pero práctico: típico programa que recibe registros en texto separados por comas. Cuantas veces habéis visto (o escrito) algo como esto:

static int[] Decode(string data)
{
  string[] dataFragments = data.Split(new char[] { ',' });
  List dataValues = new List();
  foreach (string fragment in dataFragments)
  {
    dataValues.Add(Int32.Parse(fragment));
  }
  return (dataValues.ToArray());
}

Alguien ve algo “raro”? No?

Algo tan simple como ese new char[] { ‘,’ } tan inocente es responsable de una merma en la velocidad de la función.

Si, esta función se ejecuta en muy, muy poco tiempo – si sólo se llama una vez. El problema es si hay que llamarla miles de veces. Miles de veces crearemos un array en memoria, de un carácter de capacidad, pero toca buscar memoria, asignarla, inicializarla… Una vez no pasa nada… un par de millones de veces y la cosa se complica.

Esa es la típica situación que mejora simplemente con un mínimo cambio en el programa:

private static readonly char[] splitChars = new char[] { ',' };
static int[] Decode(string data)
{
  string[] dataFragments = data.Split(splitChars);
  List dataValues = new List();
  foreach (string fragment in dataFragments)
  {
    dataValues.Add(Int32.Parse(fragment));
  }
  return (dataValues.ToArray());
}

Tan sencillo como usar una variable estática, inicializada al principio de la ejecución del programa, y eliminamos miles y miles de reservas de memoria. Para comprobarlo, vamos a escribir un programa de pruebas que ejecute las dos funciones un millón de veces, para comprobar si hay alguna diferencia:

public static void Run()
{
  Console.WriteLine("Código inicial en ejecución.");

  string record = @"23,51,181,47,193,119,651,45,213,194,413,188";
  var start = DateTime.Now;

  int sum = 0;
  for (int dd = 0; dd < 1000000; dd++)
  {
    var dataElements = Decode(record);
    sum = 0;
    for (int zz = 0; zz < dataElements.Length; zz++)
      sum += dataElements[zz];
  }

  GC.Collect();
  var span = DateTime.Now - start;
  Console.WriteLine("Suma: " + sum + " - Tiempo total: " + span.ToString());
}

Este programa simplemente decodifica el mismo registro 1 millón de veces y, por hacer algo, simplemente suma los valores de los números (así verificamos que todas las variantes devuelven los mismos resultados). Ahora ejecutamos el programa en versión Release con las dos versiones de la función:

Base y primera optimización

Simplemente por evitar esa reserva de memoria nos ahorramos 140 milisegundos: un 5% del tiempo total (he calculado el ahorro sobre varias ejecuciones – nunca tarda lo mismo). No está mal para una sola línea modificada.

Pero vayamos un poco más allá. Hay otra reserva de memoria al final de la función, al hacer una conversión a array (return(dataValues.ToArray()). Si devolvemos directamente el objeto dataValues, sin conversión, qué pasa? Cambiemos

static int[] Decode(string data)
{
  ...
  return (dataValues.ToArray());
}

por

static List<int> Decode(string data)
{
  ...
  return (dataValues);
}

y ajustamos un poco la función de prueba para que use la propiedad Count en lugar de la propiedad Length de los arrays. El resultado:

StringOptimizationRun2

Otros 50 milisegundos que hemos limado al tiempo original. Y todavía no hemos trabajado mucho para un ahorro del 7%.

Poco más se puede rascar, verdad? Pues no… aún queda mucho por hacer.

Empieza el refactoring

Hasta ahora hemos simplemente cambiado cosas de sitio o hemos eliminado alguna reserva de memoria. Cuando ese proceso no da para más, toca pensar cómo cambiar la estructura del código para que sea más eficiente.

El siguiente responsable de reservas de memoria es el objeto List<int> que usamos para almacenar los valores mientras se leen del registro de entrada. Y si en lugar de eso usamos un array permanente para devolver los valores? Vamos con el refactoring, y aquí es donde tenemos que poner un límite a los datos.

Hasta ahora nuestro código podía soportar cantidades arbitrarias de elementos en un registro, pero es necesario realmente? O podemos poner un límite de, digamos 100 elementos? 200? 500 quizá?

Cambiemos la función para que quede como sigue:

private static readonly char[] splitChars = new char[] { ',' };
static List Decode(string data)
{
  string[] dataFragments = data.Split(splitChars);
  List dataValues = new List();

  foreach (string fragment in dataFragments)
  {
    dataValues.Add(Int32.Parse(fragment));
  }
  return (dataValues);
}

Si os fijáis la función espera ahora un array de enteros que rellenará con los valores que obtenga del registro, devolviendo como resultado el número de elementos que ha leído. Otro poco más de refactoring en la función de pruebas para que quede como sigue:

int[] values = new int[512];
int sum = 0;
for (int dd = 0; dd < 1000000; dd++)
{
  var elementCount = Decode(record,values);
  sum = 0;
  for (int zz = 0; zz < elementCount; zz++)
    sum += values[zz];
}

La función reserva primero un único array de enteros que usa para todas las llamadas a la función. Da igual que el array sea local a la función o miembro de la clase: sólo se crea uno y se reutiliza continuamente. Si ejecutamos el programa ahora con la nueva versión obtenemos lo siguiente:

StringOptimizationRun3

Ahora sí se ve una mejora realmente sólida del rendimiento, del orden nada menos que del 20% con respecto al código original.

Pero se puede ir mucho, muchísimo más lejos.

El siguiente responsable de la velocidad de la función es el Split() que hacemos de la cadena. Split tiene que fabricar internamente un List<String> para almacenar cada cadena que va truncando, reservar cada objeto String de cada elemento separado, inicializar los contenidos (sacando copias de fragmentos de la cadena original), agregarlo a la colección para finalmente invocar el método ToArray() y devolvernos el array de cadenas. Y una vez que terminamos la función, todo ese trabajo se destruye y pasa al recolector de basura. Y todo eso destruye el rendimiento, cada reserva de memoria y copias por la RAM machacan los contenidos del caché del procesador, volviéndolo prácticamente una inutilidad. Los procesadores modernos son felices leyendo memoria secuencial, y el mero concepto de crear miles de objetos desperdigados por la memoria del PC va en detrimento del rendimiento.

Pero qué se puede hacer en ese caso?

Optimización final

Pues en este caso, nada tan facil como no usar lo que parece obvio. Vamos a pensar en el registro que nos dan no como en una cadena que sólo podemos utilizar por medio de otras funciones, creando copias, fragmentándola, sino como un simple array de caracteres. Porque, curiosamente, lo es.

La versión final de la función recorre la cadena de caracteres uno por uno: si el caracter es un dígito lo acumula, si el caracter es una “coma” completa la acumulación y añade el valor al array de enteros del resultado, haciendo simplemente operaciones aritméticas simples, con las que el procesador es feliz (por cierto: si sabes C++, te sentirás como en casa):

static int Decode(string data,int[] values)
{
  int charCount = data.Length;
  int valueIndex = 0;
  int acc = 0;
  for (int dd=0;dd<charCount;dd++)
  {
    char c = data[dd];
    if ((c >= '0') && (c <= '9'))
    {
      acc *= 10;
      acc += (c & 0xF);
      continue;
    }
    if (c == ',')
    {
      values[valueIndex++] = acc;
      acc = 0;
      continue;
    }
    throw new Exception("Invalid character in data.");
  }
  // Añadir el último valor
  values[valueIndex++] = acc;

  return (valueIndex);
}

Tampoco ha sido para tanto… pero veamos cuanto hemos ganado:

StringOptimizationRun4

No, no es una broma: de un proceso que tardaba 2,5 segundos en ejecutarse se ha quedado al final en 100 milisegundos. 25 veces más rápido que el original.

Y el truco simplemente consiste en optimizar el código en bloques funcionales de tamaños como el que acabamos de ver, que permiten concentrarse en un problema concreto, determinar los requerimientos exactos y escribir un código que haga “eso” y “nada más que eso”.

Pensando en la nube

Y si realmente te estás planteando hospedar tu flamante proyecto en la nube, empezar pensando en las optimizaciones como las que acabas de ver te puede ahorrar un dineral, en éste caso concreto en el costo de máquinas virtuales, o en caso de máquinas físicas poder emplear PCs mucho más económicos con el mismo rendimiento. Pero hay que ser metódico, no puedes decirte a tí mismo “bueno, ya optimizaré al final”, porque quizá, cuando llegue el momento, descubras que el “refactoring” requiere de una reescritura completa. Y créeme, llegado ese momento ya es tarde…

Por cierto, otra pequeña optimización de la que no he hablado: cuantos componentes de .NET tenéis referidos en la sección References de vuestros proyectos? Seguro que los usáis todos? Quizá puedas eliminar alguno de ellos o más de los que crees. Y una DLL que no se carga (junto con sus propias dependencias) es otro ahorro de recursos.

El proyecto completo con todas las versiones de optimización podéis descargarlo aquí. Y podéis comprobar qué referencias usa. Sólo una: System.DLL.

Categorías:Desarrollo, Tutorial Etiquetas: , , ,

Temporizadores nativos de Windows con C++ 11

Existen multitud de razones para programar en C++. Pero el API de Windows puede parecer un poco intimidante al principio y esa es la razón por la que tanta y tanta gente prefiere usar o bien librerías que se hagan cargo del bajo nivel o de entornos gestionados donde todo es mucho más fácil.

Pero la verdad es que con poquísimo esfuerzo es posible ir creando pequeñas herramientas que nos faciliten la vida y que llevan a la creación de código extremadamente legible y fácil de mantener.

El problema con los temporizadores

Hacer un temporizador en el API nativo de Windows puede ser, bien extremadamente simple o bien sorprendentemente complejo, dependiendo de los conjuntos de APIs que queramos emplear. Si usamos ventanas clásicas (ay! esos tiempos), con la función SetTimer() conseguimos que nos llegue un mensaje WM_TIMER periódicamente a nuestra ventana “y ya está”. La cosa se complica cuando queremos timers asíncronos, donde nos veremos obligados a emplear threads para hacerlo.

Lo que busco es poder aprovechar la versatilidad de C++ en su versión 11 y poder escribir algo como sigue:

CTimer timer;timer.Set(1000,1000,[]() 
  { 
    putc('.',stdout); 
  });

Bonito, verdad? Sin crear threads, sin declarar cosas globalmente, sin mensajes a ventanas. Todo ello se puede hacer empleando el API de Windows nativo, con una simple clase que nos haga un poco el trabajo por debajo aprovechando la función CreateTimerQueueTimer():

BOOL WINAPI CreateTimerQueueTimer(
  _Out_     PHANDLE phNewTimer,
  _In_opt_  HANDLE TimerQueue,
  _In_      WAITORTIMERCALLBACK Callback,
  _In_opt_  PVOID Parameter,
  _In_      DWORD DueTime,
  _In_      DWORD Period,
  _In_      ULONG Flags
);

Vamos a ver un poco por encima qué son todos esos argumentos que requiere el API. Para empezar, phNewTimer es un puntero a un HANDLE estándar de Windows (PHANDLE). Es una variable que deberemos suministrar al API y donde, una vez invocado, nos devolverá el handle sobre el que realizaremos el resto de las operaciones con el mismo. TimerQueue es un argumento opcional, que nos permite emplear colas de timers “custom” hechas a medida, si es que deseamos un control tan fino de la ejecución. En nuestro ejemplo vamos a emplear NULL para solicitar la cola predefinida por el sistema.

Aquí es donde viene la parte interesante, en el argumento Callback. Un tipo WAITORTIMERCALLBACK básicamente define un tipo concreto de función a la que el sistema operativo invocará al dispararse el temporizador. Con eso iremos en unos minutos, junto con Parameter, argumento que se pasará de vuelta a nuestra Callback.

DueTime y Period son, como podréis haber podido imaginar, los tiempos para el primer disparo y subsiguientes, definidos en milésimas de segundo. Finalmente, Flags es una variable donde podremos incluir opciones de funcionamiento de nuestro timer.

Dejémoslo bonito

Vamos a la implementación. Para empezar, necesitaremos guardar en nuestra clase CTimer el handle que el API de Windows nos devolverá al crear el timer, y que inicializamos a nulo en el constructor de la clase. También necesitaremos una forma de almacenar el código que deseamos ejecutar con el timer, y que nos llegará como expresión lambda de C++:

#include <functional>
class CTimer
{
public:
  CTimer()
  {
    m_hTimer = NULL;
  }
  virtual ~CTimer()
  {
    Cancel();
  }

private:
  HANDLE m_hTimer;
  std::function<void()> m_callback;
};

La plantilla std::function<> (declarada en functional.h) es un utilísimo almacén especializado precisamente en eso: en almacenar expresiones lambda, dada simplemente su firma (en nuestro caso “void()”). Aprovechamos para incluir el destructor y una llamada a una función, Cancel() que destruirá el timer junto con nuestro objeto. Ya que tenemos los miembros preparados e inicializados, vamos con la parte realmente divertida: la función Set() que nos permitirá configurar el timer:

HRESULT Set(DWORD dueTime,DWORD period,std::function<void()> callback)
{
  if (m_hTimer == NULL)
  {
    if (CreateTimerQueueTimer(&m_hTimer,NULL,&internalCallback,this,
                              dueTime,period,
                              period ? WT_EXECUTEDEFAULT : WT_EXECUTEONLYONCE) == FALSE)
      return(GetLastError());
  }
  else
  {
    if (ChangeTimerQueueTimer(NULL,m_hTimer,dueTime,period) == FALSE)
      return(GetLastError());
  }
  m_callback = callback;
  return(S_OK);
}

La función Set() crea o altera el timer nativo dependiendo de si la variable m_hTimer ya tiene un valor. De esta forma podremos alterar el timer al vuelo, cambiando por ejemplo los tiempos de disparo. Cada llamada a Set() tambien almacena en la variable m_callback la expresión lambda que queremos emplear – esto nos permitiría, por ejemplo, emplear el mismo timer con distintas funciones lambda dependiendo de nuestras necesidades.

Si os fijáis, al llamar al API siempre usamos la misma función callback: internalCallback. Esta función, junto con el parámetro “this” que le sigue, nos permitirán a la hora de recibir la notificación del sistema operativo, saber qué lambda debemos invocar.

La implementación de la función internalCallback() sería como sigue:

private:
static VOID CALLBACK internalCallback(PVOID lpParameter,BOOLEAN TimerOrWaitFired)
{
  CTimer *pTimer = (CTimer*)lpParameter;
  if (pTimer->m_callback)
    pTimer->m_callback();
}

La función es estática (el API de Windows no sabe de clases de C++), pero hemos incluido la instancia del objeto como argumento al crear el timer, y nos llegará en el argumento lpParameter. Simplemente castear el parámetro a CTimer* nos devuelve un puntero utilizable para determinar el contenido de la función lambda e invocarla.

Finalmente, la función Cancel() destruye el timer. Podremos invocar esta función manualmente o simplemente, dejar que se llame desde el destructor de la clase:

HRESULT Cancel()
{
  if (m_hTimer != NULL)
  {
    DeleteTimerQueueTimer(NULL,m_hTimer,NULL);
    m_hTimer = NULL;
  }
  return(S_OK);
}

La función DeleteTimerQueueTimer() del API destruye un timer creado previamente con CreateTimerQueueTimer(), pasando el handle que nos devolvió al crearlo.

Y ya está. Hemos creado desde cero un componente que encapsula el API de Windows dentro de una clase con todas las funcionalidades del lenguaje C++. Vamos a darle un poco de contexto a nuestro ejemplo al principio del artículo, para poder probar el componente:

int _tmain(int argc, _TCHAR* argv[])
{
  printf("AsyncTimer test\n");

  CTimer timer;
  timer.Set(1000,1000,[]()
  {
    putc('.',stdout);
  });

  printf("Press any key ");
  _getch();

  return 0;
}

Chorra, pero nos sirve: el programa simplemente imprime un mensaje (“Press any key”) mientras nuestro timer imprimirá un punto cada segundo hasta que pulsemos una tecla. El programa está totalmente detenido en la función __getch(), que detiene la ejecución hasta que se pulse una tecla. El timer se ejecutará de forma totalmente asíncrona, en un thread gestionado por el sistema operativo. Al terminarse el programa y salir de la función, el objeto timer se destruirá, cancelando el temporizador y limpiando los recursos antes de la salir.

A continuación, la clase completa, y a continuación, enlaces al proyecto completo de ejemplo:

#pragma once

#include <functional>

class CTimer
{
public:
  CTimer()
  {
    m_hTimer = NULL;
  }
  virtual ~CTimer()
  {
    Cancel();
  }

public:
  HRESULT Set(DWORD dueTime,DWORD period,std::function<void()> callback)
  {
    if (m_hTimer == NULL)
    {
      if (CreateTimerQueueTimer(&m_hTimer,NULL,&internalCallback,this,
      dueTime,period,
      period ? WT_EXECUTEDEFAULT : WT_EXECUTEONLYONCE) == FALSE)
      return(GetLastError());
    }
    else
    {
      if (ChangeTimerQueueTimer(NULL,m_hTimer,dueTime,period) == FALSE)
        return(GetLastError());
    }
    m_callback = callback;
    return(S_OK);
  }

  HRESULT Cancel()
  {
    if (m_hTimer != NULL)
    {
      DeleteTimerQueueTimer(NULL,m_hTimer,NULL);
      m_hTimer = NULL;
    }
    return(S_OK);
  }

private:
  static VOID CALLBACK internalCallback(PVOID lpParameter,BOOLEAN TimerOrWaitFired)
  {
    CTimer *pTimer = (CTimer*)lpParameter;
    if (pTimer->m_callback)
      pTimer->m_callback();
  }

private:
  HANDLE m_hTimer;
  std::function<void()> m_callback;
};

El proyecto completo está aquí.

Categorías:Desarrollo, Tutorial Etiquetas:

YASS ha encontrado un hogar

Los que hayáis seguido el tutorial que he publicado durante el verano acerca del desarrollo de un emulador de Sinclair ZX Spectrum, sabed que YASS (“Yet Another Spectrum Simulator”) ha encontrado un hogar adoptivo en Speccy.org – el mayor foro de usuarios de Spectrum en castellano.

YASSAtSpeccyPoco después de terminar el tutorial (imagino que ya me vale) descubrí Speccy.org, uno de los mayores (si no el mayor) foro de usuarios de Sinclair ZX Spectrum de habla hispana. Lleno a rabiar de gente que sabe muchísimo más que yo de tan adorable máquina, mi intención era simplemente dejarles saber que había un nuevo emulador y que, quizá, alguien podría encontrar mi trabajo interesante.

El recibimiento por parte de la gente de Speccy fue simplemente como si fuese uno más de la familia: mensajes de bienvenida, sugerencias para mejorar el emulador y ampliar sus capacidades, ideas a cual más interesante para continuar con el proyecto. Y para mi sorpresa, los administradores me invitaron cordialmente a hospedar YASS y los contenidos del tutorial en sus propios servidores, junto con otra inmensa cantidad de proyectos también relacionados con la emulación y preservación del ZX Spectrum.

Me pareció una excelente idea, para preservar YASS, para facilitar su acceso – tenerlo como artículos sueltos en un blog hace bastante complicado no perderse en la navegación. Así que rehíce los contenidos y podréis acceder a ellos de forma mucho más cómoda (y mejor organizada) en http://yass.speccy.org.

Desde aquí, agradecer a los administradores de speccy.org la posibilidad de sentirme uno más de una familia. Gracias!

Categorías:Desarrollo, Noticias Etiquetas: , , , , , ,

Hazte un Spectrum (8ª Parte y final)

Todos los componentes están realizados y en su sitio, el código ha ido tomando forma con una maravillosa fluidez y he aprendido lo indecible durante estas breves semanas. Aprovecho que mañana vuelvo a la dura vida cotidiana para cerrar esta serie de artículos – unos remates finales y completar el código fuente.

Hemos llegado al final del camino – unas semanas que me han servido para conocer un poco más uno de los mitos de la informática personal de los años ‘80 y que para mí, hasta hace unas semanas, era casi un completo desconocido. Hemos empezado por diseñar una máquina virtual, ir aprendiendo cómo funcionaban los diferentes componentes de la misma y emulando su comportamiento. Hoy concluye este fascinante viaje. Vamos a divertirnos un poco y darle incluso un aire más “retro” al emulador, aprendiendo de paso cómo aprovechar las capacidades de aceleración hardware de gráficos de WPF.

Vamos a empezar con una herramienta imprescindible para diseñar interfaces de usuario en aplicaciones modernas: Photoshop. Con ella vamos a realizar una capa gráfica (“overlay”) que podremos activar a voluntad para darle ese toque retro, imprescindible para mi: el “efecto TV” o, como me ha dado por llamarlo en el emulador, el “modo Telefunken” (y apuesto a que mi hermano estará de acuerdo conmigo a que el nombre le viene que ni pintado gracias a nuestras experiencias de chavales).

Una nueva herramienta de desarrollo: Photoshop

Comencemos (sorprendentemente) buscando en Google Images por un fondo apropiado. Nada más facil que navegar http://images.google.com y buscar por “TV frame”. Busco un “marco de televisor”, y he escogido el que me ha parecido más simpático, que incluso ya viene con el hueco transparente. Veamos los pasos siguientes, uno a uno:

El primer paso es abrir la imágen que más nos guste en Photoshop y ajustar su tamaño a 640×480. Invocando el menú Image/Image Size podremos ajustarlo. Si habéis cargado la imagen como “Background”, haced doble click sobre ella para convertirla en una capa normal (Layer). Ponedle como nombre “TV Frame” por ejemplo y borrad todo lo que pudiese verse dentro la imagen para que quede transparente: TVLayer-4
Vamos a crear una nueva imagen, con un tamaño algo peculiar: 1 pixel de ancho por 2 de alto. Esta imagen nos servirá como “relleno” para el familiar efecto de las líneas en pantalla TVLayer-1
Tras crear la imagen, seleccionamos la herramienta “lápiz” y rellenamos el pixel de arriba en negro, dejando el pixel de abajo transparente TVLayer-2
Seleccionad la imagen con Ctrl+A y luego Edit/Define Pattern. Yo le puse de nombre “ScanLine”. TVLayer-3
Volved ahora a la imagen principal con nuestro televisor. Añadid una nueva capa y aseguraos que queda debajo del marco del televisor. Ponedle de nombre por ejemplo “Scanlines”: TVLayer-5
Aseguraos que tenéis la capa Scanlines seleccionada y rellenadla con el patrón que diseñamos antes. Pulsad mayúsculas+BackSpace, seleccionad “Pattern” de la lista de opciones y el patrón Scanlines como “Custom Pattern”: TVLayer-6
Ya tenemos nuestro familiar efecto de líneas de televisor… simplemente ajustad la opacidad de la capa “al gusto” (40-60%) TVLayer-7
Agregad una nueva capa al diseño, y colocadla entre el marco del televisor y la capa de líneas. Yo la he llamado “Glare”: TVLayer-8
Seleccionad la capa y empleando la herramienta de selección, dibujad un rectángulo que ocupe más o menos el tercio superior de la pantalla. Luego redondead la selección con la herramienta Select/Modify/Feather y algo así como 25 píxeles: TVLayer-9
Comprobad que vuestros colores son el blanco para la tinta y el negro para el fondo, seleccionad la herramienta de gradientes y en la barra superior escoged la configuración “Foreground to transparent” (esto es, de blanco – nuestro color actual – a transparente). TVLayer-10
Ahora pulsad con el botón más o menos en la zona superior de la selección y arrastradla hasta un poco antes de la zona inferior. Luego ajustad un poco la opacidad de la capa, quizá entre 60-70%: TVLayer-11

Ya tenemos el overlay listo – simplemente guardadlo dentro del proyecto del emulador como PNG de 32 bits (con transparencia!!) y ya podemos importarlo en el proyecto. Para ello pulsad con el botón derecho del ratón sobre el proyecto en el que queráis añadir la imagen y seleccionad “Add/Existing Item”. Buscad la imagen PNG, aceptad el diálogo y aseguraos que en las propiedades de la imagen, la entrada Build Action aparece configurada como “Resource”: el compilador “incrustará” la imagen dentro del binario, permitiéndonos olvidarnos de distribuir los ficheros sueltos junto a la aplicación.

Ahora abrid el archivo MainWindow.xaml, y añadid una nueva imagen justo debajo del control zx:Spectrum:

    <zx:Spectrum x:Name="emulator" Grid.Row="1" />
    <Image x:Name="tvOverlay" Grid.Row="1" Source="/YASS;component/Resources/TVEffect.png" Stretch="Fill" Visibility="Collapsed" />

La propiedad Source especifica la imagen que debe emplear el control, en este caso Source=”/YASS;component/Resources/TVEffect.png”. La primera parte (“/YASS;”) indica el nombre del ensamblado que lleva el archivo, en este caso “YASS.exe” que será el nombre del ejecutable. Lo que sigue es el camino (o “path”) hasta el recurso: en mi caso el archivo se llama “TVEffect.png” y está dentro de la carpeta “Resources” del proyecto. Si no queréis tener que averiguar el camino al archivo, insertad una imagen sin campo “Source” y luego simplemente usad las propiedades del control en Visual Studio. Navegad hasta la propiedad Source en la lista y seleccionad directamente la imagen con el menú desplegable – mucho más fácil que ponerlo a mano y sin errores!!! Fijaos que los dos elementos (el control zx:Spectrum y la imagen “tvOverlay” están situados en la misma celda del Grid que los contiene. El efecto es que la imagen cubrirá permanentemente al control, sin tener que hacer absolutamente nada en código – solo cambiar su visibilidad.

Aprovechad tambien para añadir al menú de la aplicación una entrada para activar o desactivar el “modo Telefunken”:

<Menu>
  ...
  <MenuItem Header="Load tape..." Click="OnSelectTape"/>
  <Separator/>
  <MenuItem x:Name="tvModeMenu" Header="Telefunken mode" Click="OnTVMode" />
  ...
</Menu>

Finalmente, basta con agregar la función “OnTVMode()” al programa, que alternará la visibilidad de la capa:

  private void OnTVMode(object sender, RoutedEventArgs e)
  {
    // Invert visibility of tv overlay
    tvOverlay.Visibility = tvOverlay.Visibility == Visibility.Collapsed ? Visibility.Visible : Visibility.Collapsed;
    tvModeMenu.IsChecked = tvOverlay.Visibility == Visibility.Visible;
  }

Si ejecutamos el emulador y seleccionamos el menú “Telefunken Mode”, el efecto será un emulador incluso más retro:

YASS-OldieLook

El overlay está acelerado por hardware, así que la capa no afecta en lo más mínimo a la velocidad de ejecución del emulador, ni incrementa el consumo de la cpu – la tarjeta gráfica hace todo el trabajo.

Alguna cosita más?

Pues si, un par de cosas que he añadido al código final antes de publicarlo. Lo más significativo es que al seleccionar un archivo de cinta ya no es necesario escribir LOAD “” para iniciar la carga. La solución, rápida y simple: una función que emula la pulsación de las teclas para escribir el comando tras abrir el archivo TAP. Muy simple, pero cumple con su cometido – con la pega de que necesitamos que el Spectrum esté dispuesto a aceptar el comando para que funcione. También encontraréis opciones para parar, arrancar y resetear el emulador – funciones que he empleado para depurar algunas cosas y asegurarme de que no me dejaba nada “tirado” por ahí.

Bueno… creo que eso es todo

El emulador está completo y el viaje ha llegado a su fin. Me he divertido mucho, realmente mucho durante estas semanas y me ha encantado intentar documentar todo el proceso. El emulador que os dejo está lejos de ser completo o de ejecutar “absolutamente todos los programas del Spectrum” – no había tiempo para tanto, siendo más bien mi intención la de ofreceros un tutorial completo sobre cómo desarrollar un emulador “desde cero”, cómo combinar varios lenguajes de programación para obtener los mejores resultados y, sobre todo, mostraros cómo era la informática de los años 80. He intentado mantener el código fuente lo más limpio y estructurado posible y creo que es bastante facil de leer y, sobre todo, ampliar.

Como os decía al principio, hace unas semanas no conocía apenas nada del Sinclair ZX Spectrum, y seguro que algún error habré cometido al documentar o emular alguna funcionalidad – espero que sepáis disculparme y, sobre todo, dejadme saber dónde he metido la pata – quiero seguir aprendiendo!

Mañana me toca volver a los quehaceres diarios, pero esta “retromanía” me sigue afectando… cual podría ser el próximo proyecto?

Enlaces:

Emulador YASS para Windows
Código fuente del Emulador YASS
Código fuente del Emulador YASS – actualizado a VS2015

– Hazte un Spectrum: 1ª Parte, 2ª Parte, 3ª Parte, 4ª Parte, 5ª Parte, 6ª Parte, 7ª Parte, 8ª Parte

Categorías:Desarrollo, Tutorial Etiquetas: , , , , ,