¿Qué es programar?: Pinceladas básicas en diseño y programación

Se aproxima un capítulo denso, ya que se que muchos de vosotros no habéis programado nunca, y que la idea de tener que escribir código asusta muchas veces. Es normal, a todos nos ha pasado al principio. Para empezar, hay que entender que para programar hay que pensar de una forma distinta a la que estamos habituados. La programación tiende a ser algo lógico y lo más abstracto posible para poder generalizar.

Esto pasa porque la programación actual, que es lo que se conoce como programación de alto nivel, tiene su origen en la algoritmia. Podéis buscar en Wikipedia o en un diccionario el significado de algoritmo, pero, pensando de una forma más de andar por casa, un algoritmo no deja de ser como una receta de cocina en donde se especifica qué es lo que hay que hacer. Si la receta de cocina no tiene lógica o no está escrita de una forma legible, seguirla se convierte en un reto enorme e incluso puede ocultar errores y un mal diseño en su planteamiento. Por ejemplo, imagina que está tan mezcladas las cosas en una receta de tortilla de patatas que primero te hace freír el huevo y luego poner las patatas encima.

Es por eso que al principio cuesta mucho seguir un cierto orden y cierta lógica al programar, por lo que puede resultar muy tedioso. Aún así, voy a intentar explicarlo mediante un método muy socorrido: el pseudocódigo.

Un pseudocódigo no deja de ser una herramienta informal para describir cómo funciona un programa, pero que permite el estudio del mismo usando solo una hoja y un lápiz. Esto lo consigue representando el código del programa (lo que ejecutaría un ordenador) en lenguaje natural, sin entrar en tantos tecnicismos. Es, por tanto, un paso fundamental a la hora de analizar y diseñar un programa, y que normalmente se suele acompañar de diagramas o información adicional.

A pesar de ser informal, con el pseudocódigo podemos representar cualquier cosa que se pueda hacer en un lenguaje de programación. En este caso imitaremos lo que se conoce como lenguajes imperativos, que son un subconjunto de los lenguajes de programación que tiene la característica de representar como hay que hacer el programa mediante órdenes o instrucciones.

Variables

Por ejemplo, lo primero que quiero enseñaros son las variables, que las representaré en pseudocódigo de la siguiente forma:

a : entero = 1.

Como se puede intuir dado el ejemplo 1, las variables son elementos que guardan cierta información, la cual puede cambiar en el transcurso del tiempo. Están relacionados con la memoria del programa, ya que una variable hace referencia a una sección de la memoria del equipo donde se ejecuta. La memoria de un ordenador es binaria, guarda 0s y 1s, por lo que normalmente se le indica qué tipo de información contiene para que se pueda traducir a binario y recuperarlo.

Esto último tiene cierta chicha, ya que dependiendo del lenguaje que usemos, el tipo de los datos o tipado se manejará de dos formas distintas principalmente. La primera forma es lo que se conoce como lenguaje de tipado estático, en donde el tipo de los datos lo marca el programador en el propio código. Por otra parte están los lenguajes de tipado dinámico, donde el tipo de los datos depende de lo que el programador le asigne.

En un lenguaje de tipado estático realizar esto:

a : entero = 1.
a = "Hola mundo".

saltaría un error, mientras que en uno dinámico se ejecutaría sin problemas. Para arreglar esto en un lenguaje estático tendríamos que usar dos variables: una de tipo entero y otra de tipo texto.

Estas diferencias se deben a lo que se llama el sistema de comprobación de tipos. Es una herramienta que contienen las implementaciones de los lenguajes para saber si estamos haciendo una manipulación correcta de los datos. Hay dos formas de comprobar esto:

  • Tiempo de ejecución, que hace referencia al tiempo que pasa el programa siendo ejecutado en una máquina. Si la comprobación del tipo se hace en tiempo de ejecución, el lenguaje es de tipado dinámico.
  • Tiempo de compilación, que hace referencia al tiempo que pasa el programa traduciéndose a lo que se conoce como código máquina, que es el lenguaje binario que reconoce la máquina y que es dependiente de donde se ejecuta. Si se comprueban los tipos en tiempo de compilación, entonces el lenguaje es de tipado estático.

Compilación y ejecución de un programa

Lo mejor es informarse de cómo funciona el tipado en el lenguaje que uséis, así como los tipos de datos que podemos manipular en el lenguaje. Para este capítulo usaré los siguientes tipos:

booleano Representa un valor de certeza que puede ser verdadero o falso.
entero Representa un número entero, es decir, que no tiene parte decimal.
real Representa un número real, es decir, que puede contener una parte decimal.
texto Representa una sucesión de caracteres o símbolos, tal y como estamos acostumbrados en el día a día.
lista de textos Representa una lista que aglutina varios elementos de tipo texto. De este tipo de dato entraré en detalle más adelante.

Estructuras de control

Otro elemento a tener en cuenta en la programación son las estructuras de control, las cuales permiten controlar el flujo del programa de una forma de alto nivel. Normalmente, en un programa, su flujo es el siguiente:

Programa

Diagrama de un programa

