Archivo de la categoría: Desarrollo

Posts sobre programación y desarrollo

Patrones de diseño I – Introducción

Recientemente, un compañero de trabajo nos ha realizado una workshop acerca de diseño por capas y patrones, al que hemos asistido un grupo de 7 desarrolladores. Se han comentado bastantes cosas que desconocía pero en líneas generales eran ideas muy genéricas que está bien refrescar. En cualquier caso, me ha sorprendido enormemente que gran parte de los compañeros ni tan siquiera habían oído hablar de patrones de diseño de software. Cierto es que no somos una empresa dedicada al desarrollo como tal, pero aún así me sorprende que ni tan siquiera se hubiera oído acerca de su existencia.

Es por esto que me he decidido a escribir una serie de entradas acerca de patrones de diseño de software. Mi objetivo es publicar los patrones de diseño más habituales explicando los casos en los que puede ser útil su uso, apoyándome en ejemplos.

En primer lugar, la pregunta: ¿que es un patrón de diseño (dessign pattern)? Los patrones son soluciones a problemas comunes en el diseño de software, que ya han sido resueltos por otros programadores anteriormente (habitualmente más sabios) y en los que nos podemos apoyar a la hora de afrontar problemas similares.

Conocer estos patrones puede ayudar a los desarrolladores dado que facilita el aprendizaje y constituye una herramienta muy útil para estandarizar el modo de afrontar este tipo de retos. Los patrones de diseño se suelen clasificar en tres categorías: patrones de creación, patrones de estructura y patrones de comportamiento.

Vamos a comenzar viendo los patrones de creación. Los patrones de creación son planteamientos acerca del modo de construir nuevos objetos en la aplicación. Los más conocidos que se engloban en esta categoría son los siguientes:

  • Abstract factory
  • Factory method
  • Singleton
  • Prototype
  • Builder

Los patrones de estructura se postulan como soluciones para estructurar las clases. Los que se suelen incluir en esta categoría son:

  • Adapter
  • Bridge
  • Composite
  • Decorator
  • Facade
  • Flyweight
  • Proxy

Por último los patrones de comportamiento se centran en resolver problemas relacionados con el comportamiento del software. En esta última categoría se engloba:

  • Chain of responsibility
  • Command
  • Interpreter
  • Iterator
  • Mediator
  • Memento
  • Observer
  • State
  • Strategy
  • Template method
  • Visitor

En los próximos días, publicaré el primer post acerca de patrones de creación. Espero que os sea útil.

Hasta pronto

Mantenibilidad en proyectos

Esta semana, me he visto obligado a trabajar en uno de esos proyectos zombie, donde da igual las veces que acabes con ellos porque siempre vuelven para perseguirte. En algunos casos como el que me ocupa hoy, el código es ajeno y no puedo contactar con el ***** que lo engendró. Y es que existe una práctica muy común en las ingenierías (sobretodo en empresas pequeñas) donde se tiende a subestimar los recursos necesarios para llevar a cabo un proyecto (en muchas ocasiones esta sub-estimación es forzosa, claro), pero a medida que la empresa crece, las malas costumbres suelen persistir.

El caso es que lidiar con ese código infame me ha permitido recordar ciertas rutinas consideradas “buenas prácticas” en la universidad, pero que yo considero reglas inquebrantables cuando nos movemos en entornos más profesionales.

Leyes de la mantenibilidad de código:

  1. Comentar sí, pero sin pasarse. Citando a Steve McConnell (autor de grandes biblias como  “Code Complete”) “Good code is its own best documentation. As you’re about to add a comment, ask yourself, ‘How can I improve the code so that this comment isn’t needed?’”
  2. Modularidad. Si un método, función o rutina se va a alargar tanto que se puede perder el hilo al leerla, es buena idea separarla en varias. En general, no se me ocurren casos donde un método deba ocupar más de 200 líneas. Aumentar la modularidad, ayuda también a cumplir la siguiente regla.
  3. Límite de parámetros. No generar rutinas donde la cabecera sea ilegible. Algunos autores afirma que 7 debería de ser el número máximo de parámetros por el límite a la capacidad cognitiva que enunció Miller (1956).
  4. Tamaño de línea. Se considera que superar la longitud de 80 columnas por fila es una falta de estilo. Esto es debido a que 80 son las columnas que caben en un folio impreso. Además, facilita la lectura.
  5. Notificación al usuario. Cuando se notifique de algún error, o evento de traza para debug, se debe aportar la información justa y en ningún caso alarmar al usuario. Caso de estudio la BSOD de Microsoft.
  6. Tiempos de vida cortos. Las variables se declaran cuando van a utilizarse, y se destruyen lo antes posible. De forma que su ciclo de vida sea lo más corto (en líneas de código) posible. Nada de declarar al inicio del programa todas las variables necesarias.
  7. Excepciones. Catch vacío es mal. Distintos catches para distintas excepciones dentro del mismo try es bien.
  8. Análisis. Dedicar tiempo de análisis al inicio puede ahorrar muchas horas después. Es bueno diseñar el software con todas las armas que nos dé el lenguaje concreto para evitar pérdidas de tiempo más adelante. Desglose en clases (en POO), funciones, rutinas…
  9. No abusar de las líneas en blanco. Un número de líneas en blanco elevado (mayor al 10-20% del código) es absolutamente contraproducente. Dificulta la lectura y reduce la cohesión del código.
  10. Documentación. Quizás la parte más importante. Se suele aceptar como motivo de aplauso y regocijo general encontrar un software con documentación, tanto generada automáticamente (como doxygen, por ejemplo) como redactada por el propio programador (manuales, tutoriales, guías, screenshots …). El hecho de utilizar herramientas para documentar ya hace que se deba seguir cierto protocolo en los comentarios (Javadoc, por ejemplo), lo que enriquece el estilo. Escuchar que tal proyecto no necesita documentación (sea cual sea la excusa) es razón suficiente para abandonar una conversación.

