git reset: soft, mixed y hard.

logo de git

El comando git reset es el comando git que nos permite deshacer cambios que hemos introducido por error o antes de tiempo.

Hay varias opciones de uso para este comando, pero una de ellas sirve para devolver el repositorio git a un estado concreto del histórico de git, deshaciendo los cambios posteriores. El comando es

git reset <mode> <commit>

mode indica a qué partes del repositorio queremos afectar. Hay tres posibles

  • Histórico de commit: Tu repositorio local con sus cambios.
  • Área de Staging: El índice de tu git local, donde git guarda qué archivos tienen que añadirse en el próximo commit. Es decir, ficheros de los que has hecho git add.
  • Directorio de trabajo: Tu copia local de los ficheros, donde editas y ves tu código y que tiene los cambios que todavía no están en el respositorio.

Para que el comando afecte a cada una de las áreas, mode tiene tres posibles valores:

  • –soft : Con este flag, sólo se afecta al histórico de commit, sin tocar tu directorio de trabajo ni el área de staging.
  • –mixed: Es la opción por defecto, afecta al histórico de commit y al área de staging. No toca tu directorio de trabajo, es decir, mantiene los cambios que hayas hecho localmente.
  • –hard: Esta opción toca los tres niveles, por lo que es peligrosa. Puedes perder tus cambios, ya que desaparecen tanto de git como de tu copia local.

Imagina que hemos modificado un fichero (modificado.txt) y creado un nuevo (nuevo.txt) al que hemos hecho el git add. Si ejecutamos el comando git status, tendremos esto.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   nuevo.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   modificado.txt

Veamos como afectan cada una de las tres opciones

git reset –soft

La opción –soft sólo afecta al repositorio. Así que ese comando sin más git reset –soft no hace nada. El motivo es que por defecto resetea a HEAD, que es el úlitmo commit en el repositorio y en el que habitualmente estamos. Otra cosa es que deshagamos el último commit con el comando git reset –soft HEAD~. Esto borra el último commit del repositorio, pero no toca el área de staging. Seguiremos teniendo el fichero añadido pendiente de commit y el fichero modificado pendiente de commit, además de pendientes los cambios que hicieramos en el commit anterior que se han borrado

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   nuevo.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   modificado.txt
        modified:   modificado-commit-anterior.txt

git reset –mixed

La opción –mixed, que es la de defecto, nos limpia el staging área. Así, el comando git reset, que por defecto es equivalente a git reset –mixed HEAD, símplemente límpia los ficheros marcados con git add.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   modificado.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        nuevo.txt

El fichero pendiente de commit sigue pendiente de commit, pero el fichero nuevo dice que no está trackeado, que le hagamos un add si queremos incluirlo.

git reset –hard

Finalmente, la opción más heavy es –hard. Deshace todos nuestros cambios locales. Deshará nuestros cambios locales en el fichero modificado y borrará de nuestro disco el fichero recién creado.

nothing to commit, working tree clean

Publicado en git | Etiquetado , , , , | Deja un comentario

Renombrar una rama en git

logo de git

Tenemos una rama en git y queremos cambiarle el nombre. Podemos usar los siguientes comandos

Cambiar nombre de rama local

Podemos usar git branch -m o bien git branch –move Para cambiar el nombre de la rama local en la que estamos

git branch -m <nuevo-nombre>

Para cambiar el nombre de cualquier rama local

git brach -m <viejo-nombre> <nuevo-nombre>

Cambiar nombre de rama remota

Una vez cambiado el nombre en nuestra rama local, podemos subirla a remoto con

git push origin -u <nuevo-nombre>

Este comando no solo sube los cambios a la nueva rama, sino que también la asocia, de forma que nuestra rama nuevo-nombre queda asociada la rama remota nuevo-nombre. Cualquier git push/pull posterior no necesitará que indiquemos la rama remota.

Podemos borrar la antigua rama remota con

git push origin --delete <viejo-nombre>

Publicado en git | Etiquetado , | 1 comentario

Diferencias entre git fetch, pull y rebase

logo de git

No sé cómo estás acostumbrado a trabajar en git. En mi caso, cuando quiero traerme los cambios del servidor a mis ramas locales, uso siempre git pull. Sin embargo, sé que existen git fetch y git rebase, que nunca uso.

