Archivo de la etiqueta: #programming

💡 12 reglas de buenas prácticas de programación (con ejemplos en Java)

Aprender a programar no consiste solo en hacer que un programa funcione.

Un programa puede funcionar… y ser difícil de entender, mantener o ampliar. Por eso, además de aprender sintaxis, es fundamental adoptar una serie de buenas prácticas que nos ayuden a escribir mejor código.

Muchas de estas reglas no son nuevas: aparecen una y otra vez en libros clásicos como:

  • Clean Code — Robert C. Martin
  • Effective Java — Joshua Bloch
  • Refactoring — Martin Fowler
  • The Pragmatic Programmer — Andrew Hunt y David Thomas
  • Code Complete — Steve McConnell

A continuación se presentan 12 reglas fundamentales, con ejemplos en Java.


1. Usa nombres que expresen intención

👉 Un buen nombre debe indicar claramente qué representa o qué hace la variable, el método o la clase, sin necesidad de comentarios.

📚 Referencia: Clean Code

🔸 Variables

❌ Mal:

int x;

✔️ Bien:

int edad;

🔸 Métodos

❌ Mal:

void f() {
   // ...
}

✔️ Bien:

void calcularMedia() {
   // ...
}

🔸 Clases

❌ Mal:

class Datos {
}

✔️ Bien:

class Alumno {
}

📎 Si necesitas un comentario para explicar qué hace una variable o un método, probablemente su nombre no es adecuado.

2. Las funciones deben ser pequeñas y hacer una sola cosa

👉 Una función debe tener una única responsabilidad y ser fácil de entender de un vistazo.

📚 Referencias: “Clean Code“, “Refactoring“.

📌 Relacionado con: SRP (Single Responsibility Principle)

🔸 Ejemplo

❌ Mal:

void procesarAlumno(Alumno alumno) {
   // validar edad
   if (alumno.getEdad() < 0) {
       System.out.println("Edad incorrecta");
  }

   // calcular media de notas
   double media = (alumno.getNota1() + alumno.getNota2()) / 2;

   // guardar en fichero
   System.out.println("Guardando alumno...");
}

✔️ Bien:

void procesarAlumno(Alumno alumno) {
   validarAlumno(alumno);
   double media = calcularMedia(alumno);
   guardarAlumno(alumno, media);
}

void validarAlumno(Alumno alumno) {
   if (alumno.getEdad() < 0) {
       System.out.println("Edad incorrecta");
  }
}

double calcularMedia(Alumno alumno) {
   return (alumno.getNota1() + alumno.getNota2()) / 2;
}

void guardarAlumno(Alumno alumno, double media) {
   System.out.println("Guardando alumno...");
}

📎 Refactorización
El paso de la versión “mal” a la versión “bien” no consiste en añadir funcionalidad, sino en mejorar la estructura del código sin cambiar su comportamiento.
Este proceso se conoce como refactorización (refactoring). En este caso concreto, hemos aplicado la técnica de extracción de métodos (extract method), dividiendo una función grande en varias funciones más pequeñas y especializadas.


⚠️ Señales de alerta

  • Si el nombre de la función contiene “y” (por ejemplo, calcularYGuardar) → probablemente hace más de una cosa
  • Si necesitas poner comentarios dentro de una función → probablemente deberías dividirla en varias funciones más pequeñas
  • Si una función es difícil de explicar en una sola frase → probablemente hace demasiadas cosas

3. Evita comentarios innecesarios

👉 El código debe ser lo suficientemente claro como para no necesitar comentarios que expliquen qué hace.

📚 Referencia: Clean Code

🔸 Ejemplo con variables

❌ Mal:

// Edad
int x;

✔️ Bien:

int edad;

En el primer caso, el comentario es necesario porque el nombre de la variable no aporta información. En el segundo, el propio nombre hace innecesaria cualquier explicación.

🔸 Ejemplo con funciones

❌ Mal:

void procesarAlumno(Alumno alumno) {
   // validar edad
   if (alumno.getEdad() < 0) {
       System.out.println("Edad incorrecta");
  }

   // calcular media
   double media = (alumno.getNota1() + alumno.getNota2()) / 2;

   // guardar alumno
   System.out.println("Guardando alumno...");
}

✔️ Bien:

void procesarAlumno(Alumno alumno) {
   validarAlumno(alumno);
   double media = calcularMedia(alumno);
   guardarAlumno(alumno, media);
}

En el primer caso, los comentarios son necesarios porque el código no es claro. En el segundo, los nombres de los métodos explican exactamente qué ocurre, por lo que los comentarios dejan de ser necesarios.


⚠️ Nota importante

Esto no significa que los comentarios sean malos.

Son útiles para:

  • explicar decisiones de diseño
  • documentar APIs
  • aclarar aspectos no evidentes

Pero no deberían usarse para explicar código que podría ser más claro.


Regla práctica

  • Si un comentario explica qué hace el código → probablemente el código puede mejorar.
  • Si un comentario explica por qué se hace algo → suele ser útil.

4. No repitas código (DRY)

👉 El principio DRY (Don’t Repeat Yourself, «no te repitas») establece que cada pieza de lógica debe tener una única representación en el código. Cuando el mismo cálculo o bloque de instrucciones aparece copiado en varios sitios, cualquier corrección futura obliga a localizar y modificar todas las copias; si se pasa por alto alguna, el programa queda en un estado inconsistente.

📚 Referencia: The Pragmatic Programmer

📌 Acrónimo: DRY (Don’t Repeat Yourself)

🔸 Ejemplo

❌ Mal:

public class Main {
public static void main(String[] args) {
double radio1 = 5.0;
double area1 = 3.14 * radio1 * radio1;
System.out.printf("Area del circulo 1: %.2f%n", area1);

double radio2 = 3.0;
double area2 = 3.14 * radio2 * radio2;
System.out.printf("Area del circulo 2: %.2f%n", area2);

double radio3 = 7.5;
double area3 = 3.14 * radio3 * radio3;
System.out.printf("Area del circulo 3: %.2f%n", area3);
}
}

En este código, el cálculo del área está duplicado en varios sitios. Si en el futuro se decide aumentar la precisión de π o añadir una comprobación sobre el radio, habría que modificar todas las apariciones.

✔️ Mejor:

public class Main {

public static void main(String[] args) {
double radio1 = 5.0;
double area1 = calcularAreaCirculo(radio1);
System.out.printf("Area del circulo 1: %.2f%n", area1);

double radio2 = 3.0;
double area2 = calcularAreaCirculo(radio2);
System.out.printf("Area del circulo 2: %.2f%n", area2);

double radio3 = 7.5;
double area3 = calcularAreaCirculo(radio3);
System.out.printf("Area del circulo 3: %.2f%n", area3);
}

static double calcularAreaCirculo(double radio) {
return Math.PI * radio * radio;
}
}

Ahora, el cálculo está centralizado en un único método. Cualquier cambio se realiza en un solo lugar, lo que reduce errores y facilita el mantenimiento.

Aún así, en main() se repiten tres bloques casi idénticos. Podríamos usar un bucle y simplificar el código:

✔️ Bien:

public class Main {
public static void main(String[] args) {
double[] radios = {5.0, 3.0, 7.5};
for(int i=0; i<radios.length; i++) {
double area = calcularAreaCirculo(radios[i]);
System.out.printf("Area del circulo %d: %.2f%n",
i+1, area);
}
}

static double calcularAreaCirculo(double radio) {
return Math.PI * radio * radio;
}
}

👉 Regla práctica

  • Si repites un mismo cálculo o una misma fórmula → probablemente estás violando DRY
  • Si un cambio obliga a modificar varios sitios → hay duplicación
  • DRY no solo evita repetir código, sino evitar repetir conocimiento

⚠️ Nota importante

En Java, muchas constantes de uso habitual ya están definidas en la biblioteca estándar. Por ejemplo, el número π está disponible como Math.PI, con toda la precisión necesaria. Siempre que sea posible, es preferible usar constantes ya definidas en lugar de redefinirlas.

5. Evita los números mágicos

