Be Software Architecture Wise
Be Wise Apps

Read in English · Lire en français · Auf Deutsch lesen

Sección 0.1 de 0.8 · Principiante · 15 min de lectura

Programas, código y cómo los ejecuta una computadora

Videolección

4 min · narrado en inglés, con subtítulos
Solo escuchar (audio)
Leer la transcripción

Bienvenido a Be Software Architecture Wise. En esta primera lección empezamos por el principio de todo: qué es el software y qué ocurre realmente cuando una computadora ejecuta un programa. No necesitas ninguna formación técnica. Al final, conocerás el puñado de ideas en las que se apoya cada capítulo posterior.

Empecemos con dos palabras que vas a oír constantemente. El hardware es la máquina física: el procesador, la memoria, el almacenamiento y la tarjeta de red. El software es el conjunto de instrucciones y datos que le dicen a ese hardware qué hacer. Una imagen útil es la cocina de un restaurante. El chef es el procesador, y el software son las recetas que sigue el chef.

Un programa es una lista de instrucciones precisas, y la computadora las sigue al pie de la letra, palabra por palabra. Nunca adivina lo que querías decir. Por eso los errores en las instrucciones se convierten en bugs. La computadora no se está poniendo difícil; está haciendo exactamente lo que se le dijo. Los programadores escriben esas instrucciones como código fuente, en lenguajes como Python, Java o Go, que las personas pueden leer y modificar.

Pero el procesador no sabe leer Python ni Java. Su propio idioma es el código máquina: instrucciones muy sencillas, específicas de un tipo de procesador. Así que el código fuente tiene que traducirse. Hay dos rutas principales. En la primera ruta, un compilador traduce todo el programa a código máquina antes de lanzarlo. Los lenguajes como C, Rust y Go funcionan así. En la segunda ruta, el código se compila a una forma intermedia llamada bytecode, y un programa llamado máquina virtual lo ejecuta, traduciendo a código máquina sobre la marcha las partes más usadas. Java, Python y C sharp funcionan así.

Entre tus programas y el hardware está el sistema operativo, como Windows, macOS, Linux o Android. Las aplicaciones, como un navegador web o una aplicación de reparto de comida, no tocan el hardware directamente. Le piden al sistema operativo lo que necesitan, mediante lo que se llaman llamadas al sistema. El sistema operativo reparte el procesador, la memoria, los archivos y la red entre todos los programas que se están ejecutando, y evita que interfieran entre sí. En nuestra cocina, es el jefe de cocina que decide qué receta recibe la atención del chef.

Cuando un programa se ejecuta, el sistema operativo crea un proceso para él, con su propia memoria privada. Dentro de un proceso puede haber varios hilos, cada uno haciendo una parte del trabajo al mismo tiempo. Los hilos comparten la memoria del proceso. Eso es útil, pero si dos hilos modifican los mismos datos a la vez, aparecen algunos de los bugs más difíciles de encontrar. Y fíjate en la diferencia entre la memoria y el disco. La memoria, o RAM, es rápida, pero se borra cuando el programa se detiene o la máquina se reinicia. Todo lo que no deba perderse tiene que guardarse en un almacenamiento duradero, normalmente una base de datos.

Juntémoslo todo con un pequeño programa que calcula el precio de un pedido. Cuando lo inicias, el sistema operativo crea un proceso e inicia dentro de él el intérprete de Python. El intérprete lee el archivo y lo convierte en bytecode. Si hay un error de escritura, como un paréntesis que falta, se notifica ahora, antes de que se ejecute nada. El programa te pregunta cuántos artículos quieres, y espera. Mientras espera, no usa nada de tiempo de procesador. Después viene el cálculo: tres artículos a doce libras con cincuenta son treinta y siete libras con cincuenta, y al sumar el veinte por ciento de IVA salen cuarenta y cinco libras. Por último, se imprime el resultado y el proceso termina. Su memoria se devuelve y, como no se guardó nada en el disco, la respuesta desaparece.

