Blog

Consejos para el desarrollo de código: Pruebas unitarias, formateadores y estilizadores de código y estructura de código como paquete

En línea con el artículo de octubre Estructura, legibilidad y eficiencia en el desarrollo de código, añado algunas buenas prácticas para seguir mejorando las prácticas de desarrollo en Python.

Como sabéis, en Capitole tenemos presencia en diferentes industrias. Muchos de nosotros estamos en proyectos de tratamiento de datos, en puestos de Data Science / Desarrollo /DevOps y trabajamos tanto en servidores físicos como en máquinas en la nube en AWS, Azure u otros servicios. Para nosotros es muy importante trabajar de manera eficiente y seguir buenas prácticas en el desarrollo, dejando una buena imagen de nuestra empresa allí donde vamos y un trabajo bien hecho, que facilite las cosas a los clientes finales del producto desarrollado.

En este artículo, compartimos algunas de las reflexiones que hemos ido adquiriendo con el tiempo, a modo de tips para organizar el código. Son trucos sencillos que pueden ahorrar mucho tiempo y malentendidos en el día a día del equipo de desarrolladores.

Pruebas unitarias

Sé que pruebas tu código; si no… ¿cómo sabes que funciona?

Pero he aquí la cuestión: ¿llevas un registro de las pruebas que realizas? De lo contrario, ¿cómo se puede confiar en tu código?

¡Bienvenido al asombroso mundo de las pruebas unitarias! Esta es una de esas cosas que puede no parecer divertida al principio, pero una vez que hayas experimentado el desgaste de largas horas invertidas en depuración de código, y luego las horas y esfuerzo ahorrados gracias a las pruebas de tu código, por arte de magia se convierte en diversión y una necesidad.

Quiero enseñarte la sentencia assert, un tipo de prueba conocido como “inline tests”. Estas pruebas son útiles para comprobar si la entrada y la salida de tus funciones son correctas.

Mostraré un ejemplo. Digamos que estás trabajando con un vector de probabilidades, y quieres proyectar a 0 o 1 dependiendo de un umbral. Esta función cubre la funcionalidad:

def project_to_zero_or_one(probabilities, threshold):

# definimos el array vacío

projections = np.empty_like(probabilities)

# proyecciones

projections[probabilities < threshold] = 0

projections[probabilities >= threshold] = 1

return projections

¿Pero qué pasa si hay NaNs (valores nulos) en tu vector de entrada? ¿Y si una de las entradas es <0 o >1 (recuerda que no existe una probabilidad fuera de este rango)? ¿Y si la entrada es una matriz y no un vector?

Sería interesante que el código me dijera si algo así está ocurriendo, para notificarme de que hay algo mal en alguna parte que tengo que arreglar antes de que sea demasiado tarde. Para esto incluimos las sentencias de assert que nos pueden alertar en esos casos:

def project_to_zero_or_one(probabilities, threshold):

# comprobamos inputs

assert probabilities.ndim == 1, «El Input debe ser un vector!»

assert np.isnan(probabilities).sum() == 0, «El Input contiene valores NaN!»

assert np.sum(probabilities > 1) == 0, f»Hay probabilidades> 1!»

assert np.sum(probabilities < 0) == 0, f» Hay probabilidades < 0!»

# definimos el array vacío

projections = np.empty_like(probabilities)

# proyecciones

projections[probabilities < threshold] = 0

projections[probabilities >= threshold] = 1

return projections

Una práctica que me gusta seguir es extraer todas las sentencias assert de la función principal. Esto es particularmente útil cuando tienes otras funciones que utilizan el mismo argumento, como las probabilidades, lo que te permite reutilizar el código.

def _check_probabilities(probabilities):

assert probabilities.ndim == 1, ‘Input debe ser un vector!’

assert np.isnan(probabilities).sum() == 0, ‘Input contiene valores NaN!’

assert np.sum(probabilities > 1) == 0, ‘ Hay probabilidades > 1!’

assert np.sum(probabilities < 0) == 0, ‘ Hay probabilidades < 0!’

Formateadores y estilizadores de código

Puede que aún no te des cuenta, pero seguramente, en cualquier cliente de Capitole, pasarás la mayor parte de tu tiempo de trabajo leyendo código en lugar de escribiéndolo. Ya sea cuando trabajes en equipo y revises el código de tus compañeros, o cuando intentes resolver un problema buscando una respuesta en StackOverflow, o incluso cuando vuelvas a depurar código que escribiste hace meses. En todas esas situaciones, leerás mucho código.

