lunes, 31 de mayo de 2010

Estás realmente haciendo desarrollo ágil?(Traducido con permiso de Jake Scruggs)

Hace tiempo que los que escribimos este post venimos viendo como ha ido quedando de lado y, casi diría, en el olvido la metodología de desarrollo Extreme Programming, conocida como XP, que no trata solamente de prácticas de programación, una creencia bastante extendida. Lo que sigue es la traducción de un post interesante que escribió recientemente Jake Scruggs, Are you really doing agile development?, y que aquí publicamos con su permiso.

Recientemente en el trabajo me pidieron que ayudara a hacer a la compañia más "ágil". Bueno, como primero soy un desarrollador y segundo, un fanático estudioso de procesos, respondí con mi usual "Cuantas, de las 12 prácticas están realmente siguiendo?". Mi pregunta fue recibida con muchos ojos grandes y bocas abiertas. Parece que las 12 prácticas de XP clásicas no son fáciles de ubicar estos días en internet. Tambien parece que la palabra "ágil" (de las metodologías ágiles) ha sido tan exitosa en su difusión que muy poca gente parece recordar que XP significa Extreme Programming. Ahora bien, soy el primero en admitir que "Extreme Programming" es un nombre colosalmente estúpido, pero lo que me gusta de XP y sus 12 prácticas originales es que fueron controversiales y fáciles de evaluar: O bien las estabas siguiendo o no lo hacias.

Lo que no me gusta de la palabra "ágil" es que es tan amplia y definida de una manera tan difusa que casi cualquiera puede autoengañarse y creer que ya es completamente ágil.

Asi que, para solucionar esto, y ayudar a mi compañia a comenzar a medir mejor su "agilidad" , decidi listar las 12 prácticas de XP clásicas (A través del tiempo cambiaron...pero no para mi. Y mantenganse afuera de mi jardin!) y mi altamente subjetiva opinión sobre cada una de ellas. Al final les diré como calcular su índice de "agilidad".

* Planning Game o juego de planificación Obtener requirimientos del usuario, armar historias de usuario (cortas), estimar el tiempo que llevará hacerlas, hacerlas, medir velocidad, examinar estimaciones erradas para pistas acerca de como estimar mejor y repetir. Una historia es una promesa de conversación -- esto solo funciona si el usuario (o representante) está altamente disponible para los desarrolladores y los desarrolladores aprovechan esa disponibilidad chequeando constantemente dudas y presunciones durante el proceso de desarrollo. Nota: Historias que toman más de un dia para completarse reducen el éxito de otras prácticas de XP.

* Small Releases o versiones pequeñas LAs iteraciones deben ser de 1 o 2 semanas (dependiendo de cuan doloroso/caro sea organizar una iteración). Despues de cada iteración se debe implementar una versión en productivo si la aplicación es fácil de desplegar (como por ejemplo una aplicación web).

* Metaphor - Metáfora Nadie sigue esta práctica, puntos extras si tu lo haces.

* Simple Design - Diseño Simple Esto significa distintas cosas en distintos niveles. Cuando se esta empezando un proyecto desde cero, una semana de relevamiento de requerimientos y diseño esta bien. Para una historia individual, diseñar desde unos minutos hasta una hora está bien. Después uno comienza a programar. Importante: Se diseña a medida que uno desarrolla. Para para pasar unas horas en frente de un pizarrón está permitido, e incluso, alentado. Hacer la cosa más simple que pueda funcionar hasta que se vuelva obvio que no funciona. Entonces se rediseña. La idea es que el comienzo de un proyecto es el momento que uno sabe menos del mismo-- por eso hay que distribuir el esfuerzo de diseño a lo largo de la vida del proyecto de manera de poder tomar decisiones cuando uno tiene más conocimiento.

