Día del programador

Texto sobre fondo azul: «Es el día del programador. ¡Felicítame! Probablemente lo pasaré trabajando», con una cara triste.

«IIII IIII» día del programador

El Día del Programador se celebra el día 256 del año, el 13 de septiembre, porque 256 es el número de valores que caben en un byte. Y, como dice la imagen, lo normal es pasarlo trabajando.

Diseño

Texto sobre fondo oscuro: «Sin requerimientos o sin diseño, la programación es el arte de agregar errores a un archivo de texto vacío», cita de Louis Srygley.

«Sin requerimientos o sin diseño la programación es el arte de agregar errores a un archivo de texto vacío»

Empezar a programar sin saber bien qué hay que construir es la forma más rápida de escribir código que habrá que tirar. Dedicar un rato a entender el problema ahorra días de correcciones.

Talento

Texto sobre fondo oscuro: «Para ser un buen programador se requiere 3% de talento y 97% de esfuerzo para no distraerse con Internet».

«Para ser un buen programador, se requiere 3% de talento y 97% de esfuerzo para no distraerse con Internet»

El talento ayuda, pero lo que marca la diferencia es la constancia. Y la mayor amenaza para la constancia está a una pestaña de distancia.

Complejidad

Texto sobre fondo claro: «Controlar la complejidad es la esencia de la programación», cita de Brian Kernighan.

Brian Kernighan, coautor del libro El lenguaje de programación C, resume en una frase buena parte del oficio: dividir los problemas, ponerles nombres claros y evitar que todo dependa de todo.

Quiero cambiar al mundo…

Texto sobre fondo azul claro: «Quiero cambiar al mundo, pero no me dan el código fuente».

Si el mundo fuera un programa, bastaría con tener el código fuente para arreglarlo. Es también un guiño al software libre, cuya idea central es justamente poder leer y modificar el código.

Pregunta Java: el orden de un HashSet

Piensa la respuesta antes de abrirla. Todas las opciones parecen razonables, y casi siempre hay un detalle que lo cambia todo.

Pregunta Java · Collections

¿Cuál es el resultado de ejecutar el siguiente código?

import java.util.HashSet;
import java.util.Set;

public class Test {
    static void imprime(Set<? extends String> set) {
        for (String elemento : set) {
            System.out.format("%s ", elemento);
        }
    }
    public static void main(String[] args) {
        Set<String> set = new HashSet<String>();
        set.add("A");
        set.add("J");
        set.add("A");
        set.add("X");
        imprime(set);
    }
}
  1. imprime "A J A X"
  2. imprime "A J X"
  3. La salida no puede ser determinada
Ver la respuesta

Respuesta: 3.

java TestA X J

Un HashSet no guarda duplicados, así que la segunda "A" se descarta y quedan tres elementos. Pero no garantiza ningún orden: los coloca según su hashCode, y en nuestra ejecución salió A X J, que no es el orden en que se añadieron ni el alfabético. La documentación ni siquiera garantiza que ese orden se mantenga a lo largo del tiempo.

Si necesitas que salgan en el orden en que se insertaron, usa LinkedHashSet; si los quieres ordenados, TreeSet.

Pregunta publicada en 2013 y revisada en octubre de 2026. La respuesta se comprobó compilando y ejecutando el código con Java 25 LTS en un contenedor limpio; las salidas están copiadas de esa ejecución.

Algunos de los Errores Más Críticos en el Desarrollo de Software

1–Neutralización inadecuada de elementos especiales usados en un comando SQL (SQL Injection)

La inyección SQL es un método de infiltración de código intruso que se vale de una vulnerabilidad informática presente en una aplicación en el nivel de validación de las entradas para realizar consultas a una base de datos.
El origen de la vulnerabilidad radica en el incorrecto chequeo y/o filtrado de las variables utilizadas en un programa que contiene, o bien genera, código SQL. Es, de hecho, un error de una clase más general de vulnerabilidades que puede ocurrir en cualquier lenguaje de programación o script que esté embebido dentro de otro.



2.-Neutralización inadecuada de elementos especiales usados en un comando OS (‘OS Command Injection’)

Un ataque de inyección de comandos en el sistema operativo se produce cuando un atacante intenta ejecutar comandos de sistema a través de una aplicación vulnerable. Las aplicaciones se consideran vulnerables al ataque de inyección de comandos si se utilizan las entradas del usuario y se ejecutan directamente.

3.- Copia de buffer sin comprobar el tamaño de la entrada. (Clásico ‘Buffer Overflow’)

Es un error de software que se produce cuando un programa no controla adecuadamente la cantidad de datos que se copian sobre un área de memoria reservada a tal efecto (buffer), de forma que si dicha cantidad es superior a la capacidad preasignada los bytes sobrantes se almacenan en zonas de memoria adyacentes, sobrescribiendo su contenido original.

