Capítulo 5. El Proceso de Desarrollo

Tabla de contenidos

5.1. Pasos del ciclo
5.2. Alcance del proyecto
5.3. Formato del modelo de entrada
5.4. Un modelo de entrada representativo
5.5. Un prototipo
5.6. Desglosar el prototipo en plantillas
5.7. Escribir o modificar un generador
5.8. Comparar la salida del generador con el prototipo

5.1. Pasos del ciclo

El proceso de desarrollo de generadores de código es bastante simple, y consiste en los siguientes pasos:

  1. Determinar el alcance del proyecto.
  2. Determinar el formato del modelo de entrada.
  3. Definir un modelo representativo de las posibles entradas.
  4. Escribir o modificar el programa que debería producir el generador de código. En adelante se llamará prototipo a este programa.
  5. Desglosar el prototipo en sus plantillas componentes, definiendo el modelo de salida (el árbol de plantillas).
  6. Escribir un generador para producir la salida deseada en base al modelo de prueba, o modificarlo.
  7. Comparar la salida del generador con el prototipo. Si son diferentes, volver a 4.

5.2. Alcance del proyecto

Como todo proyecto de desarrollo, es muy importante determinar, o al menos acotar, su alcance.

5.3. Formato del modelo de entrada

Antes de escribir un generador, es necesario determinar el formato en el que se proveeran los datos al mismo. Una opción frecuente es el uso de modelos UML, pero en ocasiones otros formatos pueden ser adecuados: MOF, esquemas de bases de datos, ensamblados .NET, sistemas de ficheros, etc. Todo depende del alcance del proyecto.

También es posible utilizar XML como modelo de entrada; esto es, interpretar instancias de las clases de System.Xml según los nombres de elementos y los valores de los atributos. Sin embargo, se recomienda crear un modelo de objetos, tal vez utilizando la herramienta xsd cuando sea posible o manualmente si hace falta, ya que el nivel de abstracción que esto provee simplifica el considerablemente el trabajo.

5.4. Un modelo de entrada representativo

Durante el desarrollo del generador resulta conveniente contar con un modelo de entrada que ejercite toda la funcionalidad del generador. Este modelo no debe ser redundante, o sea, no hace falta ejercitar cada característica del generador más de una vez; por ejemplo, si nuestro generador debe ser capaz de generar un cuadro de texto por cada atributo de tipo string, con tener un solo atributo de tipo string en nuestro modelo de entrada basta para ejercitar esta característica.

5.5. Un prototipo

Así como es necesario un modelo de entrada representativo, también hace falta un ejemplo de la salida que el generador debería producir. Este ejemplo de salida es lo que llamamos prototipo.

Este prototipo servirá para detectar fallas y funcionalidad faltante del generador, ya que en todo momento podremos comparar la salida del generador con el prototipo. Para realizar esta comparación es mejor utilizar diff o alguna herramienta visual, como por ejemplo Meld.

Durante la evolución del generador, es frecuente detectar que ciertas construcciones del prototipo pueden ser reemplazadas por otras cuya lógica es equivalente, pero que son más fáciles de generar. Por ejemplo, el siguiente fragmento de código:

	string[] miArray = new string[2]
		{
			"Nombre de Atributo 1",
			"Nombre de Atributo 2"
		}

es equivalente a este otro:

	string[] miArray = new string[]
		{
			"Nombre de Atributo 1",
			"Nombre de Atributo 2"
		}

donde no se especifica el tamaño del array; sin embargo, es más sencillo escribir un generador para la segunda versión.

Por lo tanto, durante el desarrollo del generador es probable que el prototipo también evolucione.

5.6. Desglosar el prototipo en plantillas

Contando con un prototipo, es más fácil escribir las plantillas. Lo que debemos hacer es mirar el código buscando patrones, y reemplazar las ocurrencias de patrones por marcadores. Las ocurrencias originales se transforman en nuevas plantillas, a las cuales se les aplica el mismo procedimiento hasta que los marcadores sean atómicos, es decir, puedan ser reemplazados por cadenas simples.

Consideremos el siguiente prototipo:

// Archivo: Clase.txt (inicialmente copia de ClaseDePrueba.cs)
namespace Prototipo
{
	public class ClaseDePrueba
	{
		public int PropiedadInt
		{
			get { return _propiedadInt; }
			set { _propiedadInt = value; }
		}

		public string PropiedadString
		{
			get { return _propiedadString; }
			set { _propiedadString = value; }
		}
		
