Diseño de código en Python. Buenas prácticas (A3C34C2D03)

Introducción

El diseño de código efectivo y eficiente es uno de los principales retos a los que se enfrenta cualquier programador. En este sentido, Python, con su enfoque en la simplicidad y legibilidad, ofrece un medio ideal para desarrollar código que no solo sea funcional, sino que también sea fácil de entender y mantener. La adopción de buenas prácticas de diseño de código es crucial, no solo para mejorar la calidad del software, sino también para facilitar la colaboración y el mantenimiento a largo plazo del código. De hecho, seguir buenas prácticas puede ser considerado una inversión a largo plazo. Puede requerir más tiempo y esfuerzo inicialmente, pero definitivamente vale la pena a largo plazo ya que minimiza los problemas y aumenta la eficiencia.

La aplicación de buenas prácticas reduce la complejidad del código, aumenta su mantenibilidad y facilita la detección y corrección de errores, mejorando la calidad del software.

Buenas prácticas para el diseño de código en Python

A continuación, se discuten algunas de las buenas prácticas más habituales que deberías considerar en el diseño y codificación de programas.

Nombrado descriptivo

Los nombres de las variables, funciones y clases deben ser lo suficientemente descriptivos para indicar su propósito o uso en el programa. Los nombres deben ser entendibles y proporcionar una idea de lo que hace la función o lo que representa la variable. Esta práctica facilita la lectura y comprensión del código, no solo por el propio programador, sino también por otros desarrolladores que puedan trabajar o revisar el código. Por ejemplo, supongamos que estás escribiendo una función para calcular el área de un círculo. En lugar de nombrar la función de manera genérica como func1 y usar x para la variable del radio, puedes usar nombres más descriptivos:

#Nombramiento poco descriptivo

def func1(x):
    return 3.1416 * (x**2)

#Mismo ejemplo con nombramiento de variables descriptivo

def calcular_area_circulo(radio):
     return 3.1416 * (radio**2)

Uso apropiado de comentarios

Los comentarios son una herramienta útil para explicar el propósito o la funcionalidad de una sección de código, especialmente si es compleja o no está claramente indicada por el código en sí. Deberían utilizarse para agregar detalles que no son inmediatamente obvios solo con leer el código. Sin embargo, también es importante no sobrecargar el código con comentarios innecesarios, ya que esto puede hacer que el código sea más difícil de leer.

Ejemplo: supongamos que estás implementando un algoritmo que ordena una lista de números utilizando el método de la burbuja. Aquí es cómo podrías comentar correctamente ese código:

def ordenamiento_burbuja(lista):
    # Iterar sobre cada elemento en la lista
    for i in range(len(lista)):
        # Comparamos el elemento actual con el siguiente
        for j in range(0, len(lista) - i - 1):
            # Si el elemento actual es mayor que el siguiente,
            los intercambiamos
            if lista[j] > lista[j + 1] :
                lista[j], lista[j + 1] = lista[j + 1], lista[j]

Aplicación del principio de responsabilidad única

Este principio sugiere que cada función, módulo o clase debe tener una única responsabilidad. En términos más sencillos, deberían hacer solo una cosa, pero hacerla bien. El cumplimiento de este principio hace que el código sea más manejable, fácil de mantener y menos propenso a errores, ya que, si surge un problema, sabes exactamente dónde buscar.

Ejemplo: supongamos que tienes una aplicación que procesa pedidos de un e-commerce.

def manejar_pedido(cliente, producto, cantidad):
    # Verificar disponibilidad del producto
    ...
    # Actualizar inventario
    ...
    # Procesar el pago
    ...
    # Generar factura
    ...
    # Enviar correo de confirmación al cliente
    ...

En lugar de tener una función que maneje todo el proceso de pedido, sería mejor tener funciones separadas para cada paso del proceso.

def verificar_disponibilidad(producto, cantidad):
    ...
def actualizar_inventario(producto, cantidad):
    ...
def procesar_pago(cliente, monto):
    ...
def generar_factura(cliente, detalles_pedido):
    ...
def enviar_confirmacion_por_correo(cliente, detalles_pedido):
    ...

Uso de funciones y abstracción

La abstracción es un método esencial en la programación que se centra en distanciar los detalles técnicos de una parte específica del código de su aplicación práctica. En el lenguaje Python, se pueden emplear las funciones para simplificar operaciones y englobar secciones de código que cumplen con un propósito definido. El uso de la abstracción mediante funciones optimiza la legibilidad del código, su posibilidad de reutilización y facilita su mantenimiento.

Por ejemplo, si estás creando un programa que realiza cálculos matemáticos complejos varias veces, en lugar de escribir la misma lógica de cálculo una y otra vez, podrías abstraer esa lógica en una función. Entonces, en lugar de repetir el mismo código, simplemente llamarías a la función cada vez que necesites realizar ese cálculo. Así, si necesitas cambiar la forma en que se realiza el cálculo, solo tendrías que hacerlo en un lugar (dentro de la función), en lugar de buscar y cambiar múltiples instancias del mismo código.

Nota

Cuando se utilizan funciones en Python, un buen consejo es seguir el principio de que “menos es más”. Las funciones deberían ser pequeñas y hacer solo una cosa. Si encuentras que una función está creciendo demasiado o está empezando a hacer demasiadas cosas, probablemente sea el momento de dividirla en varias funciones más pequeñas.

