1.7 Programación actual
"However, pease remember that the aims are simplicity, regularity, and performance. You don't prove that you are clever by producing the most complicated code". Bjarne Stroustrup en "The State of the Language".
§1 Introducción
Loque podíamos llamar "programación tradicional", por ejemplo la que se utilizaba (utiliza?) en la confección de programas para los primitivos PCs bajo MS-DOS, o en los actuales Win-32 bajo una "ventana" DOS, es un concepto un tanto ambiguo, pero podemos intentar una definición diciendo que se basa en algunas premisas y características bastante definidas. Estas características pueden coexistir juntas o faltar alguna, pero en general se dan simultáneamente. En este capítulo intentaremos mostrar una visión sinóptica de las diferencias entre esta y la programación "moderna" [1], a la que seguramente tendrá que adaptarse el programador C++.
Sin embargo, advertiremos desde ahora que la mentada "programación tradicional" mezcla conceptos distintos e independientes, cuyas diferencias es importante tener claras antes de adentrarnos en la programación actual. Por ejemplo, la programación para Windows-32; asunto este que desde luego va más allá que la simple utilización de "objetos".
Este capítulo se ha redactado teniendo en mente la multitud de problemas con que se enfrenta el programador "tradicional" que se ve abocado a trabajar para un SO moderno (Windows inevitablemente?). Intentaremos dentro de lo posible clarificar algunas ideas de ese cúmulo de nuevos conceptos que con demasiada frecuencia producen más de una "indigestión" inicial [2].
§2 Sinopsis
Para situarse correctamente en el asunto, es fundamental entender que las diferencias entre la programación tradicional y la actual tienen su origen inmediato en la evolución de los Sistemas Operativos, evolución que ha sido posible a su vez por la evolución del hardware. Por ejemplo, máquinas capaces de direccionar más memoria. También por la evolución de las herramientas de programación (lenguajes). Las características y diferencias de la programación tradicional frente a la actual pueden resumirse en el siguiente cuadro:
|
|
§3 Características de la "Programación Tradicional"
§3.1 Representación en modo texto
Se trabaja en un entorno de texto (no gráfico), el programa en ejecución controla la información representada en la totalidad de la pantalla (no hay "ventanas"); el control de esta se realiza en término de filas y columnas (generalmente 24 x 80) y un surtido muy limitado de 256 caracteres (ASCII char set 2.2.1a). Los únicos atributos que pueden tener los caracteres suelen ser: Color de tinta y de papel (trazo y fondo); subrayado y parpadeo.
Cuando se trabaja en entornos gráficos, este tipo de aplicaciones no-gráficas se denominan "de Consola". En la nomenclatura Windows a este tipo de aplicaciones se las conoce como CUI ("Console User Interface") en contraposición con las de interfaz gráfica GUI ("Graphical User Interface") que comentamos más adelante ( §4.1) .
§3.2 Trabajo en mono-programa
El SO. no admite multiprogramación, es decir, solo corre una aplicación cada vez. Cuando ejecutamos nuestro programa no tiene que compartir recursos con ningún otro (por ejemplo, nuestras órdenes de impresión pueden ser dirigidas directamente al "puerto" de impresora). Esto hace que en general podamos utilizar rutinas y llamadas de "bajo nivel" sin peligro alguno de interferir con nada. La totalidad de los recursos, tales como el procesador, la memoria, los puertos E/S etc. están a nuestra disposición exclusiva.
§3.3 Trabajo en mono-tarea
El programa solo tiene una vía, hilo o hebra ("Thread") de ejecución, decimos que es mono-hebra. Coloquialmente podemos decir que solo hace una cosa cada vez.
§3.4 Ejecución controlada por el programa
Desde su concepción, el programador decide que ocurre exactamente en cada momento de la ejecución del programa, de forma que las vías de actuación pueden ser previstas de antemano.
Desde un punto de vista funcional, esto significa que, por ejemplo, se puede decidir en que momento se atenderá una llamada del teclado o en que momento atenderá la UART del puerto serie para atender la llegada o envío de datos.
Desde el punto de vista del código, el programa (suponemos que es C) se inicia en la función main y termina cuando esta termina. main puede llamar a otras funciones (que pueden llamar a su vez a otras funciones), pero siempre es el programa el que decide cual es la vía de acción en cada caso; que funciones se invocan y cuando.
En el fondo, esta característica es consecuencia de que el programa controla más o menos directamente sus propios procesos de entrada/salida. Puede por ejemplo "leer" el teclado o "escribir" directamente en el puerto de impresora, por lo que las decisiones pueden tomarse "desde dentro" del programa.
§3.5 Programación "procedural"
Aquí utilizamos el término "procedural" [3] para indicar que generalmente no se utilizan lenguajes orientados a objetos (POO). Los lenguajes empleados adoptan una fuerte compartimentación entre los tipos de datos disponibles y las operaciones posibles con ellos, que son fijas e incluidas en el propio lenguaje. No existen las clases como nuevos tipos que encapsulan el dato y sus operaciones ( 1.1), las entidades de mayor nivel de abstracción que pueden utilizarse son las estructuras o uniones del C clásico y similares.
§4 Características de la Programación "Moderna"
Debemos recalcar de nuevo que se trata de conceptos generales e independientes. Por ejemplo, un programa "moderno" puede ser multiprogramación pero en modo texto, o no orientado a objetos; sin embargo, la mayoría de las característica se dan juntas. En especial si se trata de programas que utilizan la interfaz gráfica de los SOs más conocidos.
§4.1 Representación en modo gráfico
El usuario dispone de una interfaz gráfica GUI ("Graphical User Interface") para trabajar en la aplicación. Este tipo de programas controlan la información representada en su "canvas" [7], un trozo (ventana) de la pantalla cuyo tamaño puede controlar el usuario la mayoría de las veces. La representación se realiza en pixels [8], y se dispone de un amplísimo surtido de herramientas y parámetros de representación; no solo un amplio juego caracteres ("Fonts") con todos sus atributos, también un pincel ("Brush"), una pluma ("Pen"), así como iconos e imágenes preconstruidas de todo tipo.
La posibilidad de multiprograma reseñada a continuación , junto con las nuevas posibilidades gráficas, hacen que la pantalla pueda contener múltiples "ventanas", representativas de otros tantos procesos en ejecución. El resultado es lo que se ha dado en llamar "Metáfora de un escritorio" ("Desktop metaphor"). A su vez la pantalla se convierte en otro dispositivo de entrada (no solo el teclado); puede arrastrarse, cortarse y copiarse información de un punto a otro. Incluso entre aplicaciones diferentes.
Ni que decir tiene que la programación de un entorno gráfico es muchísimo más compleja que para un entorno de texto, aunque afortunadamente los entornos de desarrollo actuales ofrecen multitud de soluciones preconstruidas (clases) y librerías que facilitan la labor, de forma que si un programador quiere insertar, por ejemplo, un botón en un formulario, no tiene que preocuparse en "dibujar" el botón y su posible etiqueta. Simplemente indicar su tamaño; el texto o dibujo de su etiqueta; su posición en el "canvas", y las acciones a tomar cuando en dicho objeto se produzcan determinados "eventos": que pase por encima el cursor; que reciba foco; que se haga clic sobre él, etc.).
Desde el punto de vista de la interfaz que percibe el usuario, existen dos tipos de aplicaciones: SDI y MDI.
SDI significa "Single Document interface". Son aplicaciones que se desarrollan en una sola ventana. Por su parte, MDI ("Multiple Document Interface") supone que la interfaz de la aplicación es una ventana maestra ("Frame window"), que puede contener múltiples ventanas hijas o "documentos" abiertos simultáneamente. Estas ventanas descendientes coinciden con la ventana madre cuando son maximizadas, y se cierran cuando se termina el proceso principal (que abrió la ventana maestra).
§4.2 Trabajo en multi-programa
El SO admite multiprogramación ("multiprogramming") es decir, se ejecutan múltiples aplicaciones a la vez. De hecho, incluso en un sencillo ordenador personal, el propio Sistema puede mantener en ejecución simultanea seis u ocho aplicaciones para sí mismo, además de los programas de aplicación del usuario.
Esta operación se ejecuta de forma preemptiva. Lo que significa que el SO tiene un control continuo sobre el procesador y los diversos programas en ejecución. Por ejemplo, el SO puede abortar, suspender o asignar tiempos de ejecución a cualquiera de ellos.
Nota: La ejecución no preemptiva se denomina cooperativa, y es propia de sistemas multiprograma antiguos. El control es copado por el programa en ejecución, que debe terminar por sí mismo para que pueda ejecutarse otra aplicación. En estas condiciones, si una aplicación no termina por sí misma puede bloquear el Sistema, razón por la que se necesita la "cooperación" de la aplicación para que el Sistema funcione correctamente [9].
El usuario puede estar ejecutando diversos programas simultáneamente, siendo
muy fácil saltar de uno a otro. Por ejemplo, puede estar escribiendo un
documento con un procesador de texto y simultáneamente, estar consultando
ciertos datos que necesita en el Navegador de Internet o en una hoja de
cálculo. Incluso puede estar ejecutando al mismo tiempo diversas activaciones de un mismo programa.
En estas condiciones, cuando ejecutamos nuestro programa, debe compartir recursos con todas las demás aplicaciones en ejecución. Por ejemplo, nuestras órdenes de impresión no están ya directamente dirigidas al "puerto" de impresora; en cambio se "lanza" una tarea a un programa (monitor de tareas) que recibe la petición y la encola en un proceso batch que es el que en realidad gobierna la impresión. Esto hace que en general no sea ya posible utilizar rutinas y operaciones E/S de "bajo nivel", pues estos asuntos son controlados directamente por el Sistema Operativo, todo lo más que se puede hacer es realizar "peticiones" al sistema de determinados servicios siguiendo las convenciones (a veces muy complejas) establecidas en cada caso. Tenga en cuenta que por ejemplo, en un programa Windows, los datos de la propia ventana en la que corre el programa están en una zona de memoria gobernada por el Sistema, y que esta información queda oculta al programa, que debe limitarse a manejar los mensajes que le llegan desde aquel.
Nota: En el capítulo dedicado al SO Windows ( 0.7) puede verse un punto de vista complementario, como maneja este Sistema las aplicaciones que corren en él.
La totalidad de los recursos, tales como el procesador, la memoria, los
puertos E/S etc. no están ya a nuestra disposición. Es el SO el que
los controla, procurando que nuestro programa no haga algo equivocado que
pueda dañar el resto de las aplicaciones. Cuando por error un programa
intenta salir del ámbito que se le ha asignado (por ejemplo escribiendo en
una zona de memoria equivocada), es el SO el que nos alerta. Puede
tratarse del clásico mensajito: Este programa ha
realizado una operación no válida y será interrumpido. Si el problema
persiste consulte con el proveedor del programa
(Horror, si soy yo mismo...).
Como puede suponerse, las implicaciones para el programador que trabaja sobre uno de estos entornos son enormes, puesto que además del conocimiento de un lenguaje de programación adecuado (C++ por ejemplo), se exige el conocimiento de la interfaz; lo que se denomina la API (Application Programmer Interface) del sistema ( 1.7.1). El resultado de que sea el propio SO el que controla las E/S del programa, es que el funcionamiento de este es en realidad un diálogo continuo con el Sistema, del que recibe determinada información (entradas), y del que solicita determinados servicios (salidas).
§4.3 Trabajo en multi-tarea:
El programa puede tener más de una vía, hilo o hebra (thread) de ejecución secuencial; es multi-hebra ("multithread"). Coloquialmente decimos que puede hacer varias cosas al mismo tiempo.
Esto significa que el programa puede recorrer diversas vías de ejecución simultanea (a cada una de estas vías o caminos de ejecución lo denominamos una "tarea"). El programador debe controlar dos o más vías de ejecución paralela que pueden estar o no sincronizadas entre si ( 1.7.2).
En rigor solo los equipos multiprocesador son capaces de realizar una auténtica multitarea; los dotados de un solo procesador son capaces de realizar una suerte de simulación de tiempo compartido, siempre que el SO y el lenguaje utilizado están habilitados para ello, aunque desde el punto de vista de la lógica de la aplicación este detalle sea inapreciable.
Aunque el hardware sea adecuado, no todos los Sistemas Operativos son capaces de soportar este tipo de ejecución. Por ejemplo, Windows 95 solo simula la multitarea, incluso si el hardware es adecuado. Ver algunos comentarios a estos conceptos: Multiprograma & multitarea ( 1.7b).
§4.4 Control de ejecución orientado a "Eventos"
Aparte de los otros tópicos que se mencionan en este capítulo, la forma en que se controla la ejecución de un programa para un entorno "moderno" (tipo Windows-32) es bastante distinta de la utilizada en los entornos "clásicos". Este cambio es el que suele provocar mayor desconcierto inicial, y sobre el que quizás encuentre menos información (existen muchos libros sobre Programación Orientada a Objetos y comparativamente pocos sobre Programación Orientada a Eventos). Es en mi opinión, la adaptación mental más trabajosa. El programador clásico se ve obligado a "cambiar el chip" por otro radicalmente distinto.
En contra de lo que ocurre en la programación "tradicional", en la que es el propio programa el que decide que se hace y cuando, en la programación orientada a eventos la ejecución está controlada por el SO. El programa se parece a un bucle que espera continuamente la recepción de mensajes del Sistema y responde en consecuencia ejecutando determinados procesos. Los procesos pueden incluir peticiones de nueva información, o solicitud de determinados servicios.
Los mensajes del Sistema tienen su origen en eventos (sucesos) de etiología muy distinta. Por ejemplo, una interrupción del teclado; del ratón, que puede hacer clic o doble clic en cualquier área de la ventana de la aplicación, o simplemente pasar sobre una zona determinada. Incluso la terminación del programa ocurre cuando se recibe una petición del sistema en este sentido (porque se ha hecho clic en el botón de cerrar la aplicación, o por cualquiera de los otros procedimientos típicos en las aplicaciones gráficas).
Ver algunas matizaciones a estos conceptos en "Controlar un programa" ( 1.7a).
§4.5 Programación Orientada a Objetos
Los lenguajes empleados utilizan los recursos de esta técnica de programación ( 1.1). Las principales ventajas, aparte de un mejor encapsulamiento de los datos y sus operaciones, son la herencia; la sobrecarga, y el polimorfismo. Virtualmente no existe límite a la complejidad de los "nuevos" tipos de datos que pueda crear el programador, ni de sus operaciones.
Aparte de las ventajas genéricas antes enunciadas, la POO está especialmente indicada para la programación en los nuevos entornos operativos, porque los nuevos paradigmas de programación para entornos distribuidos (Redes) conciben las aplicaciones, y sus relaciones con el mundo exterior, como un mundo de objetos que dialogan y transaccionan ( 1.7.1). Un mundo del que solo es necesario conocer las reglas de diálogo y transacción, y este es justamente uno de los paradigmas de la POO.
[1] La verdad es que el término "moderno" me produce un cierto rubor. Al paso que van la ciencia y tecnología informáticas, lo "moderno" de hoy seguramente nos producirá una sonrisa de conmiseración dentro de muy pocos años.
[2] Se trata solo de una "pincelada" sobre el tema, dado que para programar en un entorno actual como Windows-32, no solo es necesario conocer un lenguaje dotado de un compilador para 32 bits (a ser posible orientado a objetos, como C++), también un montón de otros conceptos que desgraciadamente no están en un solo libro. El resultado es que el programador proveniente de un entorno "tradicional" (modo texto naturalmente), se enfrenta a algo que no se parece a nada que haya visto anteriormente.
[3] Aunque se utiliza con frecuencia el término "procedural" para referirse a la programación clásica (no orientada a objetos) y no encontramos una descripción mejor, ni nos gusta ni nos parece acertada la palabra. Preferiríamos decir Programación orientada a tipos (de datos) predefinidos y operaciones fijas (Fixed Type Oriented Programming FTOP?).
[6] Evidentemente la MFC no es la única librería para programar en Windows; solo tiene la ventaja de ser del mismo fabricante (que no es poco) y de que es constantemente actualizada para recoger las implementaciones introducidas en estos SOs. Actualmente comprende unas 200 clases que encapsulan la práctica totalidad de la API de Windows; además va más allá de ser una simple colección de clases preconstruidas. Constituye en sí misma un completo entorno de desarrollo.
[7] "Canvas": Espacio gráfico que representa un objeto en pantalla. Es una abstracción utilizada en la programación de aplicaciones de interfaz gráfica (como Windows) que encapsula el contenido del dispositivo gráfico.
[8] El dibujo más pequeño que puede representarse en pantalla: un punto. Puede ser monocromo (un color o ausencia de él); de escala de grises, desde el blanco a negro pasando por diversos tonos de gris, o en color. En este último caso el pixel está realmente constituido de tres puntos, cada uno de un color básico (rojo verde y azul).
[9] En los entornos Windows de 16 bits (W-95) quedan vestigios de este funcionamiento cooperativo; son los momentos en que el cursor se transforma en un reloj de arena y no podemos hacer nada hasta que termina cierto proceso. En los entornos Windows de 32 bits, en los que el funcionamiento es preemptivo, sigue saliendo el reloj de arena cuando un proceso está ocupado, pero puede darse foco a otra aplicación con un clic de ratón.