Algoritmos y Programación III
9. Diseño micro y refactorización
Carlos Fontela, 2006
Temario
Diseño micro o de clases Cohesión y acoplamiento Diseño de interfaces Jerarquías Condiciones anormales
Refactorización ¿Para qué? ¿Cuándo? Condiciones previas Problemas típicos que necesitan
refactorización
Bases del diseño de clases
Una buena arquitectura. Buenas prácticas de diseño y
programación. Patrones de diseño.
Eckel: “Un diseño termina cuando no se pueden
extraer más cosas del mismo.” “Las tareas más habituales se deben poder
hacer de una forma bien sencilla.”
Clases
Cada clase con un propósito simple y claro: una clase por abstracción y una
abstracción por clase.
Separar las dependencias de una plataforma en una clase aparte.
Patologías en diseño de clases
Clases con nombres verbales: No se supone que una clase hace algo, sino
que provee un conjunto de servicios. Clases sin métodos. Clases que no introducen nuevos métodos
ni los redefinen. Sólo heredan.
Clases que se refieren a varias abstracciones: Se deberían dividir en varias.
Cohesión y acoplamiento
Cohesión: Cada módulo haga una sola cosa simple.
Acoplamiento: Independencia entre módulos.
Asegurar bajo acoplamiento y alta cohesión en: Métodos Clases Paquetes
Algunos patrones ayudan.
Interfaces de clases (I)
Es lo que ve el cliente Más clara Más consistente Más simple Más intuitiva
No quitar funcionalidad En Java: /** @deprecated */ “privatizar” lo más posible
Pocos parámetros
Interfaces de clases (II)
Implementar operaciones canónicas Comparable Serialización, toString() Clonación No necesariamente en la misma clase
No incluir opciones en métodos que realizan acciones: Hacerlo en el constructor O en forma sucesiva:
documento.establecerHoja(A4);
documento.establecerColor(rojo);
documento.imprimir();
Interfaces de clases (III)
No hay problema si podemos modificar todo el código que depende de ellas
Por eso, es mejor mantener el principio del mínimo privilegio Todo lo más privado que se pueda Publicar las interfaces sólo si es
necesario
Atributos
Atributos deberían mostrar sólo estado y los métodos sólo comportamiento
No abusar de la herencia para expresar estado: El color de una figura es un atributo No crear clases para varones y mujeres
Estado condicionado por los invariantes de clase: Se expresan como restricciones entre atributos Conviene separar atributos vinculados en una clase
aparte que controle el cumplimiento de los invariantes Cuestiones de eficiencia nos pueden llevar a que
no mantengamos siempre los invariantes de la clase
Métodos
Ojo con métodos con grandes switch en los que se hace una cosa u otra en base al valor de un atributo: if (unaFigura.getClass() == Elipse.class)
(Elipse)unaFigura.dibujar();
else if (unaFigura.getClass() == Poligono.class)
(Poligono)unaFigura.dibujar();
Habría que analizar el uso de herencia y polimorfismo
O sobrecarga
Jerarquías (I)
Poner la mayor parte de los atributos y métodos lo más arriba que se pueda: para evitar luego definiciones duplicadas
Si una porción de código se repite en muchas clases hermanas, habría que generar un método y ponerlo en la clase base
Evitar generalizar todo lo que parezca generalizable de entrada Primero debemos resolver el problema que tenemos
entre manos de la manera lo más simple posible
Jerarquías (II)
Una nueva clase descendiente debe añadir o redefinir un método (modificar la interfaz) Si no, no es necesaria Riesgo de complicar la jerarquía sin un fin práctico. Las jerarquías deben ayudar a dominar la complejidad, no a
complicarla
Es más sencillo describir una jerarquía de lo general a lo particular
Pero esto no siempre se aplica a la construcción: Jerarquía construida por demanda
Generalizaciones que surgen al descubrir atributos o métodos en común en clases ya construidas
Sí para clases adquiridas
Excepciones a la herencia (I) Aves como animales voladores.
• ¿Pingüinos, gallinas, etc.? Idiomas de Europa como indoeuropeos.
• ¿magiar, finés y turco?
El uso de herencia con excepciones es una práctica cuestionable. Utilizar subclases para expresar las
excepciones. Pero las clasificaciones con subclases no
permiten excepciones por diferentes categorías.• No voy a poder clasificar a las aves también como
americanas y europeas sin caer en herencia repetida.
Excepciones a la herencia (II)
Pocas soluciones en el terreno práctico Herencia múltiple, en los lenguajes que la
manejan
Interfaces cuando las excepciones se dan a
nivel de métodos
Caso de discusión: ¿La circunferencia es una elipse con un radio
menos? ¿Cómo lo manejamos?
Condiciones anormales (I)
Una excepción indica un error de
ejecución
Mala idea elevar una excepción si no hay error
Por ejemplo, si en una búsqueda no se
encontró el valor buscado
Máxima: “Cuando todo falle, lance una
excepción”
Condiciones anormales (II)
“Un parámetro de error consume menos recursos”
No nos guiemos por microeficiencias: privilegiar la robustez y la seguridad
Las excepciones nos obligan a hacer algo con ellas: chequeo de condiciones lógicas puede evitarse,
dando la impresión de que no ha habido un error cuando en verdad lo hay
Máxima del diseño robusto: “Nunca se debe dar la impresión de que no pasó nada cuando algo ha fallado”
Condiciones anormales (III)
Una implementación de clase debería venir con
las excepciones que puede disparar Ponerlas en el mismo paquete
Cuando se produce una excepción luego de
capturar recursos, a veces debemos liberarlos El recolector de basura se va a ocupar de la memoria
El resto los debe liberar el programador
En el bloque finally
Buena práctica: liberar en orden inverso a la adquisición
Diseño por contrato
Invento de Meyer
Hay un contrato entre implementador y cliente basado en: Invariantes de clase
Precondiciones de métodos
Postcondiciones de métodos
Facilidad para pasar de diseño a implementación
Se centra más en qué hacen las clases que en cómo se hace
Implementado directamente en Eiffel
Refactorización
“Refactoring” Mejorar el diseño de código ya escrito ¿Cómo?
Modificar estructura interna Sin modificar funcionalidad externa Un poco como las optimizaciones
Ejemplo muy simple… y trillado Eliminar código duplicado
Para qué
Mejorar código, haciéndolo más comprensible Para modificaciones Para depuraciones Para optimizaciones
Mantener alta la calidad del diseño Si no, se degrada
A la larga, aumenta la productividad
Cuándo
Antes de modificar código existente Siempre después de incorporar
funcionalidad Antes de optimizar Durante depuraciones Durante revisiones de código Siempre, si se hace TDD o XP
Condiciones previas
Riesgo alto Máxima: “Si funciona, no lo arregle”
Un paso por vez Pruebas automatizadas
Escribirlas antes de refactorizar Y correrlas luego de cada pequeño
cambio
Problemas y refactorización
“Bad smells in code” (malos olores), los llama Fowler
Son indicadores de que algo está mal, y se solucionan con refactorizaciones
Hay catálogos por todos lados También en “Piensa en Java”, de Eckel
Veamos…
Cuestiones y soluciones (I)
Código duplicado Extraer un método Extraer y llevar arriba en la jerarquía Extraer una clase, cuando no hay
jerarquía en común
Método largo Extraer métodos
• Nos podemos ayudar con los comentarios
• Y con partes condicionales y ciclos
Cuestiones y soluciones (II)
Clase grande, con muchas responsabilidades Extraer clases Extraer subclases
Lista de parámetros larga Crear clases para los parámetros Eliminar el parámetro y agregar una
llamada a método
Cuestiones y soluciones (III)
Cambios divergentes Separar las clases cuya necesidad de
cambio tenga frecuencias distintas o provenga de necesidades diferentes
“Shotgun surgery” Es lo opuesto a lo anterior, cuando cada
cambio me obliga a tocar muchas clases Mover atributos o métodos para crear
una única clase
Cuestiones y soluciones (IV)
“Envidia de características” Cuando una clase se la pasa llamando a
métodos de otras clases Poner los métodos en las clases que los
usan
Clases sin comportamiento Pueden provenir de refactorizaciones
anteriores Pueden existir, pero no es bueno
Cuestiones y soluciones (V)
Ifs y switchs abundantes Herencia y polimorfismo Patrones Estado (State) y Estrategia
(Strategy) Otras soluciones de catálogo
Jerarquías paralelas Juntar clases
Cuestiones y soluciones (VI)
“Herencia especulativa” Colapsar la jerarquía
Clases que son alternativas pero tienen interfaces diferentes Renombrar métodos y otras más
complicadas
Hay más Ver Fowler, con sus soluciones
¿Flexibilidad?
La flexibilidad oscurece el código Y agrega complejidad En general, se flexibiliza uno u otro
aspecto, según lo que se espera que cambie Es el enfoque de los Patrones de diseño
Caso I: extraer ancestro
Sin problemas para los clientes: se siguen usando las clases descendientes
Caso II: extraer descendientes
Hay que modificar clientes, haciendo una refactorización en pasos para más seguridad
Bibliografía
Refactoring Martin Fowler Básicamente, un catálogo de
refactorizaciones típicas
Refactoring to Patterns Joshua Kerievsky Patrones de diseño como un objetivo de
la refactorización
Resumen
Las clases deben tener estado y comportamiento
Mantener el principio de mínimo privilegio Nunca se debe dar la impresión de que no
pasó nada cuando algo ha fallado Refactorizar para mejorar la calidad del
código, no la funcionalidad Combinar refactorización y pruebas
unitarias y de integración constantes
Qué sigue
Concurrencia Aplicaciones distribuidas Integración de aplicaciones
Muchas Gracias.
Carlos Fontela, 2006
Top Related