CódigoHomeCódigo

Del buen Código al mejor Código:

Prácticas de Python para la
optimización y la sostenibilidad

 

En nuestro articulo del buen Código al mejor Código, queremos resaltar que las buenas prácticas son fundamentales para asegurar el éxito y la sostenibilidad de cualquier iniciativa o proyecto. Estas prácticas, aplicables en diversos contextos, facilitan la optimización de recursos, promueven una cultura de mejora continua, contribuyen a una gestión eficaz de los riesgos y mejoran la satisfacción del cliente.

 

Adoptar buenas prácticas en todas las áreas de operación no solo es estratégico sino también beneficioso para garantizar el crecimiento y la eficiencia a largo plazo. En particular, abordaremos las buenas prácticas en Python basándonos en los principios SOLID, destacando su importancia en una variedad de campos y situaciones.

 

A través de ejemplos prácticos y directrices claras, al adoptar estas prácticas, individuos y organizaciones no solo optimizarán su rendimiento operativo, sino que también avanzarán hacia una práctica más ética y sostenible en el uso de la tecnología.

 

 

Principio de Responsabilidad Única (Single Responsibility Principle – SRP)

 

Los principios SOLID representan un conjunto de conceptos que pueden ser aplicados a Python para mejorar la calidad del código, su mantenibilidad y su escalabilidad. Aunque tradicionalmente asociados con la programación orientada a objetos, estos principios también pueden ser adaptados y aplicados en un contexto más amplio para mejorar la sostenibilidad y el rendimiento del código, incluso en proyectos que no son estrictamente orientados a objetos.
.

 

Principios SOLID: La base de un código mejorado y sostenible

 

Consideremos un ejemplo práctico donde una función originalmente diseñada para realizar múltiples tareas se divide en varias funciones más pequeñas, cada una con una única responsabilidad.

 

Supongamos que tenemos una función que lee un archivo de texto, procesa el contenido para realizar alguna transformación de datos y luego guarda los resultados en otro archivo.

 

La función original podría verse algo así:

 

def leer_procesar_guardar_archivo(nombre_archivo_entrada, nombre_archivo_salida):
# Leer el archivo
with open(nombre_archivo_entrada, 'r') as archivo:
contenido = archivo.read()

# Procesar el contenido
contenido_procesado = contenido.upper()

# Guardar el contenido procesado en otro archivo
with open(nombre_archivo_salida, 'w') as archivo:
archivo.write(contenido_procesado)

 

 

Esta función no cumple con el Principio de Responsabilidad Única porque maneja tres tareas diferentes: leer un archivo, procesar el contenido y escribir en otro archivo. Para adherirnos al SRP, podemos dividir esta función en tres funciones separadas, cada una encargada de una sola responsabilidad:

 

def leer_archivo(nombre_archivo):

with open(nombre_archivo, 'r') as archivo:
return archivo.read()

def procesar_contenido(contenido):
return contenido.upper()

def guardar_archivo(nombre_archivo, contenido):
with open(nombre_archivo, 'w') as archivo:
archivo.write(contenido)

 

 

Ahora, podemos tener un método de alto nivel que utilice estas tres funciones para realizar la tarea completa. Esta función coordina el flujo sin estar directamente involucrada en los detalles de cada paso:

 

def leer_procesar_guardar_archivo(nombre_archivo_entrada, nombre_archivo_salida):
contenido = leer_archivo(nombre_archivo_entrada)
contenido_procesado = procesar_contenido(contenido)
guardar_archivo(nombre_archivo_salida, contenido_procesado)

 

Si dividimos la función original en tres funciones más pequeñas, cada una con una única responsabilidad, hemos hecho que el código sea más claro, mantenible y reutilizable. Además, al seguir el Principio de Responsabilidad Única, facilitamos la prueba y la modificación de cada función de manera independiente.

 

 

 

 

 

Conoce más: Migración Exitosa hacia el Desarrollo en Tiempo Real con Kafka

 

 

 

Principio Abierto/Cerrado (Open/Closed Principle – OCP)

 

El Principio Abierto/Cerrado (Open/Closed Principle – OCP) establece que las entidades de software (como clases, módulos, funciones, etc.), deben estar abiertas para la extensión, pero cerradas para la modificación. Esto significa que deberías poder cambiar el comportamiento de una entidad sin modificar su código fuente.

 

Para ilustrar el OCP en Python, consideremos el ejemplo de un sistema de reportes donde diferentes tipos de reportes deben ser generados. En lugar de modificar una clase existente cada vez que necesitemos un nuevo tipo de reporte, podemos diseñar nuestro sistema de manera que sea fácil añadir nuevos tipos de reportes sin cambiar el código existente.

 

Primero, definamos una clase base para nuestros reportes:

 

