Repaso por los principales olores de código que podemos encontrar en el desarrollo de software.
Obtenidos a partir del libro "Refactoring: Improving the design of existing code" de Martin Fowler
2. ¿Qué es un Code Smell?
• Son todos los síntomas que podemos
encontrar en el código fuente de un sistema
que indican que muy probablemente
existan problemas más profundos de calidad
de código, de diseño o de ambos.
• Es difícil definir si el código es malo o bueno, o
cuando deberíamos cambiarlo.
• Un buen desarrollador tiene el “olfato fino”.
3. Es VITAL aprender a detectar
y reconocer el olor de
nuestro propio código
4. ¿Qué hacer cuando se detecta un
‘Code Smell’?
Pero antes, proteger con test automatizados
aquella parte de código que se va a modificar. .
5. ¿Qué es la refactorización?
• Refactorizar significa cambiar el código
internamente sin alterar su funcionalidad
externa. En general, con motivos de mejorar
el diseño y obtener un código mas simple.
• Refactorización enseña técnicas para descubrir
el código de mala calidad y técnicas para
cambiarlo.
7. #0 Código duplicado
Duplicate code
• El peor olor de todos, el rey de la pestilencia!
• Un código duplicado 5 veces con un bug, es tener 5 bugs en tu
software.
• Si algún día quieres mejorar lo que hace un código duplicado,
tienes que modificar cada parte duplicada de tu código.
• Más líneas de código, más probabilidades de error.
POSIBLE SOLUCIÓN
• Sacar el código duplicado a un método o clase nueva y
utilizarlo en todos aquellos lugares que se necesita.
8. #1 Código muerto
• Aparición de código que no se utiliza, probablemente
procedente de versiones anteriores, prototipos o pruebas.
• También incluye aquel código comentado que se dejó por si
alguna día…
POSIBLE SOLUCIÓN
• Revisar si realmente se debe de usar, sino, borrarlo (para
recuperarlo algún día siempre está control de código fuente)
9. #2 Métodos largos
Long method
• ¿A quién le gusta un método de decenas o cientos de líneas?
• Dificulta muchísimo su compresión
• Seguramente estará realizando más de una responsabilidad.
• El extraer un trozo de código a un método y llamarlo, no
penalizará la velocidad de tu programa.
• Tu código es menos reutilizable.
POSIBLE SOLUCIÓN
• Detectar las diferentes responsabilidades y sacarlas a
métodos o clases nuevos.
• Antes de ello intentar eliminar variables temporales por
querys.
10. #3 Clases largas
Large class
• Ocurre lo mismo que con los métodos largos.
• Seguramente esté encargándose de más de una
responsabilidad.
• La pestilencia nº1 #0 (código duplicado) no estará muy lejos si
es que no le ha afectado ya…
• Un síntoma es cuando la case tiene demasiados atributos.
POSIBLE SOLUCIÓN
• Detectar las diferentes responsabilidades y sacarlas a clases
nuevas colaboradoras.
11. #4 Lista larga de parámetros
Long parameter list
• Una lista larga de parámetros es difícil de comprender y sin
muchos son del mismo tipo, fácil de equivocarte al usar el
método.
POSIBLE SOLUCIÓN
• Encapsular los parámetros relacionados en objetos tipados o
reemplazar parámetro por un métodos.
• NO usar datos globales sólo para traspasar datos.
12. #5 Cambios divergentes
Divergent Change
• Cuando una única clase que tiene que modificarse de
diferentes maneras por distintos motivos (más de una
responsabilidad).
• Esta mala señal indica que la clase no es cohesiva.
POSIBLE SOLUCIÓN
• Extraer las diferentes responsabilidades a distintas clases.
13. #6 Cirugía a escopetazos
Shotgun Surgery
• Un determinado tipo de modificación en el sistema lleva
repetidamente a la realización de cambios pequeños en
muchas clases.
• La “cirugía a escopetazos" generalmente implica una sola idea
o función lógica distribuida por varias clases.
POSIBLE SOLUCIÓN
• Trata de corregir ese problema reuniendo todas las partes del
código que tienen que modificarse en una única clase
cohesiva.
• Es recomendable tener algo de “cambios divergentes” que
este olor, ya que será más fácil de tratar.
14. #7 Envidia de características
Feature Envy
• Un método en la ClassA parece más interesado por la ClassB
que por su propia clase.
• Generalmente la envidia suele venir por los datos de otra
clase.
• La "envidia" de la ClassA por los recursos de la ClassB es una
indicación del acoplamiento fuerte de la ClassA con la ClassB.
POSIBLE SOLUCIÓN
• Mover la funcionalidad del método de ClassA a ClassB, que ya
está más cerca de la mayoría de datos implicados en la tarea.
• Si no todo el método tiene “envidia”, extraer a un método el
código envidioso y mover sólo ese método a ClassB.
15. #8 Grupos de datos
Data Clumps
• Uso de un mismo conjunto de variables o propiedades en
diferentes lugares en vez de crear una clase apropiada para
almacenar los datos, lo que a su vez provoca el incremento de
parámetros en métodos y clases.
Ejemplo:
public void DarDeAltaNuevoCliente( string Public void DarDeAltaNuevoCliente(
nombre, string apellido1, string
apellido2, string direccion, string Cliente cliente)
codigoPostal) {
{
…
…
} }
POSIBLE SOLUCIÓN
• Aglutinar los parámetros en uno o varios objetos lógicos y
pasarlos como nuevos parámetros.
16. #9 Obsesión por primitivos
Primitive obsession
• Muchas personas tienen la obsesión de negarse a utilizar
objetos pequeñitos para modelar algunos comportamientos.
• Por ejemplo: Un número de teléfono, igual sale más rentable
utilizar un objeto pequeñito que modele el comportamiento
que tener que lidiar con una cadena de caracteres para
modelarlo y tener que parsearla y tratarla.
POSIBLE SOLUCIÓN
• Crear una nueva clase con los primitivos con algo en común,
usa enumerados, subclases o el patrón State o Strategy.
17. #10 Sentencias switch
Switch statements
• La duplicación de código no está lejos con este tipo de
sentencias, puedes llegar a verlo varias veces en un mismo
sitio. Si añades una condición a un switch en un sitio, tendrás
que propagarlo donde se haya repetido.
POSIBLE SOLUCIÓN
• Generalmente, utilizar polimorfismo.
18. #11 Jerarquías de herencias paralelas
Parallel Inheritance Hierarchies
• Es un caso especial de la cirugía a escopetazos (shotgun
surgery).
• Paralelismo que aparece cuando cada vez que se crea una
instancia de una clase es necesario crear una instancia de otra
clase, evitable uniendo ambas en una única clase final.
POSIBLE SOLUCIÓN
• Mover los métodos y campos de una jerarquía de objetos a
otra para que la jerarquía de objetos referida desaparezca.
19. #12 Clase perezosa
Lazy class
• Toda clase debe de tener definida una responsabilidad, una
razón para existir. Si no es así, ¿para qué sirve esa clase?
• Cada clase que crees cuesta dinero para mantenerla y
comprenderla.
POSIBLE SOLUCIÓN
• Una clase sin apenas responsabilidad hay que dotarla de
sentido, o bien eliminarla.
20. #13 Generalización especulativa
Speculative generality
• Este olor es bastante general, y siempre comienza por las
palabras de: "Algún día necesitaré que ..." o “… por si algún
día lo necesito”.
• En ese momento suena la señal de alarma. Tienes que tener
en cuenta que si vas a montar una infraestructura para hacer
cosas complejas, puede ser compleja de comprender y eso
tiene un coste también. Si lo vas a tener que utilizar algún día,
no te preocupes; no hay plazo que no se cumpla.
POSIBLE SOLUCIÓN
• Complica tu solución sólo lo necesario y escribe la
funcionalidad que te hace falta para tus necesidades actuales
y no para las futuras.
21. #14 Campo temporal
Temporary field
• Se salta el principio de encapsulamiento y ocultación de
variables haciendo que éstas pertenezcan a la clase cuando su
ámbito debería ser exclusivamente el método que las usa.
POSIBLE SOLUCIÓN
• Si una propiedad sólo se una para un par de métodos,
conviértela en un parámetro de esos métodos.
• Otra alternativa es sacar a una clase nueva los campos y
métodos que los usan.
22. #15 Mensajes encadenados
Message Chains
• Cuando se realiza una cadena de llamadas a métodos de
clases distintas utilizando como parámetros el retorno de las
llamadas anteriores, como
A.getB().getC().getD().getTheNeededData(),
que dejan entrever un acoplamiento excesivo de la primera
clase con la última.
• Cualquier cambio en las relaciones intermedias causa cambios
en el cliente.
• No se refiere a las API’s fluidas.
POSIBLE SOLUCIÓN
• Mover los métodos a niveles anteriores o esconder el uso del
delegado para minimizar la cadena de llamadas.
23. #16 Intermediario
Middle man
• Este es quien se ocupa de delegar las llamadas, y
simplemente hace eso.
• Exploras la clase, y ves que solamente esta llamando a un
método y delegando la llamada.
• Sin intermediarios y burocracia se vive mucho mejor.
• Cuestiona la necesidad de tener clases cuyo único objetivo es
actuar de intermediaria entre otras dos clases.
POSIBLE SOLUCIÓN
• Saltarte al intermediario y usar la clase que implementa la
acción directamente. Usar “Inline Method” para unos pocos
métodos o convertir el intermediario en una subclase del
objeto real.
24. #17 Intimidad inapropiada
Inappropriate intimacy
• Se produce cuando dos clases comienzan a conocer
demasiados detalles una de otra.
POSIBLE SOLUCIÓN
• Mover métodos y propiedades de una clase a otra para
eliminar la intimidad.
• Cambiar la comunicación bidireccional por unidireccionar.
• Con clases con intereses comunes, extraer dichos intereses
comunes a una clase externa.
25. #18 Clases alternativas con diferentes interfaces
Alternative Classes with Different Interfaces
• Indica la ausencia de interfaces comunes entre clases
similares.
• También aplicable a métodos.
• Por ejemplo: CalcularFactura (); y FacturaCalcula(). Es
probable que hagan lo mismo, pero sin embargo como eliges
uno u otro?.
POSIBLE SOLUCIÓN
• Esta muy claro que si el código es duplicado, debe ser
eliminado y si no, podemos renombrar métodos o utilizar
sobrecarga u otros mecanismos que nos ayuden a entenderlo
mejor.
26. #19 Rechazo del legado
Refused bequest
• Cuando una subclase 'rechaza' métodos o propiedades
heredadas, atentando directamente contra la POO.
• Las clases que heredan de otra, heredan sus campos y sus
métodos, pero ¿Qué ocurre si no quieren o no necesitan todo
lo que se les da?
• No importa negar implementaciones, pero si interfaz legada.
POSIBLE SOLUCIÓN
• Baja los métodos no comunes del padre al hijo que lo necesite
para que el padre sólo tenga los métodos comunes.
• Elimina la herencia y haz que las clases colaboren.
27. #20 Comentarios
Comments
• Hay que tener un poco de cuidado al interpretar estas palabras, los
comentarios son útiles, pero usados con sabiduría, no como un
desodorante para tapar algo que no esta bien hecho (código malo).
• En cuanto hayamos mejorado nuestro código y hayamos localizado
los olores de nuestro código y los hayamos refactorizado, nos
daremos cuenta que muchos de ellos sobran.
• TIP: Cuando sientes la necesidad de escribir un comentario, primero
trata de refactorizarlo para que cualquier comentario resulte
superfluo.
POSIBLE SOLUCIÓN
• Extraer el trozo de código comentado a un método y darle un buen
nombre.
• Usa comentarios en zonas que no estés seguro del código o que
quieras recordar algo para futuras modificaciones.
28. Referencias
• Refactoring: Improving the Design of
Existing Code
• Refactoring to Patterns
• xUnit Test Patterns: Refactoring Test Code
• Smells to refactoring
• http://www.refactoring.com