La semana pasada estuve leyendo código con megaclases ultrareusables mediante configuración de una larga lista de parámetros. Una navaja suiza de la más fina calidad. Recordé que era (o es) una práctica muy común escribir clases con métodos que procesen estructuras de datos mediante estructuras de control tan variadas y construyendo tantos caminos lógicos que el mantenimiento se vuelve una pesadilla. Veamos, por ejemplo, cómo afecta a nuestra lógica sólo usar una variable flag para configurar el comportamiento de un entidad.

Un parámetro flag es aquel que le indica a una función que realice una operación dependiendo de su valor. Imaginemos que deseamos comprar frutas; además, hay dos tipos de frutas: normal y A1. Usando el parámetro flag obtendríamos una implementación como la siguiente:

class Fruiterer...
  Ticket pay(Fruit fruit, bool isA1) {...}

Mi consideración ante los parámetros flags es evitarlos. En su lugar, prefiero definir métodos separados.

class Fruiterer...
  Ticket payNormal(Fruit fruit) {...}
  Ticket payA1(Fruit fruit) {...}

El motivo para hacer esto es que los métodos separados expresan de manera clara la intención que tengo al momento de usarlas. En lugar de recordar el significado del flag cuando escribo {pay(apple, false)}, leo sencillamente {payNormal(apple)}.

1.   Implementación compleja

Lo primero que se me ocurre cuando veo un parámetro flag es una potencial implementación compleja, done el caso más simple es llamar diferentes métodos.

Ticket pay(Fruit fruit, bool isA1) {
  if (isA1)
    // A1 fruit implementation
  else
    // regular fruit implementation
}

Pero la lógica puede ser más enredada.

Ticket pay(Fruit fruit, bool isA1) {
  call().complex();
  methods();
  if (isA1)  // <-
    that();
  over();
  if (isA1) {  // <-
    add().extra();
  } else {  // <-
    nothing();
    it().matters();
  }  // <-
  say.bye();
}

En estos casos, es complicado extraer las implementaciones en métodos separados sin duplicar dentro de las mismas. Una opción es mantener el método con el argumento flag oculto.

class Fruiterer...
public:
  Ticket payNormal(Fruit fruit) {
    return uglyPay(fruit, false);
  }
  Ticket payA1(Fruit fruit) {
    return uglyPay(fruit, true);
  }
private:
  Ticket uglyPay(Fruit fruit, bool isA1) {...}

El punto aquí es que sólo los métodos de compras deberían llamar a {uglyPay}. Le escribo un nombre que resalte para reforzar esa idea. (Considerando, además, que es posible añadir un regex para asegurarnos que nadie más lo llama.)

Observemos que hay una situación incluso más compleja. La explosión de casos si aumentara los tipos de frutas puede llevar a la tentación de usar enums (u otros) para representarlos. Engordaríamos el método y el mantenimiento implicaría… ¡reescribir el método!. Dos pesadillas de balde.

2.   Obteniendo el flag

¿Qué sucedería si la decisión de comprar una fruta A1 sólo dependiera del estado de la fruta? Asumamos que una fruta etiquetada es A1 mientras que las demás reciben un trato normal. En este caso, la fruta misma está actuando como flag. ¿Deberíamos tener un parámetro flag?

Dependerá de quien use la función. Si la venta sólo depende de la etiqueta de la fruta, quien use la función no tiene que preocuparse por las reglas de negocio que diferencian un fruta A1 de una normal; y así, definitivamente es razonable que el método de compra obtenga obtenga ese criterio basado en el estado de la fruta. Sólo necesitamos métodos diferentes cuando quien usar la función necesita especificar lo que quiere.

3.   El método de configuración

Si bien prefería ver

void on();
void off();

que ver

void switch(bool on);

Pero, hay excepciones. Depende de cómo se usar el método. Si se está trabajando con un library para GUIs o data sources configurando una fuente booleana, preferiría tener {switch(value)} en lugar de

if (value)
  on();
else
  off();

Una API debe ser escrita pensando en su usuario. En ocasiones, podríamos proveer ambos estilos si tenemos usuarios que trabajen de ambos contextos.

El mismo argumento aplica a {fruit}. Si tuviéramos un checkbox en la pantalla y sólo pasásemos su valor a {fruit}, entonces el parámetro flag tiene alguna justificación. (Nuevamente, depende.) No diría que es una elección simple, la mayoría de veces argumentaría que es difícil de entender que un setter booleano de un argumento, y por lo tanto es preferible tener los métodos explícitos.

Anuncios