29 agosto 2008

Pasando de Delphi 7 a RAD Studio 2007 (8)

En este artículo vamos a ver como aprovechar las ventajas que incorpora RAD Studio 2007 para un analista/arquitecto de software.

EL DISEÑO DE MODELOS UML

Para cualquier tipo de proyecto tenemos en la ventana del proyecto la pestaña Model View que nos permite realizar modelos UML para crear nuestras clases. Cualquier cambio que se produzca en el diseño de una clase se reflejará inmediatamente en el código fuente.

Así mismo, si modificamos el código fuente de una clase se modificará su diseño UML de manera instantánea. Esto nos permite diseñar nuestros programas de una manera profesional sin tener que recurrir a herramientas externas.

COMO DISEÑAR UN MODELO

Supongamos que tenemos que diseñar e implementar un programa de facturación que va a tener unas cuantas clases relacionadas entre sí. En vez de picar código manualmente como hemos hecho hasta ahora lo vamos a diseñar todo utilizando modelos UML.

Lo primero que hay que hacer es crear una nueva unidad que va a contener nuestro código fuente. Seleccionamos en el menú superior File -> New -> Unit y guardamos la unidad con el nombre UFacturacion.pas.

Esto nos dejará una nueva unidad completamente limpia y lista para trabajar:


Ahora seleccionamos la pestaña Model View en la ventana del proyecto:


Al pulsar esta pestaña nos aparecerá este mensaje:


Nos dice que el proyecto actual no esta configurado para el modelado. Pulsamos el botón y aparecerá el editor de modelos con la jerarquía de nuestro proyecto:


Abrimos la rama del proyecto, seleccionamos la unidad UFacturacion con el botón derecho del ratón y seleccionamos Open Diagram:


En la parte central del IDE aparecerá una nueva pestaña con el editor de modelos listo para comenzar a trabajar:


Si nos fijamos en la carpeta que contiene los archivos de nuestro proyecto aparecerá una nueva carpeta dentro llamada ModelSuport_NombreDelProyecto que contiene todos los archivos gráficos UML que vamos desarrollando:


Estos archivos se van actualizando en tiempo real respecto a nuestro código fuente y viceversa.

Vamos a crear nuestro primer modelo para la clase TArticulo. Pinchamos el fondo del editor de modelos con el botón derecho del ratón y seleccionamos Add -> Class:


Nos aparecerá el diseño de una nueva clase llamada Class1:


que vamos a renombrar por TArticulo:


Ahora vamos a añadir una variables a nuestra clase. Pinchamos la clase TArticulo con el botón derecho del ratón y seleccionamos Add -> Field lo que nos creará una nueva variable – campo (Field) con el nombre Field1:


Debemos escribir el nombre del campo, dos puntos y el tipo de variable, por ejemplo:

ID:Integer

Al pulsar Intro aparecerá el signo + a su izquierda:


Esto significa que hemos creado una variable pública. Si seleccionamos esta variable y nos fijamos en el inspector de objetos podemos modificar todas sus propiedades:


En el campo visibility podemos elegir el ámbito de la variable:


De este modo vamos añadiendo campos a nuestra clase hasta completar su diseño:


Si pulsamos la pestaña UFacturacion podemos ver como se refleja inmediatamente en el código fuente:


Todo esto lo hemos hecho sin teclear ni una línea de código. Lo más sorprendente es que si yo añado una variable a la clase, por ejemplo el descuento:

TArticulo = class
public
var
ID:Integer;
Nombre:String;
Existencias:Real;
Precio:Real;
Descuento:Real; // Nuevo campo
end;

Si guardamos los cambios y nos vamos al diagrama veremos el cambio en el gráfico:


Esto da una gran flexibilidad al programador al permitir dar toques aquí y allá sin que quede desincronizado el código fuente ni su diseño UML.

Eso sí, no todo va a ser perfecto. Si cogemos todas nuestras variables en diseño y las ponemos como privadas...



Al irnos al código fuente veremos que nos ha hecho esta pequeña chapuza:


Pero bueno, sólo hay que eliminar las palabras private sobrantes y una vez reagrupemos las variables no pasará nada, el diagrama seguirá igual. Pese a este pequeño defecto hay que reconocer que las ventajas de trabajar son enormes.

Cuando estamos diseñando en el editor de modelos podemos seleccionar una clase o una variable de la misma y guardar información respecto al autor, el alias y la versión:


Así podemos trabajar en equipo con otros programadores y analistas diseñando clases y modificando programas ya realizados apuntando siempre quien ha hecho la última modificación y la versión del mismo.

AÑADIENDO MÉTODOS A LA CLASE

A una clase también se le pueden añadir constructores, procedimientos y funciones de manera tan fácil como hemos creado las variables:


Si por ejemplo creamos un constructor, lo llamará automáticamente Create y lo pondrá como público:


Reflejándolo en el código fuente de manera instantánea:


CREANDO REGISTROS

Al igual que hemos creado clases utilizando el editor de modelos podemos crear registros (record) desde la paleta de herramientas:


Se llama structure porque estamos creando una estructura de datos (record). La estructura tiene un gráfico similar a la clase:


Y su código fuente también se crea automáticamente:


En los diseños de los registros también podemos añadir las nuevas características en el lenguaje que hemos visto en artículos anteriores, como pueden ser constructores, funciones y procedimientos.

CREANDO ASOCIACIONES ENTRE LOS ELEMENTOS

Al tener disponible un completo editor UML podemos establecer relaciones, asociaciones y dependencias entre los elementos gráficos insertados:


Tenemos todo tipo de asociaciones para crear una estructura jerárquica perfecta que relacione nuestras estructuras de datos y clase de una manera concisa:


Sólo es cuestión de cogerse un buen libro de UML y aprender como diseñar un sistema completo.

EXPORTANDO EL DISEÑO

El diseño que hemos generado en el Model View podemos exportarlo pulsando el editor con el botón derecho del ratón y seleccionando Export to Image:


Se abrirá esta ventana:


En esta ventana podemos modificar el ancho y alto de la imagen que va a exportar. Si pulsamos el botón Preview veremos una vista previa de la imagen que vamos a generar:


Sólo hay que pulsar el botón Save y podemos guardarlo como imagen JPG, BMP, GIF, etc.

