SOLID PRINCIPLES Flashcards
¿Que es STUPID?
STUPID es simplemente un acrónimo basado en seis code smells que describen cómo NO debe ser el software que
desarrollamos
- Singleton: patrón singleton
- Tight Coupling: alto acoplamiento
- Untestability: código no testeable
- Premature optimization: optimizaciones prematuras
- Indescriptive Naming: nombres poco descriptivos
- Duplication: duplicidad de código
STUPID 1: Singleton como code smell
El patrón Singleton es un patrón de diseño que busca asegurar que una clase tenga una única instancia en toda la aplicación y proporciona un punto de acceso global a esa instancia.
¿Cómo funciona?
- La clase tiene una propiedad estática que almacena la instancia única.
- Al intentar crear una nueva instancia, el constructor verifica si ya existe una instancia en esa propiedad estática:
- Si existe, devuelve esa instancia.
- Si no existe, crea una nueva, la almacena en la propiedad estática y la devuelve.
```javascript
class Singleton {
constructor() {
if (Singleton.instance) {
return Singleton.instance; // Devuelve la instancia existente
}
this.title = “my singleton”; // Inicializa la nueva instancia
Singleton.instance = this; // Almacena la instancia única
}
}
let mySingleton = new Singleton();
let mySingleton2 = new Singleton();
console.log(“Singleton 1: “, mySingleton.title); // “my singleton”
mySingleton.title = “modified in instance 1”;
console.log(“Singleton 2: “, mySingleton2.title); // “modified in instance 1”
~~~
Problemas del Singleton
-
Exposición global del estado
- Como el Singleton es accesible globalmente, cualquier parte de la aplicación puede modificar su estado. Esto dificulta rastrear los cambios y genera dependencias implícitas entre módulos, complicando el mantenimiento.
-
Dificultad para realizar pruebas unitarias
- Las pruebas unitarias deben ser independientes, pero el Singleton mantiene su estado entre ejecuciones.
- Si una prueba modifica el estado, otra prueba podría fallar inesperadamente.
-
Acoplamiento fuerte
- Los módulos que acceden directamente al Singleton introducen una dependencia implícita, dificultando cambios futuros, como reemplazarlo por otra implementación.
Cohesión
Cohesión
La cohesión se refiere a qué tan relacionados están los elementos dentro de un módulo o clase. Un módulo tiene alta cohesión cuando todas las partes que lo componen están muy enfocadas en una única responsabilidad o propósito. Un código con alta cohesión es más fácil de entender, mantener y probar, ya que todas las piezas del código están muy conectadas entre sí para lograr un objetivo claro.
Ejemplo de alta cohesión:
Imagina una clase que solo se encarga de realizar cálculos:
```javascript
class Calculadora {
sumar(a, b) {
return a + b;
}
restar(a, b) { return a - b; } } ~~~ - Aquí, **todos los métodos de la clase están relacionados con el cálculo**. - Esto hace que la clase sea **fácil de entender** y **sólo se enfoque en una tarea** (realizar cálculos). - Esta clase tiene **alta cohesión**.
Ejemplo de baja cohesión:
Si la misma clase comenzara a hacer más tareas no relacionadas, como enviar correos y manipular texto, perdería cohesión:
```javascript
class Utilidades {
enviarCorreo(correo) {
// lógica para enviar correo
}
convertirTextoAMayusculas(texto) { return texto.toUpperCase(); } sumar(a, b) { return a + b; } } ~~~ - Aquí, **los métodos están desconectados entre sí**: enviar correos, modificar texto y realizar cálculos. - Esto crea una clase que es **difícil de entender y mantener** porque tiene responsabilidades **divididas**, lo que se traduce en **baja cohesión**.
Acoplamiento
Acoplamiento
El acoplamiento mide qué tan dependientes están los módulos entre sí. Un sistema tiene alto acoplamiento cuando los módulos están estrechamente interconectados y dependen unos de otros. Por el contrario, un sistema con bajo acoplamiento tiene módulos más independientes, lo que facilita el mantenimiento, las pruebas y la reutilización.
Ejemplo de alto acoplamiento:
Imagina una clase Pedido que depende directamente de la clase Notificador:
```javascript
class Pedido {
constructor() {
this.notificador = new Notificador(); // Dependencia directa
}
procesar() { this.notificador.enviarNotificacion("Pedido procesado"); } }
class Notificador {
enviarNotificacion(mensaje) {
console.log(“Notificación enviada:”, mensaje);
}
}
~~~
- En este caso, Pedido depende directamente de Notificador.
- Si alguna vez cambias el funcionamiento de Notificador, tendrás que modificar también la clase Pedido.
- Esto es alto acoplamiento porque los módulos no son independientes.
Ejemplo de bajo acoplamiento:
En este caso, si pasamos el Notificador como parámetro al constructor de Pedido, reducimos la dependencia directa.
Esto se llama Inyección de Dependencias:
```javascript
class Pedido {
constructor(notificador) {
this.notificador = notificador; // Inyección de dependencia
}
procesar() { this.notificador.enviarNotificacion("Pedido procesado"); } }
class Notificador {
enviarNotificacion(mensaje) {
console.log(“Notificación enviada:”, mensaje);
}
}
// Uso:
const notificador = new Notificador();
const pedido = new Pedido(notificador);
pedido.procesar();
~~~
- Aquí, Pedido no depende directamente de la clase Notificador, sino que la recibe desde fuera.
- Esto es bajo acoplamiento, porque puedes cambiar la implementación de Notificador sin necesidad de modificar la clase Pedido.
El equilibrio entre cohesión y acoplamiento
El equilibrio entre cohesión y acoplamiento
- Alta cohesión significa que los módulos o clases deben enfocarse en una sola responsabilidad y tener todo lo necesario para cumplir con esa responsabilidad. Esto mejora la comprensión y el mantenimiento del código.
- Bajo acoplamiento significa que los módulos deben depender lo menos posible unos de otros. Esto facilita la reutilización, el cambio y las pruebas, ya que cada módulo puede ser modificado de manera independiente.
El verdadero desafío en el diseño de sistemas es encontrar el equilibrio entre cohesión y acoplamiento. Si maximizas la cohesión, podrías crear módulos muy específicos pero con muchas dependencias entre ellos, lo que aumenta el acoplamiento. Por el contrario, si minimizas el acoplamiento, puedes terminar con módulos más dispersos y difíciles de gestionar. Por eso, la clave es favorecer un bajo acoplamiento sin sacrificar la cohesión.
Esto permitirá crear sistemas modulares, fáciles de mantener, probar y extender.
Código no testeable
El código no testeable suele ser el resultado de un alto acoplamiento y la FALTA de inyección de dependencias. Este último aspecto se aborda con el principio SOLID de inversión de dependencias. Aunque existen técnicas específicas para solucionar estos problemas, la clave es diseñar el sistema teniendo en cuenta la testabilidad desde el inicio. Esto nos permite detectar problemas como el alto acoplamiento y las dependencias de estado global de manera temprana. Estos temas se explorarán más a fondo en las secciones de Unit Testing y TDD.
Optimización Prematura
Evitar la Complicación Innecesaria
“Cuando lleguemos a ese río, cruzaremos ese puente”. Retrasar decisiones permite enfocarse en lo más importante: las reglas de negocio, donde está el valor real. Al aplazar estas decisiones, se obtiene más información sobre los requisitos del proyecto, lo que facilita tomar mejores decisiones.
Donald Knuth (el de The Art of Computer Programming) afirmaba que la optimización prematura es la raíz de todos los males. No se trata de escribir código no optimizado, sino de evitar desarrollar abstracciones innecesarias que compliquen el software sin necesidad.
Complejidad Esencial vs. Complejidad Accidental
La complejidad accidental ocurre cuando se introduce una solución innecesariamente compleja en un proyecto software. La complejidad esencial es la inherente al problema, la cual debe ser la única presente. Sin embargo, a menudo agregamos complejidad accidental debido al desconocimiento o falta de planificación, lo que hace el proyecto difícil de mantener y poco adaptable al cambio.
Fred Brooks, en su artículo No Silver Bullet, distinguió entre estas dos complejidades, basándose en la descomposición aristotélica del conocimiento.
STUPID 2: Indescriptive Naming
nombres poco
descriptivos. Básicamente viene a decirnos que los nombres de variables, métodos
y clases deben seleccionarse con cuidado para que den expresividad y significado a
nuestro código
Principio DRY (Don’t Repeat Yourself)
El último principio del acrónimo STUPID hace referencia al principio DRY, que nos aconseja evitar la duplicación de código. La idea central de DRY es que cada pieza de conocimiento o lógica debe tener una única representación dentro del sistema. Esto mejora la mantenibilidad, reduce errores y facilita las modificaciones en el futuro.
Aunque este principio es valioso, es importante recordar que no es absoluto. Existen excepciones donde la duplicación puede ser necesaria, por ejemplo, en casos de optimización o cuando la claridad del código se ve beneficiada.
En resumen, el principio DRY es fundamental para escribir código más limpio y fácil de mantener, pero siempre se debe considerar el contexto y las necesidades del proyecto.
Principios STUPID: Orden y Significado
- S - Singleton: Evitar la creación de instancias innecesarias de una clase.
- T - Tight coupling: Evitar el alto acoplamiento entre componentes.
- U - Untestability: Evitar código difícil de testear.
- P - Premature optimization: Evitar la optimización prematura que añade complejidad innecesaria.
- I - Inconsistent interfaces: Evitar interfaces inconsistentes que complican la interacción entre componentes.
- D - Duplication: Evitar la duplicación de código (principio DRY).
- S - Speculative generality: Evitar abstracciones innecesarias o generalizaciones especulativas que añaden complejidad.
Duplicidad Real: Qué Es y Cómo Evitarla
La duplicidad real ocurre cuando el código es idéntico y cumple la misma función en múltiples lugares del proyecto. Esto implica que, al hacer un cambio, debemos propagarlo manualmente en todos esos lugares, lo cual aumenta las probabilidades de error humano.
Ejemplo:
Imagina que tienes el siguiente código duplicado en dos funciones diferentes:
```javascript
function calcularDescuento1(precio) {
return precio * 0.9;
}
function calcularDescuento2(precio) {
return precio * 0.9;
}
~~~
Si necesitamos cambiar la lógica del descuento (por ejemplo, modificar el porcentaje), tendríamos que hacerlo en ambos lugares, lo que aumenta la posibilidad de cometer errores.
Solución:
Unifica la lógica del descuento en una sola función y reutilízala.
```javascript
function calcularDescuento(precio, porcentaje = 0.9) {
return precio * porcentaje;
}
~~~
Este formato ayuda a recordar lo que es la duplicidad real y cómo solucionarla con un ejemplo práctico.
Duplicidad Accidental: Qué Es y Cómo Evitarla
Duplicidad Accidental: Qué Es y Cómo Evitarla
La duplicidad accidental ocurre cuando el código parece el mismo pero cumple funciones diferentes. A diferencia de la duplicidad real, en este caso, no es necesario propagar el cambio a todos los lugares donde aparece el código, ya que el cambio solo afectará a uno de esos lugares. A pesar de esto, es un tipo de código que deberíamos evitar y buscar unificar.
Ejemplo:
Imagina que tienes dos funciones que calculan el impuesto en diferentes situaciones:
```javascript
function calcularImpuestoVenta(precio) {
return precio * 0.21; // Impuesto sobre ventas del 21%
}
function calcularImpuestoServicios(precio) {
return precio * 0.18; // Impuesto sobre servicios del 18%
}
~~~
Aunque el código se ve similar, cada función está haciendo un cálculo con un porcentaje diferente. Si necesitas cambiar el porcentaje, es probable que solo debas cambiar uno de los lugares, lo que introduce el riesgo de inconsistencias.
Solución:
Unifica la lógica de cálculo del impuesto y hazla flexible para manejar diferentes porcentajes:
```javascript
function calcularImpuesto(precio, porcentaje) {
return precio * porcentaje;
}
~~~
Principios SOLID: Qué Son
Los principios SOLID nos guían sobre cómo organizar nuestras funciones, estructuras de datos y componentes de software. Aunque originalmente se aplican a clases en la orientación a objetos, estos principios también son útiles para agrupaciones de funciones y datos (por ejemplo, en una Closure). Son fundamentales para cualquier tipo de producto software, ya sea orientado a objetos o no.
El acrónimo SOLID fue creado por Michael Feathers y popularizado por Robert C. Martin en su libro Agile Software Development: Principles, Patterns, and Practices. Los cinco principios tienen como objetivo mejorar la mantenibilidad, simplificar los cambios y facilitar el testing del código.
Principios SOLID: Qué Son Ancronimo
-
Single Responsibility (Responsabilidad Única)
Cada clase debe tener una única responsabilidad para evitar cambios innecesarios.
Ejemplo: Separar cálculo de salario y generación de reportes. -
Open/Closed (Abierto/Cerrado)
Las clases deben estar abiertas para la extensión pero cerradas para la modificación.
Ejemplo: Añadir nuevos descuentos sin modificar el código original. -
Liskov Substitution (Sustitución de Liskov)
Las subclases deben ser reemplazables por sus clases base sin alterar el comportamiento.
Ejemplo: SustituirPerro
porAnimal
sin problemas. -
Interface Segregation (Segregación de Interfaz)
Las interfaces deben ser específicas y no forzar la implementación de métodos innecesarios.
Ejemplo: Crear interfaces pequeñas, comoImprimible
, sin agregar métodos irrelevantes. -
Dependency Inversion (Inversión de Dependencia)
Las clases deben depender de abstracciones, no de clases concretas.
Ejemplo: Usar una interfazProcesadorDePago
en lugar de una clase concreta comoPaypal
.