👉 Los valores constantes deben tener un nombre significativo que explique qué representan y aparecer en el código a través de la constante que los representa, no codificados directamente como números mágicos.

📚 Referencia: Clean Code

🔸 Ejemplo

❌ Mal:

static double calcularPrecioConIva(double precio) {
return precio * 1.21;
}

static double calcularIva(double precio) {
return precio * 0.21;
}

En este código, el valor 0.21 aparece repetido en varias funciones. Si el tipo de IVA cambia, hay que buscar y modificar todas las apariciones, con el riesgo de pasar alguna por alto. Además, alguien que lea el código por primera vez no sabe inmediatamente qué significa ese valor.

✔️ Bien:

static final double IVA = 0.21;

static double calcularPrecioConIva(double precio) {
return precio * (1 + IVA);
}

static double calcularIva(double precio) {
return precio * IVA;
}

Ahora el valor tiene un nombre que explica su significado, y aparece en un único lugar. Si el tipo impositivo cambia, la modificación se hace una sola vez y queda actualizada en todas las partes del programa que lo utilizan.

Regla práctica

  • Si aparece un número «suelto» en el código → probablemente debería ser una constante
  • Si no sabes qué significa un valor sin leer más código → necesita un nombre
  • Las constantes hacen el código más claro y más fácil de mantener

6. Evita listas largas de parámetros

👉 Cuando una función tiene muchos parámetros, suele ser difícil de entender y utilizar correctamente.

📚 Referencia: Effective Java

No hay un número mágico, pero como regla general:

  • 1–2 parámetros → normal
  • 3 parámetros → empieza a ser sospechoso
  • 4 o más → probablemente hay un problema de diseño

🔸 Ejemplo

❌ Mal:

void imprimirUsuario(String nombre, int edad, String email, String telefono) {
System.out.println(nombre + " (" + edad + ")");
...
}

✔️ Bien:

class Usuario {
String nombre;
int edad;
String email;
String telefono;
}

void imprimirUsuario(Usuario usuario) {
System.out.println(usuario.nombre + " (" + usuario.edad + ")");
...
}

En el primer caso, la función recibe muchos datos independientes, lo que la hace más difícil de usar y más propensa a errores (por ejemplo, cambiar el orden de los parámetros). En el segundo, los datos están agrupados en un objeto que representa un concepto del dominio (Usuario), lo que hace el código más claro y más fácil de manejar.

Por ahora los campos de Usuario son accesibles directamente desde fuera de la clase. En la siguiente regla veremos por qué eso puede ser un problema y cómo solucionarlo.

Regla práctica

  • Si una función tiene muchos parámetros → revisa si puedes agruparlos en un objeto
  • Si varios parámetros están relacionados → probablemente deberían formar una clase
  • Si dudas sobre el orden de los parámetros → el diseño puede mejorarse

🔥 Nota avanzada

👉 En algunos casos, cuando es necesario crear objetos con muchos parámetros, se utilizan técnicas como el patrón Builder, que se estudiarán en asignaturas más avanzadas.

7. Prefiere objetos inmutables

👉 Un objeto inmutable es aquel cuyo estado no puede cambiar después de crearse.

📚 Referencia: Effective Java

🔸 Ejemplo

❌ Mal:

class Usuario {
   String nombre;
   int edad;
}
Usuario u = new Usuario();
u.nombre = "Ana";
u.edad = 20;
u.edad = -5; // nadie lo impide

En este código, los campos son accesibles directamente desde fuera de la clase. Cualquier parte del programa puede modificar el estado del objeto en cualquier momento, incluso asignando valores inválidos.

✔️ Bien:

class Usuario {
   private final String nombre;
   private final int edad;

   public Usuario(String nombre, int edad) {
       this.nombre = nombre;
       this.edad = edad;
  }

   public String getNombre() { return nombre; }
   public int getEdad() { return edad; }
}

Ahora los campos son privados y no pueden modificarse tras la construcción. El objeto se crea de una vez con todos sus datos y su estado no cambia.

En la regla anterior agrupábamos datos en un objeto (Usuario). Aquí damos un paso más: hacemos que ese objeto sea seguro y consistente.

Regla práctica

  • Si un objeto no necesita cambiar → hazlo inmutable
  • Si un atributo no debe modificarse → decláralo final
  • Evita exponer atributos directamente → usa encapsulación

⚠️ Nota importante

La inmutabilidad aporta varias ventajas:

  • Evita errores difíciles de detectar
  • Facilita el razonamiento sobre el código
  • Hace el código más seguro en entornos concurrentes

8. Maneja correctamente los errores

👉 Los errores no deben ignorarse: deben detectarse y tratarse de forma adecuada.

📚 Referencia: Effective Java

🔸 Ejemplo

En la regla anterior hicimos que Usuario fuera inmutable. Pero aún es posible construir un objeto con valores inválidos:

❌ Mal:

class Usuario {
private final String nombre;
private final int edad;

public Usuario(String nombre, int edad) {
this.nombre = nombre;
this.edad = edad; // no se valida
}
}
Usuario u = new Usuario("Ana", -5); // se crea sin error

El objeto se construye sin problemas, aunque la edad sea negativa. El error pasa desapercibido.

✔️ Bien:

class Usuario {
private final String nombre;
private final int edad;

public Usuario(String nombre, int edad) {
if (edad < 0) {
throw new IllegalArgumentException("La edad no puede ser negativa");
}
this.nombre = nombre;
this.edad = edad;
}

public String getNombre() { return nombre; }
public int getEdad() { return edad; }
}

Ahora el error se detecta en el momento adecuado y se comunica mediante una excepción. Es imposible construir un Usuario con un estado inválido.

🔸 Ejemplo de uso

❌ Mal:

try {
   Usuario u = new Usuario("Ana", -5);
} catch (Exception e) {
   // ignorar el error
}

✔️ Bien:

try {
   Usuario u = new Usuario("Ana", -5);
} catch (IllegalArgumentException e) {
   System.out.println("Error al crear el usuario: " + e.getMessage());
}

En el primer caso, el error se captura pero se ignora, lo que puede ocultar problemas graves. En el segundo, se gestiona de forma explícita.

Regla práctica

  • No ignores excepciones → siempre haz algo con ellas
  • Usa tipos de excepción específicos (IllegalArgumentException, etc.)
  • Valida los datos lo antes posible, preferiblemente en el constructor

⚠️ Nota importante

Lanzar una excepción no es «fallar», es:

  • detectar un problema antes de que se propague
  • evitar que el objeto quede en un estado incorrecto
  • hacer el error visible en lugar de ocultarlo

Conexión con la regla anterior

En la regla anterior hicimos el objeto Usuario inmutable: sus campos no pueden cambiar tras la construcción. Aquí añadimos validación para garantizar que el objeto nunca se construya en un estado inválido. Las dos reglas juntas aseguran que Usuario sea siempre correcto.

9. Escribe código fácil de leer (KISS)

👉 La solución más simple y clara suele ser la mejor.

📚 Referencia: The Pragmatic Programmer

📌 Acrónimo: KISS (Keep It Simple, Stupid)

🔸 Ejemplo

❌ Mal:

if ((edad >= 18 && edad <= 65 && !tieneDeudas) || esEmpleadoVIP) {
permitirAcceso();
}

✔️ Bien:

boolean edadValida = edad >= 18 && edad <= 65;
boolean clienteSinDeudas = !tieneDeudas;
boolean accesoEspecial = esEmpleadoVIP;

if ((edadValida && clienteSinDeudas) || accesoEspecial) {
permitirAcceso();
}

En el primer caso, la condición es difícil de leer y entender de un vistazo. En el segundo, el uso de variables intermedias permite expresar claramente la intención del código.

Regla práctica

  • Si una expresión es difícil de leer → divídela
  • Usa variables con nombres significativos
  • Prefiere claridad frente a “código compacto”

⚠️ Nota importante

Escribir menos código no siempre significa escribir mejor código. A veces, añadir unas pocas líneas mejora mucho la legibilidad.

Conexión con reglas anteriores

Igual que en reglas anteriores, los nombres (edadValida, clienteSinDeudas) hacen que el código se explique por sí mismo, evitando la necesidad de comentarios.

