Santiago L. Valdarrama

This blog will be populated with resources of my daily work in software engineering.

Monday, October 31, 2005

Testing Frameworks Using XP. Patterns and Strategies

MSc. Rigre G. Garciandía Sóñora.
Ing. Santiago Valdarrama del Pino.

(This pattern is the english translation of "Pruebas en Esqueletos de Software utilizando XP. Patrones y Estrategias")

Abstract.

The process of writing unit and acceptance tests is one of the most important practices in the application of Extreme Programming in the development of software projects. In order to apply this process to the development of frameworks, some changes should be applied. This paper proposes a set of practices for the development of software unit and acceptance tests in the creation of frameworks using Extreme Programming techniques. In the same way, a set of patterns are modified and adapted in order to be used to guide the test process of systems with the above mentioned characteristics.

Keywords: Extreme Programming, Tests, Frameworks, Patterns

Introduction

Together with the evolution of the Internet, there has proliferated the creation of applications for different domains for the construction, exposition and actualization of hierarchical information via the Net. Each time the necessity arises for an application with these characteristics, it is necessary to define and implement each one of the structures involved in this process, implying an enormous wasted time and the creation of a lot of potential errors. Due to these problems, the decision was made to generalize, design and implement a framework for the manipulation of resource catalogs, gaining this way, a flexible and accessible environment to manage this type of structures.

To implement the above mentioned framework, were used techniques of Extreme Programming (XP). Due to the characteristics that software frameworks present, XP cannot be strictly applied as was expounded in [1], but must be slightly modified in order to be correctly applied to the development of software applications that have these characteristics [6].

This paper attempts to propose a set of practices that should be considered when creating and applying unit and acceptance tests in the development of frameworks using Extreme Programming techniques. These practices were identified during the creation of the framework used in the manipulation of the resource catalogs previously mentioned. Similarly, a set of patterns to guide the development of one of the most important practices stated in the XP manifest [1] were selected and adapted through this paper.

Frameworks

A framework in software development is a high level design term expressed in the reutilization of code. A framework provides functionality with fixed aspects that never change, and variable aspects that will be subject to change. Different systems can be created starting from a framework depending on the configuration of its variable aspects.

A framework consists of a set of classes that cooperate between each other [5]. The interactions between these classes are, frequently, part of the fixed aspects of the framework. The high level design of the system, which defines the components of the solution, its interfaces and interactions, are reutilized within the framework, and as classes and methods become part of the fixed aspects, the code and the detailed design will also be reused [3]. Combining the use of high level designs, detailed designs and code, the framework promises high grades of reutilization.

Frameworks and XP

The development of frameworks using XP varies a little from the normal application of Extreme Programming. XP can be used for the development of frameworks with the objective of providing certain functionality to multiple, or many times, anonymous clients, but for this, it is necessary to slightly modify its structure [6].

The creation of a framework using XP presents characteristics that differ from the development of traditional applications. Amongst these we have, the loss of a single client, which provokes the absence of the role destined to determine the system requirements, and the priorities on its development. Another difference when applying XP to this type of systems is the loss of the concrete character of the user stories, arousing the necessity that these become as general as possible, satisfying the necessities of a greater number of clients. Lastly, in the development of traditional applications using XP, a mechanism of progressive development is used, not taking into consideration the order in which the characteristics of the system are implemented. However, in the development of frameworks, an early analysis must be done to determine the points in which similarities can be found to be modeled by the framework.

In [6], the authors expound a group of variations to the application of XP in the construction of frameworks. In order to create them, it cannot be considered only the set of user stories, as these could be only related to one business in particular, which could limit the reach of the final product. A valid technique in this case, is the realization of an analysis of the user stories defined by various clients in similar systems, so that it can be determined which of these could be a general character, so that the principal candidates are implemented within the framework. This is the form by which, the authors of [6] classify the stories of the clients in two fundamental groups, user stories, which contain the specific aspects for one particular system, and framework stories, which define those aspects that must be implemented according to the system to be developed.

Unit and Acceptance Testing in XP

During the development of software, unit testing increases exponentially the quality of the produced code. The tests are divided into two groups during the application of Extreme Programming; the first of these is where the writing of the unit testing is done, while the second is the writing of acceptance tests [1]. The application of unit tests and acceptance tests are some of the fundamental practices of XP [1].

The developers write the unit tests meanwhile the application source code is written. The client will write the acceptance tests while the user stories are defined. Maintaining a set of tests during the construction of the project, and executing them at regular intervals to validate the results obtained, will help in the development of the application [4].

For each one of the methods that could fail, a corresponding unit test should be written before writing the code that will pass the test. On the other hand, the developers should write the acceptance tests as soon as the user stories are written. The acceptance tests can be automatic or manual, being that the first are the preferred ones at all times, nevertheless that, on many occasions, is quite difficult to automate them.

Testing Frameworks

The tests for a framework differ a bit from the tests utilized in the development of traditional applications making use of XP. For the particular case of the frameworks, there must be taking into account two levels of acceptance tests: acceptance tests for user stories and the acceptance tests for the framework stories.

The acceptance tests for the user stories will be responsible for the verification of specific business aspects for each potential client in particular. In other words, these tests will verify the validity of each user story for each one of the clients in a set of systems that utilize the framework. The acceptance of the set of tests of the user stories will demonstrate the generality of the implemented framework. Hence, the greater the number of satisfactory acceptance tests, the greater is the guarantee that the created framework can be generalized for a greater number of case studies.

With the acceptance tests for the framework stories, an analogy can be made to the traditional acceptance tests for the user stories in the construction of systems making use of common Extreme Programming techniques. On this point, it can be analyzed from a normal application perspective, testing each one of the implemented functional aspects.

In spite of that the two tests mentioned before are a good starting place for the validation of a framework, the implementation of the example applications and the utilization of the framework in real applications will give the final word in that respect. As the framework is being applied to problems of this nature, it will gain maturity, being able to detect those aspects that were omitted in the initial planning that were not taken into account, in that moment, with the group of ideal aspects to be modeled.

In the construction of a framework, due to the absence of real applications that make use of this, it is advisable the construction of a group of example applications of a different nature, which will include the greatest number of possible functional characteristics, this way, creating test scenarios that permit the correct validation of the developed product. These applications will be the main weapon for test creation in a framework.

Patterns on Framework Testing

The utilization of patterns in the design and implementation of software is extremely important due to the advantages that they offer. A group of related patterns exists for the application of tests during the implementation of these patterns. Each of these patterns could be adapted to the implementation of frameworks making use of Extreme Programming techniques. Following a set of patterns which were modified for their application will be enumerated and discussed.

Problem Area

The Problem Area pattern, originally expounded in [2], deals with the areas of the system that should receive special attention in the realization of tests and the control of source code. The solution is to keep a list of the persistent problems areas. It should be reinforced the severeness in the construction of the unit tests, that verify these areas and prioritize the execution of such controls.

A different approach in which this pattern could be applied is when a problem arises in the execution of client applications of the implemented framework.

Due to the existent division in the documentation of requirements, defined in [6], such as user stories and framework stories, it is necessary to determine the location of the problem within one of these fields. If the problem is located in the user story, emphasis must be placed in the generality of the control of the framework, but if it is found in the framework stories, the implementation controls should be reinforced. As a result, common errors can be controlled which brings about a more reliable system free from uncertainties.

Strange Behavior

The Strange Behavior pattern, originally expounded in [2], deals with the decisions to be made when a characteristic of the implemented system is malfunctioning. Any unusual behavior should be taken as a possible problem and keep track of it. This should be done even when the identified problem is not related to the test case in question. It is necessary to be alert when the tests give acceptable results but different from the expected.

Just like the aforementioned pattern, the Strange Behavior Pattern can be analyzed from two points of view: user stories and framework stories. It is necessary to pinpoint the location of the undesired behavior within one of these fields. If the problem exists in the user story, it should be analyzed the generality of the framework as it may happen that it is not prepared to bear with the desired requirements; but if it located in the framework stories, the problem should be profoundly analyzed, in order to identify the internal algorithm that could be causing the unexpected results.

Killer Test

The Killer Test Pattern, originally expounded in [2], deals with the way in which the quality of a system in development can be determined. In order to do this, a test case should be built which can be executed anytime under any circumstances. This test case should be designed such that failure is imminent. In order for it to be constructed, all the critical and extreme test cases should be included, in the framework implementation. As the complexity of the test case increases, better is the evaluation of the quality of the system to be developed.

As the requirements of the framework are determined, a Killer Acceptation Test can be constructed at the same time. Far from being the correct implementation of the characteristics of the framework, it would center its application on the generality desired. The application of the variant of the Killer Test makes possible that the final product meets the necessities of a greater number of clients. The result of this control will be very appropriate to measure the stability of the system. When found and corrected this type of problem, the application will be more stable and efficient.

Document the Problem

The Document the Problem pattern, originally expounded in [2], deals with the way in which potential problems found during the implementation of a system should be communicated and documented. To apply the pattern, a report should be written with the identified problems. One cannot rely on memory, and promises that everything will be alright cannot be accepted; the problem should be stated in documents or error control systems. In this way, it is made sure that the problem is corrected as soon as possible, building the corresponding test case.

In the case that the problems are identified by the developers themselves, a strategy much better than the documentation of this, is the immediate writing of the corresponding test case for the correction of the problem. Violation of these considerations could lead to forget an error which could arise later on and cause the developed framework to malfunction.

Conclusions

The application of Extreme Programming in the development of frameworks should be modified to achieve a correct adaptation to systems that present these characteristics [6].

Along with this, some new techniques might be identified for the development of unit and acceptance tests of applications in the context aforementioned. The same way, the application of patterns to guide the design and implementation of such tests could lead to a dramatic improvement in the process organization of the test and control of the produced code.

During the course of this paper, basing it on the acquired experiences during the development of a framework to implement resource catalogs, a set of practices were identified and documented for the realization of tests, as well as a group of patterns utilized to guide the control process by using unit and acceptance tests in frameworks making use of Extreme Programming.

References

[1] Beck, K. Extreme Programming Explained: Embrace the Change. Addison - Wesley. (1999)

