Fundamentos de Programación

En un mundo que ha sufrido un importante proceso de transformación digital cada vez hay más aplicativos desarrollados con el fin de interactuar con los seres humanos, si estos aplicativos no están correctamente programados un atacante puede hacer uso mal intencionado del software y controlar el flujo de ejecución, pudiendo alterar la información y/o robarla.

Antes de nada, haremos una distinción fundamental entre hardware y software

  • Hardware: Todos los componentes físicos de un ordenador.
  • Software: Programas e instrucciones para ejecutar tareas en un ordenador.

Una vez tenemos claro la diferencia, en esta entrada de este blog nos centraremos en el software, y en una de sus unidades fundamentales: Los Programas.

Definiríamos programa como:

  1. Secuencia de instrucciones.
  2. Entendible por el ordenador.
  3. Tienen un objetivo o tarea concreta.

Los ordenadores son máquinas eléctricas que sólo entienden de 0 y 1, siendo:

  • 0 no hay corriente.
  • 1 hay corriente.

Este es el único lenguaje que entiende el ordenador, llamado lenguaje binario.

Un programa es un conjunto de instrucciones que se crea para realizar una tarea específica en una computadora. Estas instrucciones son escritas en un lenguaje de programación y se traducen a un lenguaje que la computadora puede entender y ejecutar. Los programas pueden ser simples, como una calculadora, o muy complejos, como un sistema operativo. En esencia, un programa es como una receta que le dice a la computadora qué hacer y cómo hacerlo. Los programadores crean programas para automatizar tareas, procesar datos, resolver problemas y realizar una amplia variedad de funciones en la computadora.

Como ya hemos dicho para crear un programa y que la computadora lo interprete y ejecute, las instrucciones deben escribirse en un lenguaje de programación. En los primeros tiempos de la computación se programaba directamente en código máquina. Escribir programas así resultaba demasiado complicado, también era difícil entenderlos y mantenerlos una vez escritos. Con el tiempo, se fueron desarrollando herramientas para facilitar el trabajo y aparecieron los lenguajes de programación, que podemos dividir en dos grupos:

  • Lenguajes de bajo nivel: son los más cercanos al binario, pero son difíciles de programar.
  • Lenguajes de alto nivel: necesitan ser traducidos antes de llegar al ordenador, pero son fáciles de programar ya que son más cercanos al lenguaje natural de las personas

Algunos ejemplos:

  • Lenguajes bajo nivel: lenguaje máquina y Ensamblador.
  • Lenguajes alto nivel: C, C++, Java, PHP, Python, Go, Rust, Ruby...

En el desarrollo de software hay muchas condicionantes que marcan que elección de lenguaje se usará en el proyecto:

  • Requisitos técnicos marcados por otras piezas de software/hardware.
  • Tiempo de entrega del software.
  • Necesidad de rendimiento.

Programar viene a ser el proceso de crear un software fiable mediante la escritura en un lenguaje (code en inglés), prueba (test), depuración (debug), compilación o interpretación, y mantenimiento (maintenance) del código fuente de dicho programa informático.

CODIFICAR ⇒ PROBAR ⇒ DEPURAR ⇒ COMPILAR/INTERPRETAR ⇒ MANTENER (y vuelta a empezar)

El funcionamiento de un equipo informático es relativamente sencillo. En un área de la memoria principal se tiene un conjunto de bits agrupados en unidades denominadas instrucciones, que son potencias de 2 y cuyo tamaño depende de la arquitectura del equipo: 8 bits, 16 bits, 32 bits, 64 bits, 128 bits. El procesador obtiene la siguiente instrucción a ejecutar, la interpreta y manda "órdenes" a los diferentes elementos del equipo como registros, memoria principal, ALU o periféricos, una vez finalizada la instrucción pasa a la siguiente instrucción de la memoria principal.


Fundamentos de los lenguajes más utilizados I
Si bien en la actualidad existen múltiples técnicas, paradigmas y filosofías en los lenguajes de programación como programación orientadas a aspectos, dirigida por eventos o basada en funciones lambda entre muchas otras, los lenguajes más utilizados se basan en unos elementos y estructuras comunes.