Bueno, esto es lo que para mí (y muchos otros autores) es fundamental a la hora de desarrollar algo que el simple hecho de contemplarlo no produzca una sensación de querer matar morir. Me despido con una cita muy acorde con el tema:

“Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.”
– Martin Golding

Voy a seguir a mi tarea de mantener este engendro. Entre tanto, puede que me sirva de ayuda un poco de relajación. Os paso el link que voy a utilizar para ello.

Enlaces:

Gestión del tiempo en el desarrollo de videojuegos

Leo en CyberHades que ya hace un año de la reaparición del código fuente del Prince of Persia por parte de Jordan Mechner, que fue el videojuego que me hizo tocar por primera vez un ordenador :cry:. Esto de por sí ya merece una serie de entradas, tanto el juego y su desarrollo (no prometo poder resistirme a escribir sobre él en el futuro) como la odisea de la recuperación posterior de dicho código de formatos físicos maltratados por el tiempo (traducción más o menos libre de éste artículo de wired).

El motivo de esta entrada, es porque leo que el gran Fabien Sanglard ha publicado las tres primeras partes de una de sus clásicas reviews de código de Prince of Persia. Para quien no lo conozca, Sanglard es un tipo que gusta de leer el código fuente de videojuegos clásicos antes de almorzar. Lecturas muy recomendables que merecen otro post.

Esto me ha hecho volver a leer algunos de sus fantásticos posts, entre los cuales he dado con uno que me gustaría comentar, más que nada porque cuando desarrollas videojuegos (a cualquier nivel) antes o después vas a topar con este problema, la gestión del tiempo en el bucle principal.

Es habitual, sobretodo entre los clásicos (yo siempre lo he hecho así) utilizar el paradigma de actualización siguiente:

  1. Obtener el lapso de tiempo entre la última actualización del juego y el momento actual
  2. Actualizar el mundo en función de ese valor y el valor de las entradas
  3. Renderizar (pintar) el mundo

Sirva como ejemplo el bucle principal de Quake (1996, idSoftware):

WinMain
{
	while (1)
	{
		newtime = Sys_DoubleTime ();
		time = newtime - oldtime;
		Host_Frame (time)
		{
			setjmp
			Sys_SendKeyEvents
			IN_Commands
			Cbuf_Execute
			/* Network */
			CL_ReadPackets
			CL_SendCmd
			/* Prediction//Collision */
			CL_SetUpPlayerPrediction(false)
			CL_PredictMove
			CL_SetUpPlayerPrediction(true)
			CL_EmitEntities
			/* Rendition */
			SCR_UpdateScreen
		}
		oldtime = newtime;
	}
}

Como vemos, se obtiene el tiempo actual del sistema, y se obtiene la diferencia con el momento de actualización anterior. En función de eso se actualiza el estado del juego. Como digo, es la forma habitual de hacerlo y habitualmente funciona a la perfección, pero en algunos casos introduce una problemática adicional. ¿Que ocurre si ese lapso de tiempo se agranda?

Actualización temporal en videojuegos (Imagen de Fabien Sanglard)

Si el tiempo de actualización se hace más grande, significa que ocurrirán más cosas en el juego que nosotros tardaremos en saber. Si este efecto se lleva al extremo, podemos llegar a perder cosas importantes como la detección de colisiones. Un ejemplo:

Progresión de movimiento en una colisión (Imagen Fabien Sanglard)

En este ejemplo, si hacemos una actualización en el instante 0, y el tiempo de actualización se demora 12 o más milisegundos no habríamos detectado la colisión de los objetos A y B, por lo que el usuario habría visto que ambos objetos se atraviesan.

El problema en realidad no es que se alargue o no el tiempo de actualización, sino su variabilidad. Existen algoritmos que solucionan el problema, uno sencillo podría ser utilizar lapsos de tiempo constantes para actualizar.

    int  gameOn = 1
    int simulationTime = 0;

    while (1){

        int realTime = Gettime();

        while (simulationTime < realTime){
            simulationTime += 16; //Timeslice is ALWAYS 16ms.
            UpdateWorld(16);
        }

        RenderWorld();
    }

Actualización en periodos constantes (Imagen Fabien Sanglard)

Como vemos, de este modo separamos lo que es la actualización del renderizado. Pudiendo detectar situaciones que de otro modo se nos escaparían. Este paradigma es el que se utiliza en la industria del videojuego actual.

Así que como dice el señor Sanglard:

No website is as good as a good book. And no good book is as good as a disassembly output.

Por lo que os enlazo un blog y un libro sobre el tema:

Salud!