¿Por qué importa esto para la arquitectura de software? Porque todo sistema que diseñes, por grande que sea, está hecho de programas que se ejecutan como procesos sobre hardware compartido. Las decisiones sobre qué vive en memoria y qué debe almacenarse de forma duradera aparecen una y otra vez, en todos los capítulos. Y como muchas cosas se ejecutan a la vez y las máquinas se reinician, los arquitectos tienen que planificar la concurrencia y los fallos desde el principio.

Esa es la base. La sección completa incluye el ejemplo de código entero y más detalles sobre el sistema operativo y los bugs. A continuación, en la sección cero punto dos, veremos cómo organizan los datos los programas. Nos vemos allí.

Lo que necesitas saber

Hardware y software

Una computadora es una máquina que sigue instrucciones muy deprisa. Las partes físicas que podrías tocar son el hardware; las instrucciones que les dicen a esas partes qué hacer son el software. Una computadora portátil, un teléfono, el servidor que ejecuta el sitio web de tu banco y el chip dentro de un termostato inteligente son todos computadoras construidas con los mismos cuatro tipos de componentes.

La CPU (unidad central de procesamiento, o procesador) ejecuta las instrucciones. Un procesador moderno tiene varios núcleos, cada uno capaz de procesar su propio flujo de instrucciones, y cada núcleo ejecuta miles de millones de pasos sencillos por segundo. La RAM (memoria de acceso aleatorio, normalmente llamada simplemente memoria) contiene las instrucciones y los datos con los que la CPU está trabajando en este momento. Es rápida, pero volátil: si se corta la corriente, su contenido desaparece. El almacenamiento (una unidad de estado sólido o un disco duro giratorio) conserva los archivos cuando no hay corriente, pero acceder a él es mucho más lento. Una tarjeta de red (por cable o Wi-Fi) envía y recibe datos hacia y desde otras computadoras.

Piensa en la cocina de un restaurante. El chef es la CPU: el único que realmente cocina. La encimera es la RAM: todo lo que el chef está usando en este momento está dispuesto allí, al alcance de la mano, pero se despeja al final de la noche. La despensa y el congelador son el almacenamiento: grandes, permanentes y a un paseo de distancia. La ventanilla que da al comedor es la tarjeta de red, por la que llegan los pedidos y salen los platos. Las recetas son el software.

Las cuatro partes que tiene toda computadora

ParteQué haceAnalogía de la cocinaQué ocurre si es el cuello de botella
CPU (procesador)Ejecuta instrucciones: aritmética, comparaciones, movimiento de datosEl chefEl trabajo se acumula; las páginas y los informes se vuelven lentos aunque sobre memoria
RAM (memoria)Contiene los programas en ejecución y los datos que están usandoLa encimeraEl sistema empieza a trasladar datos al disco y se ralentiza bruscamente, o se detienen programas
Almacenamiento (SSD o disco)Guarda archivos y bases de datos de forma permanenteLa despensa y el congeladorGuardar y cargar datos se vuelve lento; un disco lleno puede detener una base de datos
Tarjeta de redEnvía y recibe datos a otras máquinasLa ventanilla que da al comedorLas solicitudes esperan para enviarse o recibirse; los usuarios notan retrasos

Qué es un programa

Un programa es una lista de instrucciones precisas que una computadora sigue para hacer un trabajo: calcular una factura, mostrar una página web, enviar un pago. La palabra que importa es precisas. Un cocinero humano puede arreglárselas con "sal al gusto"; una computadora no. Hace exactamente lo que se le dice, exactamente en el orden en que se le dice, y nada más. Si una instrucción dice que se divida entre un número que resulta ser cero, la computadora no se encoge de hombros y sigue adelante: el programa se detiene con un error, a menos que el programador haya escrito más instrucciones que digan qué hacer en ese caso.

En el nivel más bajo, la CPU repite un ciclo mientras está encendida: obtener la siguiente instrucción de la memoria, decodificar lo que significa, ejecutarla y pasar a la siguiente. Cada instrucción es diminuta: sumar dos números, comparar dos valores, copiar un valor de un lugar de la memoria a otro, saltar a otra instrucción si una comparación resultó verdadera. Todo lo que hace una computadora, desde reproducir una película hasta entrenar un modelo de IA, se construye con miles de millones de estos pequeños pasos.

