«En mi máquina funciona» es una de las excusas más famosas de la programación. Contenedores como Docker nacieron, en buena parte, para que el entorno del desarrollador y el del servidor sean el mismo y esta frase deje de tener sentido.
Java
10 consejos para programadores que empiezan, explicados uno a uno
En 2013 publicamos aquí diez consejos para quien empieza a programar, en una lista de una línea cada uno. Más de diez años después siguen valiendo, pero merecen una explicación: qué quieren decir en la práctica y qué ha cambiado ahora que un asistente de IA puede escribir buena parte del código.
Los diez consejos
1. Recuerda siempre lo que te enseñaron tus maestros. Los fundamentos envejecen mucho más despacio que las herramientas. Estructuras de datos, complejidad de los algoritmos, cómo funciona la memoria, qué es una transacción en una base de datos: todo eso servía hace treinta años y seguirá sirviendo dentro de otros treinta. Un framework de moda dura unos pocos años; saber por qué una búsqueda en una lista es más lenta que en un diccionario vale para siempre.
2. No emplees nuevas tecnologías solo porque son nuevas. Cada herramienta nueva trae su curva de aprendizaje, sus errores aún sin descubrir y el riesgo de que deje de mantenerse. En un proyecto real, elige lo que el equipo conoce y lo que tiene una comunidad grande detrás, salvo que la novedad resuelva un problema que de verdad tienes.
3. Pero sí debes probar nuevas técnicas y tecnologías. No es una contradicción con el anterior: el sitio para probar es un proyecto personal o una prueba aparte, no la aplicación de producción. Aprender un lenguaje con otra forma de pensar, como Rust si vienes de Python o Go si vienes de Java, te hace mejor programador también en tu lenguaje de siempre.
4. Lee mucho. Libros, documentación oficial y, sobre todo, código de otros. Leer el código de un proyecto de código abierto bien mantenido enseña más sobre cómo se organiza un programa grande que cualquier tutorial. Y la documentación oficial suele ser mejor y estar más al día que la primera respuesta que aparece en un buscador.
5. No acumules errores ni malas prácticas. Cada atajo que dejas «para luego» es una deuda que se paga con intereses: un nombre de variable confuso, una función de doscientas líneas, un error que se ignora en lugar de tratarse. Arreglarlo cuando lo ves cuesta minutos; arreglarlo meses después, cuando hay código que depende de ello, cuesta días.
6. Comparte tu conocimiento. Explicar algo a otra persona es la mejor forma de descubrir si lo entiendes. Escribir un artículo, responder una pregunta en un foro, documentar bien tu proyecto o contribuir a uno de código abierto te obliga a ordenar lo que sabes, y de paso te da visibilidad.
7. Ten en cuenta la concurrencia. Casi todos los programas actuales hacen varias cosas a la vez: atienden a varios usuarios, esperan a la red o reparten el trabajo entre los núcleos del procesador. Los errores de concurrencia son de los más difíciles de encontrar, porque aparecen solo a veces. Aprende pronto qué es una condición de carrera y cómo se evitan. Tenemos ejemplos en Python, Go y Rust, y uno muy claro en String, StringBuffer y StringBuilder, donde dos hilos pierden datos al escribir a la vez.
8. Prueba tu código. No basta con que funcione la vez que lo ejecutas a mano. Las pruebas automáticas te avisan cuando un cambio rompe algo que antes funcionaba, y te dan confianza para mejorar el código sin miedo. Empieza por lo sencillo: una prueba por cada función importante y otra por cada error que corrijas, para que no vuelva.
9. Cuídate. Programar es un trabajo mental y se hace mal cansado. Dormir, moverse, descansar la vista y desconectar de vez en cuando no es tiempo perdido: muchos problemas que parecían imposibles a medianoche se resuelven en diez minutos a la mañana siguiente.
10. Diviértete. La programación tiene mucho de oficio, y los oficios se aprenden con horas de práctica. Esas horas son mucho más llevaderas si disfrutas con lo que haces. Busca proyectos que te interesen de verdad, aunque sean pequeños: un juego, una herramienta para tu día a día o un programa que automatice algo aburrido.
Por dónde empezar
Si estás dando tus primeros pasos, elige un lenguaje y quédate con él el tiempo suficiente para hacer algo útil. En decodigo.com tienes cursos completos y gratuitos de Python, Java, Go y Rust, y una guía de Git para guardar tu trabajo desde el primer día.
Los gurús y el trabajo en equipo
Sin duda, siempre existen personas brillantes que se distinguen por su buen sentido común, curiosidad y capacidad para resolver problemas difíciles. Pero siempre he sospechado que la imagen del gurú está sobrevalorada.
Algunas malas experiencias me dicen que esto podría ser así. En cierta ocasión, durante el desarrollo de un proyecto mas o menos grande y en una demostración de capacidades, el líder del equipo, que se sabía muy bueno en el desarrollo de software (el gurú), comenzó a utilizar una serie de tecnologías y frameworks de moda para impresionar al cliente y al administrador de proyecto. Como resultado, los que tenían menos experiencia comenzaron a sufrir con la curva de aprendizaje y se enfocaron mas a conocer las nuevas tecnologías que al modelo de negocio. El desarrollo se terminó con una aplicación robusta tecnológicamente, pero poco amigable para el cliente y por supuesto en mas tiempo del que se esperaba. Un pequeño desastre.
En otras ocasiones, me he topado con el típico personaje que es capaz de resolver problemas difíciles, pero que cuando tienes que dar mantenimiento a su código o simplemente necesitas usarlo, este te parece tan desordenado que te dan ganas de hacerlo de nuevo.
Otro de los riesgos de tener a una persona que tiene todo el dominio sobre cierta tecnología, herramienta o el conocimiento mas crítico de un proyecto, es que puede simplemente irse, con el todo su conocimiento.
Me agradan mas los equipos de trabajo donde los problemas difíciles o algunas decisiones tecnológicas se resuelven entre varios.
Los gurús se equivocan como cualquier otro.
Además, cuando te autoproclamas gurú, puedes quedarte solo a la hora de resolver problemas difíciles. Creo que no hay necesidad de que esto sea así, porque el criterio para resolver un problema crítico se reduce al de una sola persona y no creo que esto sea muy conveniente.
Cuando alguien te hace una pregunta complicada sobre algún problema o error, es probable que ella misma tenga mas capacidad para responderla, ya que conoce mejor el contexto del problema. Por lo que mas que tratar de dar una solución es mejor dar un punto de vista o pistas sobre como resolver el problema.
El protagonismo es algo que nos puede seducir a todos, pero yo me lo pensaría mucho antes de adoptar una postura de este tipo. Sobre todo cuando de lo que se trata es hacer mas agradable y profesional tu trabajo. Desde luego no estoy diciendo que todos los gurús son malos, de hecho también he conocido a algunos muy buenos. Pero son los menos.
Un hábito que recomiendo a todos es que cuando encuentras la solución a un problema difícil, la comentes a tus demás compañeros. Esto permite tener una base de conocimiento colectiva y evita tener que investigar demasiado sobre un problema que ya se ha sido resuelto con anterioridad.
Tómate con calma eso de ser gurú, puedes serlo sin hacer alarde. 🙂
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.
También te puede interesar
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.
"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.
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.
Debes programar…

Por si las dudas…
Una forma exagerada de decir algo muy serio: escribe código pensando en quien lo leerá después. Muchas veces esa persona eres tú mismo, seis meses más tarde.