Variables
Asociación de un nombre simbólico a un espacio de memoria, de forma que se puede referenciar por el nombre y no por la dirección, siendo posible consultar y modificar su valor. En los lenguajes de tipado fuerte es necesario indicar el tipo de dato como C o C++, en otros denominados no tipados no es necesario indicarlo como PHP o Javascript. También se ha de distinguir entre los lenguajes de tipado dinámico y estático que indican en qué momento se realiza la comprobación de tipos, en la ejecución o en la compilación.

Constantes
Al igual que las variables se asocia un nombre simbólico a un área de memoria, pero en este caso solo es posible la lectura, no la modificación.

Sentencias de control alternativas
Expresiones que permiten modificar el flujo de ejecución del programa dependiendo de ciertas condiciones. La sintaxis dependerá del lenguaje concreto utilizado. Se clasifican en alternativas o condicionales e iterativas o repetitivas. Las primeras modifican el flujo dependiendo de la evaluación de una condición lógica y se decide qué conjunto o bloque de instrucciones se ejecutarán a continuación. Las más usadas son:
  • If-else: Se indica una condición que al evaluarse devuelve cierto o falso y en función del resultado ejecuta uno u otro bloque.
                if (condición) then
                bloque instrucciones si cierto
                else
                bloque instrucciones si falso
                endif
  • Switch: Permite evaluar una expresión y ejecutar más de dos posibles opciones en función de la expresión. Dependiendo del lenguaje, la expresión se puede limitar a comparar números enteros o caracteres como en C o en lenguajes más recientes como C. con enteros, caracteres, cadenas, booleanos o enumerados.
                switch (expresión):
                case X:
                bloque instrucciones
                break
                case Y:
                bloque instrucciones
                break
                default:
                bloque instrucciones
                endswitch

Sentencias de control iterativas o repetitivas
Con este tipo de estructuras se puede repetir un bloque de código un número concreto de veces en función de la evaluación cierta de una condición lógica. Son también conocidas como bucles.
  • While: Se ejecuta el bloque contenido en la estructura mientras se cumpla la condición lógica. Se puede dar el caso de no ejecutarse ninguna vez.
                While (condición):
                bloque instrucciones.
                endwhile
  • Do-While: Se ejecuta el bloque contenido en la estructura mientras se cumpla la condición lógica. La evaluación de la condición se ejecuta al final del bloque de instrucciones.
                Do (condición):
                bloque instrucciones.
                While
  • For: En este caso, además de la condición, posee un bloque de instrucciones que se ejecutan al inicio, el bloque en el que se encuentra la condición a evaluar en cada iteración y por último un bloque de instrucciones que se ejecuta en cada iteración del bucle. Si bien es posible indicar varias instrucciones separadas por comas en cada elemento del bucle for, no suele ser usual utilizarlo ya que no es intuitivo...
                For (bloque inkialización; bloque condición; bloque iteración):
                bloque instrucciones,
                endFor

La evaluación de la condición en las estructuras While y for, se realiza al inicio. En el caso de la estructura do-while se evalúa la condición al final de la ejecución del bloque de instrucciones, por tanto se ejecuta al menos una vez.

Fundamentos de los lenguajes más utilizados II
Otro de los fundamentos de los lenguajes de programación son las funciones o procedimientos, aunque estos últimos no son muy utilizados en la actualidad. Se puede definir una función como fragmento de código identificado con un nombre, que puede ser reutilizados utilizando ese nombre, que devuelve un valor de un tipo de dato, que recibe una serie de argumentos de un determinado tipo llamados parámetros, en cada una de las llamadas. Aunque esta definición es de lenguajes fuertemente tipados, en los débilmente la definición es similar pero sin ser necesario tener en cuenta los tipos de datos utilizados.

Dependiendo del lenguaje la declaración, llamada a la función y devolución de valores puede variar en cuanto a la sintaxis, pero los fundamentos son los mismos. Un ejemplo de función en C:


Es necesario destacar que existen 2 formas de indicarle a la función que parámetros tiene que utilizar y de qué forma:
  • Por valor: Se realiza una copia de la variable y esta copia es la que recibe la función, por tanto los cambios realizados en función no tienen consecuencias en la variable original.
  • Por referencia: Ya sea con los mecanismos que provee de C (dirección de memoria del inicio de la variable que se pasa como argumento, conocida como puntero), Java (en las variable original se almacenan la dirección del objeto y se realiza una copia de la  variable a pasar corno argumento, referenciando la copia también al objeto) o de otros lenguajes, las consecuencia principal es que las modificaciones realizadas dentro de la función afectan a la variable pasada como argumento.
Modelos de Ejecución Software
Podemos clasificar los lenguajes de programación, según su modo de ejecución, en 3 tipos distintos:
  • Lenguaje compilado
    • Nuestro código fuente se transforma en un binario mediante compilación.
    • El ordenador ejecuta el binario.
    • No necesitamos por tanto ningún programa adicional.
  • Lenguaje interpretado
    • Nuestro código fuente es leído en tiempo real por un programa (intérprete) que lo traduce a código máquina (objeto binario).
    • El ordenador ejecuta ese binario.
    • Necesita por tanto un programa adicional, llamado intérprete, que hace de traductor entre las instrucciones y el código máquina.
  • Lenguaje intermedio
    • El código fuente se compila a un lenguaje intermedio.
    • Este lenguaje intermedio se ejecuta en una máquina virtual.
Lenguajes de Programación Interpretados y Compilados
Los traductores son programas cuya finalidad es convertir lenguajes de alto nivel (en los que se programa) a lenguajes de bajo nivel como ensamblador o código máquina. Existen dos grandes grupos de tipos de traductores: los compiladores y los intérpretes:
  • Un intérprete traduce el código fuente linea a línea como se describe a continuación: primero traduce la primera línea, detiene la traducción y, seguidamente la ejecuta; lee la siguiente línea, detiene la traducción y la ejecuta, y así sucesivamente. El intérprete tiene que estar cargado en memoria ejecutándose para poder ejecutar el programa. Al igual que el intérprete, el código fuente tiene que estar también en la memoria. En caso de detectar un error durante el proceso de traducción el intérprete detiene la ejecución del programa. 
    • PHP.
    • Perl.
    • Python.
    • Ruby.
  • Un compilador traduce un programa entero de un lenguaje de programación (llamado código fuente) a otro denominado lenguaje objetivo. Usualmente, el lenguaje objetivo es código máquina, aunque también puede ser traducido a un código intermedio (bytecode) o a texto. El compilador únicamente estará instalado en la máquina de desarrollo. El código generado para un sistema sólo funcionará para una arquitectura hardware y software determinadas. Si se desea ejecutar en un sistema con un hardware o software diferente habrá que volver a recompilarlo. Ejemplos de lenguajes compilados típicos son:
    • C/C++.
    • Pascal.
    • Entre otros.

Si observamos las diferencias entre compilador e intérprete, vemos claramente los puntos fuertes y débiles de cada solución para traducir el código fuente; con el intérprete, los programas se pueden ejecutar de inmediato y, por lo tanto, se inician mucho más rápido. Además, el desarrollo es mucho más fácil que con un compilador, porque el proceso de depuración (es decir, la corrección de errores) se lleva a cabo igual que la traducción, línea por línea. En el caso del compilador, primero debe traducirse todo el código antes de poder resolver los errores o iniciar la aplicación. Sin embargo, una vez que se ejecuta el programa, los servicios del compilador ya no son necesarios, mientras que el intérprete continúa utilizando los recursos informáticos.

Algunas ventajas de compilar frente a interpretar son:
  • Se compila una vez; se ejecuta muchas veces.
  • La ejecución del programa objeto es mucho más rápida que si se interpreta el programa fuente.
  • El compilador tiene una visión global del programa, por lo que la información de los mensajes de error es más detallada.
Por otro lado, algunas ventajas de interpretar frente a compilar son:
  • Un intérprete necesita menos memoria que un compilador.
  • Permite una mayor interactividad con el código en tiempo de desarrollo (e incluso, en algunos casos, mientras se ejecuta el código).