Software es el término más amplio: los programas junto con los datos, la configuración y la documentación que necesitan. Una aplicación (o app) es software que hace algo útil para una persona, como una aplicación bancaria. El software de sistema, como el sistema operativo que se describe más abajo, existe principalmente para ejecutar otro software.

Capas: las aplicaciones, el sistema operativo y el hardware

Las aplicaciones piden servicios al sistema operativo, y el sistema operativo controla el hardware: CPU, RAM, almacenamiento y tarjeta de redNavegador webApp de repartoHoja de cálc.llamadas sist.Sistema operativoplanifica la CPU, reparte memoria, archivos, redHardwareCPUejecuta instrucc.RAMrápida, temporalDiscolento, permanenteTarjeta de redenvía y recibedrivers
Las aplicaciones nunca manejan el hardware directamente: se lo piden al sistema operativo, que reparte el hardware entre ellas de forma justa y segura.

El código fuente y los lenguajes de programación

Las CPU solo entienden código máquina: instrucciones codificadas como números, específicas de una familia de procesadores. Los chips de Intel y AMD de la mayoría de las computadoras portátiles y servidores usan una familia (x86-64); los chips de los teléfonos, de los Mac recientes y de muchos servidores en la nube usan otra (Arm). El código máquina que funciona en una no funciona en la otra. Escribir código máquina a mano es lento, propenso a errores y casi nunca se hace hoy en día.

En su lugar, las personas escriben código fuente en un lenguaje de programación: una notación definida con precisión que se lee en parte como el inglés y en parte como las matemáticas. Python, JavaScript, Java, C#, Go, Rust y C son ejemplos muy usados. Se llaman lenguajes de alto nivel porque permiten a un programador decir "suma los precios de este carrito" en una o dos líneas en lugar de decenas de instrucciones máquina, y porque ocultan los detalles de la CPU concreta. El código fuente es texto plano, guardado en archivos, y es lo que los programadores leen, escriben, revisan y guardan en el control de versiones (un sistema que conserva el historial de cada cambio, explicado en la última sección de este capítulo).

Los lenguajes se diferencian en lo que facilitan. Python se valora por su legibilidad y domina el trabajo de ciencia de datos e IA; JavaScript es el lenguaje que ejecutan todos los navegadores web; Java y C# son habituales en los grandes sistemas empresariales; Go es popular para los servicios de red; C y Rust se usan donde cada microsegundo y cada byte importan, como en los sistemas operativos y las bases de datos. Ningún lenguaje es el mejor en todo, y elegir uno es en parte una decisión técnica y en parte una cuestión de qué habilidades tiene el equipo.

Del código fuente a un programa en ejecución: compiladores, intérpretes y máquinas virtuales

El código fuente tiene que convertirse en algo que la CPU pueda ejecutar. Hay tres enfoques generales, y los lenguajes reales a menudo los combinan.

Un compilador es un programa que traduce todo el código fuente de un programa a otra forma antes de que se ejecute. Los lenguajes como C, C++, Rust y Go suelen compilarse de antemano a código máquina para una CPU y un sistema operativo concretos. El resultado es un archivo ejecutable que arranca rápido y funciona deprisa, pero hay que compilar una versión distinta para cada tipo de máquina que quieras admitir, y el compilador rechaza el programa si encuentra ciertos tipos de error.

Un intérprete es un programa que lee el código fuente (o algo muy parecido) y va ejecutando sus instrucciones sobre la marcha, paso a paso. La interpretación facilita probar cosas y ejecutar el mismo programa en cualquier máquina que tenga el intérprete, a costa de la velocidad.

Muchos lenguajes populares se sitúan en un punto intermedio. El código fuente de Java lo compila una herramienta llamada javac a bytecode: instrucciones compactas para una computadora imaginaria e idealizada, en lugar de para una CPU real. La Máquina Virtual de Java (JVM) ejecuta después ese bytecode en cualquier máquina real en la que esté instalada, y así es como un mismo programa Java funciona sin cambios en Windows, macOS y Linux. La JVM observa qué partes del programa se ejecutan con más frecuencia y las compila a código máquina mientras el programa está en marcha, una técnica llamada compilación just-in-time (JIT). C# en .NET funciona de forma parecida. La implementación estándar de Python, CPython, también compila tus archivos .py a bytecode (guardando en caché el bytecode de los módulos importados en archivos .pyc) y después ejecuta ese bytecode en su propio bucle de intérprete.