Veamos qué hace cada una y cuándo podemos usarlas.

git fetch

Esta es la más básica. Se asegura que de nuestra copia local de git tiene los cambios que se hayan hecho en el servidor. NO modifica nuestro código ni nuestras ramas ni nada. Sólo se trae a nuestra caché local de git los cambios que se hayan hecho en el servidor.

Es una forma de asegurarnos de que nuestro git local es consciente de todos los cambios que se hayan hecho en el servidor, pero sin que haga cambios en nuestras copias de trabajo locales.

Esto es útil luego para comandos como git status. Si tenemos los cambios del servidor en nuestra copia local, este comando será capaz de decirnos si estamos actualizados, si nos faltan actualizaciones, etc.

git pull

git pull sí hace cosas con nuestra copia local. Primero ejecuta un git fetch para ver todos los cambios que se han hecho en el servidor y actualizar nuestra caché local. Pero luego hace un git merge para mezclar los cambios remotos con los nuestros locales. Es decir, actualiza nuestra copia de trabajo y modifica nuestro código para que tenga los cambios que se hayan hecho en el servidor remoto.

Si hay conflictos nos los indicará y los tendremos que resolver, pero nuestro copia de código quedará actualizada con lo último que haya en el servidor más los cambios que hubiéramos hecho nosotros en local.

git rebase

Este comando es similar a git pull en el sentido de que trae los cambios a tu rama de trabajo. Pero tiene dos diferencias notables

La primera es que no hace un git fetch primero de forma automática. Si quieres traer cambios del servidor remoto para mezclarlos con tu rama de trabajo, debes ejecutar primero el comando git fetch manualmente.

Y la segunda diferencia, que se la más importante, es que mezcla los cambios en tu rama pero como si los hubieras hecho en la misma rama. Vamos a ver esto con detalle para ver exactamente qué queremos decir.

Imagina que han hecho cambios en remoto y nosotros en local. Ejecutamos git pull. En local tendremos ambos cambios, pero la historia de git quedaría de esta forma

Es decir, dos ramas separadas, una con los cambios en local, la otra con los cambios en remoto y un merge de ambas, que quedaría en nuestra rama master

Si en vez de un git pull hubiéramos hecho un git rebase, tendríamos igualmente todos los cambios en nuestro lado, pero la historia quedaría así

Es decir, trae los cambios de remoto y los mete en nuestra rama como si los hubiéramos hecho nosotros mismos. La historia, desde luego, queda más limpia, pero perdemos parte de ella. No los commits, que siguen ahí, sino de dónde vienen a nivel de ramas. Qué usuario los ha hecho y en qué fecha sí se mantiene.

Peligros de git rebase

Puesto que git rebase mueve cambios de una rama a otra, tiene cierto peligro. Si tu forma de trabajo es similar a git flow, es decir, los desarrolladores hacen ramas de git para cada nueva funcionalidad, luego la mezclan en una rama común de nombre develop y cuando se llega a una versión para entregar del SW se hace una copia en una rama de versiones congeladas de nombre master, puede ser peligroso el uso de git rebase. No porque sea malo, sino porque o bien los desarrolladores saben muy bien qué están haciendo, o pueden llevarse por error commits de una rama a otras que no deben.

Por ejemplo, en git-flow las ramas feature son las de desarrollo de funcionalidades. Quizás es buena idea que una vez terminada una feature, se haga un git rebase para llevarse la feature a la rama develop. Esto nos deja una historia más limpia en develop, como si hubiésemos desarrollado directamente en ella.

Pero si por equivocación un desarrollador lo hace al revés, es decir, trae los cambios de develop a su rama feature, en develop dejarán de estar todos los cambios posteriores al momento en el que se hizo la rama para la feature. El resto de desarrolladores dejarán de verlos si el desarrollador hace un push.

git pull deja la historia más sucia, con muchas ramas entremezcladas, pero no es tan susceptible de equívocos. Las dos ramas originales permanecen inalteradas siempre y únicamente aparece un único commit para la mezcla de ambas ramas. Este último commit, en caso de error, siempre se puede echar atrás fácilmente.

Publicado en git | Etiquetado , , , , , | Deja un comentario