4.-Neutralización inadecuada de datos entrada durante la generación de Web Pages (‘Cross-site Scripting’)

XSS, del inglés Cross-site scripting es un tipo de inseguridad informática o agujero de seguridad típico en las aplicaciones Web, que permite a una tercera parte inyectar páginas web o contenido ajeno, para ser visto por el usuario usando código JavaScript u otro lenguaje script similar (ej: VBScript), evitando medidas de control como la Política del mismo origen. Este tipo de vulnerabilidad se conoce en español con el nombre de Secuencias de comandos en sitios cruzados.

5.- Falta de Autenticación en Funciones Críticas.

La falta de autenticación es común en los sistemas que por falta de tiempo para su desarrollo o descuido son dejados al final.  Los programadores se enfocan en crear los módulos críticos del sistema que son los mínimos necesarios para operar y dejan funciones de autenticación para el final.

6.- Falta de Autorización.

Este problema no es igual al del punto 5, aquí se trata de restringir a los usuarios de un sistema a ciertas funciones dentro de una aplicación.   No todos lo usuarios deben tener los mismos privilegios y la autenticación no es suficiente.  Por ejemplo, si tienes una función para crear o dar de alta a usuarios nuevos, esta función debe quedar restringida para perfiles con privilegios de administrador.

7.- Uso de credenciales “Hard-code”

Crear una contraseña oculta en el código para poder acceder sin restricciones es muy mala idea, una vez que esta contraseña se sabe la violación de seguridad es permanente.

8.- La falta de cifrado en datos confidenciales.

Siempre que se provee de autenticación a usuarios es necesario almacenar de alguna forma un username y una contraseña.  Almacenar la contraseña o cualquier otro dato crítico, sin ningún tipo de cifrado, permite que un atacante que haya podido vulnerar el acceso a una base de datos o cualquier otro medio de almacenamiento que use tu sistema, tome también los datos de acceso de los usuarios y use otras funciones críticas de la aplicación de forma directa.

9.- Carga no restringida de archivos potencialmente peligrosos.

Algunas veces se provee a los usuarios de funciones que permitan cargar imágenes, documentos o cualquier clase de archivo que la solución lo requiera.  Se debe tener cuidado en filtrar todos aquellos archivos que puedan ser ejecutados tanto por el sistema operativo como por el contenedor web, o bien simplemente utilizados con fines distintos a los que se ha diseñado la solución.

10.- Depender de Entradas que no son confiables para decisiones críticas.

No se debería por ejemplo, permitir a usuarios ejecutar funciones críticas o la descarga de contenido no apropiado basándose en los datos que proporciona, como su edad o algún otro dato que pueda ser fácilmente falseable.  A veces es necesario proveer a las aplicaciones de una serie de candados como números de seguridad, identificaciones oficiales, captchas, etc.

12 buenas prácticas en Java, revisadas para Java 25

Esta lista circula desde hace más de diez años. La mayoría de los consejos siguen siendo buenos, pero algunos han envejecido y un par de ejemplos originales ni siquiera compilaban. Los repasamos uno a uno con Java 25, ejecutando todo lo que se puede ejecutar.

1. Inicialización perezosa, solo cuando cuesta

El consejo original era no crear objetos hasta que hagan falta:

class Paises {
    private List<String> paises;
    List<String> getPaises() {
        if (paises == null) paises = new ArrayList<>();
        return paises;
    }
}

Crear una lista vacía cuesta muy poco en una máquina virtual moderna, y este patrón tiene un problema que no se ve: no es seguro con varios hilos. Si dos hilos llaman a la vez a getPaises(), los dos pueden ver null y crear cada uno su lista. Lanzamos ocho hilos contra el mismo objeto, varias veces:

java Perezosa.javados hilos recibieron listas distintas en el intento 1

En tres ejecuciones pasó en los intentos 1, 178 y 1: depende del momento, que es lo peor que puede tener un error. Lo sensato hoy es inicializar en la declaración (private final List<String> paises = new ArrayList<>();) y reservar la inicialización perezosa para objetos realmente caros, como una conexión o un archivo grande, y con el cuidado que requiera.

2. Nada de campos públicos, y cuidado con lo que devuelves

Un arreglo público lo puede cambiar cualquiera. Pero hacerlo privado y devolverlo con un get no basta: quien lo recibe tiene el mismo arreglo y puede modificarlo igual.

private static final String[] DIAS = {"Domingo", "Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado"};
private static final List<String> LISTA = List.of(DIAS);

