¿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
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?
```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
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;
} } ~~~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;
} } ~~~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);
}
}
~~~
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();
~~~
El equilibrio entre cohesión y acoplamiento
El equilibrio entre cohesión y acoplamiento
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
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
Perro por Animal sin problemas.Imprimible, sin agregar métodos irrelevantes.ProcesadorDePago en lugar de una clase concreta como Paypal.Responsabilidad Única (SRP): Resumen y Ejemplo
El principio de responsabilidad única no se refiere a tener clases con un solo método, sino a diseñar componentes que solo estén expuestos a una fuente de cambio. Cada módulo o clase debe ser responsable de un único aspecto de la funcionalidad del software.
Ejemplo:
```javascript
class UseCase {
doSomethingWithTaxes() {
console.log(“Do something related with taxes …”);
}
saveChangesInDatabase() {
console.log(“Saving in database …”);
}
sendEmail() {
console.log(“Sending email …”);
}
}
function start() {
const myUseCase = new UseCase();
myUseCase.doSomethingWithTaxes();
myUseCase.saveChangesInDatabase();
myUseCase.sendEmail();
}
start();
~~~
En este ejemplo, la clase UseCase mezcla tres capas de lógica: negocio, presentación y persistencia, lo cual viola el principio de responsabilidad única (SRP). Cada método podría ser responsable de distintos actores:
doSomethingWithTaxes.sendEmail.saveChangesInDatabase.Aunque en proyectos pequeños no siempre se percibe la necesidad, diferenciar responsabilidades aumenta la flexibilidad y tolerancia al cambio en el software.
Este resumen incluye el código de ejemplo y muestra cómo la violación del principio SRP afecta la flexibilidad del software.
Responsabilidad Única (SRP) aplicado a componentes de Vue
El principio de responsabilidad única nos dice que un componente debe encargarse de una sola responsabilidad. En Vue, esto significa que un componente debe tener un único propósito y no mezclar diferentes tipos de lógica (como la presentación, la lógica de negocio o la persistencia de datos) dentro del mismo componente.
Ejemplo de Violación del SRP en Vue
Supongamos que tienes un componente de Vue que maneja tanto la lógica de negocio (calcular impuestos), como el envío de emails y la persistencia de datos:
```vue
<template>
<div>
<button @click="handleClick">Ejecutar Acción</button>
</div>
</template>
<script>
export default {
data() {
return {
user: { name: 'Juan', email: 'juan@example.com' },
};
},
methods: {
handleClick() {
this.doSomethingWithTaxes();
this.saveChangesInDatabase();
this.sendEmail();
},
doSomethingWithTaxes() {
console.log("Calculando impuestos...");
},
saveChangesInDatabase() {
console.log("Guardando cambios en la base de datos...");
},
sendEmail() {
console.log("Enviando email a: " + this.user.email);
}
}
}
</script>En este ejemplo, el componente hace tres cosas diferentes:
1. **Cálculo de impuestos** (`doSomethingWithTaxes`)
2. **Persistencia de datos** (`saveChangesInDatabase`)
3. **Envío de emails** (`sendEmail`)
Problema con este Enfoque
Este componente **viola el principio de responsabilidad única** porque está manejando múltiples responsabilidades:
- El cálculo de impuestos puede cambiar debido a nuevas regulaciones fiscales (lo que involucra a un equipo de contabilidad).
- El proceso de enviar emails podría cambiar por necesidades de marketing.
- La lógica de persistencia podría cambiar si se cambian los requisitos de la base de datos.
Cómo Aplicar SRP en Vue
Para aplicar el **SRP**, deberías dividir este componente en **múltiples componentes** o servicios que manejen cada responsabilidad por separado.
Ejemplo de Cómo Corregirlo
```vue
<!-- TaxCalculator.vue -->
<template>
<div>
<p>Calculando impuestos...</p>
</div>
</template>
<script>
export default {
methods: {
calculateTaxes() {
console.log("Calculando impuestos...");
}
}
}
</script>```vue
<!-- EmailSender.vue -->
<template>
<div>
<p>Enviando email...</p>
</div>
</template>
<script>
export default {
methods: {
sendEmail(user) {
console.log("Enviando email a: " + user.email);
}
}
}
</script>```vue
<!-- DatabaseSaver.vue -->
<template>
<div>
<p>Guardando cambios...</p>
</div>
</template>
<script>
export default {
methods: {
saveChanges() {
console.log("Guardando cambios en la base de datos...");
}
}
}
</script>```vue
<!-- MainComponent.vue -->
<template>
<div>
<TaxCalculator></TaxCalculator>
<EmailSender :user="user" />
<DatabaseSaver></DatabaseSaver>
</div>
</template>
<script>
import TaxCalculator from './TaxCalculator.vue';
import EmailSender from './EmailSender.vue';
import DatabaseSaver from './DatabaseSaver.vue';
export default {
components: {
TaxCalculator,
EmailSender,
DatabaseSaver
},
data() {
return {
user: { name: 'Juan', email: 'juan@example.com' }
};
}
}
</script>~~~
Beneficios
TaxCalculator.vue.Este enfoque aplica el principio SRP de forma práctica a los componentes de Vue, asegurando que cada componente tenga un único propósito y facilitando el mantenimiento y evolución del código.
Vuex como Singleton
Sí, Vuex puede considerarse un Singleton en el contexto de gestión de estado, ya que:
this.$store.Ejemplo de Vuex como Singleton:
store.js (instancia única de Vuex):
```js
import { createStore } from ‘vuex’;
const store = createStore({
state: {
counter: 0,
},
mutations: {
increment(state) {
state.counter++;
},
},
actions: {
increment({ commit }) {
commit(‘increment’);
},
},
getters: {
getCounter: (state) => state.counter,
},
});
export default store;
~~~
main.js (registrando Vuex en la aplicación):
```js
import { createApp } from ‘vue’;
import App from ‘./App.vue’;
import store from ‘./store’; // Única instancia de Vuex
createApp(App)
.use(store) // Usamos la tienda Vuex en toda la aplicación
.mount(‘#app’);
~~~
¿Cómo funciona Vuex como Singleton?
- Acceso global: Todos los componentes de la aplicación usan this.$store para acceder al estado global, a las mutaciones, acciones y getters definidos en la tienda. No se crean instancias adicionales, ya que this.$store se refiere siempre a la misma instancia.
- Centralización de la lógica de estado: La tienda Vuex centraliza todo el estado compartido y la lógica de modificación de este estado, asegurando que toda la aplicación esté sincronizada con esa única fuente de verdad.
Por lo tanto, Vuex actúa como un Singleton, ya que proporciona una instancia global de la tienda de estado que puede ser accedida y modificada por todos los componentes, asegurando que solo haya una instancia de esa tienda en toda la aplicación.
Detectar violaciones del SRP
Detectar violaciones del SRP:
Saber si estamos respetando o no el principio de responsabilidad única puede ser ambiguo. A continuación, algunas señales que indican una posible violación del SRP:
OCP - Principio Open-Closed
Principio Open-Closed (Abierto/Cerrado)
*“Todas las entidades software deberían estar * ABIERTAS A EXTENSION Y CERRADAS A MODIFICACION” – Bertrand Meyer
El principio Open-Closed recomienda que, al introducir nuevos comportamientos en sistemas existentes, no se modifiquen los componentes antiguos, sino que se creen nuevos componentes. Esto se debe a que modificar clases o componentes que ya están siendo utilizados en otros lugares puede alterar su comportamiento y provocar efectos indeseados.
Ventajas:
- Mejora la estabilidad de la aplicación, evitando cambios frecuentes en las clases existentes.
- Reduce la fragilidad de las cadenas de dependencia, ya que hay menos partes móviles que podrían generar problemas.
- Facilita la extensión futura de nuevas clases sin tener que modificar el código existente.
Este principio ayuda a mantener un sistema más robusto y escalable a medida que evoluciona.
Aplicando el OCP (Open-Closed Principle)
Aunque el principio Open-Closed puede parecer contradictorio, existen varias formas de aplicarlo, dependiendo del contexto. Algunas técnicas comunes incluyen:
Ejemplo práctico: Desacoplar un elemento de infraestructura de la capa de dominio.
Imagina un sistema de gestión de tareas con una clase TodoService que realiza una petición HTTP a una API REST para obtener las tareas. El código podría verse así:
```javascript
const axios = require(‘axios’);
class TodoExternalService {
requestTodoItems(callback) {
const url = ‘https://jsonplaceholder.typicode.com/todos/’;
axios.get(url).then(callback);
}
}
new TodoExternalService().requestTodoItems(response => console.log(response.data));
~~~
Problemas en este ejemplo:
1. Acoplamiento: La clase TodoExternalService depende de una librería externa (axios).
2. Violación del OCP: Si quisiéramos reemplazar axios por fetch u otra librería, tendríamos que modificar el código existente.
Solución: Usar un patrón adaptador para desacoplar la librería y aplicar el principio Open-Closed, permitiendo cambiar la implementación sin modificar el código original.
Este enfoque ayuda a mantener el sistema flexible y fácil de extender sin modificar el comportamiento existente.
Herencia
La herencia permite que una clase (subclase) herede las propiedades y métodos de otra clase (superclase).
Perro es un Animal.Ejemplo:
```javascript
class Animal {
eat() {
console.log(“Eating…”);
}
}
class Dog extends Animal {
bark() {
console.log(“Barking…”);
}
}
const dog = new Dog();
dog.eat(); // Heredado de Animal
dog.bark(); // Definido en Dog
~~~
Composición
La composición permite que una clase incluya instancias de otras clases como propiedades, delegando comportamientos a estos objetos.
Coche tiene un Motor.Ejemplo:
```javascript
class Engine {
start() {
console.log(“Engine starting…”);
}
}
class Car {
constructor() {
this.engine = new Engine(); // Composición: Car tiene un Engine
}
drive() {
this.engine.start();
console.log(“Driving…”);
}
}
const myCar = new Car();
myCar.drive(); // Comportamiento delegando a Engine
~~~
ISP - Principio de segregación de
la interfaz
“Los clientes no deberían estar obligados a depender de interfaces que no utilicen.”
– Robert C. Martin
El principio de segregación de la interfaz establece que una clase no debe depender de métodos o propiedades que no necesita. Las interfaces deben diseñarse pensando en las clases que las usarán (principio de clase cliente), no en implementaciones existentes.
En lenguajes como JavaScript, donde no hay interfaces, se confía en el buen uso del duck typing: la validez de un objeto depende de sus métodos y propiedades, no de su jerarquía de clases.
Con TypeScript, las interfaces son herramientas poderosas para definir contratos claros y aplicar este principio de manera efectiva.
Esto es dinámico y viene del componente padre.
Esto es dinámico y viene del componente padre.
Este es el contenido del cuerpo.
Este es el contenido del cuerpo.
Contenido específico
{{ description }}
{{ description }}
{{ type }} chart rendered!
Bar chart with barWidth: {{ barWidth }}
Line chart with lineThickness: {{ lineThickness }}
Pie chart with radius: {{ radius }}