Cómo borrar una rama local y remota en Git

logo de git

A veces no te interesa una rama de git porque has hecho experimentos con ella y no quieres conservarla. O bien has terminado con ella, has hecho merge a la rama principal y quieres borrarla. Vemos como borrar esas ramas tanto en local como en remoto.

Borrar rama local de Git

Para borrar una rama local de Git tienes el comando

git branch -d <nombre-rama>

Siendo nombre-rama el nombre de la rama que quieres borrar. No puedes borrar la rama en la que estás actualmente. Y este comando te dará un aviso si intentas borrar una rama cuyos cambios no has mergeado en otra rama, avisándote que perderas esos cambios definitivamente, puesto que no hay otra rama que los tenga.

Si quieres borrar sin este aviso, puedes usar la opción -D mayúscula

git branch -D <nombre-rama>

Esto fuerza el borrado de la rama, aunque no tengas esos cambios en otro sitio. Los perderás derfinitivamente.

Borrar rama remota de Git

Para borrar una rama remota el comando es

git push origin -d <nombre-rama>

origin, por supuesto, es el nombre habitual del repositorio remoto. En tu caso podría ser otro si lo has configurado así. En versiones más antiguas de Git (anteriores a 1.7.0) el comando es algo menos intuitivo

git push origin :<nombre-rama>

Un : (dos puntos) delante del nombre de la rama en vez de una opción -d

Actualizar repositorio local

Si hemos borrado una rama remota pero todavía la tenemos en local, bien porque no la hemos borrado, bien porque la ha borrado otro programador de nuestro equipo, debemos reflejar esos cambios en local. El comando es

git fetch --all --prune

Esto borrará todas las referencias locales (ramas, tags, etc) que hayan sido borradas del remoto. Si solo tienes un remoto, normalmente origin, no es necesaria la opción –all. La opción –prune es la que se encarga de limpiar.

Publicado en git | Etiquetado , , , , | 1 comentario

Deshacer el cambio más reciente en GIT

logo de git

A veces subimos cosas a git que luego no queremos que estén subidas. Es relativamente habitual hacer un commit y darnos cuenta luego que no hemos subido exactamente lo que queríamos o que se ha subido algún fichero de más. Nos gustaría deshacer el cambio. ¿Cómo lo deshacemos?

Si no has subido con push el cambio al servidor remoto, el mecanismo es fácil.

El comando git reset hace que tu copia de trabajo apunte a un commit concreto, borrando de tu repositorio local todos los commit posteriores. Tu copia local de trabajo la deja como esté, por lo que no pierdes los cambios que has metido por error.

Tienes que tener ojo con esto, pierdes toda la historia de git posterior al commit al que hayas decidido ir. Si tu copia local no tiene las cosas que te interese mantener, tendrá que volver a hacerlas.

Si quieres deshacer justo el último commit, el comando es

git reset HEAD~

HEAD es el commit de tu repositorio local al que apunta tu directorio de trabajo. Habitualmente, HEAD suele ser el último commit, es decir, el erróneo que quieres deshacer. Puedes indicar commits anteriores con HEAD~1, HEAD~2, etc, según quieras el commit anterior, o dos anteriores, etc. Como abreviatura, HEAD~1 se puede poner simplemente como HEAD~. También es posible poner HEAD~<hash-del-commit> para ir a uno concreto sin necesidad de contar commits.

Una vez ejecutado el comando, el último commit se deshará de nuestro repositorio local, pero nosotros tendremos la copia de trabajo tal cual la teníamos, sin cambios. Ya solo nos queda corregir lo que fuera que hubieramos hecho erróneo y volver a hacer el commit.

Resumen: Para deshacer el cambio más reciente en un commit de git, si no lo has subido al servidor con un push

  • git reset HEAD~
  • // Corregir en local lo que necesitemos
  • git add … // si queremos añadir ficheros nuevos.
  • git commit …. // meter los cambios correctos.

Algunos detalles a tener en cuenta

HEAD~ funciona bien si la historia hacia atrás es más o menos lineal. Si tu commit es de hacer un merge entre dos ramas, HEAD~ elegirá una de ellas, quizás no la que quieras. En ese caso, quizás es mejor usar HEAD~<hash-del-commit>