class ReporteBase:
def generar(self, datos):
raise NotImplementedError("Debe implementar generar.")

 

 

Esta clase define un método general que las subclases deben implementar. Cada subtipo de reporte implementará este método de una manera que corresponda a su formato específico.

 

A continuación, implementamos dos tipos de reportes como subclases de ReporteBase:

 

class ReporteTexto(ReporteBase):
def generar(self, datos):
return "Reporte en Texto: " + str(datos)

class ReporteHTML(ReporteBase):
def generar(self, datos):
return f"<html><body><h1>Reporte en HTML</h1><p>{datos}</p></body></html>"

 

Cada una de estas subclases implementa el método generar para producir un reporte en el formato deseado. Hasta ahora, el sistema está diseñado de acuerdo con el OCP: podemos agregar nuevos formatos de reporte simplemente añadiendo nuevas subclases de ReporteBase, sin necesidad de modificar el código existente.

 

Ahora, imaginemos que tenemos una función que utiliza estas clases para generar un reporte:

 

def imprimir_reporte(reporte, datos):
contenido = reporte.generar(datos)
print(contenido)

 

Esta función acepta un objeto reporte que es una instancia de ReporteBase o de alguna de sus subclases, y unos datos que son procesados para generar el reporte. La función es “cerrada» para modificaciones (no necesitamos cambiarla para añadir nuevos tipos de reportes) pero “abierta» para la extensión (podemos pasarle cualquier subclase de ReporteBase).

 

Si en el futuro queremos añadir un nuevo tipo de reporte, como un reporte en formato PDF, simplemente podemos crear una nueva subclase:

 

class ReportePDF(ReporteBase):
def generar(self, datos):
# Implementaremos para generar un reporte PDF
return f"Reporte en PDF: {datos}"

 

 

 

Principio de Sustitución de Liskov (Liskov Substitution Principle – LSP)

 

El Principio de Sustitución de Liskov (LSP) establece que los objetos de una superclase deben poder ser reemplazados con objetos de sus subclases sin afectar la corrección del programa.

 

En otras palabras, las subclases deben ser sustituibles por sus clases base. Para ilustrar el LSP en Python, consideremos el ejemplo de un sistema de gestión de figuras geométricas. Comenzaremos con una clase base Figura que define métodos para calcular el área y el perímetro. Luego, crearemos subclases que representan diferentes tipos de figuras, como rectángulos y círculos.

 

Primero, definimos la clase base y una subclase:

 

class Figura:
def area(self):
raise NotImplementedError("Debe implementar el area.")

def perimetro(self):
raise NotImplementedError("Debe implementar el perimetro.")

class Rectangulo(Figura):
def __init__(self, ancho, alto):
self.ancho = ancho
self.alto = alto

def area(self):
return self.ancho * self.alto

def perimetro(self):
return 2 * (self.ancho + self.alto)

 

En este ejemplo, Rectángulo es una subclase de Figura y proporciona la implementación específica de los métodos área y perímetro. Para adherirnos al LSP, cualquier subclase adicional de Figura, como Círculo, también debe poder usarse en cualquier lugar donde se espere una Figura, sin alterar el comportamiento esperado del programa.

 

Veamos cómo podría definirse una subclase Círculo:

 

import math

class Circulo(Figura):
def __init__(self, radio):
self.radio = radio

def area(self):
return math.pi * self.radio ** 2

def perimetro(self):
return 2 * math.pi * self.radio

 

Aquí, Circulo también cumple con el contrato definido por la clase base Figura, implementando los métodos área y perímetro.

 

Esto significa que podemos usar objetos Rectangulo y Circulo de forma intercambiable sin afectar el funcionamiento del programa.

 

Para demostrar el LSP, podríamos tener una función que opera en objetos Figura:

 

def imprimir_informacion_figura(figura):
print(f"Area: {figura.area()}")
print(f"Perimetro: {figura.perimetro()}")

 

Esta función debería funcionar correctamente tanto para instancias de Rectángulo como para instancia de Círculo, sin saber de antemano el tipo específico de figura:

 

rectangulo = Rectangulo(10, 20)
circulo = Circulo(15)

imprimir_informacion_figura(rectangulo)
imprimir_informacion_figura(circulo)

 

Siguiendo el LSP, hemos asegurado que las subclases pueden ser usadas en lugar de una clase base sin que el programa deje de funcionar correctamente. Esto aumenta la flexibilidad y la reutilización del código en nuestro sistema de figuras geométricas.

 

 

 

Conoce más: Importancia del análisis de tickets de soporte en la experiencia del usuario y eficiencia del soporte técnico en proyectos de software

 

 

 

Principio de Segregación de Interfaces (Interface Segregation Principle – ISP)

 