En el próximo artículo veremos como generar la documentación de nuestro proyecto automáticamente.

Pruebas realizadas en RAD Studio 2007.

22 agosto 2008

Pasando de Delphi 7 a RAD Studio 2007 (7)

LAS UNIDADES DE PRUEBAS

Este es otro de los puntos interesantes que incorpora por defecto RAD Studio 2007 y de hecho ya viene incorporado desde las versiones de Delphi 2005 en adelante. Una unidad de prueba (también conocido como test unitario o prueba unitaria) nos permite comprobar automáticamente el comportamiento de una clase aunque luego sigamos haciendo ampliaciones.

Para ello disponemos de la unidad DUnit.pas la cual está basada en JUnit del lenguaje de programación Java. DUnit incorpora funciones para realizar una serie de test a las clases de Delphi para Win32. En Delphi para .NET se utiliza la unidad NUnit.

Vamos a ver un ejemplo de cómo podemos realizar una prueba unitaria a una clase. Supongamos que tenemos una unidad llamada UFactura.pas que contiene una clase para calcular el total de una factura:

unit UFactura;

interface

type
TFactura = class
public
BaseImponible, Iva, Total: Real;

constructor Create( Importe: Real );
procedure Calcular;
end;

implementation

constructor TFactura.Create( Importe: Real );
begin
Iva := 16;
BaseImponible := Importe;
Calcular;
end;

procedure TFactura.Calcular;
begin
Total := BaseImponible + BaseImponible * Iva / 100;
end;

end.

Para realizar una unidad de pruebas lo que hay que hacer realmente es crear un nuevo proyecto destinado exclusivamente para pruebas y añadir la unidad que vamos a testear.

Hay que seguir estos pasos:

1. En el proyecto actual que tengamos abierto mostramos el editor de código fuente (en vez del formulario) para que se pueda ver en la paleta de herramientas (Tool Palette). Abrimos la opción Unit test y hacemos doble clic sobre Test Project:


Aparecerá esta ventana:


Como se puede apreciar en la imagen, el campo Location lo que va a hacer es crear un nuevo proyecto en una carpeta llamada test dentro del la carpeta del proyecto actual. Esta carpeta la va a crear automáticamente.

2. Pulsamos el botón Next y aparecerá esta otra ventana:


En esta ventana se nos da a elegir si queremos que nos resultados los muestre en una ventana de Windows (GUI) en el modo consola (Console).

3. Lo dejamos como está y pulsamos el botón Finish. Después pulsamos el botón guardar.

Lo que hará será crear la nueva carpeta test y dentro meterá el nuevo proyecto creado:

4. Cerramos el proyecto actual y abrimos el proyecto de la carpeta test.

5. Añadimos al proyecto la unidad UFactura.pas que vamos a analizar (Project -> Add to proyect).

6. En la paleta de herramientas (Tool palette) hacemos doble clic sobre Test Case:


Nos aparecerá esta ventana:


7. Seleccionamos la unidad UFactura.pas y veremos abajo los métodos de la clase que queremos probar:


8. Lo dejamos como está y pulsamos el botón Finish.

Creará la siguiente unidad:

unit TestUFactura;
{
Delphi DUnit Test Case
----------------------
This unit contains a skeleton test case class generated by the Test Case Wizard.
Modify the generated code to correctly setup and call the methods from the unit
being tested.
}

interface

uses
TestFramework, UFactura;

type
// Test methods for class TFactura

TestTFactura = class(TTestCase)
strict private
FFactura: TFactura;
public
procedure SetUp; override;
procedure TearDown; override;
published
procedure TestCalcular;
end;

implementation

procedure TestTFactura.SetUp;
begin
FFactura := TFactura.Create;
end;

procedure TestTFactura.TearDown;
begin
FFactura.Free;
FFactura := nil;
end;

procedure TestTFactura.TestCalcular;
begin
FFactura.Calcular;
// TODO: Validate method results
end;

initialization
// Register any test cases with the test runner
RegisterTest(TestTFactura.Suite);
end.

Esta unidad contiene la clase TestTFactura que hereda de TTestCase la cual incorpora los siguiente métodos:

SetUp: Es el encargado de crear la clase que vamos a probar. En nuestro caso crearía una instancia de la clase TFactura:

procedure TestTFactura.SetUp;
begin
FFactura := TFactura.Create( 100 );
end;

Originalmente me ha creado el constructor sin parámetros, por lo que yo le he añadido manualmente el parámetro 100, que es la base imponible de la factura que voy a probar.

TearDown: Elimina la instancia del objeto creado:

procedure TestTFactura.TearDown;
begin
FFactura.Free;
FFactura := nil;
end;

TestCalcular: Es el encargado de probar el procedimiento Calcular:

procedure TestTFactura.TestCalcular;
begin
FFactura.Calcular;
// TODO: Validate method results
end;

A este último procedimiento le vamos a añadir el comando CheckEquals que evalúa el resultado obtenido con el que esperábamos:

procedure TestTFactura.TestCalcular;
begin
FFactura.Calcular;
CheckEquals( 116, FFactura.Total, 'El Total de la factura es erroneo' ); // test de prueba
end;

El primer parámetro del procedimiento CheckEquals es el número que nosotros esperamos (Total factura = Base Imponible (100) + Iva (16) = 116). El segundo parámetro el valor resultante (la variable Total de la factura). El tercer parámetro es el mensaje que queremos que vea el usuario si hay un error en el resultado).

Si ejecutamos el programa nos aparecerá esta ventana:


Pulsamos el botón Run Selected Test (el botón play de color verde) y si todo ha ido bien nos mostrará todos los puntos de color verde:


Eso significa que el resultado es el esperado. Ahora vamos a estropear el método Calcular a cosa hecha para que nos devuelva un resultado erroneo:

procedure TFactura.Calcular;
begin
Total := BaseImponible + BaseImponible * Iva / 100 + 1;
end;

Si volvemos a ejecutar el test nos aparecerá esto:


En este caso se ha producido un error en el test y si pulsamos abajo su error nos dirá que esperaba 116 y ha devuelto 117.

Esto es muy potente para evitar tener que realizar una batería de pruebas cada vez que tenemos que modificar de nuevo alguna clase.