Otra opción para deshacer cambios es git revert. La diferencia es que git revert realiza un nuevo commit para deshacer los cambios. Es decir, en los commits tendrás el commit erróneo más un nuevo commit que deshace los cambios del commit erróneo. Puede ser interesante si no estás seguro de querer perder el commit erróneo.

Publicado en git | Etiquetado , , | 1 comentario

Servicios Angular: providedIn y providers

logo de angular

Estoy jugando con los Módulos en Angular. Y como siempre, me gusta hacer un ejemplo lo más sencillo posible para asentar los conceptos. Mi ejemplo: un módulo principal «tonto» que importe un módulo «Greeting» y lo use.

Como los módulos teóricamente sirven para agrupar componentes y exponer solo algunas de ellas, pues a «Greeting» le pongo un servicio GreetingService que devuelva un «Hola» y un componente GreetingComponent que lo muestre en pantalla. La idea es exportar el componente ocultando el servicio al módulo principal.

Sin problemas por el lado del componente. En el fichero greeting.module.ts basta poner en declarations y en exports el componente GreetingComponent.

@NgModule({
  declarations: [GreetingComponent],
  exports: [GreetingComponent],
  ...
})
export class GreetingModule { }

Pero con los servicios… ¡Menudo lío!. Entenderlo me ha dado bastante guerra. Estaba empeñado en ocultar el servicio salvo que el módulo lo exportara, pero no es posible. Los servicios no tienen nada que ver con los módulos. Más allá de que un módulo diga que necesita un servicio.

Cuando declaramos un servicio, podemos ponerle providedIn y decirle dónde se registra. Hay cinco valores posibles: Un nombre de módulo o componente. Uno de los textos ‘root’, ‘platform’ o ‘any’. O bien null o dejarlo vacío, que es lo mismo. Pero tienen comportamientos muy similares y dos de esas opciones están obsoletas. ‘platform’ es un caso especial que sólo tiene sentido si varias aplicaciones Angular corren en el mismo navegador. Así que quedan

  • root‘: Se instancia el servicio una sola vez cuando alguien lo necesite y está diponible para todo el que lo quiera, sin necesidad de que lo declare en su apartado ‘providers’
  • null: Se instancia el servicio una vez cuando alguien lo necesite, pero tienen que ponerlo en su apartado ‘providers’

Así que providedIn sirve básicamente para decir si el servicio está diponible por defecto para todo el mundo o sólo para el que lo indique explícitamente en ‘providers’.

Pero hay más. Había varias opciones que están obsoletas y todas hablan de que pueda haber varias instancias del mismo servicio. ¿Es esto posible? ¿Y cómo funciona?

Con la opción ‘root’ no hay nada de esto. Instancia única para todos.

Pero la opción null sí permite esto. El mecanismo es el siguiente. Cuando se carga la aplicación para ponerla en marcha, habrá una serie de módulos angular de nuestra aplicación (los decorados con ‘@NgModule’) que se cargarán en el arranque porque la página que se muestra inicialmente los necesita.

Pero podemos crear módulos que no se necesiten en el arraque y luego, por alguna acción del usuario como pinchar un enlace o un botón, provocar la carga del módulo para mostrarle un determinado contenido nuevo. Estos modulos los llamaremos ‘lazy loaded modules’.

Y una vez soltado este rollo, ¿qué hace la opción null de providedIn?. Todos los módulos que se cargan en el arranque compartirán una misma instancia del servicio. Pero los módulos ‘lazy loaded’ obtendrán instancias nuevas de este servicio.

Resumen:

Aparte de ‘platform’, sólo hay dos opciones no obsoletas para providedIn.

‘root’ : Una instancia única del servicio para todo el que la necesite y el que la necesite no tiene que ponerla en ‘providers’. Es la forma aconsejada.

null: Una instancia única para todos los módulos que se carguen en el arranque de la aplicación. Una instancia nueva para los módulos ‘lazy loaded’ que se carguen después. El que necesite este servicio tiene que ponerlo en ‘providers’. Debería usarse sólo en caso de que el servicio tenga un estado interno propio para cada posible cliente. Y en este caso, todos los módulos, salvo quizás uno, deberían ser ‘lazy loadaded’ para asegurar que cada módulo tiene su propia instancia.

