Principios básicos de Scrum
Las metodologías ágiles surgen para proporcionar un mecanismo que facilite la adaptación al cambio …
leer másEn el vasto universo de la programación existen principios que, aunque no sean mandamientos, son una gran orientación para aquellos que deseen escribir código limpio, mantenible y eficiente. Estos principios no sólo representan buenas prácticas sino que también son el fruto de años de experiencia y aprendizaje de la comunidad de desarrolladores. A continuación, profundizaremos en algunos de estos principios.
La repetición es la ruina del programador.
Esta frase encapsula la esencia del principio DRY - Don’t Repeat Yourself (No te repitas), busca reducir duplicidades en nuestro código ya que la repetición conduce a errores, ineficiencias y dificultades en la creación de software.
Las violaciones comunes del principio DRY incluyen prácticas como copiar y pegar código de manera reiterada, implementar funciones o métodos que ejecutan operaciones muy parecidas o idénticas y diseñar clases con estructuras que presentan similitudes notorias.
# Wrong approach
def calculate_rectangle_area(width, height):
return width * height
def calculate_circle_area(radius):
return 3.14 * radius * radius
# Using DRY Principle
class Shape:
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius * self.radius
Abstraer para evitar repetición es esencial pero no querrás complicar más las cosas, antes de abstraer pregúntate si la abstracción complica el código o si sigue siendo legible. En ocasiones, un poco de repetición es mejor si facilita la comprensión.
Lo simple es mejor que lo complejo, y lo complejo es mejor que lo complicado.
Esta es una variante de la filosofía detrás de KISS - Keep It Simple Stupid (Mantenlo simple estúpido), un principio que promueve la simplicidad en diseño y código por encima de soluciones complejas y rebuscadas. La simplicidad es un objetivo deseable en el desarrollo de software ya que conduce a un código más limpio, fácil de entender y mantener.
La clave para implementar KISS es la reflexión y la autoevaluación constante. Siempre que estés por escribir o diseñar algo pregúntate: “¿Hay una forma más simple de hacer esto?”.
Supongamos que queremos filtrar números pares de una lista:
# Aproximación exagerada
def rectangle_area(width, height):
return width * height
def circle_area(radius):
return 3.14159 * radius * radius
# Y así para cada forma...
Una solución más simple y escalable es usar clases y polimorfismo:
# Usando el principio KISS
class Shape:
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius * self.radius
Con este enfoque basado en clases tienes una estructura más limpia que permite añadir nuevas figuras sin necesidad de crear una nueva función para cada una. Es una simplificación en diseño más que una reducción en la cantidad de código.
No te dejes seducir por la elegancia técnica o soluciones inteligentes. A menudo el método más directo y sencillo es el mejor. Al enfrentarte a un problema, antes de sumergirte en el código, toma un momento para pensar y planificar. Recuerda que lo que parece simple y claro para ti ahora, podría no serlo para alguien más (o incluso para ti mismo) en el futuro.
El mejor código es el código que nunca escribes.
YAGNI, que significa No Lo Vas a Necesitar por sus siglás en Inglés de You aren’t gonna need it, es un principio que anima a los desarrolladores a implementar funcionalidades sólo cuando son necesarias. Advierte contra la sobredimensión o añadir características basadas en especulaciones sobre necesidades futuras.
Muchos desarrolladores caen en la trampa de anticipar cada posible requerimiento futuro, lo que lleva a soluciones sobredimensionadas y demasiado complejas. YAGNI sugiere que sólo deberías añadir funcionalidad cuando hay un requisito definitivo para ello.
Supongamos que estás creando una plataforma de blogs. Aunque un sistema de etiquetas o una búsqueda avanzada podrían ser útiles en el futuro, si los requisitos actuales sólo especifican características básicas de blogs, YAGNI aconsejaría no implementarlas desde el principio.
# Enfoque Sobredimensionado
class Blog:
def __init__(self):
self.posts = []
self.tags = []
self.advanced_search = []
# Principio YAGNI
class Blog:
def __init__(self):
self.posts = []
Siempre prioriza los requisitos actuales y concretos sobre los especulativos. Al diseñar o codificar, pregúntate: “¿Necesito esto ahora? ¿Existe un requisito claro para esto?” Si la respuesta es “no” o “no estoy seguro”, considera posponer esa característica o funcionalidad. Evita la tentación de añadir código “por si acaso”. Recuerda, siempre puedes añadirlo más tarde cuando sea realmente necesario.
Divide y vencerás.
Este viejo adagio tiene un profundo significado en el diseño de software y la Separación de Intereses es su estandarte. Este principio nos insta a descomponer un programa en piezas distintas donde cada una tenga una responsabilidad única y claramente definida.
Siempre tener en mente la singularidad de responsabilidad y pensar en el software en términos de módulos o componentes autónomos. Si una función o clase tiene más de una responsabilidad probablemente se deba dividir en piezas más pequeñas. No es solo una buena práctica, es una filosofía de diseño que debería ser una prioridad en cualquier proyecto de software, ya que mejora la calidad, eficiencia y escalabilidad del código.
Imagina una aplicación web que involucra una base de datos, una interfaz de usuario y un servidor:
# Aproximación incorrecta
class App:
def get_user_data(self, user_id):
# Fetch from database
pass
def render_user_page(self, user_data):
# Render user page
pass
def handle_request(self, request):
# Handle web request
pass
# Aproximación correcta
class Database:
def get_user_data(self, user_id):
# Fetch from database
pass
class UserInterface:
def render_user_page(self, user_data):
# Render user page
pass
class Server:
def handle_request(self, request, db, ui):
user_data = db.get_user_data(request.user_id)
ui.render_user_page(user_data)
Imagina que estás resolviendo un rompecabezas, si mezclas las piezas de diferentes rompecabezas se tornará extremadamente difícil, si no imposible, completar la imagen. Del mismo modo, en el desarrollo de software, cada componente o función debe ser como una pieza única de un rompecabezas, con un propósito y forma definidos. Mantén tus componentes y funciones enfocados en una sola tarea o responsabilidad. Al hacerlo, no solo te será más fácil identificar y resolver problemas, sino que también podrás reutilizar, probar y entender tu código con mucha más facilidad. Cuando un componente o función comienza a manejar múltiples tareas, es hora de hacer una pausa, reevaluar y, probablemente, dividirlo.
La Ley de Deméter, a menudo referida como el Principio de menor conocimiento, tiene sus raíces en la idea de reducir las dependencias entre módulos o clases en un software. Esencialmente, sugiere que cada unidad debería tener un conocimiento limitado sobre otras unidades y sólo debería interactuar con sus “amigos cercanos”.
La Ley de Deméter se ve incumplida cuando se navega a través de múltiples objetos accediendo a métodos en cadena, como a.b().c(), cuando se inicializan objetos en una clase que deberían ser inyectados como dependencias o cuando una clase interactúa con varias otras para obtener datos o ejecutar funciones. Los beneficios en términos de claridad y estructura del código son invaluables, como con todos los principios de diseño, la clave está en aplicarlo con juicio y equilibrio.
Imaginemos un caso donde un conductor quiere encender un coche:
# Aproximación incorrecta
class Engine:
class SparkPlug:
def ignite(self):
pass
class Car:
def __init__(self):
self.engine = Engine()
def start(self):
self.engine.SparkPlug().ignite()
# Aproximación correcta
class Engine:
def start(self):
spark_plug = SparkPlug()
spark_plug.ignite()
class SparkPlug:
def ignite(self):
pass
class Car:
def __init__(self):
self.engine = Engine()
def start(self):
self.engine.start()
En el enfoque correcto, Car no necesita conocer la existencia de SparkPlug. Simplemente sabe que el motor tiene un método start(). La implementación interna de cómo el motor arranca queda encapsulada dentro de la clase Engine.
Piensa en la Ley de Deméter como un manual de instrucciones para un dispositivo, si necesitas realizar una función específica en éste el manual te dirige a un botón o comando directo para realizar esa acción. No te dice que presiones un botón que luego te lleve a otro panel, donde debes girar una perilla para luego llevarte a otro lugar donde finalmente realizas la función. En la programación este principio nos sugiere interactuar directamente con los componentes que necesitamos, en lugar de encadenar múltiples acciones a través de diferentes módulos.
Los usuarios, tanto desarrolladores como finales, esperan que los componentes, funciones o módulos del software funcionen de una manera específica. El principio de la mínima sorpresa insta a que el comportamiento de cualquier pieza de software no debería sorprender o confundir a quien lo utiliza, debería ser claro, coherente y predecible.
Cuando un usuario interactúa con un sistema ya tiene ciertas expectativas sobre cómo debería funcionar basándose en sus experiencias pasadas o en convenciones establecidas. Una aplicación se comporta de una manera que contradice esas expectativas puede causar frustración y conducir a errores.
En muchos sistemas, hacer clic en la “X” de una de las esquinas superiores de una ventana la cierra. Si, en cambio, al hacer clic en esa “X” se abriera una nueva ventana o se eliminara un archivo, esto iría en contra de la expectativa del usuario y violaría el principio de la mínima sorpresa.
# Wrong
def saveFile(file):
# logic to save the file
sendEmail()
deleteAnotherFile()
# Right
def saveFile(file):
# logic to save the file
pass
En el segundo ejemplo, además de guardar el archivo se llevan a cabo otras dos operaciones que no se esperarían basándose en el nombre de la función. Estas acciones adicionales son inesperadas y podrían ser perjudiciales especialmente si el usuario no es consciente de ellas.
Imagina que estás leyendo un libro y, sin previo aviso, en medio de una historia de detectives, aparece un dragón
lanzando fuego.
Esa súbita transición genera sorpresa y confusión. Lo mismo ocurre en el desarrollo de software, si estás diseñando una
función o componente hazlo de tal forma que sus acciones sean intuitivas y esperadas.
Mantén la coherencia y evita las “sorpresas” inesperadas en tu diseño, la claridad y previsibilidad son esenciales para
un software robusto y confiable.
El principio Fail-Fast (fallar rápidamente) sugiere que un sistema o aplicación debe identificar condiciones de error o anomalías tan pronto como ocurran, detener su operación y notificar el problema de inmediato. La idea es que es mucho mejor detectar problemas temprano y tratarlos de inmediato, en lugar de permitir que un sistema continúe funcionando en un estado potencialmente inestable.
Este principio es fundamental en muchos aspectos de la programación, desde el diseño de algoritmos hasta el desarrollo de sistemas completos. Al detectar y manejar errores tan pronto como suceden, se pueden evitar consecuencias indeseadas más adelante en el proceso, como cálculos incorrectos, corrupción de datos o incluso fallos en el sistema.
Aplicar este principio no solo ayuda a construir sistemas más robustos y confiables sino que también facilita la detección y corrección de errores durante las fases de desarrollo y prueba. Esto puede ahorrar tiempo y recursos significativos en comparación con la corrección de problemas después de que un producto se haya lanzado o implementado.
Además, al proporcionar retroalimentación inmediata sobre problemas, se mejora la experiencia del usuario o del desarrollador, ya que pueden abordar y corregir problemas con prontitud.
Imagina una función que realiza una operación de división. En lugar de permitir que la operación continúe con un divisor de cero (lo que resultaría en un error), es mejor verificar esta condición y generar un error de inmediato, informando al usuario o desarrollador sobre el problema.
# Incorrecto
def divide_incorrect(a, b):
if b == 0:
return 0 # Retorna un valor por defecto sin notificar el error
return a / b
# Correcto
def divide(a, b):
if b == 0:
raise ValueError("El divisor no puede ser cero.")
return a / b
Imagina que estás vertiendo agua en un colador creyendo que es un vaso, cuanto antes te des cuenta del error menos agua desperdiciarás. De igual manera, en el desarrollo de software, cuando algo va mal, es vital darse cuenta y actuar al instante. Si tu código detecta una condición anómala o un error potencial, no lo ignores ni lo ocultes; haz que tu sistema lo señale de inmediato. Adopta una mentalidad de fallar rápido para que tus programas te informen y no al contrario. Un sistema que falla rápidamente y de manera informativa es un sistema que se preocupa por su integridad y la de sus usuarios.
A través de los años y la experiencia acumulada en el campo del desarrollo se han establecido una serie de principios y prácticas que actúan como guías para crear código de alta calidad. No se trata de simples reglas o estándares a seguir, sino la destilación del conocimiento colectivo para diseñar e implementar software robusto, mantenible y efectivo.
Aplicar estos principios refleja el compromiso del desarrollador de producir software que no sólo funcione, sino que también sea duradero y valioso para sus usuarios. En el camino hacia el dominio de la programación estos principios son las balizas que nos guían. Sin embargo, es crucial recordar que una aplicación excesiva o rígida de estos puede ser contraproducente.
Quizá te puedan interesar
Las metodologías ágiles surgen para proporcionar un mecanismo que facilite la adaptación al cambio …
leer másEs crucial entender algunos parámetros comunes en Docker que son esenciales para su administración a …
leer másIntroducción En los capítulos anteriores de nuestra serie sobre Flutter, hemos establecido una …
leer másDe concepto a realidad