Parte 9 — Ingeniería de software políglota · ⏱️ Duración estimada: 90 min · Nivel: Intermedio ✅ Clase construida — 10 implementaciones del núcleo verificadas contra
casos.json.
El diseño es el conjunto de decisiones que reparten un sistema en piezas y definen cómo se hablan entre sí; la arquitectura es ese diseño a la escala más alta, la que fija la forma general antes de escribir una sola línea de negocio. McConnell dedica en Code Complete varios capítulos a lo que llama el desafío central del diseño de software: gestionar la complejidad dividiendo el sistema en partes que quepan, cada una, en una sola cabeza. Dos conceptos gobiernan esa división. La cohesión mide cuánto pertenecen juntas las cosas dentro de un módulo —alta cohesión es bueno, una capa que hace una cosa—; el acoplamiento mide cuánto depende un módulo de otro —bajo acoplamiento es bueno, porque permite cambiar una pieza sin arrastrar a las demás—. Hunt y Thomas, en The Pragmatic Programmer, empaquetan la misma meta bajo la palabra ortogonalidad: componentes independientes, donde tocar uno no repercute en los otros.
El programa de casos.json toma la medida más elemental imaginable de una arquitectura: contar sus capas. Recibe los nombres de las capas (web api datos) y responde capas=3. Es deliberadamente humilde —contar no es diseñar—, pero nombrar y enumerar los componentes es siempre el primer acto de razonar sobre una estructura: no puedes hablar del acoplamiento entre capas que aún no has identificado. La arquitectura en capas clásica (presentación → lógica → datos) es el ejemplo canónico, y contar sus estratos es el punto de partida.
Lo verdaderamente interesante para un curso políglota aparece en la comparación: cada lenguaje empuja hacia una forma distinta de organizar el código. Java te da paquetes y una jerarquía de directorios; Go y Rust, módulos con reglas de visibilidad propias; C# organiza con namespaces y ensamblados. La arquitectura no es neutral respecto al lenguaje: la gramática del lenguaje moldea la estructura que resulta natural.
Al finalizar, podrás:
| # | Tema | Por qué importa |
|---|---|---|
| 1 | Arquitectura y capas | Estructura de alto nivel del sistema |
| 2 | Cohesión y acoplamiento | Decide qué tan fácil es cambiar |
| 3 | Separación de responsabilidades | Cada parte hace una sola cosa |
| 4 | Módulos por lenguaje | La gramática moldea la estructura |
Heredas un servicio que "nadie se atreve a tocar". Al leerlo descubres que la lógica de negocio hace consultas SQL directas y también arma HTML: las tres responsabilidades —presentación, negocio y datos— viven mezcladas en las mismas funciones. Cambiar el formato de una fecha en pantalla te obliga a entender la consulta a la base de datos, porque están enredadas: alto acoplamiento, baja cohesión. La cura es arquitectónica: separar en capas web, api y datos, cada una con su responsabilidad, con dependencias que apunten en una sola dirección. El primer paso, antes de mover una línea, es exactamente lo que hace el programa de esta clase: nombrar las capas y contarlas, para tener un mapa sobre el que razonar. capas=3 es el diagnóstico inicial de una arquitectura que por fin se puede discutir.
capas=<cantidad>Especificación y verificación en casos.json:
| stdin | esperado |
|---|---|
web api datos |
capas=3 |
cli |
capas=1 |
web api datos cache |
capas=4 |
Contar los componentes es la medida más básica de una estructura: sin enumerarlos no hay nada sobre lo que razonar.
LEER capas ; ESCRIBIR cantidad
Mismo algoritmo, forma idiomática en cada lenguaje. Todas producen la salida de casos.json.
Cada bloque es el archivo real de implementaciones/: el enlace de cada lenguaje abre su fuente, y el comando de al lado lo ejecuta.
python/main.py · python main.pyPython resuelve el conteo con una elegancia casi telegráfica. sys.stdin.read().split() sin argumentos parte por cualquier bloque de espacios en blanco y descarta los vacíos, así que "web api datos" se convierte en una lista de tres cadenas; len(...) la mide. El detalle idiomático que Ramalho subraya en Fluent Python es que str.split() sin separador ya normaliza espacios múltiples y saltos de línea, por lo que no hace falta limpieza previa: la abstracción correcta hace desaparecer el caso borde.
import sys
capas = sys.stdin.read().split()
print(f"capas={len(capas)}")
🧬 El mismo programa en la familia Scripting dinámico: Ruby · Perl · Lua · Tcl · R
Para web api datos la lista tiene tres elementos y sale capas=3; para cli, uno, capas=1; para web api datos cache, cuatro. Fíjate en que el "modelo" del sistema —sus capas— es aquí, literalmente, una lista, y contar componentes es medir esa lista.
go/main.go · go run main.goGo contrasta en la forma de leer, pero coincide en el espíritu con strings.Fields, que es el equivalente exacto del split() sin argumentos de Python: trocea por espacios en blanco y omite los vacíos. Donovan y Kernighan, en The Go Programming Language, presentan Fields como la herramienta idiomática para tokenizar texto separado por espacios sin sorpresas. La verbosidad de importar bufio, os y strings es el precio que Go paga por su explicitud: nada de magia, cada dependencia a la vista.
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
line, _ := bufio.NewReader(os.Stdin).ReadString('\n')
capas := strings.Fields(line)
fmt.Printf("capas=%d\n", len(capas))
}
🧬 El mismo programa en la familia Sistemas: Zig · Nim · D
Que Go se apoye en strings.Fields no es casual: es un lenguaje que organiza el propio código en paquetes con visibilidad por mayúscula/minúscula, y su biblioteca estándar refleja esa mentalidad modular y sin adornos.
sql/main.sql · sqlite3 :memory: < main.sqlSQL vuelve a mostrar el otro paradigma. No hay lista ni tokens: hay una tabla donde cada capa es una fila, y contar componentes es la agregación count(*). Es una traducción fiel de la idea arquitectónica —cada componente, una entidad— al modelo relacional que Date defiende en SQL and Relational Theory: razonas sobre conjuntos de filas, no sobre una cadena que hay que partir.
-- SQL: cuenta las filas (capas).
WITH capas(nombre) AS (VALUES ('web'), ('api'), ('datos'))
SELECT printf('capas=%d', count(*)) AS resultado FROM capas;
🧬 El mismo programa en la familia Lógica y declarativa: Prolog · Datalog
javascript/main.mjs · node main.mjsimport { readFileSync } from "node:fs";
const capas = readFileSync(0, "utf8").trim().split(/\s+/);
console.log(`capas=${capas.length}`);
🧬 El mismo programa en la familia JavaScript / web: Dart · ActionScript
typescript/main.ts · pnpm exec tsx main.tsimport { readFileSync } from "node:fs";
const capas: string[] = readFileSync(0, "utf8").trim().split(/\s+/);
console.log(`capas=${capas.length}`);
🧬 El mismo programa en la familia JavaScript / web: Dart · ActionScript
java/Main.java · java Main.javaimport java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] capas = br.readLine().trim().split("\\s+");
System.out.println("capas=" + capas.length);
}
}
🧬 El mismo programa en la familia JVM: Kotlin · Scala · Groovy · Clojure
csharp/Program.cs · dotnet runusing System;
string[] capas = Console.In.ReadToEnd()
.Split(new[] { ' ', '\t', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
Console.WriteLine($"capas={capas.Length}");
🧬 El mismo programa en la familia .NET: F# · VB.NET
rust/main.rs · rustc main.rs -o main && ./mainuse std::io::Read;
fn main() {
let mut s = String::new();
std::io::stdin().read_to_string(&mut s).unwrap();
let n = s.split_whitespace().count();
println!("capas={n}");
}
🧬 El mismo programa en la familia Sistemas: Zig · Nim · D
c/main.c · cc main.c -o main && ./main#include <stdio.h>
int main(void) {
char tok[256];
int c = 0;
while (scanf("%255s", tok) == 1) c++;
printf("capas=%d\n", c);
return 0;
}
🧬 El mismo programa en la familia C / llaves: C++ · Objective-C
php/main.php · php main.php<?php
$capas = preg_split('/\s+/', trim(fgets(STDIN)));
echo "capas=" . count($capas) . "\n";
🧬 El mismo programa en la familia Scripting dinámico: Ruby · Perl · Lua · Tcl · R
SQL es declarativo: no lee de stdin como los demás; su implementación muestra la misma idea sobre una tabla de casos, y el verificador la marca como ilustrativa.
Aquí la diferencia entre lenguajes va más allá de la sintaxis del conteo: cada uno ofrece un mecanismo distinto para organizar el código en unidades, y ese mecanismo condiciona la arquitectura que emerge de forma natural.
| Lenguaje | Unidad de organización | Control de visibilidad |
|---|---|---|
| Java | paquetes (package a.b.c) + directorios |
public/protected/private/paquete |
| C# | namespaces + ensamblados | public/internal/private |
| Go | paquetes (por directorio) | mayúscula = exportado |
| Rust | módulos (mod) + crates |
pub explícito, privado por defecto |
| Python | módulos y paquetes (__init__.py) |
convención _privado |
| JavaScript/TS | módulos ES (import/export) |
export explícito |
| C | archivos + cabeceras .h |
static = interno a la unidad |
| SQL | esquemas | permisos por objeto |
| Clase de diferencia | Observación entre lenguajes |
|---|---|
| Sintáctica | split/Fields/count frente a bucle con scanf. |
| Semántica | Cada capa aísla una responsabilidad; contar es el primer diagnóstico. |
| Paradigmática | SQL cuenta filas (una capa = una fila) en vez de tokenizar. |
La estructura en componentes con responsabilidades reaparece en cada estilo arquitectónico: la arquitectura en capas las apila y dirige las dependencias hacia abajo; la hexagonal (puertos y adaptadores) pone el dominio en el centro y el mundo exterior en los bordes; los microservicios separan las capas en procesos independientes desplegables por separado. Cambian las fronteras y su coste, pero la pregunta de fondo es siempre la misma que la de esta clase: ¿cuántas piezas hay, qué responsabilidad tiene cada una y cómo dependen entre sí?
Los mismos casos para todas las implementaciones: casos.json. Verifica la equivalencia:
python scripts/verificar_equivalencia.py 149
Detalle en reto.md.
Libros de la parte:
Libros de los lenguajes del núcleo:
⏮️ Clase 148 · 📂 Parte · 📚 Índice · 🌐 Atlas · Clase 150 ⏭️