Publicado en angular | Etiquetado , | Deja un comentario

Librería collections de Python

python

Pues sigo con ChatGPT de ayudante y he encontrado una forma que me ha gustado. En vez de preguntarle algo concreto simplemente le digo algo como «dame un tip de programación en python». Y te cuenta «cosas». Y está bien, porque te cuenta cosas de python de forma aleatoria (o no) y a veces son cosas que no conocías. No son cosas complejas, nada que no puedas mirar por google en un minuto, si las buscas. La gracia es que te dice cosas que quizás no sabías que existían y que por tanto no ibas a buscar salvo que te surgiera una necesidad concreta. Es una forma de aprender cosas nuevas.

Una de las cosas que me ha contado es sobre algunas de las clases de librería collections de Python. Me ha contado cuatro clases de ahí: Counter, defaultdict, namedtuple y deque.

  • Counter nos ayuda a contar elementos de una lista que cumplan determinadas condiciones.
  • defaultdict es como un dict pero nos devuelve un valor por defecto si accedemos a una clave que no existe, en vez de dar un error.
  • namedtuple es una tupla a la que podemos acceder a sus elementos por un nombre en vez de por índice.
  • deque es una cola eficiente para añadir y retirar elementos de los dos extremos.

Y el artículo con los detalles, escrito por ChatGPT pero con algunos arreglos míos está en Uso de la librería collections en python.

Los retoques míos son más que nada por dar un poco de claridad. He revisado los ejemplos y visto que funcionan, pero leyendo tal cual lo que me contaba ChatGPT no me quedaba lo suficientemente claro, así que decidír reescribir o ampliar algunas definiciones que daba.

Publicado en python | Etiquetado , , | Deja un comentario

Decoradores de TypeScript y ChatGPT

logo de angular

Con esto de Angular, me he puesto a escribir cosas de TypeScript. En concreto, sobre Decoradores de TypeScript. Y ya puestos, pues con ChatGPT le voy pidiendo ejemplos de código y explicaciones, para llevarlos a mis pruebas TypeScript en github.

Pues bien, todo más o menos «sobre ruedas», por decir algo. ChatGPT ayuda bastante y soy más rápido haciendo mis ejemplos y luego redactando en la wiki.

Pero no es oro todo lo que reluce. Hay varios tipos de decoradores, de clase, de atributo de clase, de método y de parámetro de método.

Quise hacer a modo de prueba un decorador de forma que cuando alguien haga un new de la clase decorada, saque un log de que se ha hecho el new. Buscando por google y el ejemplo de ChatGPT te lo ponen siempre más o menos igual: una función decoradora que saca el log, una clase decorada y se hace un new para ver que sale.

Pero ni la mayoría de los ejemplos/tutoriales de google ni ChatGPT te dicen dos cositas:

  • Al decorador se le llama una sola vez y es cuando se declara la clase. No cuando se instancia. En los ejemplos hacen un solo new y sale el log del decorador, todo aparentemente «guay». Pero si no haces ningún new también sale. El log del decorador sale independientemente de si haces new o no haces new y si haces un new o quince news. Sale una sola vez. Siempre.
  • La sintaxis que te ponen es experimental, así que ni siquiera compila. Tienes que habilitar la opción experimentalDecorators.

Así que se sigue investigando y se descubre el truco. Para nuestro ejemplo, el decorador debe devolver una clase hija de la original, con un constructor nuevo que saque el log que queremos por pantalla y llame al constructor de la clase padre original.

Y algo parecido pasa con los decoradores de método. Debes devolver una función alternativa que haga lo que tú quieres (el log en nuestro ejemplo) y llamar a la función original.

Y con los atributos, un poco más raro. Debes definir un método set() y get() para el atributo que haga lo que tu quieres y asignarlo al atributo en un PropertyDescriptor. Debes definir los dos, aunque sólo quieras hacer algo en el set() o en el get()

Y la liamos con los decoradores de parámetro de método. Le pido a ChatGPT el código para hacer log cada vez que llamen a un método que tenga un parámetro decorado, me lo hace. Y no funciona. Miro ejemplos por internet y todos se quedan en lo básico, sacar un log cuando llaman al decorador la primera y única vez que lo llaman. Media hora de charla con ChatGPT para intentar arreglar el código, no hay manera.