10. Escribe tests siempre que puedas

👉 Un test permite comprobar automáticamente que una parte del programa funciona como esperamos.

📚 Referencia: Test Driven Development: By Example

📌 Relacionado con: TDD (Test-Driven Development)

🔸 Ejemplo

Supongamos que queremos implementar un método que determine si un alumno está aprobado.

❌ Sin test:

class Evaluacion {
   static boolean estaAprobado(double nota) {
       return nota > 5; // error sutil
  }
}

✔️ Con test:

import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertFalse;
import org.junit.jupiter.api.Test;

class EvaluacionTest {

   @Test
   void aprobadoConCinco() {
       assertTrue(Evaluacion.estaAprobado(5));
  }

   @Test
   void suspensoConMenosDeCinco() {
       assertFalse(Evaluacion.estaAprobado(4.9));
  }
}

En este caso, el código tiene un error (> 5 en lugar de >= 5) que es fácil de cometer y difícil de detectar a simple vista. Al ejecutar el test y ver que falla, podemos localizar enseguida el error. El test define claramente el comportamiento esperado y permite detectar el error de forma inmediata.

class Evaluacion {
   static boolean estaAprobado(double nota) {
       return nota >= 5;
  }
}

Regla práctica

  • Si una regla tiene límites (≥, ≤, etc.) → es importante testearla
  • Los tests ayudan a detectar errores pequeños pero importantes
  • Un buen test describe claramente el comportamiento esperado

⚠️ Nota importante

Los tests son especialmente útiles cuando:

  • Hay condiciones límite.
  • Hay reglas del dominio (como “aprobado”).
  • El código puede cambiar en el futuro.

Conexión con reglas anteriores

Igual que en la regla DRY, aquí también aparece una regla del sistema (“qué es aprobar”). Los tests permiten asegurar que esa regla se cumple siempre.

11. Refactoriza constantemente

👉 Mejorar el código es parte del desarrollo, no una fase posterior.

📚 Referencia: Refactoring

🔸 Ejemplo

Supongamos que ya tenemos un código que funciona correctamente:

class Evaluacion {
   static boolean estaAprobado(double nota) {
       return nota >= 5;
  }

   static String mensajeResultado(double nota) {
       if (nota >= 5) {
           return "Aprobado";
      } else {
           return "Suspenso";
      }
  }
}

El código funciona, pero hay duplicación.

✔️ Refactorizado:

class Evaluacion {
   static boolean estaAprobado(double nota) {
       return nota >= 5;
  }

   static String mensajeResultado(double nota) {
       if (estaAprobado(nota)) {
           return "Aprobado";
      } else {
           return "Suspenso";
      }
  }
}

El código inicial es correcto, pero repite la misma lógica (nota >= 5) en varios sitios. Al refactorizar, reutilizamos el método estaAprobado(), haciendo el código más claro y evitando duplicación.

Regla práctica

  • Refactorizar no es “arreglar errores”, sino mejorar el código
  • Si ves duplicación → refactoriza
  • Si algo se puede expresar mejor → refactoriza

⚠️ Nota importante

Refactorizar significa:

  • No cambiar el comportamiento.
  • Mejorar la estructura interna.
  • Facilitar futuras modificaciones.

Conexión con reglas anteriores

En esta regla se aplican varias ideas vistas antes:

  • DRY → eliminamos duplicación
  • nombres claros → estaAprobado
  • funciones pequeñas → mejor organización

📎 Un buen programador no solo escribe código que funciona, sino que mejora continuamente el código que ya funciona.

12. Piensa antes de programar

👉 Antes de escribir código, es fundamental entender el problema y diseñar una solución.

📚 Referencia: The Pragmatic Programmer

🔸 Ejemplo

Supongamos que queremos gestionar los datos de varios alumnos y determinar si han aprobado. Sin reflexionar sobre el diseño, es habitual empezar así:

❌ Mal:

String[] nombres  = {"Ana", "Luis", "María"};
double[] notas    = {8.5, 4.0, 9.0};

for (int i = 0; i < nombres.length; i++) {
   if (notas[i] >= 5) {
       System.out.println(nombres[i] + ": Aprobado");
  } else {
       System.out.println(nombres[i] + ": Suspenso");
  }
}

El código funciona para este caso concreto, pero los datos de un alumno están repartidos en arrays distintos. Si añadimos un atributo nuevo (por ejemplo, el email), necesitamos un tercer array y recordar mantener siempre los tres sincronizados. La relación entre los datos no está expresada en el código.

✔️ Bien:

class Alumno {
   String nombre;
   double nota;

   public Alumno(String nombre, double nota) {
       this.nombre = nombre;
       this.nota = nota;
  }

   boolean estaAprobado() {
       return nota >= 5;
  }
}
...

Alumno[] alumnos = {
   new Alumno("Ana",  8.5),
   new Alumno("Luis", 4.0),
   new Alumno("María", 9.0)
};

for (Alumno a : alumnos) {
   String resultado = a.estaAprobado() ? "Aprobado" : "Suspenso";
   System.out.println(a.nombre + ": " + resultado);
}

Al modelar el problema antes de codificarlo, los datos de cada alumno están encapsulados en un objeto y la lógica de negocio («¿está aprobado?») vive junto a los datos a los que pertenece. Si mañana necesitamos guardar también el email de cada alumno, en la versión con arrays tendríamos que añadir un tercer array y mantenerlo sincronizado con los otros dos. En la versión con clase, basta con añadir un campo a Alumno y el resto del código no se ve afectado.

En un programa real aplicaríamos también las reglas 7 y 8: haríamos los campos privados y validaríamos los datos en el constructor.

Regla práctica

  • Antes de programar → identifica los conceptos del problema
  • Agrupa en una clase los datos que representan una misma entidad
  • La lógica que pertenece a un dato debe estar cerca de ese dato

⚠️ Nota importante

Muchos problemas de programación no son de sintaxis, sino de diseño. Dedicar unos minutos a pensar qué entidades intervienen y cómo se relacionan suele simplificar mucho el código resultante.

Conexión con todo el artículo

Esta regla resume todas las anteriores:

  • Nombres claros → pensar qué representan los datos
  • Funciones pequeñas → diseñar responsabilidades
  • DRY → identificar conocimiento común
  • Inmutabilidad → decidir qué debe cambiar

Todas las buenas prácticas empiezan antes de escribir código.

📎 Programar bien no consiste solo en escribir código que funcione, sino en construir soluciones que otros puedan entender, mantener y mejorar. Y todo ello empieza mucho antes de escribir la primera línea de código.

📊 Resumen de reglas

#ReglaAcrónimoReferencia
1Usa nombres que expresen intenciónClean Code
2Funciones pequeñas y con una sola responsabilidadSRPClean Code, Refactoring
3Evita comentarios innecesariosClean Code
4No repitas códigoDRYThe Pragmatic Programmer
5Evita números mágicosClean Code
6Evita listas largas de parámetrosEffective Java
7Prefiere objetos inmutablesEffective Java
8Maneja correctamente los erroresEffective Java
9Escribe código simple y legibleKISSThe Pragmatic Programmer
10Escribe testsTDDTest Driven Development: By example
11Refactoriza constantementeRefactoring
12Piensa antes de programarThe Pragmatic Programmer

Estas 12 reglas resumen algunos de los principios más importantes que aparecen una y otra vez en la práctica profesional.

📎 Estas reglas no son leyes estrictas, pero sí principios que aparecen constantemente en la práctica profesional. No deben memorizarse de forma aislada. En la práctica, suelen aparecer juntas y se refuerzan unas a otras. Aprenderlas desde el principio marca una gran diferencia:

  • el código funciona,
  • se entiende
  • y puede evolucionar

Programar bien no es aplicar reglas de forma mecánica, sino desarrollar el criterio para saber cuándo y cómo aplicarlas. Porque, al final, programar bien no es solo resolver problemas, sino resolverlos de forma que otros puedan entender, mantener y continuar tu trabajo.

📚 12 libros imprescindibles para aprender a programar (bien)

Aprender a programar es relativamente fácil. Aprender a programar bien es otra historia.

