Al crear sistemas de software, es fundamental gestionar la complejidad de la base del código.
El Capítulo 11 de Clean Code analiza el diseño de sistemas modulares que son más fáciles de mantener y adaptar con el tiempo.
Podemos utilizar ejemplos de JavaScript para ilustrar estos conceptos.
A medida que los sistemas crecen, naturalmente se vuelven más complejos. Esta complejidad puede dificultar:
Un sistema bien diseñado debe ser fácil de modificar, comprobable y escalable. El secreto para lograrlo reside en la modularidad y la cuidadosa separación de preocupaciones.
En el corazón del diseño de sistemas limpios se encuentra el principio de modularidad. Usted hace que el sistema sea más manejable al dividir un sistema grande en módulos más pequeños e independientes, cada uno con una responsabilidad clara.
Cada módulo debe encapsular una funcionalidad específica, haciendo que el sistema general sea más fácil de entender y cambiar.
Ejemplo: organización de un sistema de carrito de compras
Imaginemos un sistema de carrito de compras en JavaScript. En lugar de agrupar toda la lógica en un solo archivo, puede dividir el sistema en varios módulos:
// cart.js export class Cart { constructor() { this.items = []; } addItem(item) { this.items.push(item); } getTotal() { return this.items.reduce((total, item) => total item.price, 0); } } // item.js export class Item { constructor(name, price) { this.name = name; this.price = price; } } // order.js import { Cart } from './cart.js'; import { Item } from './item.js'; const cart = new Cart(); cart.addItem(new Item('Laptop', 1000)); cart.addItem(new Item('Mouse', 25)); console.log(`Total: $${cart.getTotal()}`);
Las responsabilidades se dividen aquí: Cart administra los artículos, Item representa un producto y order.js organiza las interacciones.
Esta separación garantiza que cada módulo sea autónomo y más fácil de probar y cambiar de forma independiente.
Uno de los objetivos de la modularidad es la encapsulación: ocultar el funcionamiento interno de un módulo del resto del sistema.
El código externo solo debe interactuar con un módulo a través de su interfaz bien definida.
Esto facilita el cambio de la implementación interna del módulo sin afectar otras partes del sistema.
Ejemplo: encapsular la lógica del carrito
Digamos que queremos cambiar la forma en que calculamos el total en el carrito. Quizás ahora necesitemos contabilizar el impuesto sobre las ventas. Podemos encapsular esta lógica dentro de la clase Cart:
// cart.js export class Cart { constructor(taxRate) { this.items = []; this.taxRate = taxRate; } addItem(item) { this.items.push(item); } getTotal() { const total = this.items.reduce((sum, item) => sum item.price, 0); return total total * this.taxRate; } } // Now, the rest of the system does not need to know about tax calculations.
Otras partes del sistema (como order.js) no se ven afectadas por los cambios en la forma en que se calcula el total. Esto hace que su sistema sea más flexible y más fácil de mantener.
Un problema común en sistemas grandes es que diferentes partes del sistema se enredan.
Cuando un módulo comienza a asumir demasiadas responsabilidades, se vuelve más difícil cambiarlo o reutilizarlo en diferentes contextos.
El principio de separación de intereses garantiza que cada módulo tenga una responsabilidad específica.
Ejemplo: Manejo del pago por separado
En el ejemplo del carrito de compras, el procesamiento de pagos debe manejarse en un módulo separado:
// payment.js export class Payment { static process(cart) { const total = cart.getTotal(); console.log(`Processing payment of $${total}`); // Payment logic goes here } } // order.js import { Cart } from './cart.js'; import { Payment } from './payment.js'; const cart = new Cart(0.07); // 7% tax rate cart.addItem(new Item('Laptop', 1000)); cart.addItem(new Item('Mouse', 25)); Payment.process(cart);
Ahora, la lógica de pago está separada de la gestión del carrito. Esto facilita modificar el proceso de pago más adelante (por ejemplo, integrarlo con un proveedor de pago diferente) sin afectar el resto del sistema.
Uno de los mayores beneficios de la modularidad es que puedes probar cada módulo de forma independiente.
En el ejemplo anterior, podría escribir pruebas unitarias para la clase Cart sin necesidad de preocuparse por cómo se procesan los pagos.
Ejemplo: Unidad de prueba del carrito
// cart.test.js import { Cart } from './cart.js'; import { Item } from './item.js'; test('calculates total with tax', () => { const cart = new Cart(0.05); // 5% tax cart.addItem(new Item('Book', 20)); expect(cart.getTotal()).toBe(21); });
Con una clara separación de preocupaciones, cada módulo se puede probar de forma aislada, lo que facilita la depuración y acelera el desarrollo.
Cuando los módulos dependen demasiado unos de otros, los cambios en una parte del sistema pueden tener consecuencias inesperadas en otras partes.
Para minimizar esto, busque un acoplamiento flojo entre los módulos.
Esto permite que cada módulo evolucione de forma independiente.
Ejemplo: Inyectar dependencias
En lugar de codificar dependencias dentro de un módulo, pásalas como argumentos:
// cart.js export class Cart { constructor(taxRateCalculator) { this.items = []; this.taxRateCalculator = taxRateCalculator; } addItem(item) { this.items.push(item); } getTotal() { const total = this.items.reduce((sum, item) => sum item.price, 0); return total this.taxRateCalculator(total); } }
Este enfoque hace que la clase Cart sea más flexible y más fácil de probar con diferentes cálculos de impuestos.
¡Feliz codificación! ?
Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.
Copyright© 2022 湘ICP备2022001581号-3