Code Smells en Programación Reactiva
La programación reactiva se centra en flujos de datos y la propagación de cambios.
5.1. Smells en Programación Reactiva
- Suscripciones sin Gestión: No cancelar suscripciones (memory leaks)
- Anidación de Observables: Múltiples niveles de suscripción (Callback Hell reactivo)
- Efectos Secundarios en el Flujo: Operadores que causan efectos secundarios
- Error Handling Deficiente: No manejar errores en la cadena reactiva
- Operadores Innecesarios: Uso excesivo de operadores cuando otros más simples servirían
5.2. Ejemplo: Anidación de Observables y Suscripciones sin Gestión
import { Observable, of } from 'rxjs';
import { map, mergeMap } from 'rxjs/operators';
class UserService {
getUserById(id) {
// Retorna un Observable con datos del usuario
return of({ id, name: 'User ' + id });
}
getOrders(userId) {
// Retorna un Observable con órdenes del usuario
return of([{ id: 1, userId }, { id: 2, userId }]);
}
loadUserData(userId) {
// Problema 1: Anidación de observables
this.getUserById(userId).subscribe(user => {
console.log('User:', user);
// Problema 2: Suscripción dentro de suscripción
this.getOrders(user.id).subscribe(orders => {
console.log('Orders:', orders);
// No se manejan errores ni se cancelan suscripciones
});
});
}
}
Refactorización
import { Observable, of, forkJoin } from 'rxjs';
import { map, mergeMap, catchError, finalize, takeUntil } from 'rxjs/operators';
class UserService {
constructor() {
this.destroy$ = new Subject();
}
getUserById(id) {
return of({ id, name: 'User ' + id });
}
getOrders(userId) {
return of([{ id: 1, userId }, { id: 2, userId }]);
}
loadUserData(userId) {
// Mejora 1: Flujo encadenado en lugar de anidado
return this.getUserById(userId).pipe(
mergeMap(user => {
// Mejora 2: Uso de forkJoin para combinar resultados
return forkJoin({
user: of(user),
orders: this.getOrders(user.id)
});
}),
// Mejora 3: Manejo de errores
catchError(error => {
console.error('Error loading user data:', error);
return of({ user: null, orders: [] });
}),
// Mejora 4: Limpieza al finalizar
finalize(() => console.log('User data loading finished')),
// Mejora 5: Cancelación automática
takeUntil(this.destroy$)
).subscribe(({ user, orders }) => {
console.log('User:', user);
console.log('Orders:', orders);
});
}
// Mejora 6: Método para cancelar suscripciones
destroy() {
this.destroy$.next();
this.destroy$.complete();
}
}