Manejo de errores y excepciones

Un buen programa es aquel que es robusto y puede manejar errores y situaciones inesperadas. El manejo de errores y excepciones en Python permite a los programas manejar errores y continuar ejecutándose, incluso si algo inesperado sucede. Los programadores pueden definir lo que debería suceder si ocurre una excepción a un tipo de error particular.

Ejemplo: Imagina que tienes una función que divide dos números. En lugar de dejar que el programa falle si intentas dividir por cero, puedes capturar y manejar esa situación de la siguiente manera:

def dividir(numerador, denominador):
    try:
        return numerador / denominador
    except ZeroDivisionError:
        print(“Error: No se puede dividir por cero.”)
        return None

En este ejemplo, si intentas dividir por cero, el programa captura la excepción ZeroDivisionError, imprime un mensaje de error y devuelve None. De esta manera, el programa no se detiene y puede continuar ejecutándose, a pesar de que se intentó realizar una operación inválida. Esto hace que tu programa sea más robusto y amigable para el usuario.

Evitar código redundante

Este es un principio de desarrollo de software fundamental que sugiere que cualquier funcionalidad debería ser implementada en un solo lugar. Si encuentras que estás escribiendo el mismo código más de una vez, puede ser un signo de que deberías encapsular esa funcionalidad en una función o clase y reutilizarla.

Uso de las convenciones de estilo de Python (PEP 8)

La guía de estilo Python Enhancement Proposal 8, comúnmente conocida como PEP 8, es un conjunto de recomendaciones sobre cómo formatear el código Python. Seguir estas convenciones ayuda a mantener la consistencia y mejora la legibilidad del código. Aunque algunas de estas reglas pueden parecer arbitrarias, adherirse a ellas puede hacer que sea más fácil para otros (y para ti mismo) leer y entender tu código.

Puedes acceder a la guía en castellano en: e.digitall.org.es/pep8

Prueba siempre que tu código funciona correctamente

Las pruebas unitarias son un componente esencial de la programación saludable. Permiten al programador verificar que las piezas individuales de código (o “unidades”) estén funcionando correctamente bajo diferentes circunstancias y entradas. En Python, el módulo unittest es una herramienta poderosa para realizar estas pruebas.

Escribir pruebas unitarias para tu código puede ayudarte a identificar y corregir errores más rápido, mejorar la calidad del código, y también puede facilitar la modificación y extensión del código en el futuro.

Ejemplo: supón que tienes la siguiente función que calcula el factorial de un número:

def factorial(n):
    if n == 0:
        return 1
    else:
        return n * factorial(n - 1)

Podrías escribir la siguiente prueba unitaria para asegurarte de que la función funciona correctamente:

import unittest
class TestFactorial(unittest.TestCase):
    def test_factorial(self):
        self.assertEqual(factorial(0), 1)
        self.assertEqual(factorial(1), 1)
        self.assertEqual(factorial(2), 2)
        self.assertEqual(factorial(3), 6)
        self.assertEqual(factorial(4), 24)
        self.assertEqual(factorial(5), 120)
if __name__ == ‘__main__’:
    unittest.main()

La clase TestFactorial contiene un método llamado test_factorial que realiza varios tests sobre la función factorial. Se comprueba que el resultado de la función sea correcto para varios valores de entrada. Si alguno de estos tests falla, la función unittest.main() nos informará sobre el fallo y podríamos entonces corregir el problema en la función factorial.

Uso de patrones de diseño

Los patrones de diseño son soluciones probadas a problemas comunes en el diseño de software. Ofrecen un marco para enfrentar situaciones comunes, permitiendo una mejor organización del código y facilitando su mantenimiento y expansión en el futuro. Utilizar patrones de diseño puede ayudarte a escribir código más limpio, modular y eficiente.

Saber más

Para saber más sobre Patrones de diseño, se recomienda la lectura de Gamma, E. (2002). Patrones de diseño. España: Pearson Educación.

Documenta el código

Además de los comentarios y los docstrings en el código, también es importante proporcionar una documentación más amplia y completa. Esta puede incluir la documentación de la API para bibliotecas o módulos, los manuales del usuario para programas, y las guías de instalación o configuración para el software. La documentación es crucial para el mantenimiento y la escalabilidad de un proyecto, así como para la colaboración con otros desarrolladores.

Una buena documentación del código puede ser en forma de archivos README, wikis en el repositorio de código, o incluso páginas web dedicadas con la documentación completa del proyecto.

Saber más

Además de las buenas prácticas existen muchas más. Por ello, te recomendamos la lectura de los siguientes libros:

“Clean Code: A Handbook of Agile Software Craftsmanship” por Robert C. Martin: Aunque este libro no está escrito específicamente para Python, las lecciones y principios que enseña son aplicables a cualquier lenguaje de programación. El libro trata sobre cómo escribir código de alta calidad que sea fácil de leer, mantener y extender. Te ayudará a entender cómo los profesionales piensan sobre el diseño del software y por qué hacen las cosas de la manera que las hacen.

“Fluent Python: Clear, Concise, and Effective Programming” por Luciano Ramalho: Este libro es un excelente recurso para programadores intermedios y avanzados de Python. No solo cubre las características del lenguaje, sino que también ofrece perspectivas sobre las “formas pythonicas” de hacer las cosas. A través de este libro, puedes aprender a escribir código Python que es más idiomático, eficiente y claro.