		private int _propiedadInt;
		private string _propiedadString;
	}
}

En este prototipo encontramos un patrón: por cada atributo de la clase se implementa una propiedad pública y un campo privado. Extraemos todas las ocurrencias de este patrón a un par de plantillas externas:

// Archivo: Propiedad.txt (inicialmente parte de Clase.txt)
public string PropiedadString
{
	get { return _propiedadString; }
	set { _propiedadString = value; }
}

// Archivo: Campo.txt (inicialmente parte de Clase.txt)
// podría ser simplemente un template en línea
private string _propiedadString;

y reemplazamos las ocurrencias en la plantilla original por un marcador:

// Archivo: Clase.txt (inicialmente copia de ClaseDePrueba.cs)
namespace Prototipo
{
	public class ClaseDePrueba
	{
		$Propiedades*

		$Campos*
	}
}

Continuando con el procedimiento, reemplazamos las particularidades de cada plantilla por marcadores, obteniendo lo siguiente:

// Archivo: Clase.txt (inicialmente copia de ClaseDePrueba.cs)
namespace $EspacioDeNombres!
{
	public class $Nombre!
	{
		$Propiedades*

		$Campos*
	}
}

// Archivo: Propiedad.txt (inicialmente parte de Clase.txt)
public $TipoDeDato! $NombreDePropiedad!
{
	get { return $NombreDeCampo!; }
	set { $NombreDeCampo! = value; }
}

// Archivo: Campo.txt (inicialmente parte de Clase.txt)
// podría ser simplemente un template en línea
private $TipoDeDato! $NombreDeCampo!;

5.7. Escribir o modificar un generador

Ahora que tenemos bien definidos el modelo de entrada y el modelo de salida, podemos escribir el código que realiza la conversión.

En este punto, podemos decidir utilizar la programación convencional, utilizando el lenguaje soportado por el CLI que más nos guste (C#, VB.NET, Boo, etc.), o podemos hacer uso de la librería ExpertCoder.ExpertSystem para estructurarlo como un sistema experto. Todo dependerá de la complejidad del generador y de la necesidad que tengamos de reutilizar lógica de transformación.

Suponiendo que escogemos la programación tradicional, el código será similar a este:

	// ...
	// se asume claseUML de tipo NUml.Uml2.Class, obtenida del modelo
	// ver http://numl.sourceforge.net/index.php/Main_Page
	//
	// crea una instancia de la plantilla de clases
	MiGenerador.Templates.Clase cls = new MiGenerador.Templates.Clase();
	// asigna sus propiedades
	cls.EspacioDeNombres = claseUML.Namespace.QualifiedName.Replace("::", ".");
	cls.Nombre = claseUML.Name;
	// por cada atributo, crea una propiedad y un campo
	foreach(UML.Property attrib in cls.OwnedAttribute)
	{
		string name = attrib.Name:
		string propname = name[0].ToUpper() + name.Substring(1);
		string fieldname = "_" + name[0].ToLower() + name.Substring(1);
		string datatype = attrib.Type.QualifiedName.Replace("::", ".");
		
		// crea una plantilla para propiedades
		MiGenerador.Templates.Propiedad prop = new MiGenerador.Templates.Propiedad();
		prop.TipoDeDato = datatype;
		prop.NombreDePropiedad = propname;
		prop.NombreDeCampo = fieldname;
		// la agrega a la lista de propiedades de la clase
		cls.Propiedades.Add(prop);
		
		// idem para los campos
		MiGenerador.Templates.Campo field = new MiGenerador.Templates.Campo();
		field.TipoDeDato = datatype;
		field.NombreDeCampo = fieldname;
		cls.Campos.Add(field);
	}
	// crea un archivo cuyo nombre se obtiene del nombre de la clase
	StreamWriter sw;
	using( sw = new StreamWriter(claseUML.Name + ".cs") )
	{
		sw.WriteLine(cls.ToString());
	}

Que claro en este ejemplo que hemos separado completamente la lógica de transformación de la presentación del producto del generador mediante el uso de plantillas.

5.8. Comparar la salida del generador con el prototipo

Una vez escrito el generador, lo ejecutamos pasándole como parámetro el modelo de entrada de prueba; luego comparamos la salida con el prototipo, como ya se explicó en Sección 5.5, “Un prototipo”.

Cuando la salida del generador sea igual al prototipo, habremos terminado.