La formación en ciencias de la computación a menudo se centra en algoritmos y estructuras de datos, relegando los principios de diseño de marcos y lenguajes. Esto deja a los desarrolladores de aplicaciones mal preparados para la creación de componentes reutilizables, una tarea cada vez más crucial.
Antes de la popularidad de la programación orientada a objetos, el código reutilizable generalmente provenía de bibliotecas especializadas. En el ecosistema de Qt, esta situación ha mejorado. Programar con Qt implica frecuentemente desarrollar nuevos componentes. Una aplicación típica suele incluir varios componentes personalizados reutilizables, que a menudo se comparten en otros proyectos. Entornos como KDE amplían Qt con bibliotecas adicionales, ceintos de clases extendiendo su funcionalidad.
Pero, ¿qué constituye una API en C++ efectiva? La calidad depende de factores como el dominio del problema y el público objetivo. Sin embargo, ciertas características son deseables universalmente.
Características Fundamentales de una API Efectiva
Las APIs están dirigidas a programadores, describiendo interfaces para componentes. El objetivo es crear APIs que sean mínimas pero completas, con semántica clara y simple, intuitivas, fáciles de recordar, y que fomenten código legible.
- Mínima: Una clase debe exponer el menor número posible de miembros públicos y existir el menor número de clases necesarias. Esto simplifica comprensión, memorización, depuración y evolución.
- Completa: Debe proveer todas las funcionalidades esperadas. Esto puede entrar en conflicto con la minimalidad. Si un miembro pertenece a una clase inapropiada, los usuarios no lo encontrarán.
- Semántica Clara y Simple: Apegarse al principio de mínima sorpresa. Hacer las tareas comunes sencillas y las complejas posibles sin complejidad innecesaria. Evitar soluciones sobre-generalizadas para problemas específicos.
- Intuitiva: Debe ser usable sin consultar la documentación por usuarios con experiencia intermedia, y el código que la usa debe ser comprensible a simple vista.
- Fácil de Recordar: Utilizar nombres consistentes y precisos. Emplear patrones y conceptos reconocibles, evitando abreviaturas.
- Fomenta Código Legible: El código se escribe una vez pero se lee (y depura) muchas veces. Una API que promueve la legibilidad ahorra tiempo en el ciclo de vida del software.
Diferentes usuarios interactuarán con distintas partes de la API. Mientras instanciar una clase de Qt suele ser intuitivo, los expertos deben revisar la documentación antes de subclasificarla.
La Trampa de la Conveneincia
Un error común es asumir que menos código significa una mejor API. Recuerde que el código se lee más de lo que se escribe. Considere:
// Enfoque conciso pero menos legible
QSlider *deslizador = new QSlider(12, 18, 3, 13, Qt::Vertical, 0, "volumen");
// Enfoque explícito y más legible
QSlider *deslizador = new QSlider(Qt::Vertical);
deslizador->setRango(12, 18);
deslizador->setPasoPagina(3);
deslizador->setValor(13);
deslizador->setNombreObjeto("volumen");
El Problema de los Parámetros Booleanos
Los parámetros booleanos frecuentemente reducen la legibilidad. Agregar un booleano a una función existente suele ser un error. Ejemplo con repaint():
// Podría interpretarse como "no repintar"
widget->repaint(false);
Una API mejor podría ser:
widget->repaint();
widget->repaintWithoutErasing();
Otra solución es usar enums. Comparen:
// Qt3
cadena.reemplazar("%USUARIO%", usuario, false);
// Qt4
cadena.reemplazar("%USUARIO%", usuario, Qt::CaseInsensitive);
Polimorfismo Estático
Clases similares deban tener APIs similares. Esto facilita la sustitución y la memorización. Por ejemplo, intercambiar QListBox por QComboBox es sencillo debido a sus APIs consistentes, un concepto conocido como polimorfismo estático.
El Arte del Nombrado
El nombrado es crucial en el diseño de APIs. Evite abreviaturas, incluso obvias como "prev" para "previous". Mantenga consistencia, por ejemplo, usando objectName() en lugar de name() en Qt4 para mayor claridad.
Nombrado de Clases y Enums
Agrupe clases con sufijos consistentes, como -View para vistas basadas en modelos. Para enums, repita un elemento del tipo en los valores para claridad:
namespace Qt {
enum Esquina { EsquinaSuperiorIzquierda, EsquinaInferiorDerecha, ... };
enum SensibilidadMayusculas { SensibilidadMayusculasInsensible, SensibilidadMayusculasSensible };
...
};
tabWidget->setEsquinaWidget(widget, Qt::EsquinaSuperiorIzquierda);
cadena.indiceDe("$(QTDIR)", Qt::SensibilidadMayusculasInsensible);
Funciones y Parámetros
Los nombres de funciones deben indicar claramente si tienen efectos colaterales. En Qt3, QString::simplifyWhiteSpace() violaba esto al no modificar la instancia; en Qt4 se renombró a QString::simplified().
Para getters booleanos, use prefijos is- para atributos adjetivales, omita prefijos para verbos y sustantivos. Ejemplos: isChecked(), aceptaSolturas(), autoCompletado().
¿Puntero o Referencia?
Para parámetros de salida, el uso de punteros puede mejorar la legibilidad al indicar explícitamente la modificación:
// Uso de punteros (claro que se modifican)
color.obtenerHsv(&h, &s, &v);
// Uso de referencias (menos evidente)
color.obtenerHsv(h, s, v);
Ejemplo Práctico: QProgressBar
Veamos la evolución de la API de QProgressBar de Qt3 a Qt4.
API en Qt3 (fragmento):
class QProgressBar : public QWidget {
...
public:
int totalSteps() const;
int progress() const;
...
public slots:
virtual void setProgress(int progress);
...
};
Problemas: complejidad, falta de uniformidad con otros widgets, nombres como reset() ambiguos.
API rediseñada en Qt4 (fragmento):
class QProgressBar : public QWidget {
...
public:
void setMinimo(int minimo);
int minimo() const;
void setMaximo(int maximo);
int maximo() const;
void setRango(int minimo, int maximo);
int valor() const;
...
virtual QString texto() const;
void setTextoVisible(bool visible);
bool esTextoVisible() const;
AlineacionQt alineacion() const;
void setAlineacion(AlineacionQt alineacion);
public slots:
void restablecer();
void setValor(int valor);
signals:
void valorCambiado(int valor);
};
Mejoras: uso de minimo/maximo/valor (consistentes con QSpinBox), señales, alineación unificada.
Consejos para el Diseño de APIs
La API requiere control de calidad. Pruebe con casos de uso para verificar legibilidad. Obtenga feedback de otros programadores, tanto con como sin documentación. Escribir la documentación puede ayudar a encontrar nombres precisos; si no puede nombrarlo claramente, quizá no deba existir. Cuando sea necesario, invente nuevos términos, como se hizo con "widget" o "event".