Refactorizando lógica if / else

Tengo una clase java con mil líneas de método de lógica if / else como esta:

if (userType == "admin") { if (age > 12) { if (location == "USA") { // do stuff } else if (location == "Mexico") { // do something slightly different than the US case } } else if (age  4) { if (location == "USA") { // do something slightly different than the age > 12 US case } else if (location == "Mexico") { // do something slightly different } } } else if (userType == "student") { if (age > 12) { if (location == "USA") { // do stuff } else if (location == "Mexico") { // do something slightly different than the US case } } else if (age  4) { if (location == "USA") { // do something slightly different than the age > 12 US case } else if (location == "Mexico") { // do something slightly different } } 

¿Cómo debería refactorizar esto en algo más manejable?

Debe usar estrategias , posiblemente implementadas dentro de una enumeración, por ejemplo:

 enum UserType { ADMIN() { public void doStuff() { // do stuff the Admin way } }, STUDENT { public void doStuff() { // do stuff the Student way } }; public abstract void doStuff(); } 

Como la estructura del código dentro de cada twig más externa de su código se ve más o menos igual, en el próximo paso de la refactorización es posible que desee restar importancia a esa duplicación mediante el uso de métodos de plantilla . Alternativamente, puede convertir la ubicación (y posiblemente la edad) en una estrategia también.

Actualización: en Java4, puede implementar una enumeración de tipo seguro a mano, y usar subclases simple y antigua para implementar las diferentes estrategias.

Lo primero que haría con este código es crear los tipos Admin y Student , que heredan del tipo base User . Estas clases deben tener un método doStuff() donde ocultas el rest de esta lógica.

Como regla general, cada vez que te encuentres cambiando de tipo, puedes usar polymorphism.

¿Miles? Tal vez un motor de reglas es lo que necesitas. Drools podría ser una alternativa viable.

O un patrón de comando que encapsula toda la lógica de “hacer algo ligeramente diferente” para cada caso. Almacene cada comando en un mapa con la concatenación de edad, ubicación y otros factores como la clave. Busca el comando, ejecútalo y listo. Bonito y limpio.

El mapa se puede almacenar como configuración y leer en el inicio. Puede agregar nueva lógica agregando nuevas clases y reconfigurando.

Primero, use las enumeraciones para el tipo de usuario y la ubicación, luego puede usar instrucciones de cambio (mejora la legibilidad)

Segundo: usa más métodos.

Ejemplo:

 switch (userType) { case Admin: handleAdmin(); break; case Student: handleStudent(); break; } 

y después

 private void handleAdmin() { switch (location) { case USA: handleAdminInUSA(); break; case Mexico: handleAdminInMexico(); break; } } 

Además, identifica el código duplicado y ponlo en métodos adicionales.

EDITAR

Si alguien te obliga a codificar Java sin enumeraciones (como si estuvieras obligado a usar Java 1.4.2), utiliza ‘estática final en lugar de enumeraciones o haz algo como:

  if (isAdmin(userType)) { handleAdmin(location, age); } else if (isStudent(userType)) { handleStudent(location, age)); } //... private void handleAdmin(String location, int age) { if (isUSA(location)) { handleAdminInUSA(age); } else if (isUSA(location)) { handleAdminInMexico(age); } } //... private void handleAdminInUSA(int age) { if (isOldEnough(age)) { handleAdminInUSAOldEnough(); } else if (isChild(age)) { handleChildishAdminInUSA(); // ;-) } //... } 

Puede hacer que userType una enum , y darle un método que realice todas sus acciones de “hacer algo ligeramente diferente”.

sin más información, no hay una buena respuesta

pero supongo que sería esto: use OO

primero defina un Usuario, defina Admin, Estudiante y todos los otros tipos de usuarios y luego deje que el polymorphism se encargue del rest

Basándome únicamente en los nombres de las variables, supongo que debe subclasificar User (o lo que sea que tenga una variable userType ) en AdminUser y StudentUser (y posiblemente en otros) y usar polymorphism .

El riesgo de esto no es solo que sea antiestético, sino que es muy propenso a errores. Después de un tiempo, podría correr el riesgo de superposiciones en sus condiciones.

Si realmente puede distinguir la condición por tipo de usuario, puede como mínimo dividir el cuerpo de cada condición en una función separada. Para que compruebe según el tipo, y llame a una función apropiada específica para ese tipo. Una solución más OO es representar a cada usuario como una clase, y luego anular algún método de cálculo para devolver un valor en función de la edad. Si no puede usar clases pero puede al menos usar enumeraciones, entonces podrá hacer una statement de conmutación más agradable en las enumeraciones. Los switches en cadenas solo vendrán en Java 7.

Lo que me preocupa son las situaciones de superposición (por ejemplo, dos tipos de usuarios con algunas reglas compartidas, etc.). Si ese fuera el caso, sería mejor que representara los datos como un archivo externo (por ejemplo, una tabla) que leería y mantendría, y su código operará esencialmente como un controlador que realiza la búsqueda adecuada en esta información. conjunto. Este es un enfoque común para las reglas comerciales complejas, ya que nadie quiere ir y mantener toneladas de código.

Probablemente primero verifique si puede parametrizar el código doStuff y doSimilarStuff.

Eche un vistazo al patrón Visitor. Hace uso del polymorphism pero es un poco más flexible ya que es más fácil agregar nuevos casos más adelante.

La desventaja es que necesitarías alguna forma de convertir la información del estado en diferentes instancias. El beneficio es una manera más limpia de agregar comportamiento sin tener que modificar su jerarquía de herencia.

Puede usar el patrón de Cadena de responsabilidad.

Refactorizar if-else sentencias if-else en clases con una interfaz IUserController por ejemplo.

Inicialice su cadena dentro de una lista o un árbol o cualquier estructura de datos adecuada, y ejecute la funcionalidad deseada en esta cadena. Puede usar el patrón de Constructor para crear la estructura de datos mencionada. Se asemeja al patrón de estrategia, pero en el patrón de cadena de responsabilidad, una instancia en la cadena puede llamar instancia (s) vinculada (s).

Además, puede modelar la funcionalidad específica de la ubicación mediante el patrón de estrategia. Espero eso ayude.

Si el código en los bloques se ajusta dentro de unos patrones estándar, crearía una tabla con columnas (tipo, ubicación, minAge, maxAge, acción), donde ‘acción’ es una enumeración que indica cuál de varios tipos de procesamiento se debe hacer. Idealmente, esta tabla se leería desde un archivo de datos o se mantendría en SQL.

Luego, puede hacer una búsqueda de tabla en el código de Java para determinar la acción que debe tomar para un usuario.

Use OOP Concepts: Esto depende del rest del diseño, pero tal vez debería tener una interfaz de user , Student , Admin interfaces the extends y UsaStudent , UsaAdmin , MexicoAdmin , la implementación de MexicoAdmin que hace algunas cosas. Mantenga una instancia de User y simplemente llame a su método doStuff .

Realmente necesitas dividir estos casos en métodos de objetos. Supongo que estas cadenas y números se extraen de una base de datos. En lugar de usarlos en su forma cruda en la lógica condicional anidada gigante, necesita usar estos datos para construir objetos que modelen las interacciones deseadas. Considere una clase UserRole con una subclase StudentRole y AdminRole, una clase Region con las subclases de EE. UU. Y México, y una clase AgeGroup con las subclases particionadas apropiadas.

Una vez que tenga esta estructura orientada a objetos en su lugar, podrá hacer uso de patrones de diseño orientados a objetos bien entendidos para volver a factorizar esta lógica.