[2] DeLano, D. E. and Rising, L. System Test Pattern Language. "The Pattern Handbook. Techniques, Strategies, and Application". Cambridge University Press. (1996). pp. 97-119.

[3] Jacobson, I., Griss, M., et al. Software Reuse. Architecture, Process and Organization for Business Success. Addison - Wesley. (1997)

[4] Jeffries, R. Essential XP: Unit Tests at 100. http://www.xprogramming.com/xpmag/expUnitTestsAt100.htm. (2001). Last confirmed: November 05, 2003.

[5] Johnson, R. E. and Foote, B. Designing Reusable Classes. Object-Oriented Programming. Vol. 2. (1988). pp. 22-35.

Monday, October 24, 2005

Desarrollo Guiado por Pruebas


Sube el Telón


Software: una palabra que desde hace ya un tiempo ha pasado a formar parte muy importante en la vida de muchas personas a lo largo de todo el mundo. Unos, dependen de él para el cumplimiento normal de sus actividades diarias. Otros, lo tienen como su principal herramienta de trabajo. Algunos, un poco más dichosos, son los responsables de su creación y mantenimiento: hacen el papel de Dios como Creador, o de Madre Naturaleza, no importa como lo prefieran.

La creación de software es una actividad que desde los mismos inicios de la Era Digital ha reclutado en sus filas a miles y miles de personas que persiguen en su tarea un único objetivo: la perfección. Muchas páginas han sido escritas tratando de dar un poco de luz en el camino para lograr programas estables, flexibles, robustos y - nuevamente - , perfectos.

Tanto tiempo y - parece mentira - el hombre no ha logrado aún su principal anhelo: conjugar experiencia, destreza y conocimientos para construir un sistema - entiéndase sistema como software - libre de imperfecciones. Un sueño que ha resultado ser un paradigma prácticamente inalcanzable.

Todo creador - o programador, para ir entrando en términos técnicos - respondería tajantemente a la pregunta "¿Tiene problemas tu sistema?" con una rotunda negativa. Basta con modificar dos puntos: uno, el entrevistado; dos, el pronombre posesivo de la interrogante y formularle a un cliente del sistema "¿Tiene problemas su sistema?". Es muy probable que la lista resulte interminable y hasta irrisoria teniendo en cuenta la primera respuesta. Por alguna misteriosa razón, programador y cliente - o usuario, si lo prefiere - alimentan un volcán de diferencias y contradicciones; una batalla ancestral que nadie apoya y todos tenemos.

He tenido la suerte - o la desgracia - de vivir ambas vidas como otros tantos. He tenido respuestas, o mejor dicho, he tenido que responder las dos preguntas de la discordia. Cada una desde un punto de vista diferente, y - paradójicamente -, dando respuestas muy poco diferentes a las anteriores.

Esta posibilidad se ha convertido en inspiración y catalizador para dedicar gran parte de mi tiempo al estudio de diversas prácticas que me permitan acercarme cada vez más al cliente. Todos sabemos que la perfección, o no existe, o es demasiado costosa obtenerla; sin embargo, podemos lograr un producto final que se acerque lo suficiente como para cerrar en cierto modo la brecha que separa ambos mundos.

Primer Acto

De los cientos de técnicas que persiguen nuestro objetivo quisiera referirme no a la más moderna, sino a una de las más poderosas: el Test, o como diríamos los hispano parlantes, la Prueba. Seguramente una palabra tan oída últimamente como "calidad" nos haga pensar precisamente en este punto: el producto una vez concluido necesita pasar por una etapa de pruebas hasta tanto su calidad sea certificada.

En todo equipo de desarrollo, desde que se habla de la producción de software como ciencia, existe - o al menos debiera existir - un grupo de profesionales encargados de realizar las pruebas pertinentes al producto en desarrollo, de forma tal que el resultado final se acerque a los parámetros de calidad establecidos o deseados, en dependencia del caso.

Esto no es nuevo en ningún lugar, ni siquiera en nuestro país donde la producción de software es, si no una técnica de último momento, sí una tarea que hace muy poco tiempo es que ha comenzado a cobrar auge rápida y firmemente. La técnica de realizar pruebas al producto - en nuestro caso, al software - no tiene nada de novedoso, pero si variamos su forma de aplicación, entonces nos encontraremos en un camino prácticamente inexplorado por los equipos de desarrollo cubanos.

Más explícitamente me encuentro hablando de un concepto un tanto joven en el mundo entero: El TDD o Test-Driven Development - que en nuestro idioma sería algo así como "Desarrollo Guiado por Pruebas". Lo más sorprendente del TDD es que no incluye prácticamente ninguna idea nueva, sino que combina las prácticas existentes desde el mismo surgimiento de programas y programadores para "cocinar un delicioso guiso que garantice satisfacer su apetito de código limpio que funcione correctamente" [1].

El TDD es una disciplina de la cual pueden encontrarse cientos de documentos publicados con solo realizar una consulta en cualquiera de los buscadores de Internet. Digo disciplina y me refiero estrictamente al sentido directo de la palabra: la utilización de TDD requiere por parte de los programadores de la aplicación de un conjunto de prácticas de la forma, dónde y cuándo se requieran. No hay espacios para la improvisación o descontrol. TDD requiere de esfuerzo, conciencia, y - nuevamente - disciplina.

Para comenzar a formar parte de este mundo, un concepto debe cambiar radicalmente: probar un software no es una actividad que se limite al equipo designado para ello, es una tarea de todo programador. Cada persona será la encargada de probar aquellas secciones de código conformadas por ella. Cada sección de código que pueda fallar, debe ser probada.

Sería bueno definir el término probar. No se necesita ser un erudito para concluir que probar un software no es más que ejercitar cada una de las características funcionales del mismo, o técnicamente hablando, cada uno de sus requerimientos para detectar posibles anomalías o fallos - a los cuales comúnmente les damos el nombre de bugs - . Ahora, lo importante cuando hablamos de TDD, es definir precisamente cuáles serán los medios para hacer estas pruebas.

Un rápido estudio bibliográfico resalta dos tipos fundamentales de pruebas: pruebas unitarias o de unidad y pruebas de aceptación. Las primeras tienen su origen en la era anterior a la programación orientada a objetos y nos dicen que las pruebas individuales se concentrarán en unidades sencillas del sistema en vez de en el sistema completo [4]. Las pruebas unitarias serán construidas de forma automatizada y ejecutadas regularmente durante el desarrollo del proyecto.

Las pruebas de aceptación sirven tanto al cliente como al equipo responsable del proyecto como una medida para determinar el progreso del producto en construcción. Estas pruebas serán creadas por los clientes y especificarán la funcionalidad del sistema desde la perspectiva del usuario [2]. Normalmente, estas pruebas serán realizadas a mano y su ejecución estará estrechamente ligada con la conclusión de las etapas de entrega de los diferentes prototipos del producto.

Vamos a concentrarnos fundamentalmente en las pruebas unitarias ya que son las que tienen un mayor sentido desde el punto de vista del desarrollador. Para ello, vamos a recurrir a un ejemplo de código sencillo: la implementación más antigua de una función que determine si un número es primo o no. El código en Java es como sigue:
package cu.etecsa.samples;

public class Sample {
public static boolean isPrime(int number) {
for (int i = 2; i < number / 2 + 1; i++) {
if (number % i == 0) {
return false;
}
}
return true;
}
}

El ejemplo muestra una clase compuesta por una función estática la cual pretende devolver un valor booleano en dependencia de si el parámetro especificado es un número primo o no. Ahora bien, supongamos que queremos asegurarnos de que la función presente el comportamiento adecuado. Para esto, construimos un pequeño programa con el siguiente código:
package cu.etecsa.samples;

public class Main {
public static void main(String[] args) {
if (!Sample.isPrime(1) || !Sample.isPrime(2)) {
System.out.println("ERROR");
}
else if (Sample.isPrime(4) || Sample.isPrime(6)) {
System.out.println("ERROR");
}
else {
System.out.println("OK");
}
}
}
El código anterior sencillamente ejercitará nuestra función pasando diferentes valores para comprobar que los resultados sean los esperados. En caso de existir alguna anomalía, se imprimirá el mensaje ERROR, mientras que si todo sucede tal y como se espera, la cadena OK aparecerá en pantalla.

En pocos minutos hemos logrado construir un código y su correspondiente prueba. Si luego de cualquier cambio que sufra nuestro código ejecutamos su prueba correspondiente, podremos estar seguros si continúa trabajando o se introdujo algún error.

Cuando se desea hacer una prueba para una sección de código determinado, el escenario anterior será suficiente. El problema surge cuando deseamos que cada pieza de código que pueda fallar tenga su correspondiente prueba unitaria - precisamente estamos hablando de TDD -. Entonces necesitaremos mecanismos mucho más organizados y eficientes para clasificar y desarrollar cada una de las pruebas.

Es aquí donde entra en escena el proyecto xUnit. xUnit no es más que una plataforma para la realización de pruebas unitarias en cualquier lenguaje de programación. Existen versiones para casi todos los lenguajes como Java, Smalltalk, Delphi, C#, ASP, PHP, Visual Basic, C++ y Perl por solo mencionar algunos. Cada una de estas versiones cuenta con su propio nombre y diferentes características, pero todos persiguen el mismo objetivo final.

Veamos una prueba del ejemplo anterior haciendo uso de la biblioteca JUnit, diseñada para la construcción y ejecución de pruebas unitarias en lenguaje Java:
package cu.etecsa.samples;

import junit.framework.TestCase;

public class TestSample
extends TestCase {

public void testIsPrime() {
assertTrue(Sample.isPrime(1));
assertTrue(Sample.isPrime(2));
assertFalse(Sample.isPrime(4));
assertFalse(Sample.isPrime(6));
}
}
Ahora el código resultante es mucho más claro, flexible y fácil de mantener. JUnit - como cualquier buen miembro del proyecto xUnit - nos brinda herramientas muy poderosas para la realización de pruebas unitarias. Si comparamos este código con el ejemplo primeramente visto, caeremos en la cuenta de lo sencillo y claro que puede resultar escribir una prueba unitaria.

Segundo Acto