static String[] diasArreglo() { return DIAS; }
static String[] diasCopia()   { return DIAS.clone(); }
static List<String> dias()    { return LISTA; }
Calendario.diasArreglo()[0] = "¿?";
IO.println(Calendario.diasCopia()[0]);
salida¿?
List.of no deja cambiarla: UnsupportedOperationException

Alguien cambió el primer día desde fuera, y el cambio se ve en la copia que pide después: el original quedó estropeado para todos. Devolver clone() protege, pero copia el arreglo en cada llamada. Hoy lo más limpio es devolver una List.of, que no se puede modificar y no hay que copiar.

El ejemplo original tenía otro error, de los que no detecta el compilador: la lista de días de la versión «buena» decía "Jueves", "Sabado", "Domingo". Le faltaba el viernes y repetía el domingo.

3. Minimiza la mutabilidad

El original proponía un constructor privado con un método valueOf para hacer la clase inmutable. Eso controla cómo se crean los objetos, pero no impide cambiarlos:

public class Empleado {
    private String nombre;
    private Empleado(String nombre) { this.nombre = nombre; }
    public static Empleado valueOf(String nombre) { return new Empleado(nombre); }
    public String getNombre() { return nombre; }
    public void setNombre(String nombre) { this.nombre = nombre; }

    public static void main(String[] args) {
        Empleado e = Empleado.valueOf("Ana");
        e.setNombre("Luis");
        System.out.println(e.getNombre());
    }
}
java EmpleadoLuis

Para que sea inmutable, los campos tienen que ser final y no puede haber ningún método que los cambie. Desde Java 16 el record hace todo eso de una vez. Si tiene una lista, hay que copiarla, porque el record solo impide cambiar la referencia, no el contenido:

record Empleado(String nombre, List<String> proyectos) {
    Empleado {
        Objects.requireNonNull(nombre);
        proyectos = List.copyOf(proyectos);
    }
}
java Registro.javaEmpleado[nombre=Ana, proyectos=[web]]
no se puede: UnsupportedOperationException
true

Añadir "móvil" a la lista original después de crear el empleado no le afecta, y su lista no se puede tocar. El original también decía que los objetos inmutables «golpean el rendimiento». Crear objetos pequeños es barato, y en la práctica se ganan otras cosas: se pueden compartir entre hilos sin sincronizar y usar como claves de un mapa. Lo explicamos en qué es un objeto inmutable.

4. Interfaces antes que clases abstractas

Sigue siendo buen consejo: una clase puede implementar varias interfaces, pero solo heredar de una clase. Lo que ha cambiado es la objeción del original, que decía que añadir un método a una interfaz rompe todas las clases que la implementan. Desde Java 8 existen los métodos default, que traen su implementación, así que se pueden añadir sin romper nada. Lo vemos en el curso: herencia e interfaces.

5. Variables locales con el alcance más pequeño

Declara cada variable justo donde se usa por primera vez, y a ser posible inicializada en la misma línea. El original añadía «si no es posible, asígnale null», y eso es mal consejo: el compilador de Java ya avisa si usas una variable local sin haberle dado valor, y un null de relleno desactiva ese aviso y lo cambia por un NullPointerException en tiempo de ejecución.

6. Usa la biblioteca estándar

No reinventes lo que ya está probado. El original recomendaba «Google Collections», que hace años pasó a llamarse Guava. Además, buena parte de lo que se usaba de ella ya está en el JDK: List.of, Map.of, Optional, los streams, Objects.requireNonNull. Antes de añadir una dependencia, merece la pena mirar si no lo hace ya el propio Java.

7. Tipos primitivos antes que wrappers, y nunca == entre ellos

Integer a = 127, b = 127;
Integer c = 128, d = 128;
IO.println("127 == 127 -> " + (a == b));
IO.println("128 == 128 -> " + (c == d));
IO.println("128 equals 128 -> " + c.equals(d));

Boolean bandera = null;
try {
    if (bandera == true) IO.println("verdadero");
} catch (NullPointerException e) {
    IO.println(e.getMessage());
}
IO.println("Boolean.TRUE.equals(bandera) -> " + Boolean.TRUE.equals(bandera));
javac -g Wrappers.java && java Wrappers127 == 127 -> true
128 == 128 -> false
128 equals 128 -> true
Cannot invoke "java.lang.Boolean.booleanValue()" because "bandera" is null
Boolean.TRUE.equals(bandera) -> false

Esto es más traicionero de lo que contaba el original. Integer guarda una caché de los valores entre -128 y 127, así que == funciona con números pequeños y falla con los grandes: el error no sale en las pruebas con datos de ejemplo y aparece en producción. Con Integer, siempre equals.

