Código limpio: El malvado argumento booleano

En la última entrada repasamos qué debemos tener en cuenta cuando estamos definiendo nuestros argumentos, pero hemos querido dedicar una entrada específica para profundizar en los malvados argumentos booleanos.
Esta es la quinta entrega de una serie en la que analizamos el libro clean code de Robert C Martin publicado en el año 2008. Si quieres puedes ir a ver el índice de la serie. También puedes ver la versión en video de esta entrada en nuestro canal de youtube.
Veamos los motivos por los que deberías evitar los argumentos booleanos.
Confunden al lector
En general los argumentos booleanos confunden al lector. Veamos un ejemplo, con una clase del componente Config
de Symfony
. Esta clase en concreto es la responsable de buscar un fichero de un determinado nombre en un array de paths definidos en los que se podría encontrar.
namespace Symfony\Component\Config;
/**
* FileLocator uses an array of pre-defined paths to find files.
*/
class FileLocator implements FileLocatorInterface
{
// ..
public function locate(string $name, string $currentPath = null, bool $first = true)
{
// ..
}
// ..
}
¿Sabrías decirme qué hace exactamente el tercer argumento? Quizá tengas la siguiente intuición, si le pasamos true
probablemente devolverá la primera coincidencia, pero ¿qué ocurre si le paso un false?
. Si realmente quiero entender esta función voy a necesitar ir al código y leer que es lo que hace.
Una vez que ya nos hemos leído el código entendemos que el funcionamiento es el siguiente:
- si le paso un
true
, me devolverá la primera coincidencia - si le paso un
false
me devolverá todas.
¿No se lo pondríamos mucho más fácil al lector si somos explícitos de esta forma?
namespace Symfony\Component\Config;
/**
* FileLocator uses an array of pre-defined paths to find files.
*/
class FileLocator implements FileLocatorInterface
{
// ..
public function locateAll(string $name, string $currentPath = null)
{
// ..
}
public function locateFirst(string $name, string $currentPath = null)
{
// ..
}
// ..
}
Como siempre esto no significa que debamos duplicar el código, ya que probablemente podemos encontrar la forma de que uno llame a otro o utilizar funciones privadas más pequeñas, para evitar cualquier duplicidad.
Hace dos cosas
En muchas ocasiones un argumento booleano indica que la función hace dos cosas diferentes. Como ya vimos en la entrada dedicada a funciones, estas deberían hacer una única cosa y deberían hacerla bien.
Veamos un ejemplo con el componente DomCrawler
de Symfony
. Este componente permite crear un cliente que simula ser un navegador y que se utiliza en pruebas funcionales y para tareas de scrapping.
La función que vamos a ver se llama después de haber ejecutado una query sobre un documento HTML $message = $crawler->filterXPath('//body/p')->text();
namespace Symfony\Component\DomCrawler;
/**
* Crawler eases navigation of a list of \DOMNode objects.
*/
class Crawler implements \Countable, \IteratorAggregate
{
// ..
/**
* Returns the text of the first node of the list.
*
* Pass true as the second argument to normalize whitespaces.
*/
public function text(string $default = null, bool $normalizeWhitespace = true)
{
// ..
}
// ..
}
¿No da la sensación de que el segundo argumento responde a una necesidad del programador que a una decisión de diseño? ¿Quién realizaría un diseño así? Obviamente el siguiente ejemplo es mejor.
namespace Symfony\Component\DomCrawler;
/**
* Crawler eases navigation of a list of \DOMNode objects.
*/
class Crawler implements \Countable, \IteratorAggregate
{
// ..
public function text(string $default = null)
{
// ..
}
public function normalizeWhitespace(string $text)
{
// ..
}
// ..
}
Y una vez realizado este diseño, ¿no parece evidente que normalizeWhitespace
debería ir en otra clase? esto lo veremos más adelante en la entrada dedicada a las clases.