En un mundo lleno de símbolos, xUnit no podía quedar atrás. La utilización de colores opuestos para representar lo bueno y lo malo ha sido una técnica que ha primado a lo largo de la historia. El verde y el rojo - colores del semáforo con significados obvios - son las luces que xUnit utiliza para su propia interpretación de lo correcto y lo incorrecto. Al ser ejecutadas las pruebas, una barra indicadora tomará el color correspondiente en dependencia de si nuestras pruebas detectan errores o no.

El ejemplo anterior dará como resultado una barra verde, ya que la función producirá correctamente el valor resultante. Veamos ahora otro ejemplo donde la prueba detectará rápidamente un error en el código. Para la realización de este ejemplo, utilizaré el lenguaje C#, tan popular en nuestro país, y sobre todo, en nuestra empresa.

Supongamos que necesitamos crear una función que dados dos valores enteros devuelva la suma de ambos. Un ejemplo bien sencillo, cuya prueba haciendo uso de NUnit - versión xUnit para C# - quedará de la siguiente forma:
using NUnit.Framework;

namespace Etecsa.Samples.Test
{
[TestFixture]
public class TestCalculator
{
[Test]
public void TestSum()
{
Assert.AreEqual(3, Calculator.Sum(1, 2));
}
}
}
Teniendo la prueba, podemos ejecutarla y observar - como es lógico - que la barra resultante será roja debido a que ni siquiera hemos implementado el código de la función a probar. Pasemos a implementarla y así lograr que la prueba brinde un resultado satisfactorio al ser ejecutada:
namespace Etecsa.Samples;

public class Calculator {

public static int Sum(int number1, int number2)
{
return number1 + number1; // Error!
}
}
Al ejecutar la prueba esta vez, la barra continuará indicando un error en nuestro código. Como ya deben haber notado, deliberadamente he introducido un error en la función, realizando la suma con el primer número solamente, por lo cual el resultado final siempre estará alterado.

Si cambiamos el resultado de la función y conformamos adecuadamente la suma, la prueba correrá satisfactoriamente (barra verde). Errores como este en un código bastante complejo pueden tardar varios minutos en ser detectados sin la ayuda de pruebas unitarias.

Algo que me gustaría hacer notar en este punto es la forma de desarrollar el segundo ejemplo. Hemos construido la prueba antes de construir el código que logre que esta pueda ser ejecutada. Este estilo de programación es denominado Test-First Development - algo así como Desarrollo Primero de las Pruebas - y es el más citado por los programadores de TDD.

Tercer Acto

Dado que mi objetivo no es caer en detalles particulares de la plataforma xUnit, y mucho menos en sus variantes JUnit y NUnit, hablemos un poquito más de un conjunto de elementos teóricos imprescindibles, o resumiendo rápidamente en una pregunta ¿por qué debo construir pruebas unitarias?

Mi propio punto de vista - al menos el mismo que logró convencerme cuando aún era bastante escéptico respecto a estas prácticas - pudiera exponerlo en los siguientes cinco puntos:

Permiten detectar errores rápidamente en tiempo de diseño. Contar con un paquete de pruebas permitirá detectar de forma clara y rápida los defectos del código a medida que vayan surgiendo. Esto permitirá reducir considerablemente las horas necesarias de depuración que tanto molestan. El resultado será un producto mucho más robusto y libre de errores.

Brindan la confianza necesaria para realizar modificaciones en el código. ¿Alguna vez ha escuchado la célebre frase "Si funciona no lo toque"?. Apuesto que sí. Ciertamente nada es más cierto, pero yo preferiría - con el perdón del autor - modificar ligeramente la frase a la hora de aplicarla al contexto del que estamos tratando: "Si funciona, y no tiene forma de probar después de los cambios, no lo toque". Un conjunto de pruebas unitarias nos dará el coraje necesario para enfrentar cualquier reestructuración - o simples cambios - en nuestro código fuente. Bastará con ejecutar las pruebas para determinar si la operación resultó o si debemos dar marcha atrás.

Son la fuente de documentación más efectiva de nuestro código. Recuerdo que corría rápidamente hasta el ejemplo en el libro de texto cada vez que enfrentaba un problema cuya solución se me antojaba esquiva. Los ejemplos siempre han sido una herramienta muy poderosa para ganar conocimientos. Precisamente una prueba unitaria no es más que una forma de utilizar nuestro código, un ejemplo ejecutable de cómo explotarlo. Toneladas de documentación escrita pudieran opacarse - y hasta borrarse completamente - con un solo ejemplo.

Escribirlas resulta una tarea bastante divertida. Varios autores coinciden que escribir pruebas unitarias puede resultar una tarea adictiva [2, 3, 5]. Mi propia experiencia es que están en lo cierto. El paso del rojo al verde resulta una experiencia única que bien vale la pena disfrutar.

Cualquier característica de un programa que no tenga una prueba automatizada, simplemente no existe [2]. Este punto, a pesar de ser una cita textual, no podía dejar de incluirlo en el listado. Si algo no se ha probado ¿cómo podemos asegurar que realmente funciona? O mejor aún ¿podemos asegurar que siempre funcionará?

Estas son las fuerzas que han impulsado a tantos a convertir esta práctica como propia. Cada vez se publican más libros y artículos referentes al tema. Se realizan conferencias internacionales, se imparten seminarios y se emplean los más diversos métodos de educación para llevar el desarrollo de pruebas a cada rincón del mundo del desarrollo de software.

Por último, y unido a todo esto, los entornos modernos de desarrollo visual de lenguajes de programación incluyen herramientas para la realización de pruebas unitarias, o al menos vías simples para integrarlos con las versiones existentes de xUnit. Tales son los casos de Eclipse, JBuilder, VisualAge, Emacs, etc. Los dos primeros para Java, el tercero para Smalltalk y el último para C++. Cabe hacer un paréntesis y preguntarse cómo es posible que .NET aún no tenga integradas en su entorno de desarrollo estas herramientas para la construcción de pruebas unitarias, pero esto es tema de otra discusión.

Baja el Telón

La programación de pruebas unitarias es una tarea que resultará siempre chocante a cualquier programador en el mundo entero. Increíblemente, los ateos se convierten en fervientes creyentes una vez que experimentan a cabalidad todas sus ventajas. Su realización - mucho más si hablamos de TDD - es una tarea, como ya decía, que requiere mucha constancia y disciplina.

Son muchas las técnicas y trucos que se han escrito para escribir pruebas unitarias efectivas. Son unos cuantos los libros que engrosan los estantes en bibliotecas y librerías hablando de estos temas. No se concibe un programador moderno que no incluya la palabra prueba en su vocabulario.

Es nuestra oportunidad ahora para experimentar algo así. Empezar por lo simple hasta que logremos cada vez más adentrarnos en las más diversas formas de la construcción de pruebas. La práctica sistemática nos convertirá en sus más fieles seguidores. Déles una oportunidad y verá que no se arrepentirá.

Bibliografía

[1] Beck, Kent. "Test-Driven Development By Example". Addison-Wesley, 2002.
[2] Beck, Kent. "Extreme Programming Explained: Embrace Change". Addison-Wesley, 2000.
[3] Beck, Kent; Gamma Erich. "Test-infected: Programmers Love Writing Tests". JavaReport, 1998.
[4] Link, Johannes; Frohlich, Peter. "Unit Testing in Java: How Tests Drive the Code". Morgan Kaufmann, 2003.
[5] Massol, Vicent; Husted, Ted. "JUnit in Action". Manning Publications, 2004.

Wednesday, October 12, 2005

Programación Extrema en Pocos Minutos. Planificando la Transición.

Ing. Santiago Luis Valdarrama del Pino

(Artículo publicado en el número 2 de 2005 de la revista "Tono" de la Empresa de Telecomunicaciones de Cuba, SA)

Metodologías de Diseño

El mundo del desarrollo de software ha transitado por tres espacios bien definidos. El primero de ellos, conocido como “codificar y corregir”, trabajaba bien mientras los programas eran pequeños y los requisitos cambiaban con poca frecuencia; pero a medida que los sistemas fueron creciendo, la dificultad para agregar nuevas características y realizar modificaciones aumentó de forma exponencial.

Afortunadamente, una nueva alternativa surgió para contrarrestar los efectos negativos del “codificar y corregir”: las metodologías. Una metodología impone un proceso disciplinado en el desarrollo del software con el objetivo de lograr que éste sea lo más predecible y eficaz posible. La utilización de metodologías da lugar a un proceso que requiere de un análisis detallado, haciéndose hincapié especialmente en la planificación del producto a desarrollar.

Al igual que el primer espacio, el constante cambio de los requisitos en un sistema provoca un cambio significativo en los documentos desarrollados haciendo uso de las metodologías. Además, en la etapa de diseño es prácticamente imposible prever cada uno de los puntos con los que se deberá lidiar a la hora de programar, por lo que luego de comenzada la codificación, irán apareciendo, cada vez con más frecuencia, evidencias que cuestionen el diseño realizado. Esto provoca en el desarrollo de un proyecto que la mayor parte del tiempo se concentre en la documentación burocrática del proceso en vez de en la codificación de éste.

Las acertadamente conocidas como metodologías ligeras o de poco peso (lightweight methodologies) han ido cobrando auge en los últimos tiempos. Estos métodos surgen como contraparte de sus dos antecesores, buscando un punto central entre la no existencia de un proceso y un proceso exageradamente grande. Las metodologías ligeras – también conocidas como “metodologías ágiles” (agile methodologies) – realzan más la importancia de la programación del código que la documentación del proceso.

En nuestro país, el desarrollo de software se ha convertido en una actividad fundamental como medio de soporte a la actividad interna de las entidades que conforman la Empresa Cubana. En un mundo en constante revolución, la búsqueda de soluciones para potenciar el desarrollo de sistemas computacionales ocupa un lugar preponderante en la agenda del personal involucrado. Es por este motivo, que con el objetivo de introducir su utilización en la empresa de software cubana, realizamos un análisis de uno los exponentes más populares de las metodologías ágiles existentes en nuestros días: la Programación Extrema.

Programación Extrema