Aprender a programar no consiste solo en dominar un lenguaje. De hecho, eso es casi lo de menos. Lo verdaderamente importante es aprender a pensar como programador, a escribir código que otros puedan entender, mantener y evolucionar. Es decir, desarrollar criterio.

En la enseñanza de la programación, es habitual centrar la atención en lenguajes, herramientas o entornos de desarrollo. Sin embargo, estos aspectos son, en gran medida, circunstanciales.

Lo que realmente distingue a un buen programador es su capacidad para escribir código comprensible, diseñar soluciones adecuadas y tomar decisiones fundamentadas.

En este artículo se presenta una selección de doce libros que ayudan precisamente a eso: a ir más allá de la sintaxis y comprender qué significa realmente construir buen software. Son libros que enseñan buenas prácticas, diseño y fundamentos sólidos.

La mayoría son clásicos, pero se complementan con algunas referencias más modernas que reflejan la práctica actual.

📎 A comienzos de los años 2000, varios de los autores mencionados en este artículo participaron en la redacción del Manifiesto Ágil, un breve pero influyente texto que redefinió la forma de entender el desarrollo de software. Frente a enfoques más rígidos y planificados, el manifiesto propone priorizar a las personas y la comunicación, el software funcionando, la colaboración con el cliente y la capacidad de adaptación al cambio.

A partir de estos principios surgieron diversas metodologías y prácticas que han marcado profundamente la ingeniería del software moderna, como Scrum o Extreme Programming. Muchas de las ideas presentes en libros como Clean Code, Refactoring o Test Driven Development están estrechamente relacionadas con esta forma de entender el desarrollo: iterativo, incremental y centrado en la calidad del código.


🧼 Escribir código limpio y mantenible

1. Clean Code

Autor: Robert C. Martin

Si tuviera que recomendar un solo libro, sería este. Probablemente se trata del libro más influyente sobre calidad del código.

Su idea central es sencilla pero profunda: el código debe ser legible y expresar intención. Clean Code enseña a escribir código que se entienda, que sea legible y que transmita intención. Habla de nombres, funciones, comentarios, tests… pero, sobre todo, de disciplina.

Sobre el autor: Robert C. Martin, conocido como “Uncle Bob”, es uno de los firmantes del Manifiesto Ágil y ha sido una figura clave en la difusión de las buenas prácticas en ingeniería del software durante décadas.


2. Code Complete

Autor: Steve McConnell

Una visión sistemática y global del desarrollo de software. Desde detalles de implementación hasta calidad y mantenimiento.

Es más denso que otros libros, pero muy recomendable.

Sobre el autor: McConnell es también autor de Rapid Development y fundador de Construx Software, una consultora centrada en mejorar procesos de desarrollo en empresas. Antes, trabajó en Microsoft y Boeing. Se le atribuye la afirmación de que, de media, cada 1000 líneas de código en producción tienen entre 15 y 50 errores.


3. Refactoring

Autor: Martin Fowler

El término refactoring se refiere a modificar un código sin cambiar su funcionalidad. El principio que guía el libro es que el código no se escribe una sola vez, sino que hay que mejorarlo constantemente.

Este libro enseña cómo transformar código existente sin cambiar su comportamiento, apoyándose en pequeñas mejoras seguras.

Sobre el autor: Martin Fowler es uno de los referentes en arquitectura de software y divulgación técnica, especialmente conocido por su trabajo en patrones de arquitectura empresarial. Fue uno de los firmantes del Manifiesto Ágil.


🏗️ Diseño y arquitectura del software

4. Design Patterns

Autores: Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides.

Este libro es un clásico imprescindible. A partir de su publicación en 1994 se han escrito cientos de libros y artículos tratando o desarrollando los patrones de diseño que se describen en el libro.

Introduce soluciones reutilizables a problemas de diseño en programación orientada a objetos. Aporta un vocabulario común para hablar de arquitectura. Después de leerlo, verás patrones de diseño por todos los lados, cada vez que trabajes en código.

Sobre los autores: conocidos como la “banda de los cuatro” (GoF), varios de ellos participaron en el desarrollo de herramientas clave como Eclipse, lo que demuestra la aplicación práctica de sus ideas.


5. Clean Architecture

Autor: Robert C. Martin

Explica cómo organizar sistemas complejos para que sean mantenibles y resistentes al cambio.

Ideal cuando se da el salto de programas pequeños a aplicaciones reales.

Sobre el autor: además de su faceta técnica, Martin ha sido un activo formador y conferenciante, contribuyendo a establecer estándares de calidad en el desarrollo profesional.


☕ Buenas prácticas aplicadas (Java)

6. Effective Java

Autor: Joshua Bloch

Aquí hago una excepción: un libro centrado en un lenguaje concreto.

Effective Java es una colección de recomendaciones prácticas sobre cómo usar Java correctamente. Pero su verdadero valor es que traduce principios generales (inmutabilidad, encapsulación, diseño de APIs…) a decisiones concretas de código.

Para quien aprende Java, es probablemente el libro más útil de esta lista en el corto plazo.

Sobre el autor: Joshua Bloch fue uno de los principales arquitectos de la biblioteca estándar de Java. Durante su etapa en Sun Microsystems, fue el diseñador del Java Collections Framework, lo que da a este libro un valor especialmente sólido.


🧪 Desarrollo guiado por pruebas y programación extrema

Muchas de las prácticas modernas de desarrollo tienen su origen en la Extreme Programming (XP), propuesta por Kent Beck a finales de los años 90. XP plantea llevar al extremo una idea sencilla: si algo es bueno en el desarrollo de software, hacerlo de forma continua.

En este contexto surge el desarrollo guiado por pruebas (Test Driven Development, TDD), una práctica en la que primero se escriben las pruebas y después el código que las satisface. Este enfoque no solo mejora la calidad del software, sino que también influye directamente en su diseño, favoreciendo soluciones más simples, modulares y fáciles de mantener.

Muchas de las ideas presentes en libros como Refactoring, Clean Code o Working Effectively with Legacy Code encajan de forma natural con esta filosofía: el código se construye y se mejora de manera iterativa, con feedback constante y con una fuerte atención a su calidad interna.

7. Test Driven Development: By Example

Autor: Kent Beck

Introduce una de las prácticas más influyentes del desarrollo moderno: escribir primero las pruebas y después el código.

Más allá de la técnica, muestra cómo TDD conduce a diseños más simples, código más modular y mayor confianza en los cambios.

Sobre el autor: Kent Beck es uno de los creadores de la programación extrema (XP, Extreme Programming) y uno de los firmantes del Manifiesto Ágil, con una enorme influencia en las prácticas modernas de desarrollo.

📎 Kent Beck desarrolló XP mientras trabajaba en el proyecto Chrysler Comprehensive Compensation System (C3), donde buscaba mejorar la calidad del software y la capacidad de adaptación al cambio.

El nombre “extreme” no es casual. La idea era llevar al extremo prácticas que ya se consideraban buenas:

  • Si las revisiones de código son buenas → revisar constantemente (pair programming).
  • Si probar es bueno → probar todo el tiempo (TDD).
  • Si integrar es bueno → integrar continuamente.
  • Si el diseño es importante → refactorizar continuamente.

XP se consolidó con el libro: Extreme Programming Explained (1999), también de Kent Beck. Este libro definió prácticas como:

  • Test-Driven Development.
  • Pair Programming.
  • Continuous Integration.
  • Small Releases.

🧠 Mentalidad y oficio del programador

8. The Pragmatic Programmer

Autores: Andrew Hunt y David Thomas

Este libro, más que técnicas, enseña actitud.

Responsabilidad, aprendizaje continuo, automatización… Un libro muy accesible y lleno de ideas prácticas.

Sobre los autores: Andrew Hunt y David Thomas también fueron firmantes del famoso Agile Manifesto en 2001. Fundaron la editorial Pragmatic Bookshelf, muy influyente en la literatura moderna de programación. También se les atribuye el acrónimo del principio de desarrollo DRY: Don’t Repeat Yourself.


9. Working Effectively with Legacy Code

Autor: Michael Feathers

