Codigo limpio y refactorización

En código limpio, la decisión entre crear métodos genéricos o específicos depende del contexto y de principios de diseño como DRY (Don't Repeat Yourself), SRP (Single Responsibility Principle) y YAGNI (You Ain’t Gonna Need It). Analicemos ambas opciones:


Cuándo crear métodos genéricos

Los métodos genéricos son útiles cuando:

  • Evitan la repetición de código. Si varias partes del código requieren una lógica similar, un método genérico evita duplicaciones.
  • Son reutilizables. Si la misma operación se aplica en múltiples contextos con variaciones mínimas, es mejor un método genérico con parámetros configurables.
  • Facilitan el mantenimiento. Modificar una única función genérica es más fácil que actualizar múltiples métodos con lógica similar.
  • Reducen el acoplamiento al hacer que el código sea más modular y reutilizable.

🛑 Cuándo evitar métodos genéricos

  • Si la abstracción no aporta claridad. Un método demasiado genérico puede ser difícil de entender y usar.
  • Si la lógica es específica para un caso concreto. No tiene sentido generalizar algo que solo se usará en un contexto específico.
  • Si es difícil de extender. A veces, agregar más lógica a un método genérico lo hace más complicado que simplemente tener métodos específicos.

Cuándo crear métodos específicos

Los métodos específicos son ideales cuando:

  • Tienen una única responsabilidad clara y están diseñados para un caso de uso concreto.
  • Aumentan la legibilidad. Un método bien nombrado y con una funcionalidad clara mejora la comprensión del código.
  • No se repiten innecesariamente. Si solo se usa en un contexto, no es necesario hacer una abstracción genérica.
  • Evitan sobreingeniería. Generalizar demasiado puede llevar a métodos difíciles de entender y mantener.

🛑 Cuándo evitar métodos específicos

  • Si el código se repite en varios lugares. Esto va en contra del principio DRY.
  • Si hay una clara oportunidad de reutilización en otros módulos o clases.

🔥 Ejemplo práctico 1

Ejemplo Malo: Código muy específico

1public class MostrarNumeros {
2    public static void mostrarNumerosDel1Al100() {
3        for (int i = 1; i <= 100; i++) {
4            System.out.print(i + " ");
5        }
6        System.out.println(); // Salto de línea al final
7    }
8
9    public static void main(String[] args) {
10        mostrarNumerosDel1Al100();
11    }
12}

Opción mejor: Método más genérico y reutilizable

  • Código más flexible gracias a los parámetros inicio y fin.
  • Evitamos código rígido que solo mostraba del 1 al 100.
  • Ahora podemos mostrar cualquier rango de números fácilmente. 🚀
1public class MostrarNumeros {
2    public static void mostrarNumerosEnRango(int inicio, int fin) {
3        for (int i = inicio; i <= fin; i++) {
4            System.out.print(i + " ");
5        }
6        System.out.println(); // Salto de línea al final
7    }
8
9    public static void main(String[] args) {
10        mostrarNumerosEnRango(5, 20); // Ejemplo: Muestra números del 5 al 20
11    }
12}

🔥 Ejemplo práctico 2

Ejemplo Malo: Código con Efectos Secundarios

Supongamos que tenemos un sistema que filtra usuarios por diferentes criterios.

Aquí estamos repitiendo código con ligeras variaciones.

1import java.util.List;
2import java.util.stream.Collectors;
3
4class Usuario {
5    String nombre;
6    int edad;
7
8    public Usuario(String nombre, int edad) {
9        this.nombre = nombre;
10        this.edad = edad;
11    }
12}
13
14public class FiltradoUsuarios {
15    public static List<Usuario> filtrarPorEdad(List<Usuario> usuarios, int edad) {
16        return usuarios.stream().filter(u -> u.edad == edad).collect(Collectors.toList());
17    }
18
19    public static List<Usuario> filtrarPorNombre(List<Usuario> usuarios, String nombre) {
20        return usuarios.stream().filter(u -> u.nombre.equals(nombre)).collect(Collectors.toList());
21    }
22}

Opción mejor: Método genérico con un criterio dinámico

📌 Ventajas de este enfoque:

  • Elimina duplicación de código.
  • Es más flexible y extensible.
  • Usa una función de callback para personalizar el filtrado.
1import java.util.List;
2import java.util.function.Predicate;
3import java.util.stream.Collectors;
4
5public class FiltradoUsuarios {
6    public static List<Usuario> filtrarUsuarios(List<Usuario> usuarios, Predicate<Usuario> criterio) {
7        return usuarios.stream().filter(criterio).collect(Collectors.toList());
8    }
9
10    public static void main(String[] args) {
11        List<Usuario> usuarios = List.of(
12            new Usuario("Juan", 30),
13            new Usuario("Ana", 25),
14            new Usuario("Carlos", 40)
15        );
16
17        List<Usuario> mayoresDe30 = filtrarUsuarios(usuarios, u -> u.edad > 30);
18        List<Usuario> conNombreJuan = filtrarUsuarios(usuarios, u -> u.nombre.equals("Juan"));
19
20        System.out.println("Mayores de 30: " + mayoresDe30.size());
21        System.out.println("Usuarios llamados Juan: " + conNombreJuan.size());
22    }
23}

📌 Conclusión

No hay una única respuesta correcta, pero en general:

  • Si la funcionalidad se repite en varios lugares, conviene un método genérico.
  • Si la funcionalidad es muy específica y no se reutilizará, un método específico es mejor.
  • Un método genérico no debe ser tan abstracto que dificulte la comprensión del código.
  • Aplica el principio KISS (Keep It Simple, Stupid): si un método genérico complica más de lo que ayuda, es mejor evitarlo.

📢 Regla general: Usa métodos genéricos cuando tenga sentido y métodos específicos cuando la claridad sea más importante que la reutilización. 🚀