Aunque parezca una ejecución secuencial, dentro del propio proceso la ejecución normalmente no se realiza en orden, habiendo saltos de un lado a otro. Por ejemplo, para comprobar valores en una variable:

a : entero <= Entrada.
retorno : texto.

Si a ≥ 0 entonces:
    retorno := “El número es positivo”.
En caso contrario:
    retorno := “El número es negativo”.
Fin.
Salida <= retorno.
If

Diagrama de flujo de una estructura condicional

Esta estructura se llama estructura condicional o If-Else, y permite elegir entre varias opciones o bloques de código. El primer elemento es la condición o guarda, que es una expresión que, al evaluarla lógicamente, devuelve un valor de certeza verdadero o falso. Si da verdadero, se ejecuta el primer bloque, que normalmente está delimitado por “Si … Entonces” y “En caso contrario”. Si da falso, se ejecuta el segundo bloque, que queda entre “En caso contrario” y “Fin”.

Hay lenguajes que permiten realizar la condición sin el bloque “En caso contrario” obligatoriamente, y otros permiten una forma específica del If-Else llamado condicional anidado o If-anidado:

temperatura : real <= Entrada.
retorno : texto.

Si temperatura < 14 entonces:
    retorno := “Hace frío”.
Si no, Si temperatura < 17,5 entonces:
    retorno := “Hace fresquito”.
Si no, Si temperatura < 20 entonces:
    retorno := “Se está bien”.
Si no, Si temperatura < 24 entonces:
    retorno := “Hace calor”.
En caso contrario:
    retorno := “Hace mucho calor”.
Fin.

Salida <= retorno.

Hay lenguajes que, en vez de bloques If-anidados, permiten lo que se llama bloque de selección o Switch-Case:

cuenta : entero <= Entrada.
retorno : texto.

Según cuenta hacer:
    Caso 1:
        retorno := “Hay una manzana”.
    Caso 2:
        retorno := “Hay dos manzanas”.
    Caso 3:
        retorno := “Hay tres manzanas”.
    De otro modo:
        retorno := “Hay muchas manzanas”.
Fin.

Salida <= retorno.

La diferencia radica en que hay un indicador o variable, la cual se compara con cada uno de los valores de los casos para ejecutar el código resultante. Existe un caso especial, “de otro modo”, que se suele poner al final de todo y que se ejecuta en caso de que el el valor no coincida con los casos específicos.

Aparte de las condicionales, también existen las estructuras iterativas. Estas permiten repetir o iterar un número de veces un bloque de código. Veamos un ejemplo:

cuenta : entero = 0.

Mientras cuenta ≤ 10 hacer:
    Imprimir 2 x cuenta.
    cuenta := cuenta + 1.
Fin.
While

Diagrama de flujo de una estructura iterativa

La estructura “Mientras … hacer” o While-Do permite repetir el bloque de código que contiene mientras la guarda o condición sea cierta, en caso contrario termina el bucle. Cabe destacar que esta condición se evalúa al principio de cada iteración del bucle, por lo que si se empieza con una guarda falsa, no se ejecutará nunca.

Si lo que se quiere es ejecutar al menos una vez, para eso existe la estructura “Repetir hasta que …” o Repeat-Until, en donde la condición se evalúa siempre al finalizar cada iteración:

cuenta : entero = 11.
Repetir:
    Imprimir 2 x cuenta.
    cuenta := cuenta + 1.
Hasta que cuenta > 10.

Estos dos bucles son muy parecidos, habiendo casos en donde se puede transformar el uno en el otro de la siguiente forma:

Repetir:
    [Bloque]
Hasta que condición.
[Bloque]
Mientras no condición hacer:
    [Bloque]
Fin.

Como se puede ver en la tabla 4, una de las diferencias radica en que la guarda del bucle While-do es la contraria a la del Repeat-Until. Esto se realiza con una negación lógica, que se representa con la palabra “no” colocado antes de la condición a negar. Existen otros operadores lógicos, como pueden ser la disyunción y la conjunción, representados con “o” e “y” respectivamente, los cuales nos permiten conectar dos expresiones en una condición única.

Continuando con las estructuras de control, existe un tipo de bucle especial en el caso de que sepamos el número de iteraciones a realizar:

Para cuenta : entero desde 0 hasta 10 hacer:
    Imprimir 2 x cuenta.
Fin.

En el bucle “Para … hacer” o For no existe una condición de por sí, si no que se especifica el valor mínimo y máximo de la variable de control. Esta variable empieza con el valor mínimo y llega hasta el máximo mediante incrementos de 1 en cada iteración. Hay lenguajes que sí soportan introducir una condición, pero pueden llegar a ofuscar el funcionamiento interno y dificultan conocer el uso, por lo que nos quedaremos con la forma más básica.

Del este bucle existen dos variantes. La más sencilla es el bucle For con paso:

Para cuenta : entero desde 0 hasta 20 con paso 2 hacer:
    Imprimir cuenta.
Fin.

Esta variante nos permite decir cuánto se va a incrementar la variable de control en cada bucle. Esto nos permite hacer que los ejemplos 7 y 8 arrojen el mismo resultado, ya que mientras en uno se multiplica por 2, en el otro el bucle hace que se sume 2 en cada iteración.