Ahora supongamos que nos piden que la factura realice un descuento por pronto pago en la base imponible. Modificamos la factura para ello:

type
TFactura = class
public
BaseImponible, Iva, Total, DtoPP: Real;

constructor Create( Importe: Real );
procedure Calcular;
end;

implementation

constructor TFactura.Create( Importe: Real );
begin
Iva := 16;
DtoPP := 0;
BaseImponible := Importe;
Calcular;
end;

procedure TFactura.Calcular;
var
ImporteNeto: Real;
begin
ImporteNeto := BaseImponible - BaseImponible * DtoPP / 100;
Total := ImporteNeto + ImporteNeto * Iva / 100;
end;

Esto es lo que más asusta a un programador: volver a retocar un programa que ya esta hecho y saber si lo anterior sigue funcionando correctamente. Con nuestra prueba unitaria sabemos que si la factura tiene una base imponible de 100 (sin el descuento) el resultado por fuerza tiene que ser 116.

Si volvemos a ejecutar el test lo dará correctamente pese a la ampliación que hemos hecho con el descuento. Lo que he aplicado aquí se puede hacer perfectamente con bases de datos comprobando si tenemos descuadres de decimales, si las fechas de emisión de los documentos son correctas a partir de la forma de pago, etc.

Aparte de la función CheckEquals también tenemos estas otras:

CheckNotEquals: Comprueba si el resultado no es igual (la inversa de CheckEquals).

Check: Evalua una condición booleana.

CheckNotNull: Comprueba si la referencia a un objeto o internaz no es nula (nil).

CheckNull: Comprueba si la referencia a un objeto o internaz es nula (nil).

CheckSame: Comprueba si dos referencias a objetos o interfaces apuntan al mismo objeto el memoria.

EqualsErrorMessage: comprueba si el mensaje de error devuelto por la aplicación es igual al que le pasamos como parámetro.

Fail: Comprueba si falla una rutina.

FailEquals: Comprueba si el fallo de una rutina es igual al que le pasamos como parámetro.

FailNoEquals: Comprueba si el fallo de una rutina no es igual al que le pasamos como parámetro.

FailNotSame: Comprueba si dos errores devueltos no son iguales.

NotEqualsErrorMessage: Comprueba si el mensaje de error que le pasamos como parámetro no es igual al que devuelve la aplicación.

NotSameErrorMessage: Comprueba si el error que le pasamos como parámetro no es igual al que devuelve la aplicación.

Con todas estas funciones cubrimos todas la posibilidades en la batería de pruebas que podemos someter a un programa.

En el próximo artículo seguiremos indagando sobre RAD Studio 2007.

Pruebas realizadas en RAD Studio 2007.

15 agosto 2008

Pasando de Delphi 7 a RAD Studio 2007 (6)

En el artículo de hoy vamos a terminar de ver las novedades que incorpora el lenguaje de programación Object Pascal.

LAS CLASES PUEDEN TENER AYUDANTES

Habéis leído bien, ahora las clases pueden tener ayudantes. ¿Qué es un ayudante? Los ayudantes (Helpers) permiten ampliar la funcionalidad de una clase sin tener que heredar de la misma. Con un ejemplo lo vais a ver más claro:

Supongamos que tenemos una clase para controlar el pago de un recibo:

type
TRecibo = class
private
Numero: Integer;
ImpTotal, ImpPagado, ImpPendiente: Real;
public
constructor Create( Cantidad: Real );
procedure Pagar( Cantidad: Real );
end;

Esta sería su implementación:

constructor TRecibo.Create( Cantidad: Real );
begin
ImpTotal := Cantidad;
end;

procedure TRecibo.Pagar( Cantidad: Real );
begin
ImpPagado := ImpPagado + Cantidad;
ImpPendiente := ImpTotal - ImpPagado;
end;

Si yo quisiera añadir un nuevo procedimiento o función a esta clase sin modificarla, debería heredar de la misma y añadirle las nuevas funcionalidades. Pero ahora podemos crear una nueva clase ayudante que permite ampliar funciones o procedimientos a la clase original de este modo:

TAyudanteRecibo = class helper for TRecibo
procedure Devolver( Cantidad: Real );
procedure Pagar( Cantidad: Real ); // vuelvo a redefinir el método Pagar
end;

Esta clase no es una clase normal, ya que no podemos por ejemplo añadir nuevas variables privadas (para eso hay que heredar). Pero si podemos implementar nuevos procedimientos tal como si estuvieran en la clase TRecibo:

procedure TAyudanteRecibo.Devolver( Cantidad: Real );
begin
ImpPagado := ImpPagado - Cantidad;
ImpPendiente := ImpTotal - ImpPagado;
end;

procedure TAyudanteRecibo.Pagar( Cantidad: Real );
begin
ImpPagado := ImpPagado + Cantidad;
ImpPendiente := ImpTotal - ImpPagado;

if ImpPagado >= ImpTotal then
begin
ImpPagado := ImpTotal;
ImpPendiente := 0;
end;
end;

Lo bueno de esto es que el programador que utilice esta clase no notará la diferencia a la hora de programar:

var
Recibo: TRecibo;
begin
Recibo := TRecibo.Create( 100 ); // constructor de la clase TRecibo
Recibo.Pagar( 50 ); // método de la clase TAyudanteRecibo
Recibo.Devolver( 10 ); // método de la clase TAyudanteRecibo
ShowMessage( 'Imp. pendiente=' + FloatToStr( Recibo.ImpPendiente ) );
Recibo.Free;
end;

Este sería el resultado de la operación:


De este modo cualquier programador puede ampliar las funcionalidades de una clase hechas por otro programador escribiendo menos código y ahorrándose la herencia.

LOS ATRIBUTOS ESTRICTOS

Hasta ahora teníamos tres formas de definir un atributo (variable) en una clase:

Private: Sólo se puede acceder al mismo en la misma clase. Ni siquiera las clases que heredan pueden acceder a estas variables.

Protected: Las variables que se encuentren en la sección protected podrán ser accesibles por la clase original y por sus herederas.

Public: Se podrá acceder a las variables públicas tanto en la clase original como en sus herederas así como desde fuera de la clase.

En RAD Studio 2007 ahora tenemos dos nuevos tipos de atributos:

Strict private: Sólo se pueden acceder a estos atributos mediante métodos de la misma clase y nunca de los que heredan de esta. Ni siquiera podemos acceder a estos atributos instanciando el objeto. Por ejemplo:

type
TJuego = class
private
NumeroVidas: Integer;
public
constructor Create;
end;

constructor TJuego.Create;
begin
NumeroVidas := 3;
end;

Así como está definido puedo acceder a la variable NumeroVidas desde el objeto:

var
Juego: TJuego;
begin
Juego := TJuego.Create;
Juego.NumeroVidas := 3;
Juego.Free;
End;

Pero si hago este cambio:

TJuego = class
strict private
NumeroVidas: Integer;
public
constructor Create;
end;

Entonces el compilador me da error en esta línea:

Juego.NumeroVidas := 3;

Es decir, una variable estricta sólo puede ser accesible dentro de la implementación de la clase y nunca desde su instancia (el objeto).

Ahora heredo de esta clase e intento acceder a la variable NumeroVidas:

TMatamarcianos = class( TJuego )
public
procedure QuitarVida;
end;

procedure TMatamarcianos.QuitarVida;
begin
Dec( NumeroVidas );
end;

Al compilar me daría error en la línea:

Dec( NumeroVidas );

Esta variable sólo puede ser utilizada por la implementación de la clase padre. Para solucionar esto tenemos esta otra:

Strict protected: Sólo se pueden acceder a estos atributos mediante métodos de la misma clase y de los que heredan de esta, pero nunca los objetos instanciados de ambas. Por ejemplo:

TJuego = class
strict protected
NumeroVidas: Integer;
public
constructor Create;
end;

TMatamarcianos = class( TJuego )
public
procedure QuitarVida;
end;

procedure TMatamarcianos.QuitarVida;
begin
Dec( NumeroVidas );
end;

Este último procedimiento si está permitido. Lo que no podemos es hacer esto:

var
Matamarcianos: TMatamarcianos;
begin
Matamarcianos := TMatamarcianos.Create;
Matamarcianos.NumeroVidas := 5; // me da error en esta línea
Matamarcianos.QuitarVida;
Matamarcianos.Free;
end;

Resumiendo lo que hemos visto podemos decir que la palabra reservada strict impide acceder a las variables de una clase desde el objeto instanciado.

LOS NUEVOS TIPOS DE CLASES

Mediante una clase cerrada (sealed) podemos evitar que se pueda heredar de la misma. Por ejemplo:

type
TEntidad = class sealed
private
Numero: Integer;
Nombre: String;
public
constructor Create;
procedure SetNombre( s: String );
end;

Si intento hacer esto:

TCliente = class( TEntidad )
private
CIF: String;
end;

El compilador me dirá este error:

Cannot extend sealed class ‘TEntidad’

De este modo creamos una clase hermética que no puede ser ampliada con clases descendentes de la misma. Sería algo así como una clase final.

NUEVOS TIPOS DE DATOS EN LAS CLASES

Otra nueva funcionalidad que tenemos dentro de las clases es definir constantes que pueden consultarse sin tener de instanciarse. Por ejemplo:

type
TProveedor = class
const TIPO = 'ACREEDOR';
end;

Ahora podemos hacer algo como esto sin que provoque un access violation:

ShowMessage( TProveedor.TIPO );

Esto es muy útil para crear nuestras propias constantes dentro de una clase de modo que moviendo la clase de una unidad a otra ya llevamos incorporadas las constantes que necesita.

También podemos definir nuestros propios tipos de datos dentro de una clase:

TPresupuesto = class
type
TNumeracion = record
Serie: Char;
Numero: Integer;
Fecha: TDate;
end;

private
Numeracion: TNumeracion;
public
constructor Create( S: Char; N: Integer; F: TDate );
end;

En la implementación del constructor rellenamos el registro Numeracion:

constructor TPresupuesto.Create( S: Char; N: Integer; F: TDate );
begin
Numeracion.Serie := S;
Numeracion.Numero := N;
Numeracion.Fecha := F;
end;

Esta sería la forma de utilizar la clase TPresupuesto:

var
Presupuesto: TPresupuesto;
begin
Presupuesto := TPresupuesto.Create( 'A', 25, Date );

ShowMessage( Presupuesto.Numeracion.Serie + ' ' +
IntToStr( Presupuesto.Numeracion.Numero ) + ' ' +
DateToSTr( Presupuesto.Numeracion.Fecha ) );

Presupuesto.Free;
end;

Mostrará el resultado con total normalidad:


Pero es que además podemos crear variables en la clase de modo que podamos utilizarlas sin ni siquiera instanciarla. Voy a hacer un pedido al igual que el ejemplo del presupuesto utilizando variables:

TPedido = class
type
TNumeracion = record
Serie: Char;
Numero: Integer;
Fecha: TDate;
end;

class var
Numeracion: TNumeracion;
end;

Ahora podemos asignar un valor a la variable Numeración sin instanciar la clase:

begin
TPedido.Numeracion.Serie := 'B';
TPedido.Numeracion.Numero := 48;
TPedido.Numeracion.Fecha := Date;

ShowMessage( TPedido.Numeracion.Serie + ' ' +
IntToStr( TPedido.Numeracion.Numero ) + ' ' +
DateToSTr( TPedido.Numeracion.Fecha ) );
end;

Estos valores permanecerán siempre estáticos para cualquier objeto de la clase:


Le podemos sacar partido a esto guardando en la clase el contador del último pedido y cuando instanciemos la clase incrementamos el contador. De este modo, todos los objetos siempre saben cual es el último pedido.

También podemos utilizarlo para guardar la configuración global de un programa como si se tratase de un registro (record).

Y por último y no menos importante, podemos crear métodos estáticos en la clases de modo que se pueden llamar sin crear una instancia. Por ejemplo:

TAlumno = class
class var
Nombre: String;
public
class procedure SetNombre( N: String );
class function GetNombre: String;
end;

En esta clase he definido una función y un procedimiento estático que recogen y almacenan la variable estática Nombre:

class procedure TAlumno.SetNombre( N: String );
begin
Nombre := N;
end;

class function TAlumno.GetNombre: String;
begin
Result := Nombre;
end;

