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.

25 julio 2008

Pasando de Delphi 7 a RAD Studio 2007 (3)

EL MODO SYNC EDIT

Si en el artículo anterior vimos como renombrar una variable utilizando las opciones de refactorización, ahora vamos a ver como hacerlo en tiempo real utilizando el modo Sync Edit.

Supongamos que quiero renombrar la variable i de este bloque de código:


Para renombrar rápidamente la variable i hacemos lo siguiente:

1. Seleccionamos todo el procedimiento:


2. Pulsamos el botón Sync Edit Mode que he señalado en la imagen superior y cambiará el color de la selección:


3. Como el cursor ya aparece situado en la variable i sólo tenemos que cambiarle el nombre de i por j:


4. Volvemos a pulsar el botón Sync Edit Mode que ahora está situado al principio del procedimiento y desaparecerá la selección dejando la variable renombrada.

Este modo esta pensado para estos casos en concreto. Si intentamos renombrar por ejemplo una variable de una clase en la interfaz no cambiará en su implementación, para ello ya tenemos que utilizar las opciones de refactorización que vimos en el artículo anterior. El modo Sync Edit sólo afecta al bloque de código seleccionado.

LAS PLANTILLAS DE CÓDIGO: LIVES TEMPLATES

Las plantillas de código nos permiten aumentar la productividad escribiendo código fuente mucho más rápido. Por ejemplo, nos ponemos a crear un bucle for:


Después de escribir la palabra for y pulsar la tecla de espacio nos traerá la plantilla que trae por defecto para la palabra for:


Escribimos la variable i y pulsamos el tabulador, lo que nos llevara al valor inicial:


Como quiero hacer un bucle desde 1 hasta 10, escribo 1 y vuelvo a pulsar el tabulador:


Ahora escribimos el valor 10 y quitamos el valor -1. Pulsamos Intro después de la palabra do nos creará arriba la variable automáticamente:


Cuando nos acostumbramos a este modo de trabajar se obtiene mucha velocidad y comodidad. De todas formas, si queremos escribir por libre sin utilizar la plantilla lo que tenemos que hacer es pulsar la tecla Escape en el momento que aparezca la plantilla. De ese modo vuelve a comportarse como un editor normal.

Igualmente tenemos una platilla para un bucle while:


Otra para la sentencia case:


Para ver todas las plantillas que hay definidas por defecto nos vamos al menú View -> Templates. Aparecerá esta ventana:


Si hacemos doble clic sobre cualquier plantilla la insertará directamente donde tengamos el código fuente:


Para modificar una plantilla pulsamos el botón Edit Code Template:


Se abrirá una nueva pestaña de código en el editor mostrando como está diseñada la plantilla en XML:


Para el bucle for la plantilla se llama fors.xml. Anteriormente hemos pulsado el tabulador para pasar de un punto a otro de la plantilla. Estos puntos están definidos dentro del archivo XML como point.


El valor que hay en hint es la etiqueta de ayuda que va a mostrar al usuario. Lo que voy a hacer yo ahora es predefinir el bucle for a mi gusto, de este modo cuando escriba la palabra for y pulse espacio mostrará esta plantilla:


Una de las plantillas que lleva predefinidas el editor de código y que es muy útil es cuando escribimos begin:


y pulsamos Intro:


Investigando un poco las plantillas estándar XML que hay definidas podemos desarrollar las nuestras propias para ganar velocidad al escribir código.

EL DEPURADOR DE CÓDIGO

El debugger también ha sufrido algunos cambios que van a mejor y que permiten averiguar los errores con mucha más rapidez. Cuando ejecutamos un programa con el botón:


o con la tecla F9, el debugger está activado por defecto, aunque se puede desactivar seleccionado en el menú superior Tools -> Options y en la ventana que aparece seleccionamos a la izquierda la sección Debugger Options y desactivamos la opción Integrated Debugging:


Supongamos que quiero depurar este bloque de código:


Lo que tenemos que hacer es pinchar con el botón izquierdo del ratón en la barra gris que hay a la izquierda del código para que aparezca el punto rojo que marca el inicio de la depuración (como se muestra en la imagen superior).

Cuando ejecutamos el programa y pasa por ese procedimiento se detiene en la línea donde hemos situado el punto de interrupción (el punto rojo):


Aparece una flecha azul en la parte izquierda (encima del punto rojo) que nos va a indicar la línea que se va a ejecutar en el programa. Para avanzar a la siguiente línea debemos pulsar la tecla F8. Los puntos azules que aparecen son las líneas por donde va a pasar el puntero de ejecución.

