Código limpio: El malvado argumento booleano
Esta entrada está disponible en video
Puedes encontrar una versión en video de esta entrada en nuestro canal de youtube.
Código Limpio
El libro Clean Code o Código Limpo es considerado por muchos como "la biblia" del desarrollo de software. Probablemente esta es una afirmación exagerada, sin embargo no se puede negar que es un gran libro que merece la pena leer. En esta serie de entradas analizamos los principales capítulos del libro clean code publicado por Robert C Martin en el año 2008.
Se han escrito mil entradas sobre el que se considera uno de los más importantes libros sobre filosofía de desarrollo de software, pero nosotros en esta serie de entradas queremos profundizar un poco más en cada uno de los capítulos. Si te dedicas a programar y te interesa mejorar en tu profesión quedate. A lo largo de esta serie de entradas vamos a aprender un montón de cosas.
Si lo deseas puedes ver el resto de entradas de la serie: Código Limpio
En la entrada anterior revisamos la importancia de elegir buenos argumentos. En esta entrada vamos a centrarnos en entender los motivos de un tipo de argumento concreto. El argumento booleano. Para ello analizamos este tipo de argumentos en el código del componente Config
y el componente Crawler
de Symfony
.
Confunden al lector
En general los argumentos booleanos confunden al lector. Veamos un ejemplo, con una clase del componente Config
. 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.