Ahora viene lo más cómodo para un programador:

begin
TAlumno.SetNombre( 'JUAN' );
ShowMessage( TAlumno.GetNombre );
end;

Ni he tenido que crear la instancia del objeto ni destruirlo. Ni siquiera he tenido que declarar una variable para utilizarlo, ya que la clase TAlumno funciona como una variable global a todo el programa. ¿Cuántas veces hemos provocado access violations por olvidarnos de instanciar la clase? ¿Y cuantas pérdidas de memoria hemos tenido por olvidarnos del maldito Free? Esto no es que sea una panacea pero para ciertas rutinas es muy útil (incrementar contadores internos, acceder a las opciones del programa, etc.).

ANIDACION DE CLASES

Esto si supone una gran mejora para los que nos gusta estructurar bien la información. Ahora podemos definir una clase dentro de otra:

TAlmacen = class
private
Numero: Integer;
Descripcion: String;
public
type
TProducto = class
private
ID: Integer;
Nombre: String;
Existencias: Real;
public
constructor Create( I: Integer; N: String; E: Real );
end;

constructor Create( N: Integer; D: String );
end;

Esta sería la implementación de sus constructores:

constructor TAlmacen.Create( N: Integer; D: String );
begin
Numero := N;
Descripcion := D;
end;

constructor TAlmacen.TProducto.Create( I: Integer; N: String; E: Real );
begin
ID := I;
Nombre := N;
Existencias := E;
end;

La instanciación de ambas clases se haría de manera normal:

var
Almacen: TAlmacen;
Producto: TAlmacen.TProducto;
begin
Almacen := TAlmacen.Create( 1, 'ALMACEN GENERAL' );
Producto := TAlmacen.TProducto.Create( 1234, 'SOFA CAMA', 5 );
Producto.Free;
Almacen.Free;
end;

Esto hace que nuestro código fuente quede más elegante y con una jerarquía mejor definida.

LOS MÉTODOS FINALES

Si definimos un método como final ya no se podrá reimplementar en las clases descendentes:

type
TBiblioteca = class
private
NumLibros: Integer;
public
procedure CalcularExistencias; virtual; final;
end;

Al definir el prodecimiento CalcularExistencias como final ya no puedo reutilizarlo en las clases descendentes:

TLibreria = class( TBiblioteca )
private
NumArticulos: Integer;
public
procedure CalcularExistencias; override;
end;

El compilador me daría este error:

Cannot override a final method.

Esto tiene su utilidad para evitar que otro programador que herede de esta clase intente reimplementar un método crítico que es esencial en esa clase (reserva de memoria, apertura de archivos, etc.).

En el próximo artículo seguiremos viendo más cosas interesantes de RAD Studio 2007.

Pruebas realizadas con RAD Studio 2007.

08 agosto 2008

Pasando de Delphi 7 a RAD Studio 2007 (5)

Hoy vamos a comenzar a ver las novedades que incorpora RAD Studio 2007 en el lenguaje de programación Object Pascal respecto a Delphi 7.

REGISTROS CON MÉTODOS

Si hasta ahora no se le daba mucha importancia a los registros (record) a favor de las clases para crear y almacenar nuestros propios tipos de datos, ahora podemos aprovecharlos de nuevo con la posibilidad de añadir métodos como si fueran clases.

Y no sólo eso, sino que además se pueden tener su propio constructor e incluso métodos y propiedades. De este modo podemos encapsular tanto los datos así la manipulación de los mismos dentro de un mismo registro y además gozando de la ventaja de no tener que instanciar ni liberar la clase.

Veámoslo con un ejemplo. Supongamos que tenemos este registro para definir un artículo:

type
TArticulo = record
ID: Integer;
Nombre: String;
Existencias, Precio: Real;
end;

Pues ahora podemos hacer algo como esto:

TArticulo = record
var
ID: Integer;
Nombre: String;
Existencias, Precio: Real;

constructor Create( ExistenciasIniciales, Precio: Real );
end;

La palabra reservada var no es obligatoria, aunque deja el código más elegante. Aquí tenemos la implementación de su constructor:

constructor TArticulo.Create(ExistenciasIniciales, Precio: Real);
begin
Existencias := ExistenciasIniciales;
Self.Precio := Precio;
end;

De este modo podemos crear un artículo al igual que una clase sin instanciarlo:

var
Articulo: TArticulo;
begin
Articulo.Create( 10, 132.25 );
ShowMessage( 'Existencias=' + FloatToStr( Articulo.Existencias ) + #13 +
'Precio=' + FloatToStr( Articulo.Precio ) );
end;

Nos mostraría esto en pantalla:


También podemos crear propiedades que accedan a las variables encapsulando de este modo todo el registro:

type
TArticulo = record
var
ID: Integer;
Nombre: String;
Existencias, Precio: Real;
Comprado, Vendido: Real;

constructor Create( ExistenciasIniciales, Precio: Real );
procedure Comprar( Unidades: Real );
procedure Vender(Unidades: Real);
function CalcularExistencias: Real;
property ExistenciasReales: Real Read CalcularExistencias;
end;

Esta sería su implementación:

constructor TArticulo.Create(ExistenciasIniciales, Precio: Real);
begin
Existencias := ExistenciasIniciales;
Self.Precio := Precio;
Comprado := 0;
Vendido := 0;
end;

procedure TArticulo.Comprar(Unidades: Real);
begin
Comprado := Comprado + Unidades;
end;

procedure TArticulo.Vender(Unidades: Real);
begin
Vendido := Vendido + Unidades;
end;

function TArticulo.CalcularExistencias: Real;
begin
Result := Existencias + Comprado - Vendido;
end;

Ahora aprovechamos del registro Articulo como si fuera una clase:

var
Articulo: TArticulo;
begin
Articulo.Create( 10, 132.25 );
Articulo.Comprar( 100 );
Articulo.Vender( 50 );
ShowMessage( 'Existencias Reales=' + FloatToStr( Articulo.ExistenciasReales ) + #13 +
'Precio=' + FloatToStr( Articulo.Precio ) );
end;

Este sería el resultado:


Sin embargo, los registros todavía no gozan de muchas de las características que poseen las clases:

- Un registro no puede tener destructores.

- No se pueden crear métodos virtuales.

