1/24/2004

Repaso de JDBC

JDBC drivers in the wild

Este artículo de la revista electrónica JavaWorld, escrito por Nitin Nanda, resume las características principales de cada tipo de driver JDBC. A pesar de ser un artículo del 2000, nos sirve para tener una visión general de los drivers.



  1. Tipo 1: Bridge JDBC-ODBC

  2. Tipo 2: API nativo /driver Java parcial

  3. Tipo 3: Protocolo de red/driver todo Java

  4. Tipo 4: Protocolo nativo/driver todo Java





Tipo 1: Bridge JDBC-ODBC

Este driver traduce todas las llamadas JDBC a ODBC y las envía al driver ODBC (ver figura).

Esto nos lleva a que el driver ODBC tenga que estar en la máquina cliente.

A favor: los drivers ODBC están en todas partes.
En contra: prestaciones, instalación ODBC en el cliente.


Tipo 2: API nativo /driver Java parcial

Convierte las llamadas JDBC a llamadas propietarias del Gestor relacional. Ver figura.

A favor: mejores prestaciones que el tipo 1.
En contra: la biblioteca del vendedor ha de estar en la máquina cliente -no utilizable en aplicaciones internet-. Peores prestaciones que tipo 3 y tipo 4.


Tipo 3: Protocolo de red/driver todo Java

Sigue una arquitectura en tres capas. Si la capa intermedia es Java, ésta puede utilizar un tipo 1 ó 2 para hablar con la base de datos.

A favor: no hace falta instalación en máquina cliente del driver nativo. Puede proveer cache, balanceo de cargas, etc.
En contra: la capa intermedia requiere código para la base de datos.


Tipo 4: Protocolo nativo/driver todo Java

Convierte las llamadas JDBC a nativas de manera que el cliente se pueda comunicar directamente con el servidor gestor (ver figura).

A favor: prestaciones, sin instalaciones nativas en el cliente. Se permite la carga dinámica de drivers.
En contra: driver diferente para cada base de datos.



Conclusiones del benchmarking

Este artículo concluye que:

1. El tipo 1 no debería de utilizarse.
2. El tipo 2 es OK para intranets, pero 3 y 4 siguen siendo mejores.
3. Tipo 3 OK cuando hay multitud de bases de datos.
4. Para el resto, tipo 4.




1/23/2004

La insoportable levedad de las pruebas software

Test Infected: Programmers love writing tests

JUnit es un framework de pruebas unitarias de regresión que se ha convertido en un referente cuando deseamos realizar sw fiable.

La forma de trabajar es la siguiente, que se nos muestra en el artículo:

1. Escribe el test que, obviamente, ahora no funciona.
2. Escribe el código que hará que el test funcione.

Esta fórmula es la utilizada en las metodologías ágiles: "code a little, test a little, code a little, test a little" ;)

JUnit contiene la clase TestCase, que heredaremos en nuestra clase de prueba (p.e. MoneyTest). Esta clase irá, por convenio, en el mismo paquete que la clase a ser probada, para que tenga acceso a los métodos de paquete.

public class MoneyTest extends TestCase {
//…
public void testSimpleAdd() {
Money m12CHF= new Money(12, "CHF"); // (1)
Money m14CHF= new Money(14, "CHF");
Money expected= new Money(26, "CHF");
Money result= m12CHF.add(m14CHF); // (2)
Assert.assertTrue(expected.equals(result)); // (3)
}
}

La última línea es la más interesante, por dos temas:

1. Hay que sobrecargar el método Object.equals para comparación de objetos.
2. Se utiliza Assert.assertTrue

JUnit provee en la clase Assert diferentes tipos de métodos:

  • assertEquals para booleans, chars, double, float, int, Objects, ...

  • assertFalse

  • assertNotNull

  • assertNotSame

  • assertNull

  • assertSame




Una vez que hemos definido las diferentes pruebas a realizar en nuestra clase de pruebas, hay que definir cómo se ejecuta un caso de prueba, y cómo se ejecuta una "suite" de pruebas.

Modos de prueba en JUnit

1. Estático
2. Dinámico

1. Se sobrecarga el método TestCase.runTest y se invoca mediante una clase anónima interna:
TestCase test= new MoneyTest("simple add") {
public void runTest() {
testSimpleAdd();
}
};

JUnit implementa un patrón Template Method para ejecutar adecuadamente ese método.

2. Utilización de introspección (Java Reflection) para implementar el método runTest. Asume que el nombre del test es el nombre del método del caso de prueba:
TestCase test= new MoneyTest("testSimpleAdd");


Suites de pruebas

TestSuite es un composite de Tests:

public static Test suite() {
TestSuite suite= new TestSuite();
suite.addTest(new MoneyTest("testEquals"));
suite.addTest(new MoneyTest("testSimpleAdd"));
return suite;
}

También hay otras maneras -desde JUnit 2.0-:

public static Test suite() {
return new TestSuite(MoneyTest.class);
}

JUnit extrae todos los métodos de pruebas automáticamente.