Por eso, es importante escribir código de forma coherente y uniforme. Esto incluye decisiones como la longitud máxima de las líneas, líneas vacías entre definiciones de funciones y convenciones sintácticas como “vector[:-1]” o “vector[: -1]”  (solo cambia un espacio intermedio). Pueden parecer pequeños detalles, pero tienen un impacto significativo en la legibilidad del código. La gran pregunta es: ¿pueden automatizarse todas estas pequeñas decisiones? Pues sí.

  • Un formateador de código es una herramienta que modifica automáticamente el diseño y el estilo del código fuente para que se adhiera a un conjunto específico de reglas o directrices de formato. Recomiendo encarecidamente Black.
  • Por otro lado, un estilizador de código es una herramienta que ayuda a los desarrolladores a aplicar un estilo de codificación específico o un conjunto de directrices a su código. Aunque son similares a los formateadores de código, los estilizadores de código son más flexibles y sugieren cambios en el código en lugar de modificarlo directamente. Por ejemplo, pueden sugerir cambiar el nombre de las variables o eliminar las bibliotecas no utilizadas. Recomiendo flake8.

Estructura de código como paquete

¿Tienes problemas para importar tus propios módulos de Python? ¿Te suena el error ModuleNotFoundError: No module named ‘my_python_file’ ¿te resulta familiar la sensación de no saber si tienes los módulos instalados, la inseguridad de dónde se encuentran, o si estás usando los directorios correctos? Puede que haya llegado el momento de mejorar la estructura de tu código.

Siempre que empieces un nuevo proyecto, estructura tu código de la siguiente manera:

my_project/

├── src/

│ ├── __init__.py

│ ├── my_module.py

│ └── my_folder/

│ ├── __init__.py

│ └── my_other_module.py

├── data/

│ ├── raw/

├── scripts/

│ ├── my_script.py

├── setup.py

└── README.md

Algunas cosas a tener en cuenta:

  • Cuando Python importa un paquete, busca el archivo __init__.py en el directorio del paquete y ejecuta cualquier código dentro de él.
  • setup.py es un script Python que se utiliza para definir los metadatos y dependencias de un paquete Python. Lo más simple que puede ser es:

from setuptools import setup, find_packages

setup(

name=’my_package’,

packages=find_packages(),

)

También se pueden especificar dependencias, autores, versiones, etc:

from setuptools import setup, find_packages

setup(

name=’my_package’,

version=’0.1′,

author=’John Doe’,

author_email=’john.doe@example.com’,

description=’A simple Python package’,

packages=find_packages(),

install_requires=[

‘numpy>=1.16.0’,

‘pandas>=0.23.4’,

],

)

Una vez que tus carpetas tengan este aspecto (y estés en tu entorno virtual) ejecuta: pip install -e ruta/para/mi_proyecto/. Esto instalará tu paquete en modo editable. Esto hace que cuando modificas código, el paquete instalado se actualiza automáticamente, y no necesitas reinstalar nada.

Conclusión

En resumen, una buena estructura y prácticas de codificación no solo mejoran la eficiencia en el desarrollo, sino que también facilitan la colaboración y el mantenimiento del código a largo plazo.

  • La práctica de testing (de forma ordenada y consistente) es imprescindible para garantizar, de manera confiable y controlada, que el código cumple correctamente con las funcionalidades definidas.
  • El uso de estilizadores y formateadores de código son hábitos esenciales para homogeneizar criterios en cualquier equipo de desarrollo. La clave está en escribir código que sea fácilmente comprensible, reproducible y adaptable, lo que beneficiará tanto a ti como a tus compañeros de equipo y clientes.
  • La estructura de tu propio código como paquete es una buena práctica que facilitará el poder compartir y publicar el código a futuro y la instalación en modo editable ahorra mucho tiempo, ya que se actualiza automáticamente.

La eficiencia en el código es, en última instancia, eficiencia en los resultados.

Ines Olmos Alonso
Ines Olmos Alonso

UX/UI Designer at Capitole

Let’s shape the future together

¡Sé parte de la innovación! Suscríbete a nuestra newsletter y accede a novedades, eventos y oportunidades que te ayudarán a impulsar tu futuro.