- No se puede heredar de otro registro.

LA SOBRECARGA DE OPERADORES EN REGISTROS

La sobrecarga de operadores nos permite operar con los registros como si fueran valores numéricos. Por ejemplo:

TFactura = record
private
Importe: Real;
public
class operator Implicit( Importe: Real ): TFactura;
class operator Add( F1, F2: TFactura ): TFactura;
class operator Subtract( F1, F2: TFactura ): TFactura;
end;

Las palabras reservadas class operator sirven para redefinir los operadores +, -, =, etc. que se utilizan al tratar con los registros. En esta ocasión sólo he redefinido los operadores de suma y resta para aplicarlos a la factura.

Esta sería su implementación:

class operator TFactura.Implicit( Importe: Real ): TFactura;
begin
Result.Importe := Importe;
end;

class operator TFactura.Add( F1, F2: TFactura ): TFactura;
begin
Result.Importe := F1.Importe + F2.Importe;
end;

class operator TFactura.Subtract( F1, F2: TFactura ): TFactura;
begin
Result.Importe := F1.Importe - F2.Importe;
end;

El operador Implicit se utiliza para que podamos asignar valores al registro directamente como si fuera un valor numérico:

var
F1, F2, F3: TFactura;
begin
F1 := 50;
F2 := 100;
F3 := 20;
F1 := F1 + F2 - F3;

ShowMessage( 'Total facturas=' + FloatToStr( F1.Importe ) );
end;

Como puede apreciarse en el código he asignado los valores directamente al objeto en vez de hacer esto:

F1.Importe := 50;
F2.Importe := 100;
F3.Importe := 20;

Esto me lo ha permitido el operador Implicit. Luego he sumado las dos primeras facturas y les he restado la tercera:

F1 := F1 + F2 - F3;

Que equivale a esto:

F1.Importe := F1.Importe + F2.Importe – F3.Importe;

Al ejecutar este código este es el resultado:


Esto nos da mucha potencia a la hora de crear registros para operar con facturas, recibos, calcular saldos, asientos contables, etc.

LOS BUCLES FOR IN

Esto es algo que ya se echaba de menos respecto a otros lenguajes más modernos que Pascal, como pueden ser Java, Python, Ruby, C#, etc. Es la posibilidad de poder crear un bucle sin tener que crear una variable entera para recorrerlo.

Por ejemplo, si tengo que recorrer un array y sumar su resultado esto es lo que tenía que hacer hasta ahora:

var
Facturas: array[1..3] of Real;
Suma: Real;
i: Integer;
begin
Facturas[1] := 10;
Facturas[2] := 20;
Facturas[3] := 30;

for i := 1 to 3 do
Suma := Suma + Facturas[i];

ShowMessage( 'Suma=' + FloatToStr( Suma ) );
end;

Con este método tenemos que saber el número de elementos en el array. Ahora, mediante el bucle for in podemos recorrer un array sin tener que saber cuantos elementos contiene:

var
Facturas: array[1..3] of Real;
Suma, Factura: Real;
begin
Facturas[1] := 10;
Facturas[2] := 20;
Facturas[3] := 30;

for Factura in Facturas do
Suma := Suma + Factura;

ShowMessage( 'Suma=' + FloatToStr( Suma ) );
end;

En este caso ya no es necesaria la variable entera i para recorrer los elementos del bucle. Sólo hace falta una variable que contenga el elemento actual (Factura). Esto es muy útil para recorrer arrays dinámicos sin preocuparse en el número de elementos que tienen.

Aunque esto lo considero algo incompleto, porque no se puede hacer algo como esto:

var
Componente: TComponent;
begin
for Componente in Form1.Components do
ShowMessage( Componente.Name );
end;

Al compilarlo nos da este error:

‘[‘ Expected but ‘DO’found

Es decir, que esperaba los corchetes después de la palabra Components. Si no podemos utilizar un for in para este tipo de elementos la verdad es que nos quedamos casi igual que antes. Si sólo podemos utilizarlo en arrays y en colecciones simples la verdad es que se nos queda algo corto.

LA DIRECTIVA INLINE

Esta directiva puede aplicarse a funciones o procedimientos modificando el modo de compilación de los mismos. Vamos a verlo con un pequeño ejemplo.

Este es el típico bucle para ordenar los elementos de un array por el método de la burbuja:

var
i, j, tmp: Integer;
Elementos: array[1..10000] of Integer;
Tiempo: Dword;
begin
// Rellenamos el array con valores aleatorios

Randomize;
for i := 1 to 100 do
Elementos[i] := Random( 500 ) + 1;

Tiempo := TimeGetTime;

// Ordenamos los elementos del array con el método de la burbuja

for j := 1 to 9999 do
for i := 1 to 9999 do
if Elementos[i] > Elementos[i+1] then
begin
tmp := Elementos[i];
Elementos[i] := Elementos[i+1];
Elementos[i+1] := tmp;
end;

ShowMessage( 'Milisegundos=' + IntToStr( TimeGetTime - Tiempo ) );
end;

Para poder utilizar la función TimeGetTime es necesario añadir en uses la unidad mmsystem. Lo que he hecho es crear un array de 10000 números enteros y les he metido a cada uno un valor aleatorio entre 1 y 500. Después he utilizado el método de la burbuja para ordenarlos. Al ejecutar el programa me tarda 155 milisegundos:


Ahora supongamos que quiero refactorizar el código y quiero sacar a parte el núcleo del bucle en otro procedimiento;

procedure Intercambiar(var a, b: Integer);
var
tmp: Integer;
begin
tmp := a;
a := b;
b := tmp;
end;

De modo que el procedimiento original se nos quedaría así:

var
i, j: Integer;
Elementos: array[1..10000] of Integer;
Tiempo: Dword;
begin
// Rellenamos el array con valores aleatorios

Randomize;
for i := 1 to 100 do
Elementos[i] := Random( 500 );

Tiempo := TimeGetTime;

// Ordenamos los elementos del array con el método de la burbuja
for j := 1 to 9999 do
for i := 1 to 9999 do
if Elementos[i] > Elementos[i+1] then
Intercambiar( Elementos[i], Elementos[i+1] );

ShowMessage( 'Milisegundos=' + IntToStr( TimeGetTime - Tiempo ) );
end;

Al ejecutar el programa vemos que tarda más:


Esto tiene su explicación: cada llamada al procedimiento Intercambiar obliga a realizar un salto a otro procedimiento para luego volver a este. Si el bucle no tiene muchas iteraciones el cambio no suele notarse mucho, pero si se realiza miles de veces entonces la diferencia de tiempo entre uno y otro método suele ser notable.

Para solucionar esto tenemos la directiva inline. Esta directiva aplicada al procedimiento Intercambiar hace que se compile el código de este procedimiento como si realmente nunca lo hubiésemos sacado del bucle. De este modo ganamos claridad en el código fuente sin perder rendimiento en el código compilado.

Sólo hay que añadir la palabra inline al final del procedimiento:

procedure Intercambiar(var a, b: Integer); inline;
var
tmp: Integer;
begin
tmp := a;
a := b;
b := tmp;
end;

Sin ser tan rápido como la manera original si se obtiene un aumento de velocidad:



Quizás en un programa de gestión no es necesario utilizar esta función, pero en la programación de videojuegos algunas funciones se ejecutan tantas veces por segundo que puede marcar la diferencia entre renderizar un mundo 3D con suavidad a 40 o 50 frames por segundo o relentizarse y dar tirores a 20 frames por segundo. Cuando escriba en un futuro articulos de programación de videojuegos 3D con SDL y OpenGL veréis a que me refiero.

En el próximo artículo seguiremos viendo otras novedades en el lenguaje.

Pruebas realizadas en RAD Studio 2007.

01 agosto 2008

Pasando de Delphi 7 a RAD Studio 2007 (4)

Hoy vamos a ver dos novedades muy importantes en las que aventaja RAD Studio 2007 respecto a Delphi 7: las Auditorias y las Métricas.

LAS AUDITORIAS

Las auditorias nos permiten rastrear todo el código fuente en busca de errores de diseño, implementación y ejecución. Si bien no todo lo que muestran son errores, si que da importantes consejos sobre como implemenar el código correctamente y evitar posibles errores.

Para realizar una auditoria hay que seguir estos pasos:

1. Seleccionamos la pestaña Model View en la sección del administrador de proyectos (Project Manager):


2. Pulsamos el nombre del proyecto con el botón derecho del ratón y seleccionamos QA Audits:


Se abrirá esta ventana:


Por defecto aparecen casi todas las comprobaciones activadas:

Arrays and References (colecciones y referencias): comprueba que los arrays y las referencias a objetos no apunten a valores nulos, que no excedamos el límite de un array, campos no inicializados, etc.

Branches and Loops (bifurcaciones y bucles): examina las sentencias de control para evitar bucles infinitos, si hay bloques de código que nunca serán ejecutados, etc.

Coding Style (estilo de código): esta sección comprueba inconsistencias en el código tales como comprobar si hay variables globales que son modificadas por varios bloques de código distinto con posibilidad colisión o si por ejemplo hay varias sentencias de código de una sola línea que podrían refactorizarse.

Declaration Style (estilo de declaración): comprueba si hay métodos o variables públicas que podrían estar como privados, variables que podían ser estáticas o la sobrecarga de métodos no abstractos.

Design Flaws (Imperfecciones en el diseño): comprueba si hay subclases que tienen miembros con el mismo nombre o variables que se han declarado como temporales en toda la clase pero que son utilizadas (peligrosamente) por varios métodos.

Duplicated Code (Código duplicado): revisa el código fuente en busca de bloques de código duplicados, ya sean bloques de código que se encuentran en distintas sentencias de control como en la creación de constructores de clase.

Expressions (Expresiones): revisa el código en busca de posibles divisiones por cero, el paso de argumentos a funciones con posibles valores nulos, desbordamientos positivos o negativos en variables enteras, etc.

Naming Style (Estilo de nombres): comprueba si los nombres de las variables, objetos o clases que hemos declarado tienen correctamente puestas las mayúsculas y minúsculas o si por ejemplo el nombre de una interfaz tiene el prefijo I.

Performance (comportamiento): comprueba si as ha sido utilizado como un operador o si se ha duplicado la conversión (typecast) de una variable dos o más veces.

Possible Errors (errores posibles): inspecciona el código en busca de dos bifurcaciones o más que provoquen el mismo resultado (código repetido). También mira el retorno de valores que no han sido chequeados, posibles conversiones de tipos de variable erroneos, etc.

Superflous Content (contenido superfluo): comprueba si hay miembros de clase que nunca serán utilizados, conversiones entre variables no son necesarias o si se han pasado parámetros a funciones o procedimientos que nunca serán utilizados.

Sólo tenemos que pulsar el botón Start para que comience la auditoria:


Al terminar nos mostrará en la parte inferior del editor de código el resultado de la auditoria:


En la columna Abbreviation nos indica el posible tipo de error que hemos cometido. Estos son algunos de sus significados:

NC -> Naming Concencions (conveniones de nombres).
USP -> Use Singleton Pattern (usar el patrón singleton).
FNI -> Field Not Initialized (campo no inicializado).
MCS -> Member Can be made Static (el miembro puede ser estático).

El modo de proceder a su corrección es algo extraño. Lo normal sería que si hago doble clic sobre una supuesta línea de error debería saltar a esa línea, pero no hace nada. Tenemos que pinchar la línea de error con el botón derecho del ratón y seleccionar Show Description:


Entonces abrirá una nueva pestaña encima del editor de código mostrando la descripción del error:


Ahora si hacemos doble clic sobre la línea del error entonces se cambiará de nuevo a la pestaña del editor y seleccionará el código fuente donde se encuentra el problema:


El único inconveniente que le veo a esto es que no sitúa el editor en la línea donde está el supuesto error, sino que tenemos que ser nosotros los que movamos la barra de desplazamiento lateral del editor de código hasta encontrar el código fuente seleccionado (según el número de línea). Lo tenían que haber hecho un poco más cómodo, al igual que cuando compilamos.

A veces no todos tienen porque ser errores. En la columna Severity tenemos generalmente dos tipos de información: Warning (advertencia) e Info (Información). Por ejemplo, si yo he creado una variable con notación húngara llamada sNombre me mostrará una advertencia diciendo que hemos incumplido la forma de llamar a las variables según la convención del lenguaje Pascal, donde se supone que el nombre que se le da a una variable, clase, etc. tiene que empezar por mayúsculas.

También puede quejarse si no he colocado las palabras begin y end en bloques de código de una sóla línea. Por ejemplo:

if Importe < 100 then
ShowMessage( 'Existencias mínimas' );

Según la auditoria debería hacerlo de este modo:

if Importe < 100 then
begin
ShowMessage( 'Existencias mínimas' );
end;

Así que no hay que hacerle demasiado caso a estas advertencias ya que cada programador tiene su propio estilo de escritura de código. En la parte izquierda de la ventana de auditorias tenemos los botones para guardar el resultado de la auditoria (en XML o HTML), imprimirla, refrescarla o volver a realizarla:
Hay que tener mucho cuidado con proyectos que son muy grandes ya que realizar una auditoria de los mismos puede llegar a ser desesperante. De hecho, RAD Studio se queda bloqueado de tal modo que deja Windows medio tonto.

Si tienes un proyecto con miles de líneas de código lo mejor es que selecciones en el Model View sólo el formulario que quieres auditar.

LAS MÉTRICAS

Las métricas son otro modo de evaluar nuestro código fuente estableciendo una serie de reglas que nos permitan por ejemplo fijar un máximo número de líneas por bloque de código o el número máximo de bucles anidados. Esto puede ser muy útil para fijar unos máximos y mínimos a la hora de implementar el código fuente cuando un proyecto lo realizan dos o más programadores y conviene que todos sigan un mismo estilo.

Los pasos para analizar la métrica de nuestro código son muy parecidos a los de las auditorías:

1. Seleccionamos la pestaña Model View en la sección del administrador de proyectos (Project Maganer).

2. Pulsamos el nombre del proyecto con el botón derecho del ratón y seleccionamos QA Metrics:


Se abrirá esta ventana:


Aparecen activadas todas estas opciones:

Basic (básico): cuenta el número de líneas de código de cada unidad, interfaz, clase, etc.

Cohesion (Cohesión): comprueba la cohesión de cada clase contando el número de miembros y métodos de cada clase.

Complexity (complejidad): comprueba el número máximo de bifurcaciones en el código, el número de variables locales y el peso de cada clase (a mas líneas de implementación, más pesada).

Coupling (acoplamiento): calcula entre otras cosas los valores medios y ratios máximos en el uso de interfaces, en la duplicación de métodos abstractos y el número de accesos a variables de clase directamente o mediante métodos.

Encapsulation (encapsulación): comprueba el factor de ocultación de los miembros y métodos de cada clase.

Halstead: calcula la proporción entre el número de operadores y el número de operandos.

Inheritance (herencia): comprueba el factor de herencia entre clases, la profundidad entre la jerarquía de clases heredadas así como el número de clases hijas.

Inheritance-based coupling (acoplamiento basado en la herencia): calcula el ratio y la media de herencia entre clases.

Maximum (máximo): comprueba el número máximo de niveles de identación entre los bloques de código anidados.

Polymorphism (polimorfismo): cuenta el número de métodos añadidos o sobrecargados y el ratio de polimorfismo.

Ratio: cuenta el número de líneas de comentario que hay en el código y en la documentación.

Al pulsar el botón Start comienza el análisis:


Al terminar nos abrirá una sección en la parte inferior del editor de código fuente con los resultados de las métricas:


Las métricas pueden visualizarse respecto a todo el proyecto o respecto a una unidad, clase, método, etc.

Pongamos por ejemplo este procedimiento:

procedure CrearPedido;
var
Pedido: TPedido;
begin
// Creamos el pedido
Pedido := TPedido.Create;
Pedido.ID := 1;
Pedido.sCliente := 'TRANSPORTES GARCIA, S.L.';

// Añadimos dos artículos al pedido
with Pedido do
begin
Articulos[1] := TArticulo.Create;
Articulos[1].ID := 1;
Articulos[1].sNombre := 'RUEDAS MICHELIN';
Articulos[1].Precio := 410.25;

Articulos[2] := TArticulo.Create;
Articulos[2].ID := 2;
Articulos[2].sNombre := 'FILTRO ACEITE';
Articulos[2].Precio := 176.12;

rTotal := Articulos[1].Precio + Articulos[2].Precio;

// Eliminamos los artículos
Articulos[2].Free;
Articulos[1].Free;
end;

// Eliminamos el pedido
Pedido.Free;
end;

Si examino la métrica del mismo me dirá lo siguiente:

AID (Access of Import Data – Nº de accesos a datos importados ): 0
ALD (Access of Local Data – Nº de accesos a variables de clase ) : 2
CC (Complexity Ciclomatic – Nº de ciclos ): 1
NIC ( Number of Import Classes – Nº de clases importadas): 2
NOLV (Numer of Local Variables – Nº de variables locales): 1

Si queremos averiguar lo que significa cada abreviatura, pulsamos sobre la línea de la métrica con el botón derecho del ratón (en la columna que queremos averiguar) y seleccionamos Show Description:


Nos mostrará una nueva pestaña con el significado de la misma:


También podemos ver una representación gráfica de la métrica pulsando el botón derecho del ratón sobre la rejilla y seleccionando Kiviart Chart:


Abrirá una nueva pestaña en el editor de código fuente mostrando esta gráfica:


Esta herramienta puede ser muy útil para detectar cuellos de botella en el código fuente y evitar desperdicios de memoria declarando más variables que las que necesitamos. Sobre todo viene bien en la programación de videojuegos, donde la velocidad del procesador y el consumo de memoria es crítico para el sistema.

Las métricas que vienen predeterminadas en el IDE pueden modificarse antes de pulsar el botón Start, me modo que nosotros mismos podemos establecer los máximos y mínimos según nuestro criterio.

Al igual que ocurre con las auditorias, las métricas puedes ser realmente desesperantes cuando nuestro proyecto tiene cierta envergadura. No se con tipo de hilo de ejecución han realizado ambos análisis, pero dejan a RAD Studio fuera de juego.

En el próximo artículo veremos las novedades en el lenguaje Object Pascal.

Pruebas realizadas en RAD Studio 2007.

Publicidad