public static Test suite() {
TestSuite suite= new TestSuite();
suite.addTest(
new MoneyTest("money equals") {
protected void runTest() { testEquals(); }
}
);

suite.addTest(
new MoneyTest("simple add") {
protected void runTest() { testSimpleAdd(); }
}
);
return suite;
}

Lo mismo, pero de manera estática.




Ingeniería del Software II: fundamentos del temario (I)

LS4118: Introducción a la asignatura

La asignatura de Ingeniería del Software II se centra sobre todo en la utilización de mejores prácticas en el diseño de sistemas orientados a objetos. Sin ser éste el único objetivo de una asignatura de este tipo, sí es verdad que ahora mismo es la única asignatura obligatoria donde los alumnos salen con conocimientos de las diferentes arquitecturas existentes, de lenguajes de modelado, patrones, etc. Otras asignaturas, como Sistemas Distribuidos, Sistemas en Internet o, de alguna manera, Gestión de Red, abarcan parte de esta problemática, pero de manera insuficiente.

Vicente Orjales y yo estamos trabajando en la definición de un temario que aporte a los alumnos una máxima ganancia, aunque, sin duda alguna, con un esfuerzo por su parte bastante mayor que el realizado en el primer cuatrimestre.

1. Presentación (1 sesión)
2. Repaso a técnicas de orientación a objetos y metodologias de procesos.
En este capítulo se repasa el Proceso Unificado, y sobre todo, los conceptos de OO más importantes en cuanto a patrones: dicotomía interfaz-implementación y la guerra herencia vs. delegación (1 sesión)
3. Introducción a patrones (1 sesión)
4. Patrones estructurales: Composite. Utilizándolo como ejemplo inicial, dedicar un par de clases a explicarlo con sumo detalle, y con ejemplos en Java -¿y C#?-. Nos llevaría al menos dos sesiones, posiblemente tres. (3 sesiones)
5. Patrones estructurales: Proxy. (2 sesiones)
6. Patrones estructurales: Adapter. (1 sesión)
7. Patrones creacionales: Abstract Factory & Factory Method.(1 sesión)
8. Patrones creacionales: Builder.(1 sesión)
9. Patrones de comportamiento: State & Strategy.(1 sesión)
10. Patrones de comportamiento: Observer.(1 sesión)
11. Patrones de comportamiento: Command.(1 sesión)

Algunos de los patrones restantes se mandarán como trabajo para casa, p.e.: Prototype (C), Flyweight (S), Facade (S), Iterator (B), Visitor (B), Template Method (B), Chain of Responsibility (B).

Tras estos patrones se realizará un ejemplo que conjugue gran cantidad de ellos. El ejemplo habrá sido publicado con anterioridad para no perder el tiempo ese día, y centrarse en realizar el trabajo.
(3 sesiones)

14. Patrones Arquitectónicos: sin meternos en tanto detalle como en Sistemas en Internet, el conocimiento de los patrones de alto nivel es fundamental para futuros ingenieros sw:
14.1.: Layers (2 sesiones)
15. PA: MVC (1 sesión)
16. PA: Pipes&Filters, o Broker. (1 sesión)


Según mis cálculos, ahora mismo llevaríamos 21 sesiones + parcial.

17. Pruebas: fundamental que aprendan a hacer pruebas. Habría que utilizar JUnit y mirar Nunit a ver qué tal. Repaso a lo que dice el PU sobre pruebas, y algún patrón de Pruebas (2 sesiones).
18. Gestíón de Calidad de Software: ISO 9000, CMM (2 sesiones)

Y con esto hemos llegado a las 26 sesiones. Aunque no da tiempo a ver la parte de Orientación a Componentes y Frameworks, lo puedo dar como "culturilla general" o enlazar artículos de interés.



1/22/2004

Qué hay de nuevo en UML 2.0 -a partir del libro de Fowler- (I)

Books by Martin Fowler: UML Distilled

Me centro en diferencias entre UML1 y UML2 -alguna cosa se me escapará, claro ;)-

Cuando ya leí la segunda edición, me gustó mucho cómo empezaba el libro: de qué diferentes maneras puede ser utilizado UML:
1. Sketch: comunicación entre profesionales. Nada serio, sencillamente una muy eficaz forma de comunicación.

2. Blueprint: este es el diseño detallado del proceso unificado. Diseño a lo bestia, prácticamente la implementación -aunque algunas clases y/o algoritmos pueden verse añadidos/modificados posteriormente-.

3. Lenguaje de programación: traducción automática de modelo a código, al cuál se le añade después la "chicha".


Dónde usar UML

Fowler comenta, en cada una de los "workflows" principales, qué diagramas son susceptibles de ser utilizados:


  1. Requisitos:

    • Diagrama de casos de uso

    • Diagrama de clases, desde la perspectiva conceptual

    • Diagrama de actividad

    • Diagrama de estado



  2. Diseño:

    • Diagrama de clase, desde perspectiva sw -blueprint o implementación-

    • Diagrama de secuencia

    • Diagrama de paquetes

    • Diagrama de estado

    • Diagrama de despliegue






Propiedades

Son características estructurales de una clase: atributos y asociaciones.