La Programación Extrema, conocida coloquialmente como XP (del inglés eXtreme Programming y nada tiene que ver con el término utilizado por Microsoft en Windows XP que viene de la palabra eXPerience), pone de manifiesto un grupo de principios y prácticas que aceleran el proceso de desarrollo de software, eliminando las técnicas innecesarias utilizadas por las metodologías pesadas. XP ha rejuvenecido la noción de diseño evolutivo con prácticas que permiten que dicha evolución se transforme en una estrategia de diseño viable y seguro.

Durante años, uno de los principales problemas en el desarrollo de software ha sido que el costo de realizar modificaciones en un sistema durante su desarrollo se incrementa exponencialmente con el tiempo. Los requisitos de los usuarios o clientes cambian y serán diferentes cada año, cada mes o cada día. Si el sistema no puede cambiar rápidamente, en poco tiempo perderá la órbita prevista. La Programación Extrema acoge el cambio y permite cambiar la dirección, violenta y frecuentemente, del desarrollo de la aplicación sin afectar el producto final y el calendario previsto.

Por otra parte, un desarrollo de software exitoso es el producto de un esfuerzo de equipo, no solamente de sus desarrolladores, sino también de sus directivos y clientes. XP es un proceso de desarrollo de software simple que une a todas estas personas y las lleva juntas al éxito.