Si bien los principios SOLID fueron formulados por Robert C. Martin a comienzos de los años 2000, el acrónimo SOLID fue popularizado posteriormente por Michael Feathers (2004).

Los principios SOLID son:

  • Single responsability principle.
  • Open-closed principle.
  • Liskov substitution principle.
  • Interface segregation principle.
  • Dependency inversion principle.

Este libro explica cómo enfrentarse a código existente (y a menudo problemático).

Especialmente útil en entornos reales, donde rara vez se empieza desde cero.

Sobre el autor: Feathers es conocido por su trabajo en metodologías ágiles y por acuñar definiciones muy utilizadas, como considerar “legacy code” aquel que no tiene tests.


10. The Mythical Man-Month

Autor: Frederick P. Brooks Jr.

Este libro es un clásico sobre la gestión de proyectos software.

Explica por qué los proyectos se retrasan y desmonta mitos muy extendidos (como que añadir más desarrolladores acelera el trabajo).

Sobre el autor: Frederick Brooks es conocido como el padre del IBM System/360, pues dirigió el desarrollo del sistema operativo OS/360 en IBM, una experiencia que dio origen a muchas de las reflexiones del libro.


🧩 Fundamentos: pensar como programador

11. Structure and Interpretation of Computer Programs

Autores: Harold Abelson y Gerald Jay Sussman

“The Wizard Book”, un libro exigente, pero muy formativo.

Enseña a razonar sobre programas y a entender profundamente qué significa programar. Hay una cita de este libro que suelo decir en clase y que me encanta:

Los programas deben escribirse para que las personas los lean y, solo de forma incidental, para que las máquinas los ejecuten.

Sobre los autores: ambos son profesores del MIT, y este libro ha sido durante décadas el núcleo de uno de los cursos más influyentes de programación del mundo.


12. Introduction to Algorithms

Autores: Thomas H. Cormen et al.

El estándar en algoritmia.

No trata directamente el estilo de código, pero sí proporciona rigor: eficiencia, complejidad y modelado de problemas.

Sobre los autores: el libro reúne a varios investigadores de primer nivel y es utilizado como manual en universidades de todo el mundo.


🚀 Más allá de los clásicos: referencias modernas

Los libros anteriores comparten una característica: muchos tienen décadas… y siguen siendo fundamentales. Esto se debe a que los principios básicos del buen software cambian muy poco.

Sin embargo, el contexto sí ha evolucionado. Hoy desarrollamos sistemas distribuidos, trabajamos en equipos grandes y desplegamos continuamente.

Para comprender ese entorno, pueden complementarse con algunas obras más recientes:

Designing Data-Intensive Applications, Martin Kleppmann

Sobre el autor: Kleppmann es investigador en sistemas distribuidos en la Universidad de Cambridge, lo que aporta una base académica muy sólida al libro.


A Philosophy of Software Design, John Ousterhout

Sobre el autor: Ousterhout es el creador del lenguaje Tcl y ha trabajado durante décadas en sistemas y diseño de software.


Software Engineering at Google, Google

Sobre los autores: el libro recoge prácticas reales de ingeniería dentro de Google, una de las organizaciones con mayor escala en desarrollo software.


Modern Software Engineering, David Farley

Sobre el autor: Farley es uno de los impulsores del concepto de Continuous Delivery, ampliamente adoptado en la industria actual.

🤔 ¿Por qué mezclar tantos enfoques?

Porque programar bien no es una sola cosa. Implica:

  • escribir código claro (Clean Code),
  • diseñar bien (Design Patterns, Clean Architecture),
  • tomar buenas decisiones (Effective Java),
  • y entender los fundamentos (algoritmos).

Cada uno de estos libros ataca una parte del problema. Si estás empezando o consolidando tu formación como programador, una posible ruta sería:

  1. Clean Code y The Pragmatic Programmer.
  2. Refactoring y Effective Java.
  3. Design Patterns y Clean Architecture.
  4. Fundamentos: algoritmos o SICP.
  5. Y, posteriormente, alguna referencia moderna.

Porque, en última instancia, la programación no consiste solo en hacer que un programa funcione, sino en construir software que pueda perdurar. Y eso, más que cualquier lenguaje o tecnología, es lo que define a un buen programador.

El separador de decimales en Java

El Locale es la configuración regional que le dice a un programa informático cómo mostrar y formatear datos según un idioma, país y cultura específicos.

El Locale afecta a la forma de mostrar diversos elementos:

  • Números: separador decimal, miles
  • Fechas: DD/MM/YYYY vs MM/DD/YYYY
  • Moneda: €1.234,56 vs $1,234.56
  • Mayúsculas/minúsculas: Ñ → ñ (turco: i → İ)
  • Ordenación: ñ después de n en español

Por ejemplo, según el Locale seleccionado, estas serían distintas formas de mostrar el mismo número 1234.56:

🇺🇸 USA (en_US): 1,234.56
🇪🇸 España (es_ES): 1.234,56
🇩🇪 Alemania (de_DE): 1.234,56
🇫🇷 Francia (fr_FR): 1 234,56
🇯🇵 Japón (ja_JP): 1,234.56

El Locale consta de un idioma, un país y, opcionalmente, una variante. Por ejemplo:

Locale = Idioma + País [+ Variante]
ej: es_ES   → español (España)
    en_US   → inglés (Estados Unidos)
    pt_BR   → portugués (Brasil)

Al arrancar el sistema operativo (Windows, Linux, Mac,…) se carga un Locale por defecto que es el que usarán nuestros programas Java, salvo que indiquemos otra cosa.

Puedes comprobar el Locale que tienes cargado en el ordenador mediante la siguiente instrucción:

System.out.println(Locale.getDefault());

En mi caso, la salida ha sido:

es_ES

que corresponde al idioma español de España.

El Locale afecta a la forma de leer o escribir números double, ya sea en el terminal o en ficheros de texto.

Dentro de nuestros programas, podemos establecer el Locale de la siguiente forma:

Locale.setDefault(Locale.UK); // Locale del Reino Unido
Locale.setDefault(Locale.US); // Locale de Estados Unidos
Locale.setDefault(new Locale("es", "ES"));

Imprimir números double

Si se utilizan los métodos print() o println(), los números double se imprimen utilizando el punto como separador de decimales, independientemente del Locale activo en el ordenador:

System.out.println(1234.56); // Imprime 1234.56

El comportamiento es diferente cuando se usan métodos printf(). Con la configuración en español, cuando imprimimos un número double utilizando la instrucción printf(), el separador de decimales será la coma, no el punto:

System.out.printf("%.2f%n", 1234.56); // Imprime 1234,56

Si queremos que, además del separador de decimales, se imprima el separador de miles, hay que añadir al especificador de formato una coma antes del punto decimal:

System.out.printf("%,.2f%n", 1234.56); // Imprime 1.234,56

Si queremos forzar el uso del punto como separador de decimales podemos hacer:

Locale.setDefault(Locale.UK); // Locale del Reino Unido
System.out.printf("%.2f%n", 1234.56); // Imprime 1234.56
System.out.printf("%,.2f%n", 1234.56); // Imprime 1,234.56

Este comportamiento al escribir es el mismo cuando se escribe en el terminal o cuando se escribe en ficheros de texto.

¿Y por qué los métodos print() y println() utilizan siempre el punto como separador de decimales, independientemente del Locale que haya activo? La razón es que, internamente, estos métodos convierten los números double a cadena de texto utilizando el método toString() de la clase Double y dicho método no utiliza ningún formato regional, siempre utiliza el punto decimal en la conversión.

Hay que tener claras dos cosas:

  • Internamente, los números double no se ven afectados de ninguna manera por el Locale del ordenador. La representación interna del número utiliza otro tipo de evaluación, el Locale solo afecta a la representación textual del número al hacer entradas o salidas desde el terminal o desde ficheros de texto.
  • Al codificar, al escribir los programas, tenemos que utilizar siempre el punto como separador de decimales.

Leer números double

Con el ordenador usando el Locale español, si leemos un número double desde el terminal utilizando el método nextDouble() de la clase Scanner, deberá estar escrito con la coma como separador de decimales. Si el número está escrito con el punto, el programa lanzará una excepción y se interrumpirá abruptamente.