A los atributos se le pueden añadir un {property-string}, que indica propiedades adicionales al atributo, como por ejemplo {readOnly}.

Multiplicidad

UML1 admitía multiplicidad discontínua -p.e. un vehículo puede tener entre 2 y 8 ruedas-. UML2 no lo permite ya.


Dependencias

Existe una serie de palabras clave a utilizar para nombrar las dependencias entre clases:


  1. call

  2. create

  3. derive

  4. instantiate

  5. permit

  6. realize

  7. refine

  8. substitute

  9. trace

  10. use




Restricciones

UML permite, en el diagrama de clases, la indicación de diferentes restricciones. Se puede utilizar lenguaje natural, lenguaje de programación, u OCL (Object Constraint Language) -cálculo de predicados-.


1/19/2004

Generación de Claves Primarias por parte de un Entity Bean en J2EE

TheServerSide: Entity Bean Primary Key Generator



Este artículo plantea la utilización de un Entity Bean que actúe como contador que se vaya incrementando. Como es un EBean, es distribuído, transaccional y persistente.

El bean contiene un atributo, long nextID, que contiene el valor en cada momento. Cuando se crea la clave primaria, el bean inserta en la "tabla" el nombre de la clave y el valor actual.

Este artículo es interesante tanto por lo que cuenta, como por cómo se llega a una solución más adecuada a partir de los comentarios de la gente. Este planteamiento de trabajo es mucho más ágil y lleva a mejores resultados que los típicos artículos -como puede ser éste mismo- sin opción a réplica.

En el artículo de Marinescu, se referencia un artículo de 1999, escrito por Scott Ambler, sobre generación de object identifiers:

El autor comenta que la utilización de claves con significado de negocio es muy peligroso, debido a que los cambios en esas columnas pueden provocar graves problemas en la integridad de la bbdd -p.e. si se cambiara el número de DNI o el Social Security USA-. Por ello, los gestores relacionales ofrecen lo que se denomina "surrogate keys", mediante claves incrementales. Estas claves son valores que se almacenan en tablas ocultas, y que se incrementan cada vez que se añade una fila de una tabla. El cómo se implementan estas tablas -valores globales para todas las tablas, o diferentes para cada una- depende del fabricante.

Además de esta estrategia, existen otras, como UUID (de la Open Software Foundation) y GUID (de Microsoft):
1. UUID: valores de 128 bits a partir de un hash del ID de Ethernet y la fecha sw.
2. GUID: hashes de 128 bits de un ID SW y la fecha.


¿Cuántos valores diferentes se pueden generar por segundo? Pues como los ordenadores actuales suelen representar el tiempo hasta milésimas de segundo, sólo podemos generar como mucho 1000 claves/segundo.

Además, las aplicaciones empresariales suelen ser multibases de datos, cada vendedor tiene una estrategia diferente, se da por hecho que existen comunicaciones constantes con las bases de datos para la obtención de la clave...

Ambler plantea una estrategia nueva denominada HIGH-LOW:

- El identificador se divide en dos partes:
1. Valor HIGH que se obtiene de una fuente definida.
2. Valor LOW que la propia aplicación se asigna a sí misma

- Cada vez que se obtiene un valor HIGH, el LOW se pone a 0.

1/18/2004

ENCAPSULAMIENTO DE INFORMACIÓN SEGÚN FOWLER

IEEE Software: Data Access Routines


Este artículo discute las diferentes posibilidades de "esconder" información cuando implementamos un sistema de información.

Inicialmente recomienda la utilización de atributos públicos en el acceso a valores simples, aunque más por comodidad que por otra cosa. Yo, la verdad, no estoy del todo de acuerdo; aunque evidentemente es un "rollo" tener que estar codificando -aunque los IDEs ayudan- métodos "getter" y "setter", creo que es una buena práctica, y simple.

En el acceso a colecciones de valores, sí tiene mucha razón al explicar que, por ejemplo,

class Album {
private List tracks =new ArrayList();
public List getTracks() {
return tracks;
}
}

, no es una buena manera de encapsular datos, pues el usuario podría añadir y eliminar elementos de la lista sin conocimiento de la estructura.

Fowler establece tres maneras de leer valores, manteniendo la encapsulación:

1. Copiado
Devolver no la referencia, sino una copia.

2. Proxy de protección
Devolver un proxy que impide modificaciones sobre la estructura:
class Album {
private List tracks = new ArrayList();
public List getTracks() {
return Collections.unmodifiableList(tracks);
}
}
(en C++, se consigue con la palabra clave const).

3. Iterador
Un iterador que permite avanzar por la colección, sin tener realmente acceso a ella -aunque sí a los elementos-.


Construcción de Objetos

¿Constructores repletos de argumentos para disponer de objetos bien formados desde el principio, o constructor vacío o casi vacío, más métodos "setter"? Fowler no es definitivo en este apartado, aunque en general prefiere la primera opción. Yo me quedo con la utilización de constructores pequeños con aquellos argumentos que representan atributos inmutables, y la utilización de métodos setter para el resto. Cada cuál...