Siente el pensamiento; piensa el sentimiento
M. Unamuno

Archivos en la categoría Diseño

Refactoring Workbook

Refactoring Workbook

William C. Wake

Es un buen libro, aunque incompleto y con cierto sesgo “agilista” al intentar justificar ciertas causas de errores frecuentes al carecer de un diseño y una gestión del equipo eficiente. See also “Refactoring” by Martin Fowler.

Refactoring is the art of safely improving the design of existing code keeping the system running at all times. Refactoring provides us with ways to recognize problematic code and five us recipes for improving it.

Actualización

El libro lo puedes descargar en formato PDF gratuitamente desde este enlace.

Sin duda es más fácil escribir código que leerlo; por ello después de cada entrega ya sea por parte del programador o del equipo se debería leer el código que se ha entregado para partiendo de un código que ya funciona llegar a otro que funciona (desarrollar) y es simple de leer (mantener).

Especial atención al “safely” de la definición. Sin ir más lejos en mi actual proyecto intenté refactorizar la capa de control de la aplicación y preparé un carajal curioso : fue demasiado ambicioso, me tomó una semana de trabajo y al liberarlo entró en conflicto con los numerosos cambios que el resto del equipo fue realizando. Tendría que haber dividido la tarea en más pequeñas para conseguir que fuera seguro y no interferir con la marcha normal del proyecto.

Smells are warning signs about potential problems in code; some people prefer to talk about potential problems or flaws.

The Refactoring Cycle

Refactoring works in tiny steps. With a working system, while smells remain :

  • Choose the worst smell
  • Select a refactoring that will address the smell
  • Apply the refactoring

Smells within Classes

  • Comments

Symptoms: Comments symbols appear in the code.

Causes: Commnets may be present for the best of reasons: the author realizes that something isn´t as clear as it could be and adds a comment. Some comments are particularly helpful, as those that tell why something is done a particular way or why is’nt or thos that cite algorithms thar are not obvious. Other comments can be reflected just as well in the code itself. For example thie goal of a routine’s name as it  can through a comment.

What to do: When a comment explains a block of code you can often use “Extract Method” to pull the block out into a separate method (the comment will often suggest a name for the new method). Whe a comment explains what a method does better than the method’s name, use “Rename Method” using the comment as the basis of the new name. When a comment explains preconditions, consider using “Introduce Assertion” to replace the comment with code.

Payoff: Improves communication. May expose duplication.

Contraindications: Don’t delete comments that are pulling their own weight.

  • Long Methods

Symptoms: Large number of lines

Causes: Code is often easier to write than it is to read.

What to do: Use “Extract Method” to break up the method into smaller piedes splitting it into blocks semantically meaningful.

Payoff: Improves communication. May expose duplication. Often helps new classes and abstractions emerge.

Contraindications: Like almost all smells, the length is a warning sign – not a guarrantee – of a problem; Perhaps the best way to express something is using longer methods.

  • Large Class

Symptoms: Large number of instance varialbes, and/or methods and/or lines.

Causes: The author keeps adding just one more capability to a class until eventually it grows too big.