Así que me fuí a la documentación oficial: Parameter Decorators y veo un ejemplo rarísimo, además mezclado con un decorador de método. Y mirando con más calma, veo este aviso

NOTE  A parameter decorator can only be used to observe that a parameter has been declared on a method.

¡Aclarado!. El decorador de parámetro básicamente no sirve para nada :). Sólo te avisa que un parámetro ha sido decorado, pero no hay forma de que te avise cada vez que se llame a un método que tenga el parámetro decorador para hacer algo como sacar log o cambiar algo. De ahí que tengas que mezclarlo con un decorador de método:

  • El código que tienes que hacer en el decorador de parámetro es guaradarse en algún sito que el parámetro ha sido decorado con tal decoración. Una tabla o mapa donde pongas nombre del parámetro, de qué clase y método y qué decoración tiene.
  • Y luego tienes que hacer un decorador del método que devuelva un método alternativo que consulte eso que te has guardado para hacerle la lógica al parámetro.

Y ChatGPT, conmigo, emperrado en arreglar un código que no funciona. Y básicamente porque los montones de ejemplos que hay por internet tampoco han profundizado un poco en el tema, se quedan en que me avisa (una sola vez) cuando decoro el parámetro, independientemente de que lo use o no.

Publicado en TypeScript | Etiquetado , , , | Deja un comentario

EditorConfig

EditorConfig

Me he puesto a juguetear con Angular, una de mis asignaturas pendientes.

El primer paso, ¿cómo no?, crear un «Hola Mundo». Pero el comando ng new my-app crea demasiados ficheros y demasiadas cosas. Incluso con las opciones para hacer el «Hola Mundo», lo más reducido posible, como –minimal

Así que me pongo a revisar ficheros que crea para ver de cuáles puedo prescindir, que no sean estrictamente obligatorios para un «Hola Mundo». Y me encuentro con el fichero .editorconfig

Aparentemente es un fichero en el que se define el estilo que el editor que uses debe poner en tu código fuente. Pero ¿qué editor? ¿uno concreto? ¿todos?. Así que indago…. y resulta que la mayoría de los IDES lo soportan

En algunos viene incorporado «de fábrica», desde editores «simple» como vim o IDEs completos, como IntelliJ. Para otros hay que intalar un plugin como eclipse, netbeans o notepad++

El fichero tiene una sitaxis relativamente sencilla. Para cada extensión de fichero podemos decir el tipo de retorno de carro a usar, como hacer los sangrados (espacios, tabuladores, de qué tamaño), la codificación de caracteres a utilizar y poco más.

Puedes ver más detalles en EditorConfig.

Publicado en eclipse, IntelliJ | Etiquetado , , , , , | Deja un comentario

Map.computeIfAbsent()

Logo de Java

Aunque está ya desde Java 8, acabo de descubrir el método computeIfAbsent() de Map

Hay veces que en mi código tengo un Map cuya clave es cualquier cosa, un String por ejemplo, y que el valor es a su vez otra Collection, por ejemplo, una List

Map<String, List<Integer>> myMap = new HashMap<>();

Cuando llegaba el momento de meter un nuevo Integer en la lista valor, siempre había que comprobar primero si había o no lista para crearla en caso de que no. Luego ya se podía añadir el Integer

if (null == myMap.get("key1")) {
   myMap.put("key1", new ArrayList());
}
myMap.get("key1").put(anInteger);

Pues bien, el método computeIfAbsent() nos facilita esta tarea. El código quedaría

myMap.computeIfAbsent("key1", k->new ArrayList()).add(anInteger);

Básicamente, si el valor de «key1» existe, devuelve el valor List que corresponda. Si no existe, llama a lo de new ArrayList(), lo mete en el Map y lo devuelve

En cualquiera de los dos casos, devuelve la List. Ya solo nos queda llamar al método add() del List devuelto para meter el entero.

Una de las ventajas de tener en el IDE un analizador estático de código, estilo SonarLint y fijarse en las sugerencias que te hace.

Publicado en java | Etiquetado | Deja un comentario