sábado, 22 de junio de 2019

Paradigmas y tipos de lenguajes informáticos (3 de 3). Programación Imperativa


La entrada actual trata sobre los paradigmas de los lenguajes de programación, la ultima parte vamos a dividirla en varias entradas, debido a lo extenso de cada una, esta nos vamos a enfocar en la programación imperativa.

La palabra “paradigma” tiene en la actualidad connotaciones negativas, muchos cursos y libros de autoayuda de han encargado de eso, tampoco ayuda lo realmente mal que se introducen los paradigmas en las carreras relacionada con las ciencias de la computación, creo que a la mayoría se les introduce mediante el ejemplo del “paradigma de los monos”, el cual es bastante popular y mal aplicado en general, básicamente es un estudio (quien sabe si real) donde tiene unos monos en una jaula y les ponen comida a la mano, cuando intentan conseguir la comida, son todos golpeados, al final los mismos monos se golpean entre si para evitar que nadie se acerca a la comida, los científicos ya no necesitan golpear a los monos, y los van sustituyendo hasta que no queda ninguno de los que están de origen, los monos se siguen golpeando entre sí para evitar que nadie tome la comida, aunque ya no quedaba ningún mono de los que fueron golpeados por los mismos científicos. Esto queda bien en algunos contestos, pero es fatal para la compresión de los paradigmas y su aplicación en la ciencia y la ingeniería.

Un paradigma, consideramos aquí como el equivalente de axioma en lógica o filosofía, es algo que consideramos cierto, bueno y útil para un problema en particular, aunque no tengamos los argumentos necesarios para justificarlo. Si tenemos buenos paradigmas nuestras posibilidades de tener éxito en un problema son elevados, si tenemos malos paradigma evidente pasa lo contrario, que estamos avocados al fracaso. Aquí es donde está la diferencia entre ingeniería y ciencia, la ingeniería es práctica, necesita resolver problemas reales, si la ingeniera se planteara a cada punto si lo que hace es cierto o no, dejaría de ser practica, es más seria inútil, la ingeniería se plantea que pueden hacer las cosas, y las une para obtener un fin, la ciencia se plantea el por qué las cosas hacen lo que hacen. En realidad cada ingeniero, tiene un poco de científico, pero no puede perderse en las minucias de por que funcionan las cosas, sino que la experiencia (y las investigaciones de los científicos), le llegan a conocer intuitivamente cual es mejor camino a la resolución de un problema. Esto es importante para poder proveer el software que las necesidades actuales de la sociedad requieren.

Los algoritmos de encriptación, son un ejemplo de esto. Una de las grandes recomendaciones es que no “inventes” tus propios algoritmos, porque seguramente fracasaras creando algún algoritmo frágil y vulnerable. Los algoritmos de encriptación, son funciones matemáticas, creadas por personas con conocimiento extenso y profundo sobre ese tema en particular, y durante investigaciones que duran años, además dichos algoritmos llevan mucho tiempo probando su seguridad y validad en multitud de sistemas. Si uno que h hace programas de aplicación, por ejemplo, e intenta hacer un algoritmo de encriptación , realmente se está desviando de su propósito que es cumplir una necesidad de negocio, con lo que está dedicando tiempo y recursos a algo que ya esta resulto, Al final hará un programa malo y un algoritmo de encriptación malo ( y peligroso) , con tal de cumplir unas fechas establecidas por el mercado. Lo lógico y correcto es usar algún algoritmo de encriptación , que aunque no entendamos en profundidad su base matemática, haya demostrado su valía. (Hay que precisar que los algoritmos de encriptado de vuelven obsoletos con el tiempo, y es posible que se demuestren vulnerabilidad, es necesario claro, una actualización de los algoritmos, según sea preciso).

Otro ejemplo de paradigmas son los patrones de software, los patrones de software, son caminos demostrados mediante la práctica, que se establecen como la solución idónea para resolver un problema, de esta forma al ver un problema semejante, sabemos que tendremos éxito en resolverlo al seguir el patrón, si nos desviamos del patrón, posiblemente perdamos tiempo y nuestro sistema sea de poca calidad (aquí es curioso la existencia de anti patrones, que llegan a ser “la mejor manera de hacer algo mal”). El desafío aquí es saber identificar claramente el problema, no como resolverlo, es un problema de análisis, no de implementación.

Una vez establecido lo anterior, vamos a ver los tipos de paradigmas de programación existentes.

Lenguajes de programación según el paradigma