Prueba el siguiente programa:

package principal;

import java.util.Locale;
import java.util.Scanner;

public class Main {
   public static void main(String[] args) {
      System.out.println(Locale.getDefault());
	
      Scanner sc = new Scanner(System.in);
      System.out.print("Teclee un número con decimales: ");
      double x = sc.nextDouble();
      sc.close();
   }
}

He ejecutado el código en mi ordenador, configurado con Locale español y he tecleado el número utilizando el punto como separador de decimales. El resultado ha sido el siguiente:

Observa que el programa lanza una excepción del tipo InputMismatchException y se interrumpe.

He repetido la ejecución pero, en esta ocasión, he tecleado el número utilizando la coma como separador de decimales:

Observa que ahora el programa ha funcionado correctamente.

La clase Scanner ofrece el método setLocale(), que permite elegir un determinado Locale en las lecturas que haga. Por ejemplo, en el código siguiente, el primer double se lee utilizando el Locale del Reino Unido y, el segundo, utilizando el Locale español:

Scanner sc = new Scanner(System.in);

sc.useLocale(Locale.UK);
double x = sc.nextDouble(); // Espera leer 3.14

sc.useLocale(new Locale("es", "ES"));
double y = sc.nextDouble(); // Espera leer 3,14

Leer cadenas de texto y convertirlas en double

Otra técnica que puede ser útil al leer números con Scanner es leer una línea de texto con nextLine() y luego convertir la cadena leída a double utilizando el método estático parseDouble() de la clase Double.

El método Double.parseDouble(), al igual que sucede con el método toString(), siempre trabaja con el punto como separador de decimales. El siguiente ejemplo, leería un número double que debería estar escrito con punto decimal:

double z = Double.parseDouble(sc.nextLine());

Imponer un Locale para todo el programa

En programas profesionales, si no tomamos precauciones, puede resultar que la ejecución sea diferente según el Locale del ordenador en el que se esté ejecutando el programa. Por tanto, es necesario tomar medidas de forma que la ejecución siempre sea la misma, independientemente del Locale que esté configurado en el ordenador en el que se ejecute el programa.

Podemos imponer al principio del programa que se utilice un Locale determinado. Esta solución afectará tanto a las entradas que hagamos a través de objetos Scanner como a las salidas que hagamos con métodos printf(). Por ejemplo, podríamos imponer el Locale del Reino Unido o el de Estados Unidos y forzar a que las entradas y salidas de números decimales haya que hacerlas usando el punto como separador de decimales. La instrucción sería una de las dos siguientes:

Locale.setDefault(Locale.UK);
Locale.setDefault(Locale.US);

Otra opción sería imponer el Locale español de España. Para seleccionar el Locale de España no disponemos de una constante predefinida como en los casos anteriores y la instrucción que habría que poner al principio del programa sería:

Locale.setDefault(new Locale("es", "ES"));

También es posible imponer el Locale pasando un parámetro a la Máquina Virtual de Java al ejecutar el programa. Si ejecutamos desde el terminal y queremos imponer el Locale de Estados Unidos, habría que hacer:

java -Duser.language=en -Duser.country=US principal.Main

En Eclipse, podemos configurar la ejecución del programa y pasarle los argumentos a la JVM, como se muestra en la figura:

Cualquiera que sea la opción que uses, en programas profesionales en los que se realicen cálculos científicos con números decimales, es necesario tomar las precauciones necesarias para que el programa se ejecute de manera correcta en cualquier entorno de ejecución.

Ver la carpeta bin y los archivos .class en Eclipse

Eclipse ofrece lo que llama perspectivas, que son disposiciones concretas de las ventanas y paneles del editor. Las perspectivas se seleccionan en el menú “Window”.

Cuando utilizamos Eclipse Standard Edition para editar nuestros proyectos Java, lo más frecuente es tener activada la perspectiva Java, que es la perspectiva por defecto de la edición estándar de Eclipse.

En esta perspectiva, el panel de la izquierda está ocupado por la vista llamada Explorador de paquetes. El explorador de paquetes no muestra ni la carpeta .bin, en la que solemos guardar los ficheros compilados, ni los ficheros .class, aunque los tuviéramos en otra carpeta.

La siguiente figura muestra la vista del Explorador de paquetes de Eclipse, con un proyecto abierto, donde se puede apreciar que la carpeta bin no se muestra:

Para ejecutar un programa Java, el procedimiento habitual es pichar con el botón derecho del ratón sobre el fichero .java que contiene el método main() y elegir la opción “Run as -> Java application”.

Esto puede llevar a los programadores nóveles a pensar que están ejecutando el fichero .java y eso es un error conceptual importante. Los ficheros .java no son ejecutables, primero hay que compilarlos a bytecode (.class) utilizando el compilador de Java y luego, lo que se ejecuta, es el método main() del fichero .class que lo contenga.

Vamos a explicar cómo activar la visualización de la carpeta .bin y de los ficheros .class. Para ello, hay que mostrar la vista Explorador de proyectos, que la podemos activar desde la opción de menú “Window -> Show View -> Project Explorer“. El resultado será el de la siguiente figura:

Por defecto, esta vista tampoco muestra la carpeta bin ni los ficheros .class. Tendremos que configurar la vista de manera adecuada. Hay que abrir el menú de la vista, que son los tres puntos que se muestran en la barra de herramientas de la vista y elegir la opción denominada “Filters and customization”, como se muestra en la siguiente figura:

Al acceder a esta opción del menú de la vista, se nos ofrece una lista de tipos de elementos para los que podemos activar o desactivar el filtro: si el filtro está activado, ese tipo de elementos no se mostrará en la vista.

Tenemos que asegurarnos de no tener activados los filtros correspondientes a*.class resources y Java output folders, como se muestra en la siguiente figura:

Con esto, ya podremos ver en el explorador de proyectos la carpeta bin y los ficheros .class. Es posible que tengas que refrescar la vista para que se hagan efectivas las opciones de visualización seleccionadas, utilizando la opción de menú “File -> Refresh” (F5).

La siguiente figura muestra el resultado con el proyecto usado en los ejemplos, donde se puede ver la carpeta bin y los ficheros .class dentro de ella.

Ahora puedes hacer la siguiente prueba: borra el fichero .class correspondiente a la clase Java que contiene el método main(). A continuación, vuelve a probar a ejecutar el fichero .java correspondiente. Podrás comprobar que no se puede ejecutar el programa.

Para regenerar el fichero compilado .class, haz cualquier modificación en el fichero fuente .java y vuelve a guardarlo en el disco. Verás que, cuando grabas el fichero .java modificado, Eclipse recompila el proyecto y vuelve a generar los .class. Cuando estamos trabajando con Eclipse, cada vez que modificamos un fichero .java, Eclipse recompila el proyecto.

De modo que, a partir de ahora, cada vez que ejecutes un programa Java, ten bien presente que los ficheros .java no son ejecutables y que es necesario compilar y ejecutar.

Codificación de ficheros fuente en Java

Al compilar ficheros .java, el compilador javac asume que los ficheros tienen una codificación por defecto. En los sistemas Linux o Mac, javac asume que los ficheros están codificados en UTF-8. En Windows, las versiones posteriores a Java 17 también asumen que los ficheros fuente están codificados en UTF-8, pero hasta la versión Java 17 inclusive, javac supone que los ficheros utilizan la codificación de Windows. En España, la codificación que utiliza Windows suele ser Windows-1252.

Esto es así, independientemente de la página de códigos que tengamos activa en el terminal.

Podemos indicar al compilador que utilice una codificación específica para los ficheros fuente .java, utilizando el parámetro -encoding:

javac -encoding UTF-8 *.java

Vamos a ver un ejemplo completo utilizando una variante de programa Hola Mundo, que utiliza caracteres especiales del español y servirá para entender el problema.

Vamos a compilar y ejecutar la siguiente clase Java:

public class Hola {
   public static void main(String[] args) {
      System.out.println("¡Hola, ¿qué tal?, ¡Vaya año llevamos!");
   }
}