La segunda variante es algo más complicada, así que la explicaré con un ejemplo:

alumnos : lista de textos = [“Julia”, “Ernesto”, “María”, “Anacleto”].

Para todo alumno : texto  alumnos hacer:
    Imprimir alumno.
Fin.

El bucle “Para todo” o For-each nos permite iterar conjuntos de datos. Para ello la variable de control tiene que ser del mismo tipo que cada uno de los datos del conjunto a iterar, es decir, si quiero iterar un conjunto de números reales, mi variable de control es real. Aunque no lo parezca, con esto podemos acceder y manipular los elementos que aglutinan las estructuras de datos más sofisticadas, las cuales explicaré en el siguiente capítulo.

Funciones y procedimientos

Tener estructuras de control nos permite evitar repetir código muchas veces, ya que no es lo mismo escribir 100 líneas con la instrucción Imprimir “No copiaré en clase” haciendo todo el rato Ctrl + V que resolviendolo con un bucle for, el cual puede ocupar 3 líneas a lo sumo.

Esta no es la única forma de ahorrar código en el mundo de la programación, ya que existen unas estructuras pensadas para ello: las funciones y los procedimientos. La única diferencia entre las dos es que una función se usa de la misma forma que su homónima matemática: recibe un elemento que lo transforma y devuelve otro elemento.

Función booleana Es par ( número : entero ):
    Si número módulo 2 = 0 entonces:
        devuelve Verdadero.
    En caso contrario:
        devuelve Falso.
    Fin.
Fin.

El ejemplo 10 nos proporciona una forma de saber si un número es par o no, para ello hace uso del operador módulo, que nos devuelve el resto al realizar una división entera, es decir, sin calcular los decimales. Para usarla podemos hacer lo siguiente.

valor : entero <= Entrada.
retorno : texto.

Si Es par valor entonces:
    retorno := “Has introducido un número par”.
En caso contrario:
    retorno := “Has introducido un número impar”.
Fin.

Salida <= retorno.

En contra, los procedimientos no devuelven nada, si no que se dedican a realizar operaciones o pasos y listo.

valor : entero <= Entrada.
retorno : texto.

Procedimiento Imprimir ( datos : texto ):
    Abrir salida : fichero de texto.
    salida <= datos.
    Cerrar salida.
Fin.

Si Es par valor entonces:
    Imprimir “Has introducido un número par”.
En caso contrario:
    Imprimir “Has introducido un número impar”.
Fin.

Hay lenguajes de programación que no diferencian entre una cosa y la otra, si no que los procedimientos los tratan como funciones que devuelven un tipo de dato nulo o void para indicar que no devuelve nada.

También comentar que, tal y como habéis visto he indicado el nombre de una variable y su tipo en paréntesis. Estas variables reciben el nombre de parámetros, y puede no haber o haber varios. Lo especial de este tipo de variables no queda solo aquí, y es que, dependiendo del lenguaje, la manipulación y modificación de estas variables puede variar y dar resultados extraños.

Esto es debido a que no hay una forma estándar de implementar el paso de parámetros, dependiendo de las especificaciones del lenguaje. Aún así, lo más extendido es el paso por valor, es decir, que el valor de la variable que se le pasa a la función es copiado en una variable interna de la función. Esto hace que si modificas la variable interna, la externa no se verá afectada.

valor : entero = 0.

Procedimiento AñadirUnidad (parámetro : entero):
    parámetro := parámetro + 1.
    Imprimir(“Valor dentro del procedimiento: ”, parámetro).
Fin.

AñadirUnidad valor.
Imprimir(“Valor fuera del procedimiento”, valor).
> “Valor dentro del procedimiento: 1”
> “Valor fuera del procedimiento: 0”

Otra forma muy usada es el paso por referencia, donde el nombre del parámetro es un alias a la variable que se le pasa. Es decir, si modificas la variable interna, la externa se modifica.

valor : entero = 0.

Procedimiento AñadirUnidad (parámetro : entero):
    parámetro := parámetro + 1.
    Imprimir(“Valor dentro del procedimiento: ”, parámetro).
Fin.

AñadirUnidad valor.
Imprimir(“Valor fuera del procedimiento”, valor).
> “Valor dentro del procedimiento: 1”
> “Valor fuera del procedimiento: 1”

Existe otra tercera forma que en esencia es un mix de las anteriores, call by-sharing. Aquí lo que se hace es que los tipos primitivos sean por valor y los tipos más complejos se pasen por referencia. Sobre los tipos primitivos y los tipos complejos lo veremos en el siguiente capítulo.

Como esto es un lío, lo mejor es leer la documentación del lenguaje para saber cómo funcionan los parámetros y que consecuencias tiene manipularlos dentro de una función o procedimiento.

Anuncios

Un pensamiento en “¿Qué es programar?: Pinceladas básicas en diseño y programación

  1. Pingback: Aprende a programar un videojuego: desde lo básico a lo avanzado | El blog de NEKERAFA

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s