El Boolean a null lanza la excepción porque, para compararlo con true, Java tiene que convertirlo a boolean, y null no tiene conversión. Boolean.TRUE.equals(bandera) es la forma segura.

El ejemplo original usaba new Integer(10). Ese constructor está obsoleto desde Java 9 y el compilador lo avisa. Se usa Integer.valueOf(10), o directamente Integer x = 10;.

8. Las cadenas, con cuidado

Dos cosas. new String("texto") crea un objeto innecesario: escribe directamente "texto". Y no acumules cadenas con += dentro de un bucle, porque el coste crece con el cuadrado del número de vueltas. Lo medimos en concatenar cadenas en un bucle.

9. Devuelve colecciones vacías, no null

List<String> buscarNull(String texto) { return texto.isEmpty() ? null : List.of(texto); }
List<String> buscar(String texto)     { return texto.isEmpty() ? List.of() : List.of(texto); }
java Vacias.javavacía: 0
null: NullPointerException

Con una lista vacía, el for simplemente no da ninguna vuelta. Con null, el for lanza la excepción, y cada uno de los que llamen al método tiene que acordarse de comprobarlo. Devolver List.of() no cuesta prácticamente nada, así que no hay excusa de rendimiento.

10. Las copias defensivas te salvan

El ejemplo clásico es una clase que guarda una fecha que le pasan:

class Estudiante {
    private final Date nacimiento;
    Estudiante(Date nacimiento) { this.nacimiento = nacimiento; }
    Date getNacimiento() { return nacimiento; }
}

class EstudianteCopia {
    private final Date nacimiento;
    EstudianteCopia(Date nacimiento) { this.nacimiento = new Date(nacimiento.getTime()); }
    Date getNacimiento() { return new Date(nacimiento.getTime()); }
}
Date fecha = new Date(100, 4, 17);
var e = new Estudiante(fecha);
var c = new EstudianteCopia(fecha);
fecha.setYear(2019);
salidasin copia: 3919
con copia: 2000
LocalDate: 2000-05-17 / 2019-05-17

La versión sin copia ve el cambio, porque guarda el mismo objeto que tiene quien la llamó. Y el año sale 3919, no 2019: setYear cuenta los años desde 1900. Es uno de los motivos por los que Date está obsoleta casi entera.

La copia del original, new Date(fechaNacimiento), no compila, porque Date no tiene ese constructor:

javac DateMal.javaDateMal.java:5: error: no suitable constructor found for Date(Date)
        Date copia = new Date(original);
                     ^
    constructor Date.Date(long) is not applicable
      (argument mismatch; Date cannot be converted to long)
    constructor Date.Date(String) is not applicable
      (argument mismatch; Date cannot be converted to String)

Lo correcto es new Date(fecha.getTime()), en el constructor y en el get. Aunque la solución de verdad es no usar Date: LocalDate, de java.time, es inmutable, así que no hace falta copiarla. withYear devuelve una fecha nueva y deja la original como estaba (última línea de la salida).

11. Que no se escape una excepción de un finally

El original solo daba el título. Esto es lo que pasa:

int dividir(int a, int b) {
    try {
        return a / b;
    } finally {
        throw new IllegalStateException("fallo al cerrar");
    }
}

int conReturn(int a, int b) {
    try {
        return a / b;
    } finally {
        return -1;
    }
}
java Finally.javaexcepción que llega: java.lang.IllegalStateException: fallo al cerrar
con return en finally: -1
try-with-resources: java.lang.ArithmeticException: / by zero
  suprimida: java.lang.IllegalStateException: fallo al cerrar

Las dos divisiones son entre cero, pero esa excepción desaparece. En la primera, la del finally la sustituye y nadie llega a saber que hubo una división entre cero. En la segunda, el return del finally se la traga y el método devuelve -1 como si todo hubiera ido bien.

try con recursos lo resuelve: si al cerrar el recurso también falla algo, la excepción original es la que llega, y la del cierre queda adjunta como «suprimida», sin perderse. Es la forma de cerrar archivos y conexiones desde Java 7.

12. No lances Exception a secas

Tampoco tenía explicación. throw new Exception("algo falló") obliga a quien te llama a capturar Exception, que es tanto como capturarlo todo: también los errores de programación que deberían verse. Lanza la excepción más concreta que exista (IllegalArgumentException si te pasan un dato inválido, IllegalStateException si el objeto no está listo, IOException si falla la entrada o salida) o crea la tuya. Lo vemos en el curso: excepciones y Optional.

Basado en 10 Most Useful Java Best Practice Quotes, de Viral Patel.

Entrada publicada en 2013 y revisada en octubre de 2026. Todo el código se compiló y se ejecutó con Java 25 LTS en un contenedor limpio; las salidas están copiadas de esa ejecución.