Vamos a utilizar, en primer lugar, la siguiente configuración: Windows 10, Windows Terminal con la página de códigos activa en UTF-8, compilación y ejecución con Java 17 y ficheros .java codificados en UTF-8. La salida obtenida es la de la siguiente figura:

Se puede ver que no se reconocen los caracteres especiales del español. Lo que está pasando es que javac 17 asume que los ficheros fuente están codificados en Windows-1252. Y este comportamiento es independiente de la codificación que tengamos activa en el terminal.

Si compilamos con javac 17, utilizando el parámetro -encoding UTF-8, el fichero fuente se interpreta de manera adecuada:

Java proporciona un comando para poder ver el valor de las diferentes variables que usa:

java -XshowSettings:properties -version

La salida del comando anterior, en la configuración indicada, fue la siguiente:

Se puede observar que Java asume que los ficheros están codificados en Windows-1252.

Ahora vamos a utilizar la versión 21 de Java. En el terminal de Windows, vamos a dejar activa la página de código 850 y compilar y ejecutar el mismo programa. La salida es la siguiente:

Observa que ahora la interpretación de los caracteres especiales del español es correcta. Una vez más, independientemente de la codificación activa del terminal, que en este caso no era UTF-8.

Si consultamos las opciones que está utilizando Java 21:

Vemos que ahora Java 21 asume que los ficheros fuente están codificados en UTF-8.

Podemos hacer una última prueba: compilar con Java 21, pero diciéndole al compilador que el fichero Hola.java está codificado en Windows-1252:

Observa que la salida es la misma que obteníamos con Java 17, cuando interpretaba mal la codificación del fichero fuente.

Por tanto, al compilar ficheros fuente codificados en UTF-8, si utilizamos la versión Java 17 o una inferior, tendremos que utilizar el parámetro -encoding UTF-8 para obtener resultados correctos. Si usamos una versión de Java superior a la 17, ya se presupone que los ficheros fuente están codificados en UTF-8 y no será necesario utilizar el parámetro -encoding.

¿Y si estamos trabajando en Eclipse? Bueno, Eclipse proporciona una opción para indicar la codificación de los ficheros fuente y luego se encarga de pasarla al compilador o al entorno de ejecución. La opción la podemos configurar en: “Propiedades del proyecto -> Resource -> Text file encoding“, como se ve en la siguiente figura:

Programación en C

https://www.garceta.es/catalogo/libro.php?ISBN=978-84-1903-425-0&idd=12

Aprender a programar constituye hoy en día una de las competencias fundamentales en la formación de cualquier ingeniero. Incluso en un mundo en el que las herramientas de Inteligencia Artificial están cada vez más presentes y facilitan muchas tareas, comprender cómo funciona un programa, cómo se organiza un algoritmo y cómo se traduce una idea en código sigue siendo imprescindible. El ingeniero no puede limitarse a utilizar soluciones ya hechas: debe ser capaz de analizar un problema, diseñar una estrategia de resolución y llevarla a la práctica con precisión y rigor.

Este libro está destinado a los estudiantes universitarios que se enfrentan por primera vez al aprendizaje de la programación. No se requieren conocimientos previos: el texto parte desde los conceptos más básicos y avanza de forma gradual, apoyándose en una gran cantidad de ejemplos explicados con detalle. Sin embargo, el camino recorrido conduce hasta un nivel medio de programación, que permitirá al lector enfrentarse a problemas de cierta complejidad y sentar las bases para aprendizajes más avanzados.

La extensión y profundidad del libro hacen que difícilmente pueda agotarse en un único semestre. Más bien, debe entenderse como un manual de referencia que acompaña al estudiante durante toda su formación inicial: una obra a la que volver en distintos momentos para repasar conceptos, reforzar técnicas o explorar nuevas posibilidades del lenguaje C.

A lo largo de sus capítulos se incluyen cientos de ejemplos repartidos estratégicamente para ilustrar cada idea, junto con más de doscientos ejercicios propuestos que permiten practicar y afianzar los contenidos. Cada ejercicio va acompañado de su solución, de manera que el alumno pueda comprobar su progreso y, al mismo tiempo, aprender distintas formas de abordar un mismo problema.

Pero aprender a programar no consiste solo en lograr que un programa funcione. La programación es también un medio de comunicación entre personas: el código debe ser comprensible, legible y mantener una estructura clara. Por ello, este libro insiste de manera constante en las buenas prácticas de programación, en el estilo y en la importancia de escribir programas que puedan ser entendidos y mantenidos por otros.

La elección del lenguaje C como punto de partida responde a varias razones. Se trata de un lenguaje con una larga tradición en el mundo de la ingeniería y la informática, en el que se apoyan muchos otros lenguajes modernos. Aprender C significa adquirir un conocimiento profundo de los fundamentos de la programación, de la relación entre el código y la máquina, y de los principios que siguen vigentes en entornos actuales. C, con su sencillez y potencia, ofrece al principiante una oportunidad única de comprender de verdad cómo se construye un programa desde sus cimientos.

En suma, este libro no pretende únicamente enseñar a programar en C, sino formar en el arte de pensar de manera algorítmica, rigurosa y creativa. Su meta es que los estudiantes, además de aprobar un curso, desarrollen una herramienta intelectual que les será útil durante toda su vida profesional como ingenieros.

Espero que este libro te acompañe en la aventura de descubrir la programación y que en cada página encuentres, no solo respuestas, sino también nuevas preguntas que despierten tu curiosidad como futuro ingeniero.

Santiago Higuera de Frutos

Doctor Ingeniero de Caminos

Profesor en Teleco Campus Sur

GeoRust

Introducción

Desde el año 2015, de acuerdo con la encuesta anual que realiza Stack Overflow entre más de 80.000 programadores de todo el mundo, Rust ha resultado ser el lenguaje que más gustaba (The most loved language).

Rust se creó con el objetivo de obtener un lenguaje de prestaciones similares o superiores a las de C o C++, pero poniendo énfasis en la seguridad del código, aspecto en el que estos lenguajes han dado lugar a numerosos problemas.

Pero Rust no solo ofrece código seguro. Rust ofrece una documentación de alta calidad, permite la programación concurrente de manera eficiente y segura, permite programar en WebAssembly, ofrece un alto rendimiento en el procesamiento de grandes cantidades de datos y, además, dispone de un compilador muy efectivo y un entorno de desarrollo completo, con servicios para documentación de programas, servicios para test unitarios o de integración, servicios para control de versiones y mucho más.

Rust es un lenguaje de código abierto. Inicialmente se desarrolló al amparo de la empresa Mozilla. Desde 2021, el desarrollo está coordinado por la Fundación Rust, que es apoyada y financiada por las grandes empresas del software.

Rust no es un lenguaje orientado a objetos propiamente dicho, aunque se pueden emular muchas de las técnicas que se utilizan en ese paradigma de programación. La mayores influencias en el lenguaje Rust provienen de SML, OCaml, C++, Cyclone, Haskell y Erlang. La programación funcional sí casa mejor con la filosofía del lenguaje Rust, que sin ser un lenguaje funcional estricto, permite realizar una programación funcional eficiente. Puede ser una buena herramienta para pasarse a la programación funcional.

Desde su primera versión estable de enero de 2014, Rust se utiliza en los desarrollos de grandes empresas, como Amazon, Discord, Dropbox, Facebook (Meta), Google (Alphabet) y Microsoft. Actualmente, Rust se utiliza para desarrollar el nucleo de sistemas operativos como Linux, Windows y Android.

A pesar de que se trata de un lenguaje relativamente nuevo, dispone ya de una amplia librería de utilidades que facilitan la programación dentro de cualquier ámbito.

Por supuesto, todas estas ventajas que ofrece Rust son a costa de una curva de aprendizaje un poco más pendiente al principio. Programar en Rust requiere más esfuerzo que programar en otros lenguajes, como por ejemplo, Python. Pero los resultados que se obtienen compensan el esfuerzo dedicado.


GeoRust

