Banner funciones PHP

Vi esta pregunta hace poco en un foro de programación y me respuesta fue que en teoría una función debería contener unas 5 líneas de código o menos. Incluso he visto funciones muy útiles que contienen una sola línea de código. Si esta regla te parece exagerada o quieres saber cómo puedes escribir procedimientos completos en funciones de tan pocas líneas, por favor acompáñame en el resto del artículo:

Esta semana he estado escribiendo una nueva versión del componente Styde\Html que NO va a extender del paquete LaravelCollective\Html. Por ende he estado diseñando clases de más bajo nivel que serán capaces de generar código HTML desde cero.

Mi primera meta fue crear un método como el siguiente:

Que genere el código:

Para poder probar que la función me devuelve el código HTML esperado sin tener que abrir y revisar el resultado en el navegador a cada rato -y poder refactorizar más adelante- escribí la siguiente prueba unitaria con PHPUnit:

Aprende más sobre pruebas en Introducción al diseño de clases con pruebas unitarias en PHPUnit.

Escribir la lógica de un método puede parecerte una tarea difícil o no, dependiendo de la complejidad del método y de tu experiencia como programador. Una técnica que yo uso para simplificar el trabajo es dividir una tarea grande en varias tareas pequeñas. Por ejemplo, para generar una etiqueta HTML debemos:

  • Imprimir el código para abrir una etiqueta.
    • Imprimir los atributos HTML dentro de la etiqueta de apertura.
      • Algunos atributos serán pares (como type="text") y otros impares como readonly.
  • Imprimir el contenido de la etiqueta (puede ser texto y/o otras etiquetas HTML).
  • Imprimir el cierre de la etiqueta.
  • Algunas etiquetas no tendrán contenido ni cierre, como por ejemplo la etiqueta input

Esto nos puede llevar al siguiente resultado:

Esta función, excluyendo su declaración, espacios en blanco y comentarios, tiene 17 líneas de código. Si tienes un muy buen monitor con muy buena resolución quizás seas capaz de visualizar toda la función sin tener que hacer scroll:

Vista del código monitor con resolución de 2048 x 1152

Por supuesto podría comprimir más el código, eliminando comentarios y espacios en blanco:

Una función larga sin líneas en blanco ni comentarios ¿Es más difícil o fácil de leer?

Como puedes ver no es un método que sea agradable a la vista ni fácil de leer ni entender.

Pero volvamos a la parte del artículo donde yo proponía dividir la tarea de generar una etiqueta HTML en pequeñas tareas más sencillas:

  • Abrir una etiqueta.
    • Generar atributos HTML.
  • Imprimir contenido de la etiqueta (si aplica).
  • Cerrar la etiqueta (si aplica).

Divide y vencerás

¿Qué tal si dividimos estas pequeñas tareas también en pequeños métodos y hacemos que el método render se encargue simplemente de llamar a estos nuevos métodos?

Rediseñemos el método render:

¡Ahora tenemos un pequeño método de tan sólo 4 líneas de código! Este método se puede leer sin scroll aunque por alguna razón estés trabajando en un monitor CRT de 13” como el que yo tenía cuando estaba aprendiendo a programar.

Por supuesto aún tenemos que definir y escribir el resto de los métodos:

Para imprimir la apertura de una etiqueta:

Nota que aquí a su vez estoy delegando al método renderAttributes():

Este método renderAttributes por si solo ocupa 10 líneas de código y nota que el código dentro de los condicionales tiene bastantes niveles de sangrado*.

*Según la Real Academia Española deberíamos decir “sangrado” en vez de “indentado” lo cuál para mí suena más correcto puesto que siento que mis ojos comienzan a sangrar cuanto más sangrado está un código a la derecha).

¿Cómo podemos evitar tener varios niveles de sangrado indentación en nuestro código?

El método renderAttributes debe encargarse de varias tareas: recorrer el listado de atributos por un lado para generarlos, concatenarlos y finalmente retornar el resultado, pero como puedes ver generar atributos incluye algo de lógica extra ¿Podríamos pasar esta lógica a un nuevo método, quizás renderAttribute (en singular)?

Ahora el método renderAttributes tiene tan sólo 6 líneas de código, aunque siendo creativos podríamos reducirlo aún más:

