Posibles razones para refactorizar código, según Code Complete:
- Código duplicado
- Una función es demasiado larga
- Un bucle es demasiado largo o está demasiado anidado (demasiada complejidad)
- Una clase tiene poca cohesión
- Una interfaz no proporciona un nivel consistente de abstracción
- Una lista de parámetros tiene demasiados parámetros
- Los cambios dentro de una clase tienden a afectar a otras clases
- Las jerarquías de herencia tienen que ser modificadas en paralelo (afecta a todas las subclases un mismo cambio)
- Los atributos que se utilizan por varias clases no están organizados en una clase (se repiten)
- Una función utiliza más características propuestas por otra clase que de su propia clase
- Un tipo de datos primitivo esta sobrecargado
- Una clase no tiene una finalidad clara (no aporta mucho)
- Un objeto intermediario no hace nada
- Una función tiene un nombre que no especifica de forma clara su objetivo
- Los atributos de una clase son públicos
- Una subclase sólo utiliza un porcentaje mínimo de las funciones que hereda
- Se utilizan comentarios para explicar código díficil de entender
- Se usan variables globales
- Una función contiene código que parece que será necesario algún día pero en la actualidad no se utiliza
- Reemplazar un número utilizado directamente por una constante
- Renombrar una variable para darle un nombre más claro o explicativo
- Convertir una variable que se usa en múltiples sitios en múltiples variables de único uso
- Usar una variable local para propósitos locales en lugar de un parámetro de una función
- Convertir el uso de datos primitivos en el uso de una clase
- Convertir un array (de bajo nivel) en un objeto
- Encapsular una colección
- Descomponer una expresión lógica
- Convertir una expresión lógica compleja en una función lógica con un nombre correcto para su definición
- Unificar fragmentos de código duplicado en diferentes partes de una expresión condicional
- Eliminar el uso de
breakoreturnen lugar del uso de variables de control en los bucles - Devolver lo más rápido posible la solución en lugar de asignar el valor a una variable dentro de instrucciones
if-else - Reemplazar condicionales (especialmente las sentencias
case) por el uso de polimorfismo
- Extraer una función desde código que se repite en varios lugares
- Convertir una función demasiado larga en una clase
- Sustituir un algoritmo complejo por uno simple
- Combinar funciones similares en una única
- Pasar el objeto completo en lugar de seleccionar algunos campos específicos (en el caso de que sean muchos)
- Pasar algunos campos específicos en lugar del objeto completo (en el caso de que sean pocos campos los utilizados).
- Extraer código especializado en subclases
- Combinar código similar en superclases
- Mover una función a otra clase en la que tenga más coherencia
- Convertir una clase demasiado larga en dos
- Eliminar una clase sin utilidad
- Reemplazar herencia por composición (en el caso de que sea necesario)
- Reemplazar composición por herencia (en el caso de que sea necesario)
- Unificar una superclase y una subclase si su implementación es muy similar.
- Cambiar la asociación unidireccional de clases a bidireccional (en caso de que sea necesario)
- Cambiar la asociación bidireccional de clases a unidireccional (en caso de que sea necesario)
- Proveer de una factoria para crear los objetos en lugar de usar un constructor simple
- Reemplazar los códigos de error con excepciones (en caso de que sea necesario).
Un data stream representa una secuencia de elementos que soportan diferentes tipos de operaciones para realizar cálculos sobre ellos
Las operaciones sobre un stream pueden ser intermediarias o terminales
- Las operaciones intermediarias devuelven un nuevo stream permitiendo encadenar múltiples operaciones intermediarias sin usar punto y coma
- Las operaciones terminales son nulas o devuelven un resultado de un tipo diferente, normalmente un valor agregado a partir de cómputos anteriores
public class Main{
public static void main(String []args){
List<String> myList =
Arrays.asList("a1", "a2", "b1", "c2", "c1");
myList
.stream()
.filter(s -> s.startsWith("c"))
.map(String::toUpperCase)
.sorted()
.forEach(System.out::println);
}
}-
Las operaciones que se aplican sobre un stream aceptan algún parámetro en forma de interfaz funcional o expresión lambda
- Una interfaz funcional es un objeto cuyo tipo (clase) representa a una función ejecutable con un cierto número de parámetros (normalmente 0, 1 o 2)
- Una expresión lambda es una interfaz funcional anónima, que especifica el comportamiento de la operación, pero sin especificar formalmente su nombre y tipo de parámetros
-
Las operaciones aplicadas no pueden modificar el estado del stream original
En el ejemplo anterior, se puede observar que:
filter,mapysortedson operaciones intermediariasforEaches una operación terminal- Ninguna de las operaciones modifica el estado de
myListañadiendo o eliminando elementos - Sólo se filtran ciertos elementos, se transforman a mayúsculas, se ordenan (por defecto, alfabéticamente) y se imprimen por pantalla
List<String> myList =
Arrays.asList("a1", "a2", "b1", "c2", "c1");
myList
.stream()
.filter(s -> s.startsWith("c"))
.map(String::toUpperCase)
.sorted()
.forEach(System.out::println);
myList
.stream()
.reduce( (a,b) -> a + " " + b )
.ifPresent(System.out::println);- Winterbe: Java 8 stream tutorial
- Oracle: Procesamiento de datos con streams de Java
- Oracle: Introducción a Expresiones Lambda y API Stream en Java
Dado los siguientes fragmentos de código, responder a las siguientes preguntas:
public class GroupOfUsers {
private static Map<String, Integer> usersWithPoints =
new HashMap<String, Integer>() {{
put("User1", 800);
put("User2", 550);
put("User3", 20);
put("User4", 300);
}};
public List<String> getUsers() {
List<String> users = new ArrayList<String>();
//Sorting users by points
usersWithPoints.entrySet()
.stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
.forEachOrdered(x -> users.add(x.getKey()));
//Capitalizing the names of the users
List<String> usersCapitalized = new ArrayList<String>();
users.forEach(x -> usersCapitalized.add(x.toUpperCase()));
return usersCapitalized;
}
} ...
GroupOfUsers group = new GroupOfUsers();
List<String> users = group.getUsers();
System.out.println("The users are: " + users);
...En la siguiente lista se incluyen 10 posibles problemas que pueden encontrarse en el código de la implementación anterior:
- Código duplicado
- Funciones con nombre que no especifica de forma clara su objetivo
- Rutinas demasiado largas
- Bucles demasiado largos o demasiado anidados
- Funciones con demasiada responsabilidad (no tienen asignada una única responsabilidad u operación a resolver)
- Lista de parámetros con demasiados parámetros
- Los cambios de una clase tienden a afectar a otras
- Se utilizan comentarios para explicar código díficil de entender
- Se usan variables globales
- Los cambios dentro de una clase tienden a afectar a otras clases
a) ¿Existe algún tipo de problema en la implementación anterior de los que se incluye en la lista anterior? ¿Es necesario aplicar refactoring en este caso? En el caso de que existan problemas, indique cuáles son y qué tipos de problemas piensa que generarían en el futuro si no se aplica el refactoring ahora.
b) En el caso de que la implementación necesite la aplicación de refactoring, realice los cambios oportunos e indique las mejoras que aporta su implementación respecto a la original.
Dado los siguientes fragmentos de código, responder a las siguientes preguntas:
public class GroupOfUsers {
private static Map<String, Integer> usersWithPoints_Group1 =
new HashMap<String, Integer>() {{
put("User1", 800);
put("User2", 550);
put("User3", 20);
put("User4", 300);
}};
private static Map<String, Integer> usersWithPoints_Group2 =
new HashMap<String, Integer>() {{
put("User1", 10);
put("User2", 990);
put("User3", 760);
put("User4", 230);
}};
private static Map<String, Integer> usersWithPoints_Group3 =
new HashMap<String, Integer>() {{
put("User1", 1000);
put("User2", 200);
put("User3", 5);
put("User4", 780);
}};
public List<ArrayList<String>> getUsers() {
List<String> users1 = new ArrayList<String>();
List<String> users2 = new ArrayList<String>();
List<String> users3 = new ArrayList<String>();
List<ArrayList<String>> users = new ArrayList<ArrayList<String>>();
//Sorting users by points
usersWithPoints_Group1.entrySet()
.stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
.forEachOrdered(x -> users1.add(x.getKey()));
usersWithPoints_Group2.entrySet()
.stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
.forEachOrdered(x -> users2.add(x.getKey()));
usersWithPoints_Group3.entrySet()
.stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
.forEachOrdered(x -> users3.add(x.getKey()));
//Capitalizing the names of the users
List<String> usersCapitalized1 = new ArrayList<String>();
List<String> usersCapitalized2 = new ArrayList<String>();
List<String> usersCapitalized3 = new ArrayList<String>();
users1.forEach(x -> usersCapitalized1.add(x.toUpperCase()));
users2.forEach(x -> usersCapitalized2.add(x.toUpperCase()));
users3.forEach(x -> usersCapitalized3.add(x.toUpperCase()));
//Adding users to the main list
users.add((ArrayList<String>)usersCapitalized1);
users.add((ArrayList<String>)usersCapitalized2);
users.add((ArrayList<String>)usersCapitalized3);
return users;
}
} ...
GroupOfUsers group = new GroupOfUsers();
List<ArrayList<String>> users = group.getUsers();
System.out.println("The users are: " + users);
...Responda a las siguientes cuestiones, teniendo en cuenta la lista de los 10 posibles problemas del ejercicio anterior
a) El software del ejercicio anterior ha evolucionado añadiendo nueva funcionalidad en su implementación. ¿Existe algún tipo de problema en esta versión de la implementación de los que se incluyen en la lista? ¿Es necesario aplicar refactoring en este caso? En el caso de que existan problemas, indique cuáles son y qué tipos de problemas piensa que generarían en el futuro si no se aplica el refactoring ahora.
b) En el caso de que la implementación necesite la aplicación de refactoring, realice los cambios oportunos e indique las mejoras que aporta su implementación respecto a la original.
[1] Code Complete: A Practical Handbook of Software Construction, Second Edition.