Programación en Java
Convenciones de código en Java: nombres, estructura, Javadoc y reglas automatizables
Principiante11 min de lecturaActualizado
Esta página se tradujo del inglés con ayuda de IA y se cotejó con el original. Leer la versión en inglés
Las convenciones de código existen porque el código se lee muchas más veces de las que se escribe, y casi siempre lo lee alguien distinto de su autor. Esta guía recoge las convenciones en las que coinciden la mayoría de los equipos Java —nombres, estructura de las clases, formato, imports, Javadoc, manejo de null, modismos para cadenas y logs—, explica el porqué de cada una para que las apliques con criterio y muestra cómo automatizarlas con un formateador, Checkstyle, Error Prone y SonarQube en lugar de insistir en cada code review.
Lo que aprenderás
- Aplicar las convenciones de nombres estándar de Java a paquetes, tipos, métodos, constantes y genéricos
- Organizar una clase según el orden convencional de miembros, con imports limpios y métodos cortos
- Escribir Javadoc que documente el contrato en lugar de repetir la firma
- Tratar los nulls, las cadenas, los números mágicos y los logs de forma idiomática
- Automatizar el estilo con un formateador y análisis estático para que las revisiones se centren en el diseño
La mayor parte de la vida de un código es mantenimiento, y la mayor parte del mantenimiento es lectura. Un estilo coherente no es cuestión de estética: elimina las pequeñas decisiones (dónde va la llave, si esto es una constante o una variable, qué significa mgr) para que quien lee dedique su atención a lo que hace el código. Java tiene convenciones documentadas desde los años noventa y, desde entonces, el sector ha convergido en un conjunto de reglas bastante reducido, la mayoría de las cuales puede comprobar una herramienta. Esta guía presenta esas reglas con sus motivos, usa Java 21 como referencia y termina explicando cómo hacerlas cumplir para que el estilo no vuelva a ser tema de conversación en una code review.
Nombres
Los nombres son la parte más importante de una convención, porque transmiten un significado que ningún formateador puede añadir. Las convenciones de Java son precisas:
| Elemento | Convención | Ejemplos |
|---|---|---|
| Paquete | todo en minúsculas, dominio invertido, sin guiones bajos | com.example.billing.invoice |
| Clase, interfaz, enum, record | UpperCamelCase, sustantivos | InvoiceService, PaymentResult |
| Método | lowerCamelCase, verbos o sintagmas verbales | calculateTotal(), isExpired(), findByEmail() |
| Variable, campo, parámetro | lowerCamelCase, sustantivos | retryCount, customerEmail |
Constante (static final inmutable) | UPPER_SNAKE_CASE | MAX_RETRIES, DEFAULT_TIMEOUT |
| Parámetro de tipo | una sola letra mayúscula | T, E, K, V, R |
| Constante de enum | UPPER_SNAKE_CASE | Status.IN_PROGRESS |
| Método de test | descriptivo, se permiten guiones bajos | rejectsNegativeAmount() o withdraw_insufficientFunds_throws() |
Más allá de la mecánica, los buenos nombres comparten tres propiedades. Son pronunciables y fáciles de buscar: customerRepository se encuentra con grep y se puede decir en voz alta; custRepo2 no hay forma de encontrarlo y x coincide con todo. Incluyen la unidad o el tipo cuando hay ambigüedad: timeoutMillis, priceCents, sizeBytes evitan el clásico bug del factor 1000 mejor que cualquier comentario. Y evitan las palabras de relleno: InvoiceData, InvoiceInfo, InvoiceObject no dicen nada que no diga ya Invoice.
Los booleanos se leen mejor como predicados: isActive, hasChildren, canRetry. No metas la negación en el nombre (isNotReady), porque quien lo llame acabará escribiendo !isNotReady. Los acrónimos siguen el camel case como si fueran palabras normales —HttpClient, parseXml, userId—, porque HTTPURLConnection es ilegible y XMLHTTPRequest es una advertencia que nos deja la historia.
Un campo static final solo es una constante si su valor es profundamente inmutable. static final List<String> NAMES = new ArrayList<>() es una variable global mutable disfrazada: o usas List.of(...), o la nombras en lowerCamelCase para indicar que puede cambiar.
Estructura del archivo fuente y de la clase
Un único tipo público de nivel superior por archivo, con su mismo nombre. Dentro del archivo, el orden convencional ayuda a orientarse:
- La cabecera de licencia o copyright, si el proyecto la usa
- La sentencia
package - Los imports: primero los estáticos, luego los normales, sin comodines y sin imports sin usar
- El Javadoc de la clase y su declaración
- Los campos estáticos (constantes primero) y después los de instancia
- Los constructores y los métodos factoría estáticos
- Los métodos, agrupados por propósito: primero la API pública, o en orden de llamada para que se pueda leer de arriba abajo
equals,hashCode,toString- Los tipos anidados
Los imports con comodín (import java.util.*;) ocultan de dónde viene un tipo y provocan conflictos silenciosos cuando dos paquetes acaban teniendo una clase con el mismo nombre; los IDE los agrupan automáticamente, así que configura el tuyo para que no lo haga. Dentro de una clase, mantén los campos juntos al principio en lugar de repartirlos junto a los métodos que los usan: quien lee debería poder ver todo el estado de un objeto en una sola pantalla.
El orden de los modificadores lo fija la recomendación de la especificación del lenguaje y lo comprueban todos los linters: public protected private abstract static final transient volatile synchronized native strictfp. Escribe public static final, nunca final static public.
Formato
El formato es la parte de una convención que debería ser totalmente automática. Las reglas de consenso:
- Sangría: 4 espacios (Oracle) o 2 espacios (Google); nunca tabuladores, porque su ancho cambia de un editor a otro.
- Longitud de línea: 100 o 120 columnas. El antiguo límite de 80 columnas es anterior a las pantallas anchas, pero las líneas muy largas siguen siendo difíciles de recorrer.
- Llaves: estilo K&R (la llave de apertura en la misma línea), y siempre presentes, incluso en un
ifde una línea. El buggoto failde Apple era unifsin llaves con una línea duplicada. - Una sentencia por línea, una declaración de variable por línea, lo más cerca posible de su primer uso.
- Las líneas en blanco separan grupos lógicos: entre métodos, entre campos y constructores, y dentro de un método largo para marcar pasos; pero un método que necesita muchos grupos así debería dividirse.
- Espacios: después de las palabras clave (
if (,for (), alrededor de los operadores binarios, después de las comas; ninguno dentro de los paréntesis.
// Consistent, tool-formatted
public Optional<Invoice> latestFor(CustomerId customerId, Clock clock) {
Objects.requireNonNull(customerId, "customerId");
LocalDate today = LocalDate.now(clock);
return invoices.stream()
.filter(invoice -> invoice.customerId().equals(customerId))
.filter(invoice -> !invoice.issuedOn().isAfter(today))
.max(Comparator.comparing(Invoice::issuedOn));
}
La decisión de formato más valiosa que puede tomar un equipo es dejar de tomar decisiones de formato: adopta google-java-format (o el formateador del IDE con una configuración compartida y versionada mediante Spotless) y ejecútalo al guardar y en la CI. Un formateador que reescribe el archivo entero, en lugar de comprobar un subconjunto de reglas, acaba para siempre con cualquier discusión sobre espacios.
Tamaño de métodos y clases
Las unidades pequeñas son más fáciles de nombrar, probar y reutilizar. Los límites prácticos que muchos equipos imponen con Checkstyle son: métodos de menos de unas 30 líneas, como mucho 4 o 5 parámetros, clases de menos de unos pocos cientos de líneas y no más de tres niveles de anidamiento. Son señales que conviene investigar, no leyes: un switch sobre un enum con 20 constantes puede ocupar 40 líneas sin problema, y un parser puede tener 500 con toda legitimidad.
Dos refactorizaciones resuelven la mayoría de los incumplimientos. Extraer método convierte un bloque comentado en una llamada con nombre. Introducir objeto parámetro convierte create(String name, String email, String street, String city, String zip) en create(Person person, Address address), lo que además impide que quien llama intercambie dos argumentos String. El anidamiento profundo suele arreglarse con cláusulas de guarda: se retorna o se lanza la excepción pronto en los casos inválidos y luego se escribe el camino principal sin sangrar.
// Nested
public void ship(Order order) {
if (order != null) {
if (order.isPaid()) {
if (!order.isShipped()) {
carrier.dispatch(order);
}
}
}
}
// Guard clauses
public void ship(Order order) {
Objects.requireNonNull(order, "order");
if (!order.isPaid()) throw new IllegalStateException("Order " + order.id() + " is not paid");
if (order.isShipped()) return;
carrier.dispatch(order);
}
Comentarios y Javadoc
Javadoc es documentación de API: describe el contrato de los tipos y métodos públicos para quienes los van a llamar sin leer la implementación. Se procesa a HTML y se lee en los tooltips del IDE, así que la estructura importa.
/**
* Reserves stock for every line of the order.
*
* <p>Reservation is all-or-nothing: if any line cannot be satisfied, no stock is
* reserved and an {@link InsufficientStockException} identifies the first failing SKU.
* Reservations expire after {@value #RESERVATION_MINUTES} minutes unless confirmed.
*
* @param order the order to reserve for; must contain at least one line
* @return a reservation token to pass to {@link #confirm(ReservationToken)}
* @throws InsufficientStockException if any line exceeds available stock
* @throws IllegalArgumentException if the order has no lines
*/
public ReservationToken reserve(Order order) { ... }
La primera frase es un resumen que aparece en los índices, así que haz que sea una afirmación completa de lo que hace el método. El cuerpo explica el contrato: efectos secundarios, atomicidad, qué ocurre si algo falla, thread-safety, unidades. @param, @return y @throws documentan cada parámetro, el resultado y cada excepción que quien llama podría manejar. No documentes las RuntimeException que solo se lanzan por errores de programación, salvo que la condición forme parte del contrato.
Lo que un Javadoc no debe contener: una reformulación de la firma («Returns the name»), detalles de implementación que acabarán desfasados, o una etiqueta de autor y un historial de cambios; el control de versiones hace eso mejor. Desde Java 23 existe la forma Markdown ///, pero en Java 21 quédate con la forma HTML, con <p> entre párrafos.
Dentro del cuerpo de los métodos, usa comentarios // para el porqué: el ticket que motivó un apaño, la razón por la que un bucle está escrito a mano por rendimiento, el invariante del que depende un fragmento de código. Un comentario que dice qué hace un bloque es un nombre de método esperando a existir. TODO y FIXME son aceptables con un responsable o una referencia a un ticket; los TODO sin dueño son permanentes.
Modismos del lenguaje
Varias reglas de casi todas las convenciones tienen que ver con la corrección, no con la apariencia.
Nulls. Nunca devuelvas null en lugar de una colección: devuelve una vacía. Devuelve Optional para un valor único que puede no existir. Valida los parámetros al principio de los métodos públicos con Objects.requireNonNull y documenta los parámetros que admiten null (con una anotación como @Nullable de los proyectos JSpecify o Checker Framework, que los analizadores estáticos entienden).
Cadenas. Compara con equals (o equalsIgnoreCase), nunca con ==. Usa String.isBlank() en lugar de trim().isEmpty(). Concatenar con + está bien en una sola línea —desde Java 9 el compilador genera una concatenación eficiente basada en invokedynamic—, pero dentro de un bucle usa StringBuilder, y para plantillas de varias líneas, text blocks (""") y formatted().
Números mágicos. Cualquier literal que no sea 0, 1 o -1 merece una constante con nombre: if (retries > 3) pasa a ser if (retries > MAX_RETRIES). El nombre documenta la intención y el valor tiene un único sitio donde cambiarse.
Igualdad y hash. Sobrescribe equals, hashCode y toString a la vez, o usa un record. Nunca los bases en campos mutables si los objetos se usan como claves.
Inmutabilidad por defecto. final en los campos, en los parámetros que se usan en lambdas y en las variables locales que nunca se reasignan; colecciones no modificables en los getters; records para los valores.
Recursos. Todo lo que sea AutoCloseable, dentro de un try-with-resources; nunca un close() suelto en un finally.
Switch. Usa la forma con flecha (case A -> ..., Java 14) para que no haya fall-through; usa switch expressions cuando el resultado sea un valor; omite default con tipos sealed y enums cuando quieras que el compilador avise de las constantes nuevas.
Logs. Usa SLF4J con mensajes parametrizados —log.warn("Payment {} declined: {}", id, code)—, nunca concatenación, que paga el coste del formateo aunque el nivel esté desactivado. Pasa la excepción como último argumento para que se capture la stack trace. Nunca registres secretos ni datos personales completos. Una línea de log por evento en la frontera, no una por capa.
Visibilidad. El ámbito más reducido que funcione: campos private, clases auxiliares package-private, protected para los métodos que necesitan las subclases y public solo para la API real. Las clases que no están diseñadas para la herencia son final.
Los tests también forman parte de la convención
El código de test sigue las mismas convenciones con dos relajaciones: los nombres de los métodos pueden llevar guiones bajos para leerse como una frase, y es preferible algo de duplicación entre tests a una jerarquía de helpers que oculte qué comprueba cada uno. Estructura cada test como arrange / act / assert (o given / when / then), un comportamiento por test, con un nombre que enuncie ese comportamiento: returnsEmptyWhenCustomerHasNoInvoices. Las aserciones usan una librería fluida (AssertJ) o assertEquals(expected, actual) de JUnit 5, en ese orden de argumentos para que los mensajes de fallo sean correctos. La materia dedicada al testing trata las herramientas en detalle.
Hacer cumplir la convención
Una convención que vive en una wiki es una sugerencia. Hazla ejecutable:
Adopta un formateador
Añade Spotless (Gradle o Maven) configurado congoogle-java-formato con el XML de formateador de Eclipse/IntelliJ que comparta el equipo. EjecutaspotlessApplyen local yspotlessChecken la CI. Haz un único commit que solo reformatee todo el código para que el historial quede limpio.Añade Checkstyle para la estructura
Checkstyle impone lo que un formateador no puede: nombres, orden de los imports, longitud de los métodos, número de parámetros, Javadoc ausente en la API pública, orden de los modificadores, nada de imports con asterisco. Parte de la configuración de Google o de Sun y quita las reglas con las que no estés de acuerdo en lugar de añadir excepciones en el código.Añade un analizador de patrones de bugs
Error Prone (un plugin dejavac) y SpotBugs detectan problemas de corrección:equalssobre arrays, valores de retorno ignorados, cadenas de formato que no cuadran,@Overrideausente. PMD añade reglas de diseño. Trata sus hallazgos como fallos de build, no como avisos, o acabarán ignorándose.Controla el resto con SonarQube
Un quality gate sobre el código nuevo —cobertura, duplicación, ningún problema crítico nuevo— evita que un código legacy empeore sin exigir que se arregle todo de golpe.Mantén el IDE coherente
Versiona.editorconfigy la configuración de estilo de código del IDE para que el editor de cada desarrollador formatee igual que la CI, y activa las acciones al guardar para organizar los imports y reformatear.
La guía de análisis estático de la materia de testing profundiza en la configuración de estas herramientas. El principio es el mismo para todas: las reglas que puede comprobar una máquina debe comprobarlas una máquina, para que la revisión humana se dedique a los nombres, al diseño y a si el código resuelve el problema correcto.
Errores comunes
Notación húngara y prefijos de tipo. strName, iCount, m_field describen el tipo, que el compilador ya conoce, y quedan desfasados en cuanto el tipo cambia.
Abreviaturas. calcTtlAmt ahorra seis pulsaciones una vez y obliga a cada lector a descifrarlo. Escríbelo completo.
Código comentado. El control de versiones ya lo recuerda; el comentario solo confunde.
Supresiones indiscriminadas. @SuppressWarnings("all") sobre una clase oculta problemas reales. Suprime un único aviso, en el ámbito más pequeño posible, con un comentario que explique por qué.
Campos públicos y setters para todo. Los setters generados convierten una clase en un struct mutable. Expón comportamiento, no estado, como defiende la guía de fundamentos de objetos.
Parámetros booleanos. render(true, false) es ilegible en el punto de llamada. Usa un enum o dos métodos.
Ortografía incoherente en el código. colour en un módulo y color en otro hacen que las búsquedas no sean fiables. Elige una variante del inglés para los identificadores (el inglés americano coincide con el JDK) y una para la prosa.
Puntos clave
- Los nombres transmiten el significado: sigue al pie de la letra las convenciones de camel case, incluye las unidades, evita abreviaturas y palabras de relleno, y haz que los booleanos se lean como predicados.
- Organiza las clases en el orden convencional, con imports explícitos, los campos arriba y métodos cortos modelados con cláusulas de guarda.
- El formato es un problema resuelto: adopta un formateador y no vuelvas a discutir sobre espacios.
- Javadoc documenta los contratos de la API pública; los comentarios en línea explican el porqué, no el qué.
- Los modismos que previenen bugs forman parte de la convención: nada de colecciones null,
equalspara las cadenas, constantes con nombre, inmutabilidad por defecto, try-with-resources y logs parametrizados. - Hazla cumplir con Spotless, Checkstyle, Error Prone o SpotBugs y un quality gate de SonarQube, para que las revisiones se centren en el diseño.
Fuentes y lecturas recomendadas
Preguntas frecuentes
- ¿Qué guía de estilo de Java debería adoptar mi equipo?
- Elige una que pueda hacer cumplir una herramienta y deja de debatir. La Google Java Style Guide con google-java-format es la opción más habitual, porque el formateador no deja nada que discutir; las convenciones originales de Oracle son la referencia histórica de la que derivan casi todas las demás. Lo que importa es que todo el código siga un único estilo y que la CI rechace las desviaciones.
- ¿Cuánto debería medir un método?
- Lo bastante poco como para entenderlo sin hacer scroll y para ponerle un nombre honesto. Muchos equipos fijan un límite orientativo de unas 20 a 30 líneas y un límite estricto en Checkstyle algo por encima. Un método que necesita un comentario para separar sus secciones está pidiendo que lo dividan; un método al que solo se llama una vez pero que tiene un nombre claro sigue mereciendo la extracción por legibilidad.
- ¿Todos los métodos deben tener Javadoc?
- No. La API pública —todo lo que llama otro módulo u otro equipo— debe documentar su contrato: precondiciones, valor de retorno, excepciones, thread-safety. Los métodos privados y los getters evidentes no deberían llevar un comentario que repita la firma. Un Javadoc que dice «Gets the name» en getName() es ruido que acostumbra a los lectores a saltarse los comentarios.
- ¿Los comentarios son señal de mal código?
- Los que explican qué hace el código suelen serlo, porque un nombre mejor los haría innecesarios. Los que explican por qué —el apaño para un bug de un driver, una regla de negocio con su número de ticket, una decisión de rendimiento nada obvia— son valiosos y difíciles de sustituir. Escribe los segundos y borra los primeros.