En su libro “Extreme Programming Explained: Embrace the Change”, Kent Beck subraya los valores fundamentales de la Programación Extrema. Estos valores pueden ser resumidos de la siguiente forma:

  1. Comunicación: No puede existir un equipo de desarrollo exitoso sin la presencia de una buena comunicación entre sus miembros. La supervisión debe concentrarse menos en decirle a las personas qué hacer, y más en potenciar la comunicación para lograr que las personas lleguen a respuestas creativas por ellos mismos. El modelo de trabajo de la Programación Extrema hace imposible que exista una pobre comunicación.
  2. Retroalimentación: Para lograr el éxito es necesaria una retroalimentación constante manteniendo al equipo informado de la situación actual en la cual se encuentra. Una buena retroalimentación logrará que clientes y desarrolladores avancen todo el tiempo por el camino de la victoria.
  3. Simplicidad: Dos de los principios más citados de la Programación Extrema son “Haga la cosa más simple que posiblemente funcione” (Do the Simplest Thing that Could Possibly Work) y “Usted no va a necesitarlo” (You Aren't Going to Need It – YAGNI). La idea fundamental perseguida con estos principios es no malgastar esfuerzos adicionando funcionalidad que no será necesitada en el producto planificado.
  4. Coraje: Si el equipo de proyecto no se mueve a la máxima velocidad, el trabajo fallará sin remedio alguno. El coraje es un arma fundamental en la Programación Extrema para hacer frente a situaciones difíciles donde una decisión podría significar el éxito o fracaso del proyecto.

Algo realmente impresionante de XP es que entre las prácticas que propone hay muy poco de novedoso. Cada uno de sus principios ha sido utilizado y discutido a lo largo de mucho tiempo; sin embargo, puestos en conjunto, logran una perfecta armonía haciendo de la Programación Extrema una disciplina perfectamente organizada que brinda una vía para incrementar velocidad y eficiencia en el desarrollo de software.

Para lograr entender qué es y cómo hacer XP, veamos grosso modo cada una de las doce prácticas de la Programación Extrema.

Prácticas de la Programación Extrema

Planeamiento del Juego

El Planeamiento del Juego o “The Planning Game” es la etapa donde clientes y desarrolladores construyen un plan inicial para el desarrollo del proyecto y a medida que éste avanza, van refinándolo sucesivamente hasta su culminación. El proceso de planificación puede ser visto como un juego, siendo el cliente y los desarrolladores los jugadores; las fichas, pequeños requisitos escritos sobre tarjetas, y una serie de movimientos válidos que incluyen cada una de las responsabilidades de los jugadores. La planificación será realizada frecuentemente a lo largo de todo el proceso de desarrollo, permitiendo que el equipo de trabajo perfeccione su concepción acerca del sistema, y brindando un excelente control sobre el desarrollo de éste.

Pruebas

Las Pruebas o “Testing” son divididas en dos grupos durante la aplicación de la Programación Extrema; el primero de ellos es el que comprende la escritura de Pruebas Unitarias (Test Units), mientras que el segundo es la escritura de Pruebas de Aceptación (Acceptance Tests). Los desarrolladores escribirán las pruebas unitarias a medida que escriban el código. El cliente escribirá las pruebas de aceptación a medida que defina los requisitos del sistema. Mantener un conjunto de pruebas durante la construcción del proyecto, y correrlas a intervalos regulares para validar los resultados obtenidos, asegurará mantener la calidad esperada del producto final.

Programación en Parejas

Una de las prácticas más discutidas es la Programación en Parejas o “Pair Programming”. La producción de código en un proyecto utilizando Programación Extrema será realizada en parejas de desarrolladores, los cuales compartirán un mismo ordenador, monitor y teclado. Podría parecer un tanto ineficiente la utilización de dos programadores en la realización de una misma tarea, pero la realidad ha demostrado todo lo contrario. Investigaciones realizadas en este campo demuestran que dos programadores trabajando en conjunto generan, en el mismo espacio de tiempo, códigos de mejor calidad que el producido trabajando de forma separada.

Refactorización

La Refactorización o “Refactoring” es el proceso de cambiar un sistema de software de forma tal que el comportamiento externo del código no sea alterado. En la aplicación de la Programación Extrema, los miembros del equipo de desarrollo refactorizarán el código del proyecto siempre y cuando sea necesario. El principal objetivo de la refactorización en XP es lograr un código más simple y legible, eliminando cualquier vestigio de duplicados. La refactorización eliminará fuentes potenciales de errores y disminuirá posibles problemas a futuros desarrolladores guiándolos en la dirección correcta.

Diseño Simple

Como fue mencionado anteriormente, el Diseño Simple o “Simple Design” establece que siempre deben ser realizadas las tareas de la forma más simple posible. Para lograr un diseño simple, nunca serán adicionadas características funcionales que no formen parte de la propuesta de requisitos analizada en la planificación del juego. En la Programación Extrema, un buen diseño es esencial para asegurar el éxito esperado.

Propiedad Colectiva del Código

La Propiedad Colectiva del Código o “Collective Code Ownership” establece que cualquier miembro del equipo de desarrollo tendrá la autoridad y la capacidad de realizar cambios en el código del proyecto para lograr una mejora en éste. Cada uno de los miembros del equipo de desarrollo rotará en la implementación de cada módulo, logrando una completa capacitación en todo el producto. Cada desarrollador estará informado de las modificaciones realizadas en todas las secciones implementadas.

Integración Continua

La Integración Continua o “Continuos Integration” estipula que deberán realizarse cada día varias integraciones del código del proyecto. Esta práctica evitará la mayor parte de los problemas que ocurren cuando un equipo integra el trabajo pasado largo tiempo, y comienzan los errores sin saber nadie dónde y por qué. La Integración Continua mantendrá al equipo de desarrollo moviéndose a la máxima velocidad, evitando congelamientos de código y una gran fuente potencial de errores. Una integración frecuente aumentará la posibilidad de descubrir de forma temprana los errores que vayan surgiendo en el proyecto y de esta forma corregirlos inmediatamente.

Cliente en el Lugar

El Cliente en el Lugar o “On-site Customer” establece que para lograr funcionar de forma óptima, el equipo de Programación Extrema deberá contar con el cliente a su lado para todo el proceso de planificación para aclarar posibles dudas y tomar decisiones críticas en las reglas de negocio de la aplicación. Una comunicación cara a cara entre cliente y desarrollador eliminará malentendidos así como posibles cuellos de botella que retrasen el desarrollo del proyecto.

Entregas Pequeñas

Las Entregas Pequeñas o “Small Releases” es la planificación y entrega de versiones estables y funcionales del código del proyecto luego de culminada cada iteración, logrando de esta forma una constante retroalimentación por parte del cliente que ayude a mantener al equipo de desarrollo al tanto de la actividad proyectada. La entrega de versiones pequeñas conlleva a un análisis más rápido y efectivo por parte del cliente, evitándose malentendidos con los requisitos por parte de los desarrolladores. Además, podrán ser tomadas decisiones que no afecten o den al traste con el trabajo de una gran cantidad de días en caso de que el resultado no satisfaga las necesidades proyectadas.

40 Horas a la Semana

Las 40 Horas a la Semana o “40-hours Week” establecen el tiempo promedio de trabajo del equipo de desarrollo del proyecto en 5 días hábiles. Una persona promedio, independientemente de su capacidad laboral, disminuye su rendimiento pasadas las 8 horas diarias de trabajo. Esto provoca que, paralelamente, disminuya la calidad del código producido. Una sobredosis en el tiempo de desarrollo no es la respuesta adecuada a los problemas en un proyecto, sino que es un síntoma de un problema aún más grave. Una correcta dosificación del tiempo logrará que los recursos humanos se comporten a su máxima capacidad, manteniendo sin irritaciones la calidad del producto final.

Estándares de Código

La aplicación de Estándares de Código o “Coding Standards” logrará un ambiente familiar en el código entre cada uno de los miembros del equipo de desarrollo. Sin la aplicación de éstos será bien difícil la realización de refactorizaciones, construcción de pruebas unitarias y la comprensión por parte de otros programadores. El objetivo de esta práctica es lograr de cada pieza de código un espejo de sus vecinas en cuanto a claridad y simplicidad, no importa la persona que la realice.

Metáfora

La Metáfora o “Metaphor” es análoga o lo que la mayoría de las metodologías llaman arquitectura. La metáfora brindará al equipo de desarrollo una visión común del funcionamiento del sistema a través de una evocativa y simple descripción. En caso de que no pueda ser encontrada una buena variante para la construcción de la metáfora de un proyecto, el equipo de XP usará un sistema de nombres comunes para asegurarse que cada uno de sus miembros comprenda el modo de funcionamiento general del sistema.

La Transición

La introducción de la Programación Extrema en nuestra empresa de desarrollo de software supone un paso de avance en materia de ingeniería, pero introduce dificultades con las que los directivos encargados de equipos de proyecto tendrán que lidiar. XP ha polarizado la industria de desarrollo de software: es difícil encontrar desarrolladores que sean neutrales en cuanto a temas relacionados con la Programación Extrema, debido a que muchas de sus prácticas difieren de las formas tradicionales de programación. Por ejemplo, la programación en parejas y la introducción de pruebas unitarias a medida que se desarrolla el sistema son prácticas acerca de las cuales los desarrolladores que tratan de aplicar XP comentan las dificultades de llevarlas a cabo.

La llave del éxito en la transición hacia el desarrollo de software guiado por la Programación Extrema se encuentra centrada en tres conceptos fundamentales: métodos, aprendizaje y dirección. El primero de ellos, se refiere a seguir los principios y estrategias de la Programación Extrema. El segundo hace referencia a la necesidad de dejar a un lado los viejos conceptos y salir en busca de nuevos conocimientos que provoquen el desarrollo. El tercero resalta la necesidad de un líder en el equipo que vea el proceso como un todo y llame la atención de los miembros para impedir el desvío por caminos errados.

El hombre teme equivocarse. Cuando no conoce prefiere mantenerse al margen. ¡Pero la equivocación es esencial para el aprendizaje! La persona que se avergüenza de cometer errores, se avergüenza de aprender. El error más grande que se comete es temer constantemente equivocarse. Este comportamiento hace que los desarrolladores estén en constante defensiva, no teniendo el coraje para probar otras alternativas y ser creativos, ¡y así no funciona XP! Para la utilización de XP simplemente deben aplicarse sus principios haciendo uso del mejor esfuerzo. Contar con buenos comunicadores y tener a los miembros del equipo en el lugar correcto juegan un papel fundamental hacia la transición a la aplicación de la Programación Extrema. Con la ayuda del personal capacitado, XP debe convertirse es un gran desafío para poco a poco llegar a aplicar sus prácticas al más alto nivel.

Por último, y posiblemente lo más importante, contar con un equipo de personas que se relacionen bien el uno con el otro, ayudará en gran medida a la adopción de las prácticas más difíciles de XP. La Programación Extrema es una forma social de desarrollo de software. Gran parte de su éxito se encuentra concentrado en las personas relacionadas con el producto que se pretende desarrollar. Todo esto es importante para la creación de una cultura donde los miembros del equipo no teman en ayudarse unos a otros a realizar las tareas que lleven el proyecto al éxito final.

¿Dónde encontrar más información?

Varios autores han escrito diferentes artículos y libros acerca de XP. Estos materiales, conjuntamente con los sitios en Internet a los que hacemos referencia en esta sección son de vital importancia para la comprensión a cabalidad del proceso de desarrollo de software haciendo uso de metodologías ágiles, más específicamente, haciendo uso de la Programación Extrema.

Entre los libros, primeramente tenemos el manifiesto de XP; “Extreme Programming Explained: Embrace the Change”, donde su autor, Kent Beck, expone los conceptos y la filosofía de la Programación Extrema. Este libro enseña el qué y el por qué de XP.

“Extreme Programming Installed” por Ron Jeffries, Chet Hendrickson, y Ann Anderson trata cada una de las prácticas específicas de la programación Extrema. El objetivo fundamental del material es enseñar cómo aplicar XP en un proyecto de desarrollo de software.

En el sitio oficial de Martin Fowler (www.martinfowler.com), conocido científico del mundo del software, existe una gran cantidad de artículos relacionado con la Programación Extrema y cada una de sus prácticas. Muy especialmente, el artículo “The New Methodology” brinda una reseña de las metodologías ágiles y discute varias ideas en cuanto a la forma de usarlas.

En Internet, www.xprogramming.com contiene un conjunto de historias, lecciones y artículos relacionados con la aplicación de la Programación Extrema en el proyecto de software C3. www.xpdeveloper.com es un sitio de discusión acerca de cómo realizar XP y www.extremeprogramming.org contiene una guía excelente de todos los conceptos relacionados con la Programación Extrema.

No sin antes decir…

Finalmente, no quisiera terminar el artículo sin antes decir que lograr un impulso en el desarrollo actual de la producción de software es tarea de todo el personal involucrado en esta esfera cada día más importante. De este modo queda abierta la discusión y me gustaría ser partícipe de la misma. Todos los comentarios al respecto son bienvenidos en mi dirección electrónica svpino@cmg.tel.etecsa.cu. A menudo, las cosas pequeñas son las que logran a largo plazo grandes diferencias. Espero que este sea el comienzo de una revolución interna que aumente cada día más la eficiencia del producto desarrollado.

Pruebas en Esqueletos de Software utilizando XP. Patrones y Estrategias

MSc. Rigre G. Garciandía Sóñora.
Ing. Santiago Valdarrama del Pino.

(Este patrón es la traducción en español del artículo "Testing Frameworks Using XP. Patterns and Strategies")

Resumen

Este artículo describe un conjunto de prácticas para el desarrollo del proceso de control de pruebas unitarias y de aceptación en la confección de esqueletos de software haciendo uso de técnicas de Programación Extrema. Del mismo modo, son identificados y documentados un conjunto de patrones encargados de guiar el proceso de pruebas en la realización de sistemas con las características antes mencionadas.

Introducción

Conjuntamente con el desarrollo de Internet ha proliferado la creación de aplicaciones de disímiles dominios para la construcción, exposición y actualización de información en forma jerárquica a través de la Red de Redes. Cada vez que comienza el desarrollo de una aplicación con estas características, es necesario realizar la definición e implementación de cada una de las estructuras involucradas en el proceso, conllevando esto a una enorme pérdida de tiempo y creando una gran fuente de errores potenciales. Debido a estos problemas, se decidió generalizar, diseñar e implementar un esqueleto para la manipulación de catálogos de recursos, logrando de este modo un entorno flexible y asequible para la manipulación de este tipo de estructuras.

Para la implementación del esqueleto fueron utilizadas técnicas de Programación Extrema (XP). Por las características que presentan los esqueletos de software, la aplicación de XP no puede ser completamente llevada a cabo de la forma expuesta en [1], sino que debe sufrir ligeras modificaciones para lograr una correcta adaptación a aplicaciones que presenten estas características [6].

Este trabajo pretende exponer un conjunto de prácticas a tener en cuenta en la confección y aplicación de pruebas unitarias y de aceptación en el desarrollo de esqueletos de software haciendo uso de técnicas de programación extrema. Dichas prácticas son el fruto de las experiencias adquiridas durante la confección del esqueleto para la manipulación de catálogos de recursos antes mencionado. De la misma forma, fueron seleccionados y adaptados un conjunto de patrones para guiar el desarrollo de una de las prácticas más importantes expuestas en el manifiesto de XP [1].

Esqueletos de Software

Un esqueleto en el desarrollo de software es un término de diseño de alto nivel expresado en la reutilización de código. Un esqueleto provee funcionalidad con aspectos fijos que nunca cambiarán y aspectos variables que estarán sujetos a cambios. Sistemas diferentes pueden ser creados a partir de un esqueleto en dependencia de cómo sean configurados sus aspectos variables.

Un esqueleto de software abarca un conjunto de clases que cooperan entre sí [5]. Las interacciones entre estas clases son, frecuentemente, parte de los aspectos fijos del esqueleto. El diseño de alto nivel del sistema, el cual define los componentes de la solución, sus interfaces y sus interacciones, es reutilizado dentro del esqueleto, y a medida que clases o métodos completos pasen a formar parte de los aspectos fijos de éste, el código y el diseño detallado podrán ser también reutilizados [3]. Combinando el uso de diseños de alto nivel, diseños detallados y código, los esqueletos prometen altos grados de reutilización.

Esqueletos de Software y XP

El desarrollo de esqueletos de software utilizando XP varía un tanto de la aplicación habitual de la Programación Extrema. XP puede ser utilizado para el desarrollo de esqueletos de software con el objetivo de brindar determinada funcionalidad a múltiples, o muchas veces, clientes anónimos, pero para esto, es necesario modificar ligeramente su estructura [6].

La confección de un esqueleto de software haciendo uso de XP presenta características que lo diferencian del desarrollo de aplicaciones tradicionales. Entre estas tenemos, la perdida de un único cliente, lo que provoca la ausencia del rol encargado de determinar los requisitos del sistema, así como las prioridades en el desarrollo de éste. Otra de las diferencias al aplicar XP a este tipo de sistemas, es la pérdida del carácter concreto de las historias de usuarios, surgiendo la necesidad de que éstas sean lo más general posible para de este modo, satisfacer las necesidades de un mayor número de clientes. Por último, en el desarrollo de aplicaciones tradicionales haciendo uso de XP, se utiliza un mecanismo de desarrollo progresivo, no importando el orden en que las características del sistema son implementadas; sin embargo, en el desarrollo de esqueletos de software, debe ser realizado un análisis a priori para determinar los puntos en los cuales se pueden encontrar similitudes a modelar por el esqueleto.

En [6], los autores exponen un grupo de variaciones a la aplicación de XP en la construcción de esqueletos de software. Para la confección de éstos, no se puede asumir un conjunto único de historias de usuario, ya que éstas pudieran estar únicamente relacionadas a un negocio en particular, lo que podría limitar el alcance del producto final. Una técnica válida en este caso, es la realización de un análisis de las historias de usuarios definidas por varios clientes en sistemas similares, para así determinar cuales de éstas pudiesen ser de carácter general, siendo las principales candidatas a ser implementadas en el esqueleto. De esta forma, los autores de [6] clasifican las historias de los clientes en dos grupos fundamentales, historias de usuarios, las cuales contienen los aspectos específicos a un sistema en particular, e historias de esqueleto, las cuales definirán aquellos aspectos que deben ser implementados por el sistema a desarrollar.

Pruebas Unitarias y de Aceptación en XP

Las pruebas unitarias durante el desarrollo de software incrementan exponencialmente la calidad del código producido. Las Pruebas son divididas en dos grupos durante la aplicación de la Programación Extrema; el primero de ellos es el que comprende la escritura de Pruebas Unitarias, mientras que el segundo es la escritura de Pruebas de Aceptación [1]. La aplicación de pruebas unitarias y de aceptación son unas de las prácticas fundamentales de XP [1].

Los desarrolladores escribirán las pruebas unitarias a medida que escriban el código. El cliente escribirá las pruebas de aceptación a medida que defina las historias de usuario. Mantener un conjunto de pruebas durante la construcción del proyecto, y correr las mismas a intervalos regulares para validar los resultados obtenidos, ayudará el desarrollo de la solución propuesta [4].

Para cada uno de los métodos del código que pudiese fallar, deberá ser escrita la correspondiente prueba unitaria. Dichas pruebas se desarrollarán antes de escribir el código que logrará que éstas pasen. Por otra parte, los clientes desarrollarán las pruebas de aceptación luego de que sean escritas las historias de usuario. Las pruebas de aceptación pudiesen ser automáticas o manuales, siendo las primeras las preferidas en todo momento, a pesar de que en muchas ocasiones es bastante difícil la construcción de éstas.

Pruebas en Esqueletos de Software

Las pruebas para un esqueleto de software difieren un tanto de las pruebas utilizadas en el desarrollo de aplicaciones tradicionales haciendo uso de XP. Para el caso particular de los esqueletos hay que tener en cuenta dos niveles de pruebas de aceptación: las pruebas de aceptación para las historias de usuarios y las pruebas de aceptación para las historias de esqueleto.

Las pruebas de aceptación para las historias de usuarios serán las responsables de verificar los aspectos específicos del negocio para cada cliente potencial en particular, o sea, que estas pruebas verificarán la validez de cada historia de usuario para cada uno de los clientes en un conjunto de sistemas que utilicen el esqueleto. La aceptación del conjunto de pruebas de historias de usuario demostrará la generalidad del esqueleto implementado. Mientras mayor sea el conjunto de pruebas de aceptación de historias de usuario satisfactorias, mayor será la garantía de que el esqueleto confeccionado pueda ser generalizado a un mayor número de casos de estudio.

Con las pruebas de aceptación para las historias de esqueleto puede hacerse una analogía con las tradicionales pruebas de aceptación para historias de usuarios en la construcción de sistemas haciendo uso de las técnicas comunes de la Programación Extrema. En este punto, se puede analizar el esqueleto desde la perspectiva de una aplicación normal, probando cada uno de los aspectos funcionales implementados.

A pesar de que los dos tipos de pruebas mencionados con anterioridad son un buen punto de partida para la validación de un esqueleto de software, la implementación de aplicaciones de ejemplos y la utilización del esqueleto en aplicaciones reales, dirán la última palabra al respecto. A medida que sea aplicado el esqueleto en problemas de esta índole, irá ganado en madurez, pudiendo detectarse aquellos aspectos omitidos en la planificación inicial por no contarse, en ese momento, con el conjunto idóneo de aspectos a modelar.

En la construcción de un esqueleto de software, debido a la ausencia de aplicaciones reales que hagan uso de éste, es aconsejable la construcción de un conjunto de aplicaciones de ejemplos de diferente índole, las cuales, abarquen el mayor número posible de características funcionales, creándose de esta forma escenarios de prueba que permitan la correcta validación del producto desarrollado. Estas aplicaciones serán el arma fundamental para la realización de pruebas a un esqueleto de software.

Patrones en Pruebas para Esqueletos de Software

La utilización de patrones en el diseño e implementación de software es extremadamente importante de acuerdo con las ventajas que éstos brindan. Existe un grupo de patrones relacionados con la aplicación de pruebas a sistemas durante la implementación de estos. Cada uno de estos patrones pudiese ser adaptado a la implementación de esqueletos de software haciendo uso de las técnicas de Programación Extrema. A continuación serán enumerados y argumentados un conjunto de patrones que fueron modificados para su aplicación a la realización de pruebas a esqueletos de software.

Área del Problema

El patrón Área del Problema, originalmente expuesto en [2], especifica las áreas del sistema que deberán recibir especial atención en la realización de pruebas y control de código fuente. La solución es mantener una lista de las áreas con problemas persistentes. Debe ser reforzada la rigurosidad en la construcción de las pruebas unitarias que verifiquen estas áreas y priorizar la ejecución de dichos controles.

Otro enfoque en el cual pudiera aplicarse este patrón, es desde el punto de vista de la aparición de un problema en la ejecución de las aplicaciones clientes del esqueleto implementado. Debido a la división existente en la documentación de los requerimientos, definidos en [6] como historias de usuario e historias de esqueleto, es necesario determinar la ubicación del problema dentro de uno de estos campos. Si el problema se encuentra en las historias de usuario, deberá hacerse énfasis en los controles a la generalidad del esqueleto, mientras que si se encuentra en las historias de esqueleto, deberán reforzarse los controles a la implementación de éste. Como resultado podrán ser controlados errores que ocurren con determinada frecuencia, lo que trae consigo un sistema mucho más confiable y libre de desaciertos.

Comportamiento Raro

El patrón Comportamiento Raro, originalmente expuesto en [2], trata sobre las decisiones a tomar cuando una característica del sistema implementado se encuentra funcionando pero no de la forma esperada. Debe tomarse cualquier comportamiento inusual como un posible problema y seguir su pista. Esto debe ser realizado aún cuando el problema identificado no se encuentre relacionado con el caso de prueba en cuestión. Es necesario estar alerta cuando las pruebas arrojen resultados aceptables pero diferentes de lo esperado.

Al igual que el patrón expuesto anteriormente, el patrón Comportamiento Raro se puede analizar desde dos perspectivas, historias de usuarios e historias de esqueleto. Es necesaria la ubicación del comportamiento no deseado dentro de uno de estos campos. Si el problema se encuentra en las historias de usuario, deberá analizarse la generalidad del esqueleto ya que puede ser que no esté preparado para soportar los requerimientos deseados; mientras que si se encuentra en las historias de esqueleto, deberá analizarse a fondo el problema, tratando de llegar a la identificación del algoritmo interno que puede estar dando al traste con los resultados esperados.

Control Asesino

El patrón Control Asesino, originalmente expuesto en [2], especifica la forma en que la calidad de un sistema en desarrollo puede ser determinada. Para esto debe ser construido un caso de prueba que pueda ser ejecutado en cualquier momento bajo cualquier circunstancia. Este caso de prueba debe encontrarse diseñado de forma tal que sea inminente su fallo. Para su construcción, deben ser incluidos todos los casos críticos y extremos en la implementación del esqueleto. Mientras más compleja sea la semántica del caso de prueba, mejor podrá evaluarse la calidad del sistema en desarrollo.

A medida que se vayan determinando los requerimientos del esqueleto, pudiera irse construyendo también un Control Asesino de Aceptación. Éste, lejos de probar la correcta implementación de las características del esqueleto, centraría su aplicación en la generalidad deseada. La aplicación de esta variante del patrón Control Asesino trae consigo que el producto final esté acorde a las necesidades de un mayor número de clientes. El resultado de este control será un buen termómetro para medir la estabilidad del sistema. Al ser encontrados y corregidos este tipo de problemas, la aplicación será gradualmente mucho más estable y eficiente.

Documentar el Problema

El patrón Documentar el Problema, originalmente expuesto en [2], explica la manera en que los problemas potenciales encontrados durante la implementación de un sistema deben ser comunicados y documentados. Para su aplicación es necesario escribir un reporte con los problemas identificados. No puede confiarse en la memoria, no pueden ser aceptadas promesas de que todo será corregido; el problema debe quedar plasmado en documentos o sistemas de control de errores. De esta forma, se asegura que el problema sea corregido en el menor tiempo posible, escribiéndose los correspondientes casos de prueba.

En caso de que los problemas sean identificados por los propios desarrolladores del sistema, una estrategia mucho mejor que la documentación de éste, es la escritura inmediata del correspondiente caso de prueba para la corrección del problema encontrado. Violar estas consideraciones pudiera acarrear el olvido de un error que más tarde dará al traste con el buen funcionamiento del esqueleto desarrollado.

Conclusiones

La aplicación de la Programación Extrema al desarrollo de esqueletos de software debe ser modificada para lograr una correcta adaptación a sistemas que presenten estas características [6]. Conjuntamente con esto, pueden ser identificadas nuevas técnicas para el desarrollo de aplicaciones de pruebas unitarias y de aceptación en el contexto antes descrito. De la misma forma, la aplicación de patrones para guiar el diseño e implementación de dichas pruebas podría acarrear en una mejora sustancial en la organización del proceso de control y prueba del código producido.

Durante el transcurso de este trabajo, teniendo como basamento las experiencias adquiridas durante el desarrollo de un esqueleto para la implementación de catálogos de recursos, fueron identificados y documentados un conjunto de prácticas para la realización de pruebas, así como un grupo de patrones encargados de guiar el proceso de control mediante pruebas unitarias y de aceptación en esqueletos de software haciendo uso de la Programación Extrema.

Referencias Bibliográficas

[1] Beck, K. Extreme Programming Explained: Embrace the Change. Addison - Wesley. (1999)
[2] DeLano, D. E. and Rising, L. System Test Pattern Language. "The Pattern Handbook. Techniques, Strategies, and Application". Cambridge University Press. (1996). pp. 97-119.
[3] Jacobson, I., Griss, M., et al. Software Reuse. Architecture, Process and Organization for Business Success. Addison - Wesley. (1997)
[4] Jeffries, R. Essential XP: Unit Tests at 100. http://www.xprogramming.com/xpmag/expUnitTestsAt100.htm. (2001). Last confirmed: November 05, 2003.
[5] Johnson, R. E. and Foote, B. Designing Reusable Classes. Object-Oriented Programming. Vol. 2. (1988). pp. 22-35.
[6] Meszaros, G., Andrea, J., et al. Framework XP - Building Frameworks using XP. Extreme Programming and Agile Methods - XP2002. Springer - Verlag. Lecture Notes in Computer Science. (2002). pp. 113-116.

Refactoring Using Tools

Pattern Name: Refactoring Using Tools

Problem

How do you avoid bugs that may be introduced through human error during refactoring?

Context

You are developing software and you want to make some refactorings before continuing coding. You followed "The First Refactoring Step". You are now ready to refactor your code.

Forces
  • Refactoring by hand is time consuming.
  • Refactoring by hand is prone to introduce new errors into the system.
  • Tools and technologies are available to allow refactoring to be done quickly and relatively painlessly.

Solution

Use available tools for refactoring. Take advantage of the features that are provided by your development environment.

Resulting Context

"Refactorings are primarily intended to be safe" [1]. As Martin Fowler says "(...) a safe refactoring is one that doesn't break a program" [2], so using tools to carry out refactorings, together with "Refactoring In Very Small Steps" will increase the safety changing a program. The use of tools reduces the human intervention and this will diminish the introduction of bugs during refactoring.

"Tool-assisted refactoring affects testing" [3]. Much less testing has to occur because refactorings are performed automatically, but there will always be refactorings that cannot be automated, so you still need to "Test Every Refactoring" in those situations.

Rationale

Using a tool to carry out refactorings may be very beneficial. "It can make many simple but tedious checks and flag in advance problems that if left unchecked would cause the program to break as a result of refactoring" [2]. With automatic refactoring tools, "(...) we can allow the design to be more fluid because changing it is much less costly" [3].

References

[1] Sue Bushell. Code OF Practice. http://cio.idg.com.au/index.php/id;1688864994;fp;4;fpid;10. Last confirmed: 10/05/2004.

[2] Martin Fowler, Kent Beck, John Brant, William Opdyke, Don Roberts. Refactoring: Improving the Design of Existing Code. Addison - Wesley, 1999.

[3] Don Roberts, John Brant. Refactoring Tools. In "Refactoring: Improving the Design of Existing Code." Addison-Wesley, pp. 328-332. 1999.

Test Every Refactoring

"I think it is fair to say, that virtually there is no software that does not contain defects. However, we as software engineers still try to make as few errors as possible and we are also always looking for ways of improving the quality of what we deliver" [1].

Pattern Name: Test Every Refactoring

Problem

How do you avoid the introduction of errors that may break the system during refactoring?

Context

You are developing software and you want to make some refactorings before continuing coding. You followed TheFirstRefactoringStep, then you applied RefactoringInVerySmallSteps.

Forces

  • If you introduce an error during refactoring, it is probably hard to go back without throwing all your changes away.
  • Trusting in the correctness of applied refactorings will cause frequent mistakes.

Solution

It is absolutely necessary that you test every refactoring. Unit Tests [2] are great for this. Remember that "the tests are your safety net, protecting you from breaking the system during fast evolution" [3].

Resulting Context

Testing systems every time that refactoring is applied will maintain a strict fence on the modified code sections. After each refactoring, if a problem was introduced, tests will alert immediately the causes and the conflicting location.

Rationale

"One way to improve software quality on the functional level is to have good tests in place" [1]. Besides, "work produced must be continuously validated through testing" [4]. Kent Beck and Erich Gamma expose in [5] their development methodology as "code a little, test a little (...)". Paraphrasing Beck and Gamma, refactoring should be guided by "refactor a little, test a little".

Refactoring introduces changes in the source code of the system, so after applying a refactoring, you should test the program to validate its functional integrity. If the tests fail after refactoring, it will be only necessary to verify the code sections involved in the process.

Origins

This pattern come from the WIKI pages about refactoring [6]. The original reduced pattern was: "You don't want to find out after 37 refactorings that somewhere around the 21st you made a mistake (...). Therefore, test every refactoring. Unit Tests are great for this (...)".

References

[1] Manfred Lange. It's Testing Time! Patterns for Testing Software. Proceedings of Sixth European Conference on Pattern Languages of Programs (EuroPLoP 2001). Irsee, Germany. July, 2001.

[2] Robert V. Binder. Testing Object-Oriented Systems. Addison -Wesley, 2000.

[3] Ron Jeffries, Ann Anderson, Chet Hendrickson. Extreme Programming Installed. Addison - Wesley, 2000.

[4] Ken Auer, Erik Meade, Gareth Reeves. The Rules of XP. Available at: http://www.rolemodelsoftware.com/moreaboutus/publications/rulesofxp.php. 2003. Last confirmed: April 13, 2004.

[5] Kent Beck, Erich Gamma. Test Infected: Programmers Love Writing Tests. Java Report, 3 (7). July, 1998.

[6] Wiki Pages About Refactoring. Available at: http://c2.com/cgi/wiki?WikiPagesAboutRefactoring. Last confirmed: June 26, 2004.

Refactoring In Very Small Steps

Pattern Name: Refactoring In Very Small Steps

Problem

How do you avoid to get lost by doing several things at the same time during refactoring?

Context

You are developing software and you want to make some refactorings before continuing coding. You followed TheFirstRefactoringStep. You are now ready to refactor your code.

Forces

  • Bigger refactorings are like labyrinths without exit: you will get lost after few steps.
  • Bigger refactorings will cause bigger opportunities of making mistakes.
  • Refactoring in smaller steps will slow down the speed of the development process.

Solution

Refactor in very small steps. A common routine to follow can be: find the smallest useful change you can make. Make it. Then TestEveryRefactoring.

Resulting Context

Introduced errors during refactoring will be easy to find because the changes will be so small. You won't have to spend a lot of time debugging to find bugs. After refactoring, you should apply TestEveryRefactoring. If the volume of changes involved in the refactoring is big enough, use the BacktrackIfRefactoringFails to keep your code safe.

Rationale

Refactorings change the code of a system. As these changes are bigger, people run the risk of making mistakes that may break the system functionality. Besides, carrying out several operations at the same time may confuse developers, causing that they need to throw all their changes away.

Therefore, you should apply refactorings step by step. After each small refactoring, you must to compile and test the system to validate its integrity. As Martin Fowler wrote in his book [1], followed this approach, "(...) if you made a mistake, it is easy to find the bug". You'll never get lost because you have complete control over applied changes because they are very small.

Origins

This pattern come from the WIKI pages about refactoring [2]. The original reduced pattern was: "You want to refactor code, and you don't want to introduce errors while you're doing it. You also don't want to get lost by doing several things at the same time. Therefore, refactor in very small steps. Find the smallest useful change you can make. Make it. Then Test Every Refactoring".

References

[1] Martin Fowler, Kent Beck, John Brant, William Opdyke, Don Roberts. Refactoring: Improving the Design of Existing Code. Addison - Wesley, 1999.

[2] Wiki Pages About Refactoring. Available at: http://c2.com/cgi/wiki?WikiPagesAboutRefactoring. Last confirmed: June 26, 2004.

The First Refactoring Step

"You should think of it (having tests before refactoring) as walking a tightrope without a net. If you are good at walking a tightrope, and it's not that high up, then you might try it. But if you've never walked a tightrope before, and it's over Niagara Falls, you probably want a good net" [1].

Pattern Name: The First Refactoring Step

Problem

How do you make sure you are ready to follow a refactoring process?

Context

You are developing software and you want to make some refactorings before continuing coding.

Forces
  • Finding errors through debugging can be a nightmare.
  • Programmers need tools to validate the refactoring results.
  • Building tests may sound counterintuitive to many programmers.
Solution

Before you start refactoring, check that you have a solid suite of tests for the section of code that will be changed.

Resultant Context

The use of this pattern together with the BacktrackIfRefactoringFails pattern give you the security you need to change a working program because "the tests provide immediate feedback when we broke something in the code" [4]. You can apply TestEveryRefactoring and RefactoringInVerySmallSteps to go forward.

Rationale

A set of tests is essential to applying refactorings because even though you follow RefactoringInVerySmallSteps to avoid chances for introducing errors, "(...) you're still human and still make mistakes" [2]. Also "even if you are fortunate enough to have a tool that can automate refactorings, you still need tests (...)" [2] because you could use the wrong refactoring to solve the problem.

Origins

The name of this pattern came from a subchapter in the Martin Fowler book [2] titled "The First Step in Refactoring". There, Martin exposed his point of view about the importance of having suite of tests before refactoring.

Eric Burke and Brian Coyner wrote the following in their cookbook: "Refactoring works hand-in-hand with automated unit testing. Before you refactor code, make sure you have a working unit test on hand" [3].

Acknowledgments

  • Jonathan Rasmusson from Calgary, Alberta, Canada.

References

[1] Bill Venners. Refactoring with Martin Fowler. A Conversation with Martin Fowler. Available at: http://www.artima.com/intv/refactorP.html. 2002. Last confirmed: July 01, 2004.

[2] Martin Fowler, Kent Beck, John Brant, William Opdyke, Don Roberts. Refactoring: Improving the Design of Existing Code. Addison - Wesley, 1999.

[3] Eric M. Burke, Brian M. Coyner. Java Extreme Programming Cookbook. O'Reilly, 2003.

[4] Jonathan Rasmusson. Introducing XP into Greenfield Projects: Lessons Learned. IEEE Software, 20 (3), pp. 21-28. May / June, 2003.

Backtrack If Refactoring Fails

"By definition, you only refactor code that is already working, which where I come from is scary. Working code is a valuable asset since it is hard to get right (...) and takes time. Touching working code is always a risk and so there should be a darned good reason for doing it" [1].

Pattern Name: Backtrack If Refactoring Fails

Problem

How do you assure the quick recovery of the code in case the refactoring process introduces irreversible changes?

Context

You are applying refactorings as the source code evolves. You followed "Refactoring in Very Small Steps", but the volume of changes involved in each refactoring is big enough that the code cannot be taken to its original position in a simple way.

Forces

  • Rewriting a code may cause an unjustified backwardness in the work schedule.
  • Rewriting a code may cause the appearance of new errors in the system.
  • The programming team do not want to alter a code section after it is functionally correct.
  • Rolling back a source code that is connected to external data sources may cause unexpected results.


Solution

Maintain backup copies of the state of the code before applying refactorings. Use version control systems to carry out this activity. A common routine to follow can be: check out the code to be changed, apply the refactoring, test the changed code, and then, if things went well, commit the new version, otherwise roll back the changes.

Resulting Context

Keeping backup copies of the source code using version control systems offers a safe method to carry out refactorings without fear to lose the functionality already programmed. If your refactoring is defective, you can back out the adjustments, so you'll never need to rewrite already programmed code.

You should pay special attention to rolling back a source code that is connected with external data sources, because there are no clear ways to roll back them to the point that matched the code rollback without significant data loss or, sometimes, there is no way to synthesize previously required data from newer data sets.

Rationale

"Program testing is an effective and practical way of improving correctness of software (...)" [3], however, "(...) no system can be completely tested" [4]. Therefore, there are no mechanisms to assure that after applying refactorings, errors will not be introduced in the code in a manner that they can't be detected by existent tests. The development team needs this pattern to be sure that refactorings will not decompose the performed work. This pattern breaks the barrier created by fear and allows people to run the risk of applying refactorings.

Origins

William Opdyke in his doctoral thesis [2] exposed "one way to prevent errors from happening would be to save the current version of a program before each refactoring, apply the refactoring (...), and then recompile the program. If an error is flagged, fall back to the old version".

In the WIKI pages about refactoring, Falk Bruegmann wrote "(...) make sure you can rollback the refactoring without to much work in case the tests do fail. (...) at least for larger changes, have a backup copy of the state of your program (...)" [5].

Acknowledgments

  • Bill Trost from Portland, Oregon.
  • Falk Bruegmann from Munich, Germany.
  • Glyph Lefkowitc
  • Toivo Lainevool
  • Garry Hamiltom from Carson City, Nevada.

References

[1] Robert X. Cringely. Refactoring Refactoring: Sometimes (Even in Computer Programming) What Everyone Knows Isn't Always Correct. Available at: http://www.pbs.org/cringely/pulpit/pulpit20030508.html. 2003. Last confirmed: July 01, 2004.

[2] William F. Opdyke. Refactoring Object-Oriented Frameworks. PhD Thesis, University of Illinois. Urbana, Illinois, 1992.

[3] Yoonsik Cheon, Gary T. Leavens. A Simple and Practical Approach to Unit Testing: The JML and JUnit Way. Proceedings of 16th European Conference Object-Oriented Programming (ECOOP), pp. 231-255. 2002.

[4] Ivar Jacobson, Grady Booch, James Rumbaugh. El Proceso Unificado de Desarrollo de Software. Addison - Wesley, 2000.

[5] Wiki Pages About Refactoring. Available at: http://c2.com/cgi/wiki?WikiPagesAboutRefactoring. Last confirmed: June 26, 2004.

Saturday, October 01, 2005

Replace Iteration With Indexing

You are iterating over a collection or array using the enhanced for loop, and you need to have access to the iterator or index variable in order to make some operation with it.

Replace the for-each construct with a classic for loop.
(Possible only since java 1.5 aka 'Tiger')

Replace this:

int locate(int[] values, int value) {
int index = 0;
for(int v : values) {
if (v == value) {
return index;
}
index++;
}
return -1;
}

With this:

int locate(int[] values, int value) {
for(int index = 0; index < values.length; index++) {
if (values[index] == value) {
return index;
}
}
return -1;
}

Motivation

In some situations, the use of the enhanced for loop, also called for-each, is not suitable for iterating over collections and arrays. In the example above, the code shows a method that finds a value into an array and returns its position. The for-each construct hides the index variable, so to use it, we need to create a temporary variable and increment it on each iteration to later return the value position. This process is automatically carried out by a traditional for loop, so it's better for this kind of problem.

Another situation when we should decline to use iteration is when you need to replace elements in a collection or array as you traverse it. Also, the for-each construct is not usable for loops that must iterate over multiple collections in parallel.

Using the wrong loop may cause behavioral problems or, in the best case, decrease code clarity.

Mechanics

  • Check that you really needs to have access to the iterator or index variable.
  • If not is so, you shouldn't trunc the for-each construct.
  • Replace the for-each loop construct with a for loop construct.
  • Compile and test.

Example
Lets look at this example where we want to expunge all empty strings from a linked list:

void expunge(LinkedList <String> list) {
int index = 0;
for(String value : list) {
if (value.equals("")) {
list.remove(index);
}
index++;
}
}

The code above seems to be right, but it isn't. Run it and you'll get a ConcurrentModificationException. The problem is that the for-each construct uses a fail-fast iterator to move over collections, so using a for-each loop you can't remove elements while iterating over any collection without getting a ConcurrentModificationException.

To get the proper behavior, and simplify the method, apply ReplaceIterationWithIndexing:

void expunge(LinkedList <String> list) {
for(Iterator<String> iterator = list.iterator();
iterator.hasNext();) {

if (iterator.next().equals("")) {
iterator.remove();
}
}
}

I compile and test, and no exception is thrown. Looking at the code, we don't need anymore the temporary index variable because we are using an iterator embedded with the for loop construct. The code works fine, and it shows its intention in a clearer manner.

Acknowledgments:

Thanks to J. B. Rainsberger (Diaspar Software Services) for the names of these refactorings.

Replace Indexing With Iteration

You are iterating over a collection or array in a ugly old manner.

Replace the iteration code with a for-each loop.
(Possible only since java 1.5 aka 'Tiger')

Replace this:

void process(Collection<Process> processes) {
for (Iterator<Process> p = processes.iterator();
processes.hasNext();) {
processes.next().process();
}
}

With this:

void process(Collection<Process> processes) {
for (Process p : processes) {
p.process();
}
}

Motivation

Iterating over a collection with a traditional for loop is just clutter and prone to introduce errors. If you look carefully at the code, the iterator variable occurs three times in each loop, so you have two chances to get it wrong. Furthermore, the code using the traditional for loop is bigger than it needs to be. Length increase complexity, and complexity makes the code harder to read and understand.

You can make your intention clearer by replacing the for loop with a for-each loop. The for-each construct gets rid of the clutter and the opportunity for error. After the refactoring, the code will be more readable and less prone to host bugs.

Mechanics
  • Check that you are using the iterator or index variable only to access the elements in the collection or array.
  • The for-each loop hides the iterator or index variable while the iteration takes place, so you cannot use these variables to anything else.
  • Replace the for loop construct with a for-each loop construct.
  • Compile and test.

Example: Using collections

I start with a simple and fictitious populate method:

void populate(
Collection<Row> rows,
Collection<Column> columns) {

for (Iterator<Row> r = rows.iterator();
r.hasNext(); ) {

Row row = r.next();
for (Iterator<Column> c = columns.iterator();
c.hasNext(); ) {

Column column = c.next();
matrix.add(new Position(row, column));
}
}
}

The ugly method above has the intention of populate a matrix object with a collection of rows and columns. The iterator variables in both collections are used only to get access to the next element in the chain, so I feel secure to make the refactoring one loop at a time. I start with the inner one:

void populate(
Collection<Row> rows,
Collection<Column> columns) {

for (Iterator<Row> r = rows.iterator();
r.hasNext(); ) {

Row row = r.next();
for (Column column : columns) {
matrix.add(new Position(row, column));
}
}
}

At this point, I compile and test and things should work. Now, I will refactor the outer loop:

void populate(
Collection<Row> rows,
Collection<Column> columns)

for (Row row : rows) {
for (Column column : columns) {
matrix.add(new Position(row, column));
}
}
}