Compilación en tiempo de ejecución
Para compensar los puntos débiles de ambas soluciones, también existe el llamado modelo de compilación en tiempo de ejecución (en inglés, Just-in-time-compiler, o "compilador justo a tiempo"). Este tipo de compilador, que a veces también se conoce por el término inglés compreter(acránimo de com piler e in ter preter), rompe con el modelo habitual de compilación y traduce el código del programa durante el tiempo de ejecución, al igual que el intérprete. De esta forma, la alta velocidad de ejecución típica de los compiladores se complementa con la simplificación del proceso de desarrollo.

En un entorno de compilación en tiempo de ejecución, la compilación a bytecode es el primer paso, reduciendo el código fuente a una representación intermedia portable y optimizable. El bytecode se despliega en el sistema de destino. Cuando dicho código se ejecuta, el compilador en tiempo de ejecución lo traduce a código máquina nativo. Esto puede realizarse a nivel de fichero (programa) o de funciones, compilándose en este último caso el código correspondiente a una función justo cuando va a ejecutarse (de aqui el nombre de just-in-time, «justo a tiempo»).

El objetivo es combinar muchas de las ventajas de la compilación a código nativo y a bytecode: la mayoría del «trabajo pesado» de procesar el código fuente original y realizar optimizaciones básicas se realiza en el momento de compilar a bytecode, mucho antes del despliegue: así, la compilación a código máquina del programa resulta mucho más rápida que partiendo del código fuente.

El bytecode desplegado es portable, a diferencia del código máquina para cualquier arquitectura concreta. Los compiladores dinámicos son más fáciles de escribir, pues el compilador a bytecode ya realiza buena parte del trabajo.

Java es uno de los ejemplos más conocidos de lenguaje basado en compilación en tiempo de ejecución: el compilador JIT, que figura entre los componentes del Java Runtime Environment (JRE), mejora el rendimiento de las aplicaciones Java traduciendo el código de bytes en código máquina de manera dinámica.

Entornos de desarrollo
Los entornos de desarrollo, comúnmente conocidos como IDE (Entorno de Desarrollo Integrado, por sus siglas en inglés Integrated Development Environment), son herramientas que los programadores
utilizan para escribir, depurar y compilar su código. Algunos aspectos importantes de los entornos de desarrollo incluyen:
  • Editor de Código: Los IDEs incluyen editores de código que ofrecen resaltado de sintaxis, sugerencias de autocompletado y otras características para mejorar la eficiencia de escritura.
  • Depuración: Los entornos de desarrollo proporcionan herramientas de depuración que permiten a los programadores detectar y corregir errores en su código de manera eficiente.
  • Gestión de Proyectos: Los IDEs suelen ofrecer funciones para organizar proyectos, gestionar bibliotecas y dependencias, y facilitar la colaboración en equipo.
  • Integración con Herramientas: Pueden integrar herramientas de control de versiones, compiladores, analizadores estáticos y otros recursos que hacen que el desarrollo sea más efectivo.
  • Personalización: Los entornos de desarrollo suelen ser altamente personalizables para adaptarse a las preferencias del programador.
  • Soporte Multiplataforma: Muchos IDEs se pueden utilizar en sistemas operativos como Windows, macOS y Linux.
Existen multitud de entornos de desarrollo y te recomendamos probar alguno porque te puede ayudar y
simplificar las tareas que se van a desarrollar durante el curso. Algunos ejemplos son:


El poder analizar el código fuente es una práctica fundamental para mejorar la calidad, seguridad y eficiencia del desarrollo de software. Ayuda a prevenir errores, garantizar el cumplimiento de estándares y a mantener la integridad del código a lo largo del ciclo de desarrollo. Los entornos de desarrollo son herramientas vitales para escribir y analizar código, así como para realizar pruebas y auditorías de seguridad. Hoy en día la mayoría de los entornos de desarrollo permiten realizar el análisis estático de código mientras el desarrollador escribe el código.