Al pulsar F8 para pasar a la siguiente línea dejará seleccionada la primera y saltará al bucle for:


La flecha azul se sitúa ahora al comienzo del bucle mientas que en la línea anterior que ha ejecutado nos pone una señal verde de confirmación para indicarnos que se ha ejecutado correctamente.

Como ya ha ejecutado la primera línea, si situamos el cursor del ratón encima de la variable c nos dirá su contenido:


Conforme vamos pulsando F8 y viendo como se va repitiendo el bucle también tenemos a la izquierda del editor de código una sección en la que nos muestra en tiempo real el contenido de las variables locales:


Podemos ver incluso el contenido del array bidimensional que he creado:


Si situamos el cursor encima de Mapa nos muestra todo el contenido del mismo permitiendo incluso observar el contenido de cada dimensión del array:


Este procedimiento que estamos monitorizando no contiene llamadas a otros procedimientos, pero si las tuviera podríamos entrar a los mismos pulsando el botón F7 en lugar de F8.

Podemos salir del modo de depuración con la tecla F9 que lo que hace es continuar la ejecución del programa o podemos detener el programa seleccionando Run -> Program Reset o pulsando la combinación de teclas CTRL + F2.

Veamos otro ejemplo un poco más complicado. Supongamos que tenemos un pedido con una serie de artículos. Lo vamos a controlar con estas dos clases:

type
TArticulo = class
ID: Integer;
sNombre: String;
rPrecio: Real;
end;

TPedido = class
ID: Integer;
sCliente: String;
Articulos: array[1..50] of TArticulo;
rTotal: Real;
end;

Ahora creamos un procedimiento que crea un pedido y le añade artículos:

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].rPrecio := 410.25;

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

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

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

// Eliminamos el pedido
Pedido.Free;
end;

Ahora situamos el punto de interrupción en la primera línea y vamos ejecutando el pedido con la tecla F8 hasta ejecutar la línea:

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


Si situamos el cursor encima del objeto Pedido podemos ir rastreando todo su contenido en tiempo real:


De este modo podemos ver la jerarquía de clases de un solo vistazo y sin tener que poner mensajes de depuración para ver su contenido. Esto es muy útil cuando queremos ver el contenido de los registros de las bases de datos que estamos procesando.

Por si esto fuera poco además podemos mirar a la izquierda la jerarquía y contenido de los objetos como si de un explorador se tratase:


Esto da mucha seguridad y potencia al programador para realizar una completa auditoria del código fuente en tiempo real, sobre todo cuando tenemos que modificar el código fuente que ha realizado otro programador y no tenemos ni idea de su comportamiento.

COMPROBANDO LAS PERDIDAS DE MEMORIA

Uno de los grandes problemas que tenemos en el lenguaje Object Pascal es que no libera automáticamente de memoria las variables y objetos que hemos creado (al contrario de otros lenguajes más modernos como Java, C#, Python, Ruby, etc.).

En las versiones de Delphi for Win 32 y RAD Studio tenemos disponible una variable global que permite averiguar que variables no hemos liberado al finalizar la ejecución del programa.

Al inicio de nuestro programa tenemos que activar esta variable:

ReportMemoryLeaksOnShutdown := True;

En el ejemplo del pedido que vimos anteriormente supongamos que no he liberado el artículo 2:

procedure CrearPedido;
var
Pedido: TPedido;
begin
ReportMemoryLeaksOnShutdown := True;

// 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].rPrecio := 410.25;

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

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

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

// Eliminamos el pedido
Pedido.Free;
end;

Como podéis ver en el código fuente he activado la variable global ReportMemoryLeaksOnShutdown para comprobar las perdidas de memoria.

Al finalizar la ejecución del programa nos aparecerá este mensaje:


Nos dice la clase de objeto que no hemos liberado y los bytes de memoria que se han perdido. El fallo que tiene es que no dice ni en que unidad se ha producido la pérdida de memoria y el número de líneas o procedimiento donde se produjo el error.

Esto es muy útil para zonas localizadas del programa cuando sabemos por donde anda el error, pero no es muy práctico en grandes proyectos donde no sabemos ni siquiera la unidad que está sufriendo las pérdidas de memoria.

Personalmente recomiendo utilizar el experto EurekaLog, que se integra perfectamente en cualquier versión de Delphi y le da cien vueltas al depurador de memoria de CodeGear.

En el próximo artículo seguiremos exprimiendo al máximo RAD Studio 2007.

Pruebas realizadas con RAD Studio 2007.

Publicidad