I compile and test again and, if no problem occurs, the work has finished. As a final point, make a comparison between both methods, before and after refactoring and you will be much more comfortable.

Example: Using arrays

Suppose you have a method that computes the total amount stored in a price array:

double totalAmount(double[] prices) {
double total = 0;
for (int i = 0; i < prices.length; i++) {
total += prices[i];
}
return total;
}

Once the for loop temporal index variable (i) is used only to get access to the price elements, we can refactor the for loop to get the following:

double totalAmount(double[] prices) {
double total = 0;
for (double price : prices) {
total += price;
}
return total;
}

I compile, test and enjoy the resultant code.

Acknowledgments:

Thanks to J. B. Rainsberger (Diaspar Software Services) for the names of these refactorings.

Replace Constant Interface With Static Import

You are subtyping a class or interface in order to get access to the static members defined on it

Replace the subtyping with static import
(Possible only since java 1.5 aka 'Tiger')

Replace this:

public class Order
implements Constants {
double price() {
return basePrice() + shipping() + TAX;
}
}

With this:
import static Constants.TAX;
public class Order {
double price() {
return basePrice() + shipping() + TAX;
}
}

Motivation

Sometimes, people place static members into an interface and inherit from it looking for avoiding prefixing static members with class names. This produce the so-called "Constant Interface" antipattern.