* Testing Testear antes de programar. Cubrimiento del 80% para Java, 90% para Ruby (menos chequeo de exepciones en ruby hace más fácil aumentar el porcentaje de código cubierto). Tests Robustos, atómicos, rápidos. (los Test de unidad deben correr en menos de 5 minutos, todos, en realidad es mejor si es menos de un minuto pero la mayoria cree que es imposible (Les adelanto la respuesta: ES posible )

* Refactoring - Refactorización de código En breve: Rojo, verde, refactorizar!, respuesta larga: Escribir un test que falle, hacerlo pasar, refactorizar el código. Tambien significa que cuando estás pasando por un lugar (de código) desordenado lo dejas un poco mejor de lo que lo encontrastes.

* Pair Programming - Programación de a pares

Más del 80% del tiempo. En serio. Tareas en las que parece que no se puede programar de a pares a menudo significan que no estas usando las técnicas de pair programming correctas. Programar de a pares es una habilidad sobre la que se trabajar. Practiquen para ser mejores en ella.

* Collective Code Ownership - Compartición de código colectiva

Cualquiera puede cambiar cualquier parte del código. Si, deben consultar al que conozca más acerca del código a modificar pero no hay ninguna parte del codigo que sólo el desarrollador X puede tocar.

* Continuous Integration - Integración contínua

Cada check-in en el repositorio remoto dispara un set extensivo de tests de unidad. Los programadores deben ocuparse si el build falla. Por ejemplo, cuando el build falla nadie hace más check-ins hasta que se soluciona. También debe haber algúna penalización social para el que rompe el build (Debe usar un sombrero estúpido por el resto del dia, un deshonroso trofeo "Rompedor del build" que se ubique en el escritorio del susodicho, debe comprar donas o algo para todo el equipo, etc.). Todo esto se basa en la presunción de que el build es confiable y que una falla significa un problema real.

* 40-hour Week - Semana de 40 horas

Ahora es llamada "Paso sostenible" porque "semana de 40 horas" tendía a asustar a los gerentes y corridas de final del proyecto son a veces necesarias incluso en XP. Esto no debe durar por más de 2 semanas, tener un punto claro y definido a partir del cual las horas de más se terminan y no debe suceder más de dos veces en el año. En verdad, si sucede dos veces en el año significa que se deben mejorar las habilidades de estimación.

* On-site Customer - Usuario in-situ

No sucede a menudo para proyectos internos. Se usan en general representanes de Usuarios (o proxies). Estos representantes necesitan tomarse el trabajo de hablar con los usuarios reales constantemente (no solo con los gerentes de los usuarios) y estar totalmente disponibles para contestar preguntas y dudas de los programadores.

* Coding Standards - Estandares de desarrollo

No importan cuales sean pero los programadores necesitan llegar a un consenso y mantenerlo. El consenso no significa que todos digan "si" en la reunión y luego ignoren los estandars cuando les conviene.

Asi que, como obtenemos el índice de agilidad? Tomen la cantidad de prácticas que realmente siguen, dividanlo por 12 y multipliquenlo por 100-- este es el porcentaje de ágiles que están haciendo. No se den un punto si no implementan real y completamente la práctica. Tienen integración continua pero algunos de los tests fallan de manera aleatoria? ningún punto. Programan de a pares "cuando es necesario" lo cual termina siendo el 40% del tiempo? ningún punto! Hacen iteraciones pero no estiman las historias? ningún punto!!

Este es un momento para mirarse objetiva y seriamente ante un espejo, no una fiesta de amor hippie, i.e, la Hippie-Love Fest.

jueves, 20 de mayo de 2010

Código Muerto

Si hay algo que me frusta cuando estoy leyendo código es dedicar un largo rato a entender que hace un método solo para descubrir que en realidad ese método no es utilizado en ninguna parte de la aplicación.

Esto pasa porque muchos programadores se resisten a borrar código aun cuando este ya no cumpla ninguna función (de hecho cuando hicimos el Code Retreat los participantes se resistían bastante). Entonces en vez de borrarlo simplemente lo dejan por ahí, con la esperanza de poder utilizarlo algún día. Después de todo, no cuesta nada,¿no?

Bueno, en realidad cuesta mucho. En primer lugar dificulta la lectura y todos sabemos que los programadores pasamos mucho más tiempo leyendo leyendo y entendiendo código existente que escribiendo código nuevo. Además, debe ser mantenido, ya que es probable que esté utilizando otros métodos que con el tiempo van cambiando.

Por otro lado, aumenta el costo de mantenimiento de la aplicación ya que este crece en forma no linear con respecto al tamaño: el costo de mantener un programa de 1000 líneas de código es más que el doble del costo de mantener uno de 500.

Lo más gracioso de la situación es que aún si se diera el caso donde realmente necesitemos el código borrado, es sencillo recuperarlo mediante nuestro software de control de versiones (y si no están utilizando control de versiones lo mejor que pueden hacer es suspender inmediatamente lo que están haciendo y ponerse a instalar uno).

Todas las variantes de esta costumbre son horribles: variables que no se usan, métodos publicos o privados que nadie llama, código comentado ,clases que no se utilizan,etc. Incluso hay sistemas con features completas o a medio terminar que nunca se pusieron en producción pero cuyo código sigue existiendo y aumentando el costo de desarrollo.

Cuando estamos usando tests de unidad se da una variante un poco distinta que es la de tener dos tests que verifican exactamente lo mismo. Muchas veces nos resistimos a borrar uno de los dos tests porque nos sentimos más seguros por la doble verificación, pero esa sensación es falsa: uno de los tests no nos está sirviendo de nada y debe ser borrado.

El código muerto no se da solo en los lenguajes de programación. sino que se produce también en la miriada de archivos de configuracion que se desparraman y se replican una y otra vez porque en algun momento se utilizaron. Archivos de properties, xml deconfiguración, archivos de configuracion de log4j, de caches distribuidas, de colas mq, etc y etc y etc, que provocan comportamientos no determinados y que hace que una aplicacion quefuncionaba al ser desplegada en otro ambiente deje de funcionar (o viceversa).

En definitiva, es importante darse cuenta de que cada línea de código tiene un costo de mantenimiento y solo debe seguir existiendo si provee un valor que justifique ese costo.

lunes, 17 de mayo de 2010

Con Scrum no alcanza

Empezando un poco con el tema de metodologías ágiles en general quiero pasar a este blog, de alguna manera repitiendo su publicación, un post que habia escrito respecto a una conversación dentro del marco de la lista sobre metodologías agiles en habla hispana, foro-agiles@yahoogroups.com. De paso la idea es "aggiornalo" o mejor dicho, ponerlo un poco mas en contexto dentro de este blog.

Como ya contamos comenzamos a interesarnos en las metodologías ágiles allá por los años 2002/2003, cuando leímos un par de libros sobre eXtreme Programming (XP), "XP Explained" de Kent Beck y "XP Installed", de Ron Reffries que llegaron en el momento justo, cuando estabamos en crisis, en la búsqueda de ciertas respuestas. LLevabamos en ese momento casi 10 años de trabajo profesional y estabamos seguros de que tenía que haber otras formas, mejores, de encarar el desarrollo de software. Ni Waterfall ni RUP, ni la utilización de una gran etapa de relevamiento con UML habian cambiado en lo mas mínimo la mala situación de la mayoría de los proyectos en los que participabamos. Al final sucedia siempre lo mismo: El grupo de desarrollo, realizando una marcha forzada contra reloj, se esforzaba heroica pero inútilmente en terminar un sistema que al usuario luego no le servía.

En los años siguientes, las metodologías ágiles comenzaron a ser cada vez más populares, y se ensanchó grandemente su audiencia. Esto sucedió básicamente a través de la difusión masiva de Scrum y sus certificaciones, por encima de XP (y su relacionada Industrial-XP ) y Cristal Clear (de Alistair Cockburn) , que han quedado mucho mas relegadas. Creo que en parte ganó la noción que estas otras metodologías, particularmente XP, solo se ocupaban de la programación y no de la gestión y organización del proyecto. Esta idea es totalmente errónea pero es un tema que trataremos en otro momento.

LLendo al titulo del post, aclaramos por si las dudas: Estamos a favor de Scrum. Personalmente creo que es una buena metodología/ framework / conj. de buenas prácticas/ comoseaqueunolodefina, de lo mejor que hay dando vuelta por el ala ágil del arco de desarrollo (es la mas difundida, seguro).
Lo que desafío es la noción de que con Scrum sólo alcanza para obtener excelentes resultados en un proyecto de desarrollo de software. Más bien me juego por lo contrario, con Scrum solo solo (valga la redundancia) alcanza para fracasar. Lo digo en serio. Cuando mucho será un fracaso menor o más rápido que con una metodología tradicional, de las monumentales, con más participación del usuario, pero fracaso al fin.

A ver, en teoría es suficiente con hacer muchos Sprints, muchas retrospectivas, cierta transpiración y luego de un cierto numero de iteraciones....voila! van a ir surgiendo en el grupo un conjunto de buenas práacticas de desarrollo, se van a ir corrigiendo errores, eliminando impedimentos y llegaremos a tener un proceso aceitado que permite conseguir un producto de alta calidad deleitando a nuestros usuarios, verdad?

FALSO (IMHO) porque:

  1. Muchos proyectos no duran tanto para lograr mejorar la calidad del producto a través de retrospectivas y permitir que surjan buenas practicas de desarrollo, en parte por "culpa" del mismo Scrum. El hecho de realizar reviews cada 2 o 3 semanas del Sprint implica una Alta Exposición ante el usuario (claro que estoy a favor del feedback constante), el hecho es que si estamos construyendo...crap, es decir, porquerías vamos a estar mostrando porquerías... y los usuarios suelen tener poca paciencia... (y ni hablar de nuestros "ágiles" gerentes...).

  2. Los equipos de desarrollo no suelen estar juntos y evolucionar juntos en distintos proyectos (algo que mejoraría el punto anterior porque no tiene que producirse la evolución en el proyecto presente sino que puede venir de proyectos anteriores) por la altísima rotación producto (bendita sea) del mercado dinámico y de ciertas condiciones de contratación precarias. Es la realidad en que vivimos por lo menos aquí en Argentina, y me atrevería a arriesgar en muchos lugares del mundo incluso a pesar del impacto de la crisis mundial.

  3. Un grupo de desarrolladores (Programadores) no aprende de la nada a ser bueno, de la noche a la mañana, no se aprende a realizar buenos Diseños Simples de manera evolutiva e incremental. Lleva mucho tiempo y trabajo aprender a realizar tests de unidad efectivos y mantenibles en variados ambientes tecnológicos y tipos de proyectos. Cuesta y mucho aprender a Programar Orientado a Objetos, pero en serio, de manera profunda no solo aplicando patrones de diseño a diestra y siniestra. Requiere estudio, requiere mucha práctica, mucho esfuerzo y cierta habilidad o capacidad mínima.

  4. Cuesta tiempo aprender a coordinar al grupo, Desarrolladores, Testers, Analistas Funcionales, Usuarios, tiempo este que crece exponencialmente con el tamaño del mismo.

  5. Nos enfocamos mucho en el proceso.... Planning Meeting - Sprint - Review - Retrospectiva y nos estamos olvidando de la calidad de la gente.... People over Processes , no se si les suena. Y es la gente la que siempre termina dando la solución.

  6. Hay un proceso.... dudo en llamarlo... de pauperización o deterioro de calidad de la programación y desarrollo de software que me preocupa, cada vez mas los programadores solo se centran en dominar el siguiente framework X-Y-J, llamese Enterprise Library, WCF, Spring security-MVC-Whatever, Hibernate, Seam, el siguiente "juguete" o arma que les promete la famosa "bala de plata" en lugar de buscar mejorar activamente la calidad del desarrollo y su capacidad de concepción de un desarrollo de más alto nivel, de practicar y adoptar buenas practicas que permitan pensar en algo mas estratégico en lugar de la táctica de corto alcance...

  7. Gran parte del mercado de desarrollo se dirige a una complejización cada vez mayor de la tecnología, lo que nos lleva a estructuras de aplicaciones y ambientes cada vez mas complejos e interdependientes.
Todos estos puntos hacen que los problemas de mala gestión, falta de empiriscmo, falta de autogestion y de feedback que vienen a solucionar Scrum sean males "menores" comparados con no empezar a trabajar bien desde un punto de vista técnico desde el primer dia, utilizando buenas prácticas de desarrollo.

En síntesis es poco realista pretender que aplicando Scrum "a la Talibana" y después de unas pocas iteraciones o muchas el grupo vaya evolucionando y mejorando (ojo, que lo hace peroo no) a una velocidad tal que permita desarrollar un producto de muy buena calidad, sin deuda técnica, con alta cobertura de tests de unidad y con un Diseño de objetos solido y extensible, en pocas palabras, un sistema Mantenible (asi con "M" Mayuscula).

Es como pretender realizar un viaje en barco y comenzar el viaje sabiendo que el barco esta lleno de agujeros y se comienzan a tapar en medio del viaje. Lo mas probable es que cuando lleguemos a mitad del camino, en aguas profundas, el barco tenga tanta agua (léase Deuda Técnica) que ,aunque seamos cada vez mejores y mas rápidos en tapar agujeros, el barco (léase Proyecto) se hunda irremediablemente y sin necesidad de chocar contra algún tempano.

miércoles, 12 de mayo de 2010

Patrones de test de unidad 2 : Humble Object

Este es el segundo post de nuestra serie sobre patrones de test de unidad. Hoy vamos a ver como hacer más poderosa nuestra suite de tests de unidad extendiendo su cobertura.

En todos los proyectos hay objetos para los cuales nos parece imposible escribir tests automáticos. Muchas veces el problema es que son objetos que no podemos instanciar en un test de unidad por que están muy acoplados a la infraestructura de la aplicación.

Un ejemplo típico de esta situación es la verificación del comportamiento de un objeto que debe correr dentro de un Application Server, como por ejemplo un EJB session bean. Este objeto no se puede instanciar en un test de unidad porque los tests de unidad no corren dentro de un Application Server y por lo tanto no vamos a poder verificar su comportamiento.

La solución a este problema es extraer a un objeto plano la lógica de negocio incluida en el objeto que no podemos instanciar y verificar el comportamiento de este nuevo objeto. El objeto original se limita a manejar su relación con el contexto y delega en el nuevo objeto todo el resto de sus viejas responsabilidades.

Este patrón se conoce como Humble Object, ya que el objeto acoplado a la infraestructura pierde la lógica de negocios y se transforma en un objeto mas "humilde".

Una variante se da cuando tenemos un método que no podemos probar porque tiene efectos colaterales (por ejemplo hacer commit en la base de datos). Entonces extraemos la lógica de negocio a otro método y dejamos solo el manejo de transacciones en el método original.

La ventaja más obvia de este pattern es que vamos a tener más código cubierto por tests de unidad y por lo tanto menos bugs. Sin embargo hay otra ventaja más sutil y quizás más importante que es que estamos bajando el acoplamiento de nuestro código. Por lo tanto tendremos más posibilidades de reuso, porque como el nuevo objeto no tiene dependencias puede ser utilizado en otras partes del código. Además mejoramos la mantenibilidad, porque podemos cambiar nuestra infraestructura sin necesidad de reescribir el código de negocios.

Esta situación donde trabajamos para hacer que nuestro código sea más testable y conseguimos como efecto colateral que también sea de mayor calidad es muy común. El motivo es que en general el código díficil de testear es código de mala calidad, de manera que la dificultad para escribir tests para un objeto funciona como un indicador de la existencia de problemas de diseño.

Veamos un ejemplo simple: un programa que recibe pedidos via HTTP. Los pedidos que recibe contienen dos parámetros y el programa devuelve la suma de los mismos.

package arrogant;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
import....
 
public class HttpSum {
    public static void main(String[] args) throws Exception {
        InetSocketAddress addr = new InetSocketAddress(8080);
        HttpServer server = HttpServer.create(addr, 0);
 
        server.createContext("/", new HttpSumHandler());
        server.setExecutor(Executors.newCachedThreadPool());
        server.start();
    }
 
}
 
class HttpSumHandler implements HttpHandler {
 
 
    public void handle(HttpExchange exchange) throws IOException {
        String result = calculateSum(exchange);
        sendResponse(exchange,result);
    }
 
    private String calculateSum(HttpExchange exchange) {
        int a = Integer.parseInt(getHeader(exchange, "a"));
        int b = Integer.parseInt(getHeader(exchange, "b"));
        return new Integer(a + b).toString() ;
    }
 
    public String getHeader(HttpExchange exchange,String name) {
        Headers requestHeaders = exchange.getRequestHeaders();
        return requestHeaders.get(name).get(0).toString();
    }
 
 
    private void sendResponse(HttpExchange exchange, String result) throws IOException {
        Headers responseHeaders = exchange.getResponseHeaders();
        responseHeaders.set("Content-Type", "text/plain");
        exchange.sendResponseHeaders(200,result.getBytes().length );
        OutputStream responseBody = exchange.getResponseBody();
        responseBody.write(result.getBytes());
        responseBody.close();
    }
}

El código que podríamos llamar "de negocio" se encuentra en el método calculateSum, pero no podemos testearlo porque está totalmente mezclado con el código que se ocupa de la interfaz HTTP.

Un primer intento para extraer la funcionalidad a testear es el siguiente:

package humble1;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
import...
 
 
public class HttpSum {
    public static void main(String[] args) throws Exception {
        InetSocketAddress addr = new InetSocketAddress(8081);
        HttpServer server = HttpServer.create(addr, 0);
 
        server.createContext("/", new HttpSumHandler());
        server.setExecutor(Executors.newCachedThreadPool());
        server.start();
    }
 
}
 
class HttpSumHandler implements HttpHandler {
 
    public void handle(HttpExchange exchange) throws IOException {
 
        String result = calculateSum(exchange);
 
        sendResponse(exchange,result);
 
    }
 
    private String calculateSum(HttpExchange exchange) {
        int a = Integer.parseInt(getHeader(exchange, "a"));
        int b = Integer.parseInt(getHeader(exchange, "b"));
        Adder adder = new Adder(a,b);
        return new Integer(adder.sum()).toString();
    }
 
    public String getHeader(HttpExchange exchange,String name) {
        Headers requestHeaders = exchange.getRequestHeaders();
        return requestHeaders.get(name).get(0).toString();
    }
 
 
    private void sendResponse(HttpExchange exchange, String result) throws IOException {
        Headers responseHeaders = exchange.getResponseHeaders();
        responseHeaders.set("Content-Type", "text/plain");
        exchange.sendResponseHeaders(200,result.getBytes().length );
        OutputStream responseBody = exchange.getResponseBody();
        responseBody.write(result.getBytes());
        responseBody.close();
    }
}
 
class Adder {
    int a;
    int b;
 
    public Adder(int a, int b) {
        this.a = a;
        this.b = b;
    }
 
    public int sum() {
        return a+b;
    }
 
}

Aquí la lógica de negocio se extrae a una clase Nueva (Adder) que recibe todos los parámetros que necesita en su constructor. La clase Adder no tiene dependencias y puede ser fácilmente testeada.

Sin embargo este enfoque puede no ser adecuado cuando se reciben muchos parámetros del contexto, ya que en esos caso la construcción del objeto que se ocupa de la lógica de negocios es demasiado compleja. En esta situación puede ser mejor la siguiente idea:

 
package humble2;
 
 
 
import java.io.IOException; 
 
import java.io.OutputStream;
 
import java.net.InetSocketAddress; 
 
import java.util.concurrent.Executors; 
 
 
 
import ... 
 
 
 
public class HttpSum {
 
   public static void main(String[] args) throws Exception {
 
   InetSocketAddress addr = new InetSocketAddress(8082);
 
   HttpServer server = HttpServer.create(addr, 0); 
 
   server.createContext("/", new HttpSumHandler());
 
   server.setExecutor(Executors.newCachedThreadPool());
 
    server.start();
 
  }
 
}
 
 
 
  class HttpSumHandler implements HttpHandler {
 
        public void handle(HttpExchange exchange) throws IOException {
 
         String result = calculateSum(exchange);
 
         sendResponse(exchange,result);
 
    }
 
 
 
    private String calculateSum(HttpExchange exchange) {
 
         IAdderInput input = new HttpAdderInput(exchange);
 
         Adder adder = new Adder(input);
 
        return new Integer(adder.sum()).toString() ;
 
    }
 
 
 
    private void sendResponse(HttpExchange exchange, String result) throws IOException {
 
 
 
        Headers responseHeaders = exchange.getResponseHeaders();
 
        responseHeaders.set("Content-Type", "text/plain");
 
        exchange.sendResponseHeaders(200,result.getBytes().length );
 
        OutputStream responseBody = exchange.getResponseBody();
 
        responseBody.write(result.getBytes());
 
        responseBody.close();
 
    }
 
 }
 
 
 
   interface IAdderInput {
 
         int getA();
 
         int getB();
 
     }
 
 
 
    class HttpAdderInput implements IAdderInput {
 
        HttpExchange exchange;
 
        public HttpAdderInput(HttpExchange exchange) {
 
        this.exchange = exchange;
 
   }
 
 
 
   @Override public int getA() {
 
             return getIntegerParameter("a");
 
    }
 
 
 
   @Override public int getB() {
 
            return getIntegerParameter("b");
 
    }
 
 
 
   private int getIntegerParameter(String name) {
 
             return Integer.parseInt(getHeader(name));
 
 
 
   }
 
  private String getHeader(String name) {
 
             Headers requestHeaders = exchange.getRequestHeaders();
 
             return requestHeaders.get(name).get(0).toString();
 
         }
 
   }
 
 
 
 
 
  class Adder {
 
         IAdderInput input;
 
 
 
        public Adder(IAdderInput input) {
 
             this.input = input;
 
        } 
 
 
 
        public int sum() {
 
               return input.getA() + input.getB();
 
          }
 
  }

En este caso cuando se crea el objeto de negocios este recibe como parámetro una instancia de una interfaz que representa todos los parámetros que se toman del contexto. En el ambiente de ejecución, recibe una implementación que es un Wrapper sobre el código HTTP original y que se ocupa de la extracción de los parámetros necesarios. En ambiente de tests de unidad, seguramente recibirá un Mock de la interfaz que devolverá los valores que nos interesen para los tests.