Las computadoras en su versión más simple, son maquinas que ejecutan una orden tras otra,  comienzan desde la primera línea de un programa, y paso a paso las van ejecutando, este paradigma se llama imperativo, se tiene una orden y se cumple.

Aunque los humanos a veces organizamos nuestros procesos de dichas forma, creando listas que se ejecutan paso a paso, realmente nuestro pensamiento natural no funciona así, nosotros pensamos mas en clasificar los elementos que posee el mundo real, y concederles ciertas propiedades y características, nos interesa más lo que pueden hacer dichas elementos y no el como lo hacen, en ultima instancian nos concentramos en nuestras necesidades y no como estas pueden ser resultas, por ejemplo, si vamos a una cafetería, pedimos un café, y expresamos como lo queremos, pero los mecanismos internos por los cuales el café es hecho, nos da igual, no es objeto de nuestro interés, esto es  se llama “declarativo”, declaramos lo que queremos, no el proceso para resolverlo.

Todos los paradigmas de programación se encuentran en algún punto que está entre los lenguajes imperativos  y los lenguajes declarativos

Paradigma Imperativo



En el paradigma imperativo los programas se ejecutan línea a línea, como si fuera una lista, cuando queremos usa una bifurcación en el flujo de nuestro código, usamos una sentencia condicional que nos lleve a otra parte del código donde seguirá ejecutándose liana a línea de la misma forma.

Se llama lenguaje imperativo, que significa “orden”, porque la maquina va cumpliendo las ordenes, según las vaya analizando.

Veamos el siguiente código en BASIC tradicional:



Ejemplo sacado es de la Wikipedia https://es.wikipedia.org/wiki/BASIC

Programación Imperativa
 
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
10 INPUT "Cuál es su nombre:"; NN$
20 PRINT "Bienvenido al 'asterisquero' ";NN$
25 PRINT
30 INPUT "con cuántos asteriscos inicia [Cero sale]:"; N
40 IF N<=0 THEN GOTO 200
50 AS$=""
60 FOR I=1 TO N
70 AS$=AS$+"*"
80 NEXT I
90 PRINT "AQUI ESTAN:"; AS$
100 INPUT "Desea más asteriscos:";SN$
110 IF SN$="" THEN GOTO 100
120 IF SN$<>"S" AND N$<>"s" THEN GOTO 200
130 INPUT "CUANTAS VECES DESEA REPETIRLOS [Cero sale]:"; VECES
140 IF VECES<=0 THEN GOTO 200
150 FOR I=1 TO VECES
160 PRINT AS$;
170 NEXT I
180 PRINT
185 REM A repetir todo el ciclo (comentario)
190 GOTO 25
200 END
 
 
10 INPUT "Cuál es su nombre:"; NN$
20 PRINT "Bienvenido al 'asterisquero' ";NN$
25 PRINT
30 INPUT "con cuántos asteriscos inicia [Cero sale]:"; N
40 IF N<=0 THEN GOTO 200
50 AS$=""
60 FOR I=1 TO N
70 AS$=AS$+"*"
80 NEXT I
90 PRINT "AQUI ESTAN:"; AS$
100 INPUT "Desea más asteriscos:";SN$
110 IF SN$="" THEN GOTO 100
120 IF SN$<>"S" AND N$<>"s" THEN GOTO 200
130 INPUT "CUANTAS VECES DESEA REPETIRLOS [Cero sale]:"; VECES
140 IF VECES<=0 THEN GOTO 200
150 FOR I=1 TO VECES
160 PRINT AS$;
170 NEXT I
180 PRINT
185 REM A repetir todo el ciclo (comentario)
190 GOTO 25
200 END

Al principio lo que más destaca es la dificultad para leerlo y comprenderlo. Es más hay que leer todo como un bloque y de arriba abajo. Básicamente la salida del programa es la siguiente:

Cuál es su nombre: Jose Luis
Bienvenido al 'asterisquero' Jose Luis

con cuántos asteriscos inicia [Cero sale]: 5
AQUI ESTAN:*****
Desea más asteriscos: S
CUANTAS VECES DESEA REPETIRLOS [Cero sale]: 5
************************

Es más relevante como se van ejecutando las ordenes, una a una, porque para BASIC, en su versiones tradicional, es necesario programar escribiendo el numero de línea, en que se van ejecutando las ordenes.

Adicionalmente como podemos ver si queremos ir a otra parte del programa usamos una sentencia if y un comando goto (para cambiar de línea).

Programación estructurada