What to do: If the class has Long Methods, address that smell first.The most common approaches to break up a class are “Extract Class” if you can identify a new class that has part of this class`s responsibilities, “Extract Subclass” if you can divide responsibilities between the class and a new sublcass and “Extract Interface” if you can identify subsets of features that clients use.

Payoff: Improves communication. May expose duplication.

  • Long Parameter List

Symptoms: A method has more than one or two parameters

Causes: You may be trying to minimize coupling between objects letting the caller locate everything, or a programmer generalizes the routine to deal with multiple variations and a lot of control parameters.

What to do: If the parameter value can be obtained from another object this one already knows, “Replace Parameter with Method”; if the parameters come from a single object, try “Preserve Whole Object”; If the data is not from one logical object, you still might group them via “Introduce Parameter Object”.

Payoff: Improves communication. May expose duplication. Often reduces size.

Contraindications: Take a look on dependencies between two classes, or you cann’t design meaningful groups.

  • Type Embedded in Name (Including Hungarian)

Symptoms: Names are compound words consisting of a word plus the type of the arguments, for example a method addCourse (Course C); names are in Hungarian notation, where the type of an object is encoded into the name, for example iCount as an integer member variable; variable names reflect theri type rather than their purpose or role.

What to do: “Rename Method” or field or constant to a name that comunicates intent without being so tied to a type.

Payoff: Improves communication. May make it easier to spot duplication.

Contraindications: rarely you might have a class that wants to do the same sort of operations to two different but related types, for expample a Graph class with addPoint() and addLink() methods.

  • Uncommunicative Name

Symptoms: a name doesn´t communicate its intent well enough : one or two character namens, names with vowels omitted, numbered variables like pane1, pane2…, odd abbreviations, misleading names.

Causes: you haven’t took a minute, fucking lazy boy/girl.

What to do: Use “Rename Method”

Payoff: Improves communication.

Contraindications: i/j/k are oftenly used for loop indexes and c form characters if their scope is reaseonably short.

  • Inconsistent Names

Symptoms: One name is used in one place and a different name is used for the same thing somewhere else, for expample find() and list() for the same basic feature.

Causes: different people may create the classes at differente times, vamos que falta un diseño único documentado y consistente.

What to do: Pick the best name and use “Rename Method” or field or constant to give the same name to the same things. Then look for duplication smell. Note that the Eiffel language uses a common pool of words for the names of its library features.

Payoff: Improves communication. May expose duplication.

  • Dead Code

Symptoms: a variable, parameter, field, code fragment, method or class is not used anywhere or just in tests.

Causes: Requirements have changed or new approaches are introduced without adequate cleanup. Complicated logic results in some combinations of conditions that can’t actually happen.

What to do: delete the unused code and any associated test.

Payoff: Improves communication. Improves simplicity. Reduces size.

Contraindications: be aware that code aparently dead can be used dinamically or by another software modules.

  • Duplicated Code

Symptoms: Two fragments of code look nearly identical or have nearly identical effects.

Causes: programmers working independently (vamos una mala gestión y una carencia en el diseño), and copy and paste code that is almost right and make slight alterations.

What to do: If the duplication is within the same class, use “Extract Method” to pull the common part out into a separate method; if it is in two related classes use Pull Up Field or Method to bring the common parts together; if it is in two unrelated classes use Extract Class for extracting the common part into a new class, or decide in which class should be.

Payoff: Reduces duplication. Reduces size. Can lead to better abstractions and more flexible code.

  • Null Check

Symptoms: there are repeated occurrences of code like this : if (xxx == null) …

Causes: someone decides “we’ll use null to mean the default”

What to do: if there’s a resonable default value, use that. Otherwise “Introdue Null Object” tocreate a default object that you explicitly use. Watch out for a case where null means two or more differente things in different contexts.

Payoff: Reduces duplication. Redeces logic errors and exceptions.

Contraindications: if only occurs in only one place, it’s usually not worth the effort to create a separate Null Object. If you can’t define a safe behavior for each method, you may not be able to use a Null Object. Precisamente por esto prefiero, facilita detectar errores no previsto e interrumpe el flujo de ejecución en caso de error no previsto ni controlado.

  • Complicated Boolean Expression

Symptoms: code has complex conditions involving and, or and not.

Causes: injustificable.

What to do: Introduce “Explaining Variable” to make each clause clearer.

Payoff: Improves communication.

Contraindications: if simpler exressions comunicates less well.

Smells between Classes

Objects are about data and behavior together, and your code will be more robust if you organize objects by behavior.

  • Primitive Obsession

Symptoms: Uses primitive or near primitive types as int, float, String…; Constants and enumerations representing small integers; String constants representing field names.

Causes:

What to do: For missing objects, encapsulate primitives types into new classes.

Payoff: Improves communication. May expose duplication. Improves flexibility. Often exposes the need for other refactorings.

  • Data Class

Symptoms: the class consists only of public data members or of simple getting and setting methods. This lets clients depend on the mutability and representation of the class.

Causes:

What to do: “Encapsultate Fields” to block direct access to the fields allowing access only through getters and setters; “Remove Settings Methods” for any method you can and use “Hide Method” to eliminate access to the getters and setters where they’re not used (aquí no estoy de acuerdo, coste / beneficio no suele merecer nunca salvo en un Façade); “Encapsulate Collection” to remove direct acces to any collection-type fields.

Payoff: Improves communication. May expose duplication.

  • Message Chains

Symptoms: Yo see calls like a.b().c().d()

What to do: Use “Hide Delegate” to make the method depend on one object only, so rather a.b().c().d() put the d() method on the “a” object. (Revisar, mi primera idea es que sería mejor crear un nuevo objeto x que contenga el método d(), y que “a” invoque a “x”)

Notes: Follow the object oriented programming maxim Tell, Don´t Ask so instead of asking for objects so that you can manipulate them, you simply tell them to do the manipulation for you, as the Law of Demeter says: A method should only talk to itself, never to strangers.

4 conceptos imprescindibles de Diseño para Construir Software

En mis años de aprendizaje en mi escuela aprendí que la construcción de un software de calidad se basaba en dos pilares, la modularidad y la reutilización. Mi experiencia profesional me ha demostrado su enorme importancia : he podido disfrutar de sistemas que han seguido estos principios y he tenido que sufrir sistemas que no los siguieron. También que son insuficientes.

La modularidad es básica en software, como bien nos decía Jack el destripador :

Vamos por partes

La curva de aprendizaje es más suave, se puede desarrollar en paralelo reduciendo el tiempo de entrega, es más fácil realizar pruebas unitarias, es más fácil de mantener porque se aislan los errores en módulos.

Pero con qué criterios debemos dividir un sistema monolítico en módulos ? La reutilización es la respuesta, ya que nos permite disminuir el número de módulos necesarios en el sistema e incluso aprovechar módulos para futuros desarrollos.

Estos dos conceptos son suficientes ? Creo que se deberían completar con otros dos conceptos : desacoplo y sencillez.

El desacoplo se consigue con un buen interfaz y la reducción de dependencias externas. Debe ser un criterio complementario a la reutilización a la hora de dividir nuestro sistema en módulos. Por ejemplo si queremos hacer una llamada a base de datos para preguntar el número de usuarios de nuestro sistema, podemos tener un módulo para cada SGDB (uno para MySQL, otro para Oracle, otro para SQL Server…) o un único módulo que obtenga el mismo resultado para cualquier SGBD utilizado. Este módulo será un poco más complejo pero se convierte en mucho más reutilizable.

Ya lo dice el refrán :

Lo simple es bello

La sencillez es la meta común de todo lo anterior, y no sólo por alcanzar la belleza : nuestro sistema global es más sencillo, lo que se traduce en un sistema más barato de desarrollar, reducimos el tiempo del desarrollo, más barato de mantener, más fácil de aprender, reducimos el tiempo de respuesta a incidencias, conseguimos una calidad mayor y nos resultará más fácil de repetir con éxito en el futuro.

La aplicación de estos cuatro simples principios redunda en un beneficio para el cliente, para la empresa de software y para todas las personas participantes en el proyecto.

No olvides además que siempre hay tiempo para hacer las cosas bien, lección que he tenido que aprender en mis propias carnes : no se pierde un día, se invierte un día para luego ahorrarnos una semana.

Patrones de Diseño – Singleton

El Patrón de Diseño Singleton (en español, Instancia Única) se utiliza para garantizar que una clase sólo tenga una única instancia y para facilitar un punto de acceso global a la misma.

Se utiliza cuando se necesita :

  • que sea un único objeto el que coordine acciones a lo largo de todo el sistema
  • que el objeto posea estado
  • variables globales en el sistema (es más limpio y eficiente utilizar Singleton)

La propia clase es única responsable de crear la única instancia (ocultando al constructor) y de facilitar el acceso global a la instancia. Un ejemplo de implementación en Java :

public class Singleton {

      //Instancia como variable estática

    private static Singleton instanciaUnica;

    //Constructor privado : no se puede acceder desde fuera

    private Singleton() {}

    //Método para obetener una instancia

    public static Singleton getInstancia() {

      synchronized(Singleton.class) {

              if (instanciaUnica == null) {

                  instanciaUnica = new Singleton();

              }

      }

      return instanciaUnica;

    }

}

Es delicado en sistemas multihilo, ya que si dos hilos de ejecución intentarán crear una instancia al mismo tiempo y ésta no existiese todavía, sólo uno de los dos debería de lograrlo. La solución clásica para este problema es utilizar exclusión mutua en el método de creación de la clase que implementa el patrón.

En algunas ocasiones se aplica este patrón de una forma menos restrictiva, permitiendo que exista un número máximo de instancias en lugar de una única.

Patrones de Diseño – Façade

El Patrón de Diseño Façade (o Facade, o en español Fachada) es en mi opinión uno de los más básicos y simples, que todos hemos implementado alguna vez aún sin conocerlo.

Una fachada no se trata más que de un objeto que ofrezca una sencilla interfaz que ocultará uno o varios sistemas más complejos y sus interacciones.

Con este patrón ofrecemos un acceso sencillo y desacoplamos al máximo nuestro sistema cliente (el que accede a la fachada) de los sistemas ocultos.

Típicamente se utiliza en librerías o en sistemas diseñados en capas. Desde luego si estás construyendo un sistema que van a utilizar otros 20 sistemas de tu empresa, implementa una fachada.

Algunas ventajas que disfrutaremos en el propio desarrollo son :

  • Facilitamos la utilización y comprensión (acompañando la interfaz con una documentación mínima) de los sistemas ocultados
  • Los clientes se olvidan de toda la complejidad del negocio, sólo les importa los resultados obtenidos.

Y a la larga, el mantenimiento será más fácil

  • En los clientes reducimos las dependencias del código externo.
  • Aislamos a los clientes de cambios sustanciales en los sistemas ocultos, ya sea un cambio de requisitos, de diseño de base de datos, de tecnología utilizada…
  • Hace el código cliente más legible

Este patrón de diseño se puede implementar como una interfaz o como una clase abstracta, sin detallar los detalles de implementación (es justo lo que queremos ocultar).

Es habitual que se implemente la fachada utilizando también el patrón Singleton (sólo se necesita una fachada).

Como vemos es un patrón de diseño muy simple e intuitivo, que nace del sentido común y experiencia. Los beneficios que nos ofrece son enormes en comparación con su coste de implementación.

Pero ojo, su no utilización nos provocará enormes problemas tanto a la hora de desarrollar como sobre todo en el mantenimiento. Podemos ver algo parecido a lo que te sucederá si no lo utilizas en la viñeta siguiente… Así que no te dejes de utilizarlo !

Manejo de Excepciones y Errores en una arquitectura Java

Podemos considerar que una Excepción en programación es una situación poco habitual que provoca resultados inesperados o incoherentes en nuestros métodos, pero que es contemplada en el análisis, diseño y construcción de nuestro sistema : el sistema se puede recuperar de estas situaciones.

Como ejemplos podemos citar envíos duplicados de información, una caída de nuestra base de datos, información inválida o incosistente como un número de teléfono con un formato incorrecto…

Para mantener códigos robustos que manejen estas situaciones, los lenguajes de programación incorporan mecanismos para el manejo de excepciones como parte de sus estructuras de control.

Y las situaciones poco habituales que provocan resultados inesperados o incoherentes y que no controlamos ? Eso es un error en tiempo de ejecución en nuestro sistema, que puede desembocar en una inconsistencia de datos en nuestra base de datos o en la finalización inesperada de nuestro sistema.

Un buen indicador de la calidad de un sistema software es comprobar cómo responde cuando se producen fallos, ya sean esperados y controlados (excepciones) o inesperados e incontrolados (errores).

Excepciones en Java

En el caso de Java se utilizan bloques try – catch – finally, throw y throws, y las subclases de java.lang.Exception. Un método lanzarán (mediante un throw) una excepción (una clase que hereda java.lang.Exception) provocando que se abandone el flujo de ejecución normal y cediendo el control al bloque en el que se capturará (en un bloque try – catch – finally). En la declaración del método se incluye throws para avisar de que puede lanzar una excepción.

Existen muchas excepciones predefinidas en Java, pero también nosotros podemos crear excepciones particulares a nuestros sistemas : basta crear una clase que extienda de java.lang.Exception.

Existen dos grandes tipos de excepciones en Java :

  • Checked Exceptions (excepciones controladas). Se corresponden a errores previstos, controlados en el codigo y por tanto permite al sistema recuperarse. Son lo que hemos definido anteriormente como excepciones. El compilador Java es una gran ayuda ya que te obliga a capturarlas en un bloque try – catch – finally o a declarar que el método puede devolver una excepción (declarando un throws).
  • Unchecked Exceptions (excepciones no controladas). Son excepciones no previstas ni controladas, y por tanto puede provocar inconsistencia de datos y la finalización inesperada del sistema : lo que hemos definido anteriormente como errores en tiempo de ejecución. En Java se implementan como subclases de java.lang.RuntimeException (que a su vez hereda de java.lang.Exception) y el compilador no nos obliga a capturarlas ni a declarar que nuestros métodos pueden lanzarlas. NullPointerException o IllegalArgumentException son dos ejemplos de estas excepciones.

No olvides utilizar la parte finally de los bloques try – catch – finally para liberar recursos como conexiones o archivos.

Manejo de Excepciones en una arquitectura Java

Al invocar un método deberíamos obtener como resultado o bien el resultado normal de la ejecución o bien no hacer absolutamente nada. Las excepciones nos servirán para comunicar a las capas superiores información sobre por qué no se ha hecho nada si fuese necesario.

La utilización de bloques try – catch – finally es más costoso en términos de tiempo de ejecución y recursos que la utilización de bloques if else.

  • Por tanto es recomendable utilizar excepciones para controlar el flujo, es mejor utilizar los mecanismos de control de flujo ya existentes que consumen menos y son más fácilmente mantenibles.
  • Tampoco es recomendable capturar una excepción en un bloque catch y no hacer nada, o no hacer nada más que lanzarla nuevamente.

Es aconsejable que las excepciones no rompan la encapsulación. Por ejemplo si en una capa inferior se produce una excepción (por ejemplo una SQLException en la capa de persistencia), a la capa superior no le interesa conocer los detalles de implementación, sino:

  • si se puede recuperar del error, es aconsejable transformar la excepción original en otra excepción controlada o checked con la información necesaria para recuperar que la capa superiro se pueda recuperar.
  • o si no puede recuperarse del error pasamos a la capa superior una excepción no controlada o unchecked si no se debe continuar el flujo normal, o bien un resultado nulo si no se quiere hacer nada especial.

Sólo deberíamos de crear excepciones propias cuando aporten algo útil como información extra cuando sean capturadas o comportamientos propios (como un tratamiento especial en un log).

Al capturar excepciones también capturamos las que heredan de ésta. Por tanto al capturar una excepción del tipo java.lang.Exception también estamos capturando todas las unchecked (ya que heredan de java.lang.RuntimeException y ésta hereda de java.lang.Exception). Hay que tener cuidado de si esto es realmente lo que queremos.

Por todo lo dicho anteriormente, la gestión de las excepciones nos debería de servir para recuperarnos ante errores esperados. Pero deberíamos tener en cuenta que pueden existir errores no controlados. Podríamos gestionar estos errores en un único punto, desviando todas las excepciones no capturadas a un action donde se tratarán (típicamente presentación de una página de error con un mensaje y las acciones permitidas para el usuario, así como la auditoría del error). Podemos indicar en el web.xml esta redirección :

<error-page>

      <exception-type>java.lang.Throwable</exception-type>

      <location>/gestionErrores.do</location>

</error-page>

Enlaces recomendados

Comenta