Dos rutas del código fuente a la CPU

Dos rutas del código fuente a un programa en ejecución: compilar directamente a código máquina, o compilar a bytecode que ejecuta una máquina virtualRuta 1: compilado de antemano (C, C++, Rust, Go)Código fuente.c .rs .goCompiladorantes de lanzarCódigo máquinapara 1 tipo de CPUCPUejecución directaRuta 2: bytecode y una máquina virtual (Java, Python, C#)Código fuente.java .pyCompiladorjavac, CPythonBytecode.class .pycMáquinavirtualCPUinterpreta bytecode; puede compilar (JIT)las partes más usadas a código máquina
Compilar de antemano produce código máquina para un tipo de CPU; compilar a bytecode permite que un mismo archivo se ejecute en cualquier lugar donde haya instalada una máquina virtual adecuada.

Comparación de los enfoques

EnfoqueLenguajes típicosVentajasCostos
Compilación de antemano a código máquinaC, C++, Rust, GoArranque y ejecución rápidos; un único archivo autónomo que desplegarHay que compilar una versión distinta por CPU y sistema operativo; el ciclo de editar y probar es más lento
Bytecode más una máquina virtual con JITJava, Kotlin, C#Una misma compilación funciona en muchas plataformas; el JIT puede hacer muy rápidos los servicios que se ejecutan durante mucho tiempoHay que instalar el runtime; arranque más lento y memoria adicional mientras el JIT entra en calor
Bytecode más un intérpretePython (CPython), RubyRápido de escribir y probar; muy portableSuele ser más lento en cálculos pesados; las partes críticas para la velocidad a menudo se escriben en C y se llaman desde Python
Compilación JIT en el navegador o el servidorJavaScript (V8 en Chrome y Node.js)Funciona en cualquier lugar donde funcione un navegador; lo bastante rápido para la mayor parte del trabajo webEl rendimiento puede ser menos predecible que el del código compilado

La función del sistema operativo

Cuando enciendes una computadora, el primer programa importante que se inicia es el sistema operativo (SO): Windows, macOS, Linux, Android o iOS. La mayoría de los servidores de internet ejecutan Linux. El sistema operativo tiene cuatro funciones principales.

Primero, reparte la CPU. Una computadora portátil puede tener unas cuantas decenas de programas en marcha pero solo un puñado de núcleos, así que el SO alterna cada núcleo entre los programas muchas veces por segundo, lo bastante rápido como para que todos parezcan ejecutarse a la vez. Segundo, gestiona la memoria: da a cada programa su propia región e impide que un programa lea o sobrescriba la de otro. Tercero, proporciona archivos y dispositivos: los programas piden al SO que abra un archivo, dibuje en la pantalla o envíe datos por la red, y el SO, mediante pequeños programas llamados drivers, se ocupa del hardware concreto. Cuarto, aplica la seguridad: usuarios, permisos y la regla de que una aplicación no puede leer tus fotos a menos que se lo permitas.

Los programas hacen estas solicitudes mediante llamadas al sistema, un conjunto fijo de servicios que ofrece el SO. Este es el primer ejemplo del curso de una idea arquitectónica importante: una capa que oculta los detalles engorrosos tras un conjunto estable de solicitudes. Quien escribe una aplicación de reparto de comida no necesita saber de qué marca es el chip de red del teléfono.

Procesos, hilos, memoria y disco

Un archivo de programa guardado en el disco no es más que instrucciones almacenadas. Cuando lo inicias, el SO crea un proceso: una instancia en ejecución del programa con su propia memoria privada, su propia identidad y su propia parte del tiempo de CPU. Abre dos ventanas del navegador y puede que tengas varios procesos; inicia un servidor web y puede ejecutarse como un solo proceso o como muchos.

Dentro de un proceso puede haber varios hilos. Un hilo es una secuencia de instrucciones que se está ejecutando. Un proceso con varios hilos puede hacer varias cosas a la vez, por ejemplo atender las solicitudes de dos clientes al mismo tiempo, y todos sus hilos comparten la memoria del proceso. Compartirla es cómodo pero arriesgado: si dos hilos modifican el mismo dato en el mismo momento sin coordinarse, el resultado puede ser erróneo de formas que solo aparecen de vez en cuando. Estos bugs de concurrencia están entre los más difíciles de encontrar, y esa es una de las razones por las que muchos sistemas prefieren procesos separados, o diseños en los que cada solicitud se atiende de forma independiente.

La diferencia entre la memoria y el disco explica gran parte del diseño de software. Acceder a los datos en RAM es rápido, aproximadamente una décima de microsegundo, pero desaparecen cuando el proceso termina, falla o la máquina se reinicia. Los datos en disco se conservan, pero acceder a ellos lleva mucho más tiempo: decenas de microsegundos o más en una unidad de estado sólido, y varios milisegundos en un disco giratorio. Por eso, cualquier información que no deba perderse, como un pedido o un pago, tiene que escribirse en un almacenamiento duradero, normalmente a través de una base de datos, antes de que el sistema le diga al cliente "listo".

Un proceso, sus hilos, su memoria y el disco

Un proceso con dos hilos que comparten un bloque de memoria y escriben en disco los datos que debe conservar, junto a otro proceso con su propia memoriaProceso: app de pedidos activaHilo 1atiende al cliente AHilo 2atiende al cliente BMemoria (RAM) del procesocomún a ambos hilos; se pierde al reiniciarDisco (SSD)archivos y BDresisten reiniciosguardaOtro procesomemoria propia, aparte
Los hilos de un proceso comparten su memoria; los procesos separados no; solo lo que se guarda en disco sobrevive a un reinicio.
PRICE_PER_ITEM = 12.50   # pounds
VAT_RATE = 0.20          # 20 per cent

def total_with_vat(quantity):
    subtotal = quantity * PRICE_PER_ITEM
    return subtotal * (1 + VAT_RATE)

quantity = int(input("How many items? "))
print("Total to pay:", round(total_with_vat(quantity), 2))
Un pequeño programa en Python: calcular el precio de un pedido con IVA incluido

Ejemplo práctico

Ejemplo práctico: qué ocurre cuando se ejecuta el programa

Supón que el código anterior está guardado como order.py y escribes python order.py, y después introduces 3.

1. InicioEl sistema operativo crea un proceso nuevo, le asigna memoria e inicia dentro de él el intérprete de Python (que a su vez es un programa compilado).
2. CompilaciónCPython lee order.py del disco, comprueba que es Python válido y lo compila a bytecode. Un paréntesis o unos dos puntos que falten se notificarían aquí como un error de sintaxis, antes de que se ejecute nada.
3. DefiniciónEl intérprete ejecuta el bytecode desde el principio. Guarda 12.5 y 0.2 en la memoria con los nombres PRICE_PER_ITEM y VAT_RATE, y registra la función total_with_vat sin ejecutarla todavía.
4. Entradainput() pide al SO que lea del teclado. El proceso espera, sin usar CPU, hasta que escribes 3 y pulsas Intro. int() convierte el texto "3" en el número 3.
5. CálculoLa función se ejecuta: 3 por 12.5 es 37.5, y después 37.5 por 1.2 es 45.0. Cada paso es un puñado de instrucciones de bytecode, y cada una de ellas se convierte en muchas instrucciones máquina en la CPU.
6. Salidaprint() pide al SO que muestre Total to pay: 45.0 en la pantalla.
7. FinEl programa llega a su última línea, el proceso termina y el SO recupera su memoria. No se guardó nada en el disco, así que el resultado se pierde a menos que lo hayas copiado.

Incluso este programa de nueve líneas depende del sistema operativo para la memoria, el teclado y la pantalla, y de un runtime del lenguaje para traducirlo para la CPU.

Si escribieras three en lugar de 3, int() fallaría con un error en tiempo de ejecución y el programa se detendría: el código nunca dijo qué hacer con una entrada incorrecta.

El programa guarda el dinero en un número de coma flotante para ser breve. Los sistemas financieros reales guardan peniques enteros o usan un tipo decimal, por razones que explica la sección siguiente.

Bugs: cuando las instrucciones están mal

Un bug es un error en el software que hace que se comporte de forma distinta a la prevista. Como las computadoras siguen las instrucciones al pie de la letra, casi todos los bugs son una diferencia entre lo que el programador quería decir y lo que realmente escribió, o una situación en la que nadie pensó.

Conviene distinguir tres tipos. Un error de sintaxis rompe la gramática del lenguaje (un paréntesis que falta), así que el compilador o el intérprete se niega a ejecutar el programa; estos se detectan de inmediato. Un error en tiempo de ejecución ocurre mientras el programa se ejecuta, como dividir entre cero o intentar leer un archivo que no existe; el programa suele detenerse con un mensaje de error. Un error lógico es el más peligroso: el programa se ejecuta sin problemas y da la respuesta equivocada, por ejemplo cobrando el IVA dos veces. Nada falla de forma visible, así que solo las pruebas, una revisión cuidadosa o un cliente descontento lo pondrán al descubierto.

Los equipos profesionales cuentan con que habrá bugs y construyen defensas: pruebas automatizadas, revisión de código, lanzamientos graduales y una monitorización que detecta cuándo aumentan las tasas de error. Muchas de estas defensas son arquitectónicas, y aparecen a lo largo de todo este curso.

Por qué esto importa para la arquitectura

La arquitectura trata de las grandes decisiones que son costosas de cambiar, y varias de ellas se basan en las ideas de esta sección. La elección del lenguaje y del runtime afecta al tiempo de arranque, al uso de memoria, a qué bibliotecas puedes usar y a quién puedes contratar. La división entre memoria y disco decide qué debe almacenarse de forma duradera y cómo se recupera un sistema después de un fallo. Los procesos y los hilos son las unidades que se ejecutan, fallan y escalan: un capítulo posterior plantea si un sistema debe ser un solo proceso o muchos que cooperan. Y el diseño en capas del sistema operativo, que oculta el hardware tras un conjunto estable de solicitudes, es el primer ejemplo de un principio que volverás a encontrar como ocultación de información.

La sección siguiente examina aquello con lo que realmente trabajan los programas: los datos, y las estructuras y algoritmos que hacen que trabajar con ellos sea rápido o lento.

Comprueba lo que has entendido

¿Por qué desaparecen los datos de un programa cuando falla, a menos que se hayan guardado?

Mientras un programa se ejecuta, sus datos están en la RAM, que pertenece al proceso y se borra cuando el proceso termina o la máquina se reinicia. Solo sobreviven los datos escritos en un almacenamiento como un disco o una base de datos.

¿Cuál es la diferencia entre un compilador y un intérprete?

Un compilador traduce todo el programa a otra forma (código máquina o bytecode) antes de que se ejecute; un intérprete ejecuta las instrucciones a medida que las lee. Muchos lenguajes, como Java y Python, compilan primero a bytecode y después ejecutan ese bytecode en una máquina virtual.

¿Por qué un programa Java compilado en una computadora portátil con Windows suele poder ejecutarse sin cambios en un servidor Linux?

Java se compila a bytecode, no a código máquina para una CPU concreta. Cualquier máquina que tenga instalada una Máquina Virtual de Java puede ejecutar ese bytecode.

¿Por qué los programas piden al sistema operativo que lea un archivo o envíe datos, en lugar de controlar ellos mismos el disco o la tarjeta de red?

El sistema operativo reparte el hardware de forma segura entre los programas, aplica los permisos y oculta los detalles de cada dispositivo tras las llamadas al sistema, de modo que las aplicaciones no necesitan saber en qué hardware se están ejecutando.

Siguiente · 0.2Datos y algoritmosCómo representan las computadoras los números y el texto, las estructuras de datos de uso diario (listas, diccionarios, colas, árboles, grafos), qué es un algoritmo y cómo la notación Big-O explica por qué algunos sistemas se vuelven más lentos a medida que crecen los datos.Continuar →

Fuentes y lecturas recomendadas

¿Tienes una pregunta sobre esta sección? ArchitectBot, el asistente de estudio con IA del curso, responde primero a partir del curso.

Pregunta: “Si Python es más lento que C, ¿por qué tantas empresas construyen sus sistemas de backend en Python?”