Los sistemas informativos se hacen más complicados, se hace patente que la programación imperativa tiene características que hacen que los sistemas sean difíciles de mantener. El uso de goto y saltos de líneas sin control hace que muy difícil seguir el flujo de un programa, si el programa es grande, se acaba convirtiendo en una tarea imposible.

La programación estructurada viene a mejorar la calidad y el mantenimiento de los sistemas software. Consiste en la eliminación de la sentencia goto en los programas, debiendo recurrir nuestro programa solo a subrutinas, y a tres estructuras básicas:

· Sentencias: básicamente como programación  imperativa, se ejecuta una sentencia tras otra.

· Condicionales: Estructuras condicionales con secciones muy claras, que no implican un salto de línea. Son las clausulas if, else y swicth.

· Iteraciones: repeticiones de código, como fors, y whiles.

Por otro lado la subrutinas son segmentos de código que se ubican en otra parte de nuestro sistema, y que cuando son llamas regresan el control, al punto siguiente de la llamada, son las funciones  (cuando devuelve un valor) y los procedimientos (funciones que no devuelven ningún valor).

Veamos el siguiente código en C (adaptándolo al de BASIC)


Programación Estructurada
 
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#include <stdbool.h>
 
int main() {
 
    char nombreUsuario[20];
    bool salir = false;
     
    printf("¿Cuál es su nombre? ");
    scanf("%[^\n]s", nombreUsuario);
    printf("Bievenido al 'asterisquero', %s", nombreUsuario);
     
    do {
        int nroAsteriscos = 0;
        printf("\n¿Cuántos asteriscos quieres [Cero sale]: ");
        scanf("%d", & nroAsteriscos);
         
        if (nroAsteriscos != 0) {
             
            printf("AQUI ESTAN: ");       
            for (int i = 0; i < nroAsteriscos; i++) {
                printf("*");
            }          
            printf("\n");
             
        } else {
             
            salir = true;
        }
 
    } while (!salir);
 
}
 
 
#include <stdio.h>
#include <stdbool.h>

int main() {

    char nombreUsuario[20];
    bool salir = false;
    
    printf("¿Cuál es su nombre? ");
    scanf("%[^\n]s", nombreUsuario);
    printf("Bievenido al 'asterisquero', %s", nombreUsuario);
    
    do {
        int nroAsteriscos = 0;
        printf("\n¿Cuántos asteriscos quieres [Cero sale]: ");
        scanf("%d", & nroAsteriscos);
        
        if (nroAsteriscos != 0) {
            
            printf("AQUI ESTAN: ");        
            for (int i = 0; i < nroAsteriscos; i++) {
                printf("*");
            }           
            printf("\n");
            
        } else {
            
            salir = true;
        }

    } while (!salir);

}

La salida de este código será:

¿Cuál es su nombre? Jose Luis
Bievenido al 'asterisquero', Jose Luis
¿Cuántos asteriscos quieres [Cero sale]: 5
AQUI ESTAN: *****
¿Cuántos asteriscos quieres [Cero sale]: 0

Vemos que el simple hecho que le código este indentado nos facilita la lectura, también vemos que no existen sentencias goto, todo esta estructurado con sentencias if (condicionales), y de interacción (while) y en definitiva es mucho más sencillo de entender, que el código en BASIC.

Existen algunos lenguajes que si bien son estructurados (como C), todavía tiene la sentencia goto entre sus sentencias posible (aunque de ninguna forma se recomienda), otros, los más modernos (como java), generalmente no lo tienen o tienen su uso restringido.

Existe un debate, sobre si las sentencias, break (para interrumpir una interacción), continue (para ir a la siguiente interacción), o más de un return por función son recomendables, porque son sentencias “goto” enmascaradas. La respuesta es “si”, si deben usarse, son sentencias que nos llevan a un punto muy concreto del código y estas asociadas a las mismas sentencias de control, Si no las usuramos deberías crear sentencias de control a bases de if, que complicarían nuestro código y su legibilidad, recordemos que el objeto de la programación estructura no es eliminar los saltos de línea como tal, sino facilitar la creación de un código de calidad que sea mantenible y entendible.

Programación Modular


La programación modular divide un programa grande y complejo, en “programas”, más pequeños y sencillos, de maneras que son más sencillos de resolver. A cada uno de estos subprogramas los llamamos módulos.

Cada modulo se encarga de solucionar un problema, de esta manera son más sencillos de crear y de mantener, el problema final resultara de la combinación de dichos módulos.

Cada modulo tiene sus propias estructuras de datos (que idóneamente) estas aislado del resto de los módulos, de forma que cada modulo,  expone solo lo necesario para poder comunicarse con los demás. Cuanto menos expongamos, menos acoplamiento generamos y más sencillo es mantener el código.