En este caso estoy usando la función array_reduce de PHP para “reducir” el array de atributos a simplemente una cadena de texto y con esto ¡Ahora el método renderAttributes técnicamente contiene una sólo línea de código! Aunque podemos ver 3 líneas en realidad hay una única línea que es el llamado a la función array_reduce con el callback y el resto de los argumentos. Incluso dentro del callback tenemos también una sola línea de código.

Finalmente necesitamos el código para imprimir el contenido y el cierre de una etiqueta, intenta diseñarlos por ti mismo. Yo te mostraré el código del resultado final:

Como puedes ver a pesar de que ahora tenemos 6 métodos en vez de 1 solo, todos nuestros métodos tienen 4 líneas o menos, incluso refactorizando logré hacer que algunos métodos ocupen incluso una sola linea de código. Es importante notar que en todos los pasos que he hecho a lo largo del tutorial he mantenido las pruebas unitarias en verde:

Sin las pruebas unitarias que agregué al inicio, hacer esta refactorización sería riesgosa, puesto que podría introducir bugs que no estaban antes*.

*Aún con pruebas unitarias o manuales, siempre hay posibilidades de introducir bugs por cada mínimo cambio que hagamos al código, con pruebas por supuesto el riesgo es mucho menor, por lo tanto vale la pena escribirlas.

Hablando de pruebas unitarias, sería muy fácil ahora escribir pruebas para cada paso de la cadena:

En este caso te recomendaría escribir pruebas para los métodos que son públicos en la clase.

Además es importante notar que aunque ahora tenemos 6 métodos en vez de 1, nosotros podemos utilizar estos métodos por separado, para imprimir únicamente la etiqueta de apertura / cierre de un elemento HTML o generar únicamente los atributos, esto podemos ahora lograrlo sin tener que repetir el código por lo tanto aplicamos otro principio llamado DRY (don’t repeat yourself).

Por último además fíjate que los nombres de los métodos pueden actuar como una buena documentación de lo que está sucediendo dentro del código del método y esto es especialmente efectivo en métodos cortos. Ahora ya no necesitamos un comentario como el siguiente:

// Vamos a concatenar el contenido de la etiqueta (puede ser un array de etiquetas o una cadena)

Puesto que el nombre del método renderContent habla por sí solo, o en todo caso podríamos agregar más comentarios en el encabezado del método, como un DocBlock:

¿Realmente la refactorización nos ayuda a mejorar nuestro código?

Podrías argumentar -y con toda razón- que la versiones anteriores de renderAttributes y sobretodo de renderContent, eran más fáciles de leer que el resultado actual, a pesar de que me parece innegable que la refactorización como un todo ha hecho que nuestro método render quede mucho mejor, la mejora del método renderContent me parece debatible, en este caso no hay reglas fijas, sino consejos que aplican a cada situación. Muchas veces puedes empeorar tu código intentando mejorarlo, así que debes tener mucho cuidado. Te recomiendo primero que nada que escribas pruebas unitarias y segundo que no temas presionar CONTROL + Z si en algún momento consideras que estás empeorando el resulta y sobretodo que guardes tu código en un control de versiones (como Git) para que puedas resguardar tu progreso, deshacer algunos cambios con mayor facilidad y evitar pérdida de código.

Ejercicio

Como puedes ver en el método render, estoy utilizando la propiedad content como una bandera para determinar cuando se debe o no imprimir la etiqueta de cierre, esto puede considerarse una mala práctica por varias razones, una de ellas es que es difícil entender este comportamiento cuando declaremos un nuevo elemento con new HtmlElement('input', false). A simple vista cuesta entender qué significa que pasemos un valor falso como segundo argumento. ¿Cómo podrías mejorar esto? Puedes compartir tus ideas y soluciones en los comentarios o, si tienes cuenta en Styde, usando el canal #php de nuestro Slack.

Continua aprendiendo en styde.net

También puedes continuar aprendiendo con nosotros con los siguientes cursos: Curso de programación orientada a objetos con PHP y Curso de Git y suscribirte a nuestro boletín para recibir contenido para mejorar tus habilidades de programación:

Suscríbete a nuestro boletín

Te enviaremos publicaciones con consejos útiles y múltiples recursos para que sigas aprendiendo.

Además si ya eres parte de Styde, puedes unirte ya mismo al canal #styde_html de nuestro canal de Slack para participar en el desarrollo de la nueva versión de este componente y aprender mucho más sobre refactorización y pruebas. Eres bienvenido, no importa tu nivel actual de programación:

Únete a la discusión

Regístrate en Styde y obtén una invitación a nuestro Slack.