Metaprogramación y el boicot de C++

No estoy programando dispositivos de hardware, ni aplicaciones de sistema, estoy tratando de programar una aplicación clásica de base de datos, y está escrita en C++. ¿Ha sido un error? Yo insisto que sí.

Uno de mis principios más encumbrados es que la computadora debe hacer más trabajo que uno. Para que esto se cumpla, uno tiene que ser lo suficientemente inteligente para decirle a la computadora que ella sea la que escriba los programas y no uno. A esto se llama metaprogramación.

Un ejemplo clásico de metaprogramación es precisamente este tipo de aplicaciones, la que manipulan información en una base de datos. Las buenas prácticas dicen que se debe tener una representación de objeto por cada entidad utilizable en la base de datos. Es decir, si tenemos una tabla inventario, con los atributos id, descripcion y precio, deberomos tener un objeto que nos represente un elemento de dicha tabla y, obviamente, tendrá los mismos atributos, además de los métodos Get/Set para su fijado.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class dbInventario: virtual public DataBase {
private:
       unsigned int id;
       char descripcion[51];
       long precio;
public:
       dbInventario(unsigned int id, const char *descripcion, long precio);
       dbInventario();
       ~dbInventario();
       void findbyPrimaryKey(unsigned int id);

       unsigned int getID() { return id; };
       void setID(unsigned int id);
       const char *getDescripcion() const { return descripcion; };
       void setDescripcion(const char *descripcion);
       long getPrecio() { return precio; };
       void setPrecio(long precio);
}

Donde DataBase es una clase heredada que contendrá todos los métodos necesarios para el acceso a la base de datos. Más delante volveremos a esta clase.

Sin embargo, lo primero que nos salta a la vista es la méndiga hueva de hacer una clase por cada entidad en nuestra base de datos, máxime si es una tabla grande. La solución más elegante es la metaprogramación: observamos que el esqueleto de todas estas clase de entidad tiene un patrón muy similar, ¡entonces escribamos un programa que escriba programas! La respuesta más obvia es programarlo en un lenguaje de Dioses: Perl. Ya estamos pensando inteligentemente. Este programa se conectará a la base de datos, extraerá el esquema de dicha base de datos y creará un archivo .hpp y uno .cpp para cada entidad que encuentra, extrayendo también los campos y su definición, para mapearlos en C++ y dejar un bonito esqueleto para cada clase de entidad. El resto sería agregar las validaciones específicas para cada atributo de entrada.

Hasta ahora viento en popa y buenas corrientes. Entonces es cuando debemos volver a nuestra clase base DataBase. Esta clase es un proxy, una representación de la conexión a la base de datos, manejando tal vez un pool de conexiones, administrando los recursos justos del DBMS y será la responsable de hacer todas las operaciones sobre él: select, insert, update (básicamente). Las clases de entidad, al ser derivadas de esta última, tendrán todas sus capacidades.

Meditándolo ahora, creo que no es buena opción hacerlo a través del mecanismo clásico de la herencia, tal vez un patrón proxy sería mejor opción, aprovechando su propiedad singleton para controlar el número de conexiones a la base de datos.

En fin, esta clase DataBase, que ya de por sí es especial por el control de recursos, es además la encargada de generar dinámicamente las operaciones sobre la base de datos, es decir, esta generará el código en SQL necesario para la operación sobre la entidad manipulada. Para poder hacer esto, esta superclase deberá ''descubrir'' los atributos de la clase actualmente manipulada y generar la consulta debida. Para descubrir esto en tiempo de ejecución, los lenguajes de programación decentes utilizan un mecanismo conocido como introspección. Y aquí es donde la marrana tuerce el rabo. Lástima, nuestra clase DataBase ya no podrá ser tan inteligente como nosotros quisieramos, y estaremos oblicados a teclear cada consulta SQL necesaria dentro de cada clase de entidad.

C++ es un lenguaje de tipeado duro, con pocas habilidades para la manipulación de tipos en tiempo de ejecución. Esto quiere decir que la introspección es algo inexistente. Y aquí, practicamente se acabó la magia, ya no podemos hacer un código que genere código, porque estamos obligados a que cada instrucción SQL necesaria, la tengamos lista a tiempo de compilación.

En conclusión, cuando programamos cosas en las que vemos patrones repetitivos, lo mejor es evitar teclear a favor de que la computadora lo haga, sin embargo, en un lenguaje de bajo nivel como C++, esto ya no es tan posible. Sean inteligentes, utilizen la mejor herramienta para cada problema.

ADDEUDUM:

Existen preprocesadores, como OpenC++, que permite la introspección de clases en C++, sin embargo utilizarlos endurecería el ciclo de desarrollo al aplicar una capa más de software.