Al facilitar que todo lo que necesita un modulo para funcionar este junto, y que además este aislado de lo que no necesita, aumentamos la cohesión, esto es garantizar la proximidad en código de los elementos que consumimos (funciones,  estructuras de datos) en mismo lugar  y garantizar que se usan entre sí, nuevamente hacemos un código más sencillo y mas mantenible.

Pascal es uno de los lenguajes que implementa la programación modular (y posiblemente unos de los más estructurados), fuente de inspiración para multitud de lenguajes modernos, veamos el siguiente ejemplo en pascal:


El ejemplo solicita dos puntos (con coordinadas X e Y) y calcula la distancia entre los dos. Vamos a crear un modulo (llamado unidad en pascal), con todo lo que requerimos para trabajar con puntos.

Programación Modular
 
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
unit Puntos;
 
interface
 
type
 
//Representan un punto
TPunto = record
     x:integer;
     y:integer;
end;
 
//Obtiene un punto desde el teclado
function obtenerPunto(): TPunto;
 
//Cacula la distancia entre dos puntos
function calcularDistancia(punto1: TPunto; punto2:TPunto): real;
 
implementation
Uses Math;
 
function obtenerPunto(): TPunto;
var
  punto:TPunto;
begin
   write('Introcuzca la X del punto: ');
   readln(punto.x);
   write('Introcuzca la Y del punto: ');
   readln(punto.y);
 
   obtenerPunto:= punto;
end;
 
function calcularDistancia(punto1: TPunto; punto2:TPunto): real;
begin
   calcularDistancia:=  sqrt( power(punto2.x-punto1.x,2) + power(punto2.y-punto1.y,2))
end;
 
end.
 
 
unit Puntos;

interface

type

//Representan un punto
TPunto = record
     x:integer;
     y:integer;
end;

//Obtiene un punto desde el teclado
function obtenerPunto(): TPunto;

//Cacula la distancia entre dos puntos
function calcularDistancia(punto1: TPunto; punto2:TPunto): real;

implementation
Uses Math;

function obtenerPunto(): TPunto;
var
  punto:TPunto;
begin
   write('Introcuzca la X del punto: ');
   readln(punto.x);
   write('Introcuzca la Y del punto: ');
   readln(punto.y);

   obtenerPunto:= punto;
end;

function calcularDistancia(punto1: TPunto; punto2:TPunto): real;
begin
   calcularDistancia:=  sqrt( power(punto2.x-punto1.x,2) + power(punto2.y-punto1.y,2))
end;

end.

El código principal usara el modulo anterior, para solicitar los datos y mostrar los resultados:

Programación Modular
 
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
program Ejemplo;
uses
    Puntos, SysUtils;
 
var
   punto1 :TPunto;
   punto2 :TPunto;
   distancia: real;
 
 
begin
 
  writeln('Introduzca el punto uno: ');
  punto1:= obtenerPunto();
  writeln();
 
  writeln('Introduzca el punto dos: ');
  punto2:= obtenerPunto();
  writeln();
 
  distancia:= calcularDistancia(punto1, punto2);
 
  writeln();
  write('La distancia entre el punto uno y el punto dos es: ');
  writeln(Format('%.2f', [distancia]));
  writeln();
  writeln('Pulse enter para salir...');
  readln();
 
end.
 
 
program Ejemplo;
uses
    Puntos, SysUtils;

var
   punto1 :TPunto;
   punto2 :TPunto;
   distancia: real;


begin

  writeln('Introduzca el punto uno: ');
  punto1:= obtenerPunto();
  writeln();

  writeln('Introduzca el punto dos: ');
  punto2:= obtenerPunto();
  writeln();

  distancia:= calcularDistancia(punto1, punto2);

  writeln();
  write('La distancia entre el punto uno y el punto dos es: ');
  writeln(Format('%.2f', [distancia]));
  writeln();
  writeln('Pulse enter para salir...');
  readln();

end.

La salida será:

Introduzca el punto uno:
Introduzca la X del punto: 10
Introduzca la Y del punto: 10
Introduzca el punto dos:
Introduzca la X del punto: 5
Introduzca la Y del punto: 5
La distancia entre el punto uno y el punto dos es: 7.07
>Pulse enter para salir...



Si te ha gustado la entrada, ¡Compártela! ;-)

Nota: Puedes encontrar todo el código fuente de este artículo en https://github.com/jbautistamartin/ParadigmasTiposLenguajes