Interfaces are intended for defining types, not providing access to static members. When a class implements an interface, it becomes part of the class's public API, creating an undesirable connection between classes and confusing clients. Even if you remove the access to static members, you still have to implement the constant interface as clients now depend on it.

Using static imports, you can remove the class name prefix from static members without inheriting from the type containing them, and avoiding the aforementioned problems. This is a very clean alternative.

Mechanics
  • Remove the dependency between your class and the "Constant Interface".
  • Compile and use the compiler feedback to find the unknown static members.
  • Import each static member (you can also import all static members from the class or interface using the * facility).
  • Compile again and test.

Example

Suppose we have the following class fragment:
public class Order
extends Values
implements Constants {
double price() {
return basePrice() + shipping() + TAX;
}
double shipping() {
return Math.min(basePrice(), SOME_VALUE);
}
}

We both agree that the Order class is not a "Values" neither a "Constants", so we are in presence of a clear "Constant Interface" antipattern. This example needs an urgent refactoring, so I start step by step by removing the Values class dependency:


public class Order
implements Constants {
double price() {
return basePrice() + shipping() + TAX;
}
double shipping() {
return Math.min(basePrice(), SOME_VALUE);
}
}

Now, when I compile, the compiler signals that it doesn't know who is SOME_VALUE, so I'll include the corresponding static import:

import static Values.SOME_VALUE;
public class Order
implements Constants {
double price() {
return basePrice() + shipping() + TAX;
}
double shipping() {
return Math.min(basePrice(), SOME_VALUE);
}
}

I compile and test and things work well, so I continue by repeating the previous steps with the second dependency. After the changes, the code will look like this:

import static Values.SOME_VALUE;
import static Constants.TAX;
public class Order {
double price() {
return basePrice() + shipping() + TAX;
}
double shipping() {
return Math.min(basePrice(), SOME_VALUE);
}
}

At this time, I removed completely the use of the "Constant Interface" antipattern. I compile, test, and save the changes.