Para ilustrar el Principio de Segregación de Interfaces (ISP) en Python, consideremos el caso de un sistema de control de dispositivos. Supongamos que tenemos una interfaz general para todos los dispositivos que incluye métodos para encender, apagar y reiniciar. Sin embargo, no todos los dispositivos pueden ser reiniciados. Seguir el ISP nos llevaría a separar estas responsabilidades en interfaces más pequeñas:

 

class Encendible:
def encender(self):
raise NotImplementedError

class Apagable:
def apagar(self):
raise NotImplementedError

class Reiniciable:
def reiniciar(self):
raise NotImplementedError

 

De esta manera, un dispositivo concreto como una lámpara, solo necesita implementar la funcionalidad de encendido y apagado, no necesita implementar la interfaz Reiniciable:

 

class Lampara(Encendible, Apagable):
def encender(self):
print("Lampara encendida")

def apagar(self):
print("Lampara apagada")

 

En este ejemplo, la Lámpara no está forzada a implementar un método reiniciar que no necesita, cumpliendo con el ISP. Este principio nos ayuda a evitar el acoplamiento innecesario y mejora la modularidad del código.

 

Principio de Inversión de Dependencias (Dependency Inversion Principle – DIP)

 

El Principio de Inversión de Dependencias (DIP) sugiere que los módulos de alto nivel no deben depender de los módulos de bajo nivel, sino que ambos deben depender de abstracciones. Además, estas abstracciones no deben depender de los detalles; en cambio, los detalles deben depender de las abstracciones.

 

Para demostrar el DIP en Python, consideremos un ejemplo en el que un módulo de reporte de alto nivel necesita recuperar datos de una fuente de datos de bajo nivel. En lugar de depender directamente de una implementación concreta de la fuente de datos, el módulo de reporte debería depender de una abstracción de la fuente de datos:

 

class FuenteDatos:
def obtener_datos(self):
raise NotImplementedError

class FuenteDatosMySQL(FuenteDatos):
def obtener_datos(self):
return "Datos de MySQL"

class Reporte:
def __init__(self, fuente_datos: FuenteDatos):
self.fuente_datos = fuente_datos

def generar_reporte(self):
datos = self.fuente_datos.obtener_datos()

print(f"Reporte generado con: {datos}")

 

 

En este ejemplo,Reporte es un módulo de alto nivel que depende de la abstracción FuenteDatos, y FuenteDatosMySQL es un módulo de bajo nivel que implementa esta abstracción. Al hacer que tanto los módulos de alto nivel como los de bajo nivel dependan de abstracciones, el DIP nos permite desacoplar el módulo de reporte de los detalles específicos de la fuente de datos, facilitando la extensión y el mantenimiento del código.

 

 

La adopción de buenas prácticas en cualquier proyecto o iniciativa empresarial es un pilar fundamental para asegurar la calidad, la eficiencia y la sostenibilidad a largo plazo. Mientras que en el contexto del desarrollo de software, los principios SOLID proporcionan una base sólida para la creación de código robusto y mantenible, los conceptos subyacentes a estas prácticas son igualmente aplicables a la gestión general de proyectos, la operación de negocios y la estrategia organizacional.

 

Para los clientes y las organizaciones, implementar y fomentar buenas prácticas en sus proyectos y procesos operativos ofrece numerosas ventajas:

 

  • Optimización de recursos: La eficiencia operativa se mejora mediante la eliminación de redundancias y la optimización de procesos, lo que resulta en un uso más efectivo de los recursos y una reducción de costos.

 

  • Mejora continua: La adopción de un enfoque basado en buenas prácticas facilita la identificación de áreas de mejora y promueve una cultura de innovación y excelencia continua.

 

  • Gestión de riesgos: Las buenas prácticas ayudan a identificar, evaluar y mitigar riesgos de manera proactiva, protegiendo a la organización de posibles contingencias y asegurando la continuidad del negocio.

 

  • Satisfacción del cliente: La mejora en la calidad del producto o servicio, resultado de procesos bien definidos y eficientes, conduce a una mayor satisfacción y lealtad del cliente.

 

  • Competitividad en el mercado: La capacidad de adaptarse rápidamente a cambios en el mercado y responder de manera efectiva a las demandas de los clientes es esencial en el entorno empresarial actual. Las buenas prácticas son clave para mantener esta agilidad y competitividad.

 

 

La inversión en buenas prácticas es una inversión en el futuro de la organización. No se trata solo de seguir un conjunto de reglas o procedimientos, sino de adoptar un enfoque estratégico y consciente hacia la calidad, la eficiencia y la sostenibilidad. Por ello, es fundamental que los clientes y las organizaciones se comprometan con la implementación de estas prácticas en todos los niveles de sus operaciones. Al hacerlo, no solo garantizan el éxito y la longevidad de sus proyectos individuales, sino que también aseguran la salud y el dinamismo de la organización en su conjunto.