Existe un ecosistema muy amplio de herramientas geoespaciales para trabajar con Rust. En este artículo se va a hacer una introducción al uso de la librería geo_types, aunque hay otras que servirán para ampliar conocimientos con posterioridad:

  • geo: utiliza los tipos de geo_types y añade un gran número de funciones para cálculos geoespaciales.
  • geojson: proporciona métodos para leer y escribir objetos Geo-JSON según la especificación IETF RFC 7946.
  • proj: proporciona servicios de cambio de coordenadas utilizando bindings a proj.
  • gdal: proporciona bindings para usar la librería GDAL, que permite procesar formatos geoespaciales raster y vectoriales.
  • Librerías para formatos específicos: geotiff, kml, netCDF, osm, shapefile, tilejson y algunas más.

Además se dispone de diversas librerías con utilidades para geocodificación y otras tareas de análisis geoespacial.

La totalidad de librerías se puede consultar en el siguiente enlace:

https://georust.org/


Crate geo_types

Esta librería proporciona estructuras para los tipos de geometría habituales. Se añade al proyecto con:

cargo add geo_types

Si se necesitan algoritmos geoespaciales, hay que usar la crate geo, que reexporta los tipos de datos de geo_type. Se realiza a continuación una introducción al uso de algunas de las primitivas fundamentales.

Coord

La estructura en la que se basan los demás tipos es geo::geometry::Coord, que es una pareja de coordenadas de algún tipo numérico, por defecto, f64.

pub struct Coord<T = f64> where
    T: CoordNum,{
    pub x: T,
    pub y: T,
}

El trait CoordNum proporciona operaciones sumar, restar, multiplicar, dividir y algunas otras, sobre objetos del tipo Coord. Este trait lo implementan los números float y los enteros. Em los algoritmos que solo tienen sentido con floats, como el cálculo de un área, hay que usar tipos que implementen CoordFloat.

También hay una macro, coord!{}, que permite crear instancias de Coord. Se puede acceder a los campos x o y de manera individual y también hay un método x_y()que devuelve una tupla con las coordenadas (x, y):

use geo_types::coord;

let c = coord! {
    x: 40.02f64,
    y: 116.34,
};
assert_eq!(c.x, 40.02);
assert_eq!(c.y, 116.34);
let (x, y) = c.x_y();
assert_eq!(y, 116.34);
assert_eq!(x, 40.02f64);

Coord no es una primitiva geoespacial, pero forma parte de todas las primitivas.


Point

La estructura Point se utiliza para representar un punto en dos dimensiones.

pub struct Point<T = f64>(pub Coord<T>)
where
    T: CoordNum;

Se pueden crear instancias de Point utilizando el método Point::new(), la macro point!, o, utilizando from(), a partir de una tupla, de una instancia de Coord o de un array:

use geo_types::{coord, point, Point};

let p0: Point = Point::new(3.14, 6.28);
let p1 = point! { x: 1.5, y: 0.5 };
let p2: Point = (0., 1.).into();
let p3: Point = [1.0, 2.0].into();
let c = coord! { x: 10., y: 20. };
let p4: Point = c.into();

El tipo Point es una estructura no etiquetada cuyo único campo es una instancia de Coord. Se puede acceder a x e y utilizando los métodos x(), y() o el método x_y(), que devuelve una tupla:

use geo_types::{Point};

let p: Point = Point::new(3.14, 6.28);
assert_eq!(p.x(), 3.14);
assert_eq!(p.y(), 6.28);
assert_eq!(p.x_y(), (3.14, 6.28));

Line

La estructura Line representa un segmento con dos Coord:

pub struct Line<T = f64>where
    T: CoordNum,{
    pub start: Coord<T>,
    pub end: Coord<T>,
}

Se pueden crear instancias de Line utilizando el método new() y a partir de un array de tuplas:

use geo_types::{Coord, Line};

let line_1: Line = Line::new(
    Coord{x:3.14, y:6.28}, 
    Coord{x:10., y: 6.}
);
let line_2: Line = [(3.14, 6.28), (10., 6.)].into();

assert_eq!(line_2.start_point().x(), 3.14);

Dispone de unos métodos start_point() y end_point() que devuelven los puntos inicial y final. También hay un método points() que devuelve una tupla con los dos puntos. Hay métodos dx() y dy() que devuelven el incremento en cada dirección. El método delta() devuelve una tupla con ambos incrementos. El método slope() devuelve la pendiente del segmento.


LineString

Es una colección ordenada de dos o mas Coord que representa la trayectoria entre dos puntos.

pub struct LineString<T: CoordNum = f64>(pub Vec<Coord<T>>);

Una LineString puede ser cerrada si no tiene puntos o si el primer y último puntos son el mismo.

Se puede crear una LineString llamando directamente a la función new(), utilizando la macro line_string!, convirtiendo un vector de tuplas de Coord o un vector de arrays de Coord:

use geotypes::{coord, LineString, linestring};

let line_string = LineString::new(vec![
   coord! { x: 0., y: 0. },
   coord! { x: 10., y: 0. },
]);
let line_string = line_string![
   (x: 0., y: 0.),
   (x: 10., y: 0.),
];
let line_string: LineString = vec![(0., 0.), (10., 0.)].into();
let line_string: LineString = vec![[0., 0.], [10., 0.]].into();

También se pueden obtener un LineString a partir de iteradores de Coord:

let mut coords_iter = vec![
    coord! { x: 0., y: 0. }, 
    coord! { x: 10., y: 0. }].into_iter();
let line_string: LineString<f32> = coords_iter.collect();

LineString ofrece cinco iteradores: coords, coords_mut, points, lines y triangles.

use geo_types::{coord, LineString};

let line_string = LineString::new(vec![
    coord! { x: 0., y: 0. },
    coord! { x: 10., y: 0. }
]);
linestring.coords().for_each(|coord| println!("{:?}", coord));

for point in line_string.points() {
   println!("Point x = {}, y = {}", point.x(), point.y());
}

Si en un bucle se utiliza directamente el iterador procedente del trait IntoIterator, hay que cuidar la propiedad del LineString:

for coord in &line_string {
   println!("Coordinate x = {}, y = {}", coord.x, coord.y);
}
for coord in line_string {
   println!("Coordinate x = {}, y = {}", coord.x, coord.y);
}

A partir de un LineString se puede obtener directamente un vector de Coord o un vector de Point:

use geo_types::{coord, LineString, Point};

let line_string = LineString::new(vec![ coord! { x: 0., y: 0. }, coord! { x: 10., y: 0. }, ]); let coordinate_vec = line_string.clone().into_inner(); point_vec = line_string.clone().into_points();


Polygon

Es un área bidimensional. La frontera exterior es una LineString. Puede tener huecos, que estarán definidos por otras LineStrings.

Se puede consultar en:

Polygon in geo_types::geometry – Rust

Geometry

Es un enum representando todos los tipos de geometrías:

pub enum Geometry<T: CoordNum = f64> {
    Point(Point<T>),
    Line(Line<T>),
    LineString(LineString<T>),
    Polygon(Polygon<T>),
    MultiPoint(MultiPoint<T>),
    MultiLineString(MultiLineString<T>),
    MultiPolygon(MultiPolygon<T>),
    GeometryCollection(GeometryCollection<T>),
    Rect(Rect<T>),
    Triangle(Triangle<T>),
}

Todas las primitivas geométricas se pueden convertir en un Geometry utilizando Into::into(). De manera similar, se puede utilizar TryFrom::try_from()para obtener la primitiva a partir de un Geometry:

use std::convert::TryFrom;
use geo_types::{Point, point, Geometry};

let p = point!(x: 1.0, y: 1.0);
let pe: Geometry = p.into();
let pn = Point::try_from(pe).unwrap();

JSON

Como ejemplo, se va a mostrar cómo pasar una Line a JSON:

fn test_json() -> serdejson::error::Result<()>  {
   use geotypes::{Coord, Line};
   use serdejson;

   let line: Line = [(3.14, 6.28), (10., 6.)].into();
   let cad = serde_json::to_string(&line)?;
   println!("{}", cad);
   let value: serde_json::Value = serde_json::from_str(cad.as_str())?;
   assert!(value.is_object());
   Ok(())
}

(Santiago Higuera de Frutos – Enero 2024)