🧑‍💻 FASE 1: Fundamentos Reforzados - Lectura y Refactorización de Código

Recetas prácticas para dominar los fundamentos técnicos del desarrollo de software


📚 Tabla de Contenidos

  1. Receta 3.1: Code Smells - Identificar problemas
  2. Receta 3.2: Principios SOLID

Lectura y Refactorización de Código

Receta 3.1: Code Smells - Identificar problemas

¿Qué son? Indicadores de que el código puede tener problemas de diseño, aunque técnicamente funcione.

Code Smells comunes y soluciones:

# ❌ CODE SMELL 1: Función muy larga (>20 líneas)
def process_order_bad(order):
    # Validar orden
    if not order.get('items'):
        return {"error": "No items"}
    
    if not order.get('customer'):
        return {"error": "No customer"}
    
    # Calcular total
    total = 0
    for item in order['items']:
        price = item['price']
        quantity = item['quantity']
        discount = item.get('discount', 0)
        total += price * quantity * (1 - discount)
    
    # Aplicar shipping
    if total < 50:
        total += 10
    
    # Aplicar impuestos
    total *= 1.08
    
    # Guardar en DB
    # ... 10 líneas más
    
    return {"total": total}

# ✅ SOLUCIÓN: Extraer métodos (Extract Method)
class OrderProcessor:
    TAX_RATE = 0.08
    SHIPPING_THRESHOLD = 50
    SHIPPING_COST = 10
    
    def process_order(self, order):
        """Punto de entrada claro y conciso"""
        if error := self._validate_order(order):
            return error
        
        subtotal = self._calculate_subtotal(order['items'])
        total = self._apply_fees(subtotal)
        
        return {"total": total}
    
    def _validate_order(self, order):
        """Validación separada"""
        if not order.get('items'):
            return {"error": "No items"}
        if not order.get('customer'):
            return {"error": "No customer"}
        return None
    
    def _calculate_subtotal(self, items):
        """Cálculo de subtotal aislado"""
        return sum(
            item['price'] * item['quantity'] * (1 - item.get('discount', 0))
            for item in items
        )
    
    def _apply_fees(self, subtotal):
        """Aplicar shipping e impuestos"""
        with_shipping = self._add_shipping(subtotal)
        return with_shipping * (1 + self.TAX_RATE)
    
    def _add_shipping(self, subtotal):
        """Lógica de shipping separada"""
        return subtotal + self.SHIPPING_COST if subtotal < self.SHIPPING_THRESHOLD else subtotal

# ❌ CODE SMELL 2: Parámetros largos
def create_user_bad(name, email, age, address, phone, country, city, zip_code, preferences):
    pass

# ✅ SOLUCIÓN: Objeto de parámetros
from dataclasses import dataclass

@dataclass
class UserData:
    name: str
    email: str
    age: int
    address: str
    phone: str
    country: str
    city: str
    zip_code: str
    preferences: dict

def create_user_good(user_data: UserData):
    # Mucho más legible y extensible
    pass

# ❌ CODE SMELL 3: Números mágicos
def calculate_discount_bad(price, customer_type):
    if customer_type == 1:
        return price * 0.9
    elif customer_type == 2:
        return price * 0.85
    elif customer_type == 3:
        return price * 0.75
    return price

# ✅ SOLUCIÓN: Constantes con nombres descriptivos
from enum import Enum

class CustomerType(Enum):
    REGULAR = 1
    PREMIUM = 2
    VIP = 3

DISCOUNT_RATES = {
    CustomerType.REGULAR: 0.10,
    CustomerType.PREMIUM: 0.15,
    CustomerType.VIP: 0.25,
}

def calculate_discount_good(price: float, customer_type: CustomerType) -> float:
    discount_rate = DISCOUNT_RATES.get(customer_type, 0)
    return price * (1 - discount_rate)

# ❌ CODE SMELL 4: Código duplicado
def send_email_to_customer(customer):
    email = customer['email']
    subject = "Thank you"
    body = "Thanks for your purchase"
    # Código de envío...
    pass

def send_email_to_supplier(supplier):
    email = supplier['email']
    subject = "New order"
    body = "You have a new order"
    # Mismo código de envío...
    pass

# ✅ SOLUCIÓN: DRY (Don't Repeat Yourself)
def send_email(recipient_email: str, subject: str, body: str):
    """Función reutilizable para enviar emails"""
    # Código de envío centralizado
    print(f"Sending to {recipient_email}: {subject}")

def send_email_to_customer(customer):
    send_email(
        customer['email'],
        "Thank you",
        "Thanks for your purchase"
    )

def send_email_to_supplier(supplier):
    send_email(
        supplier['email'],
        "New order",
        "You have a new order"
    )

# ❌ CODE SMELL 5: Comentarios que explican QUÉ hace el código
def process_bad(data):
    # Crear una lista vacía
    result = []
    
    # Iterar sobre cada elemento
    for item in data:
        # Multiplicar por 2
        doubled = item * 2
        # Agregar a la lista
        result.append(doubled)
    
    return result

# ✅ SOLUCIÓN: Código auto-documentado (los nombres explican el QUÉ)
def double_all_values(numbers):
    """Los comentarios explican el POR QUÉ, no el QUÉ"""
    return [number * 2 for number in numbers]

Lista de verificación de Code Smells:


Receta 3.2: Principios SOLID

S - Single Responsibility Principle

# ❌ VIOLACIÓN: Clase hace demasiado
class UserManager:
    def create_user(self, data):
        # Validar
        # Guardar en DB
        # Enviar email
        # Registrar log
        pass

# ✅ CORRECTO: Una responsabilidad por clase
class UserValidator:
    def validate(self, user_data):
        if not user_data.get('email'):
            raise ValueError("Email required")
        return True

class UserRepository:
    def save(self, user):
        # Solo interacción con DB
        print(f"Saving user: {user.email}")
        return user

class EmailService:
    def send_welcome_email(self, user):
        # Solo envío de emails
        print(f"Sending email to: {user.email}")

class UserService:
    """Orquesta otros servicios"""
    def __init__(self):
        self.validator = UserValidator()
        self.repository = UserRepository()
        self.email_service = EmailService()
    
    def create_user(self, user_data):
        self.validator.validate(user_data)
        user = self.repository.save(user_data)
        self.email_service.send_welcome_email(user)
        return user

O - Open/Closed Principle

# ❌ VIOLACIÓN: Modificar clase existente para nuevos tipos
class AreaCalculator:
    def calculate(self, shape):
        if shape.type == 'circle':
            return 3.14 * shape.radius ** 2
        elif shape.type == 'rectangle':
            return shape.width * shape.height
        # Agregar nuevo tipo requiere modificar esta clase

# ✅ CORRECTO: Abierto para extensión, cerrado para modificación
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius
    
    def area(self):
        return 3.14 * self.radius ** 2

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def area(self):
        return self.width * self.height

class Triangle(Shape):  # Nueva forma sin modificar código existente
    def __init__(self, base, height):
        self.base = base
        self.height = height
    
    def area(self):
        return 0.5 * self.base * self.height

def calculate_total_area(shapes):
    return sum(shape.area() for shape in shapes)

L - Liskov Substitution Principle

# ❌ VIOLACIÓN: Subclase cambia comportamiento esperado
class Bird:
    def fly(self):
        return "Flying"

class Penguin(Bird):
    def fly(self):
        raise Exception("Can't fly!")  # Rompe contrato

# ✅ CORRECTO: Jerarquía apropiada
class Bird:
    def move(self):
        pass

class FlyingBird(Bird):
    def move(self):
        return "Flying"
    
    def fly(self):
        return "Flying"

class Penguin(Bird):
    def move(self):
        return "Swimming"
    
    def swim(self):
        return "Swimming"

def make_bird_move(bird: Bird):
    print(bird.move())  # Funciona con cualquier Bird

make_bird_move(FlyingBird())
make_bird_move(Penguin())

I - Interface Segregation Principle

# ❌ VIOLACIÓN: Interfaz muy amplia fuerza implementaciones innecesarias
class Worker:
    def work(self):
        pass
    
    def eat(self):
        pass
    
    def sleep(self):
        pass

class Robot(Worker):
    def work(self):
        return "Working"
    
    def eat(self):
        pass  # Robots no comen, método innecesario
    
    def sleep(self):
        pass  # Robots no duermen

# ✅ CORRECTO: Interfaces pequeñas y específicas
class Workable:
    def work(self):
        pass

class Eatable:
    def eat(self):
        pass

class Sleepable:
    def sleep(self):
        pass

class Human(Workable, Eatable, Sleepable):
    def work(self):
        return "Working"
    
    def eat(self):
        return "Eating"
    
    def sleep(self):
        return "Sleeping"

class Robot(Workable):
    def work(self):
        return "Working 24/7"

D - Dependency Inversion Principle

# ❌ VIOLACIÓN: Alto nivel depende de bajo nivel
class MySQLDatabase:
    def save(self, data):
        print("Saving to MySQL")

class UserService:
    def __init__(self):
        self.db = MySQLDatabase()  # Acoplado a implementación concreta
    
    def save_user(self, user):
        self.db.save(user)

# ✅ CORRECTO: Ambos dependen de abstracción
class Database(ABC):
    @abstractmethod
    def save(self, data):
        pass

class MySQLDatabase(Database):
    def save(self, data):
        print(f"Saving to MySQL: {data}")

class PostgreSQLDatabase(Database):
    def save(self, data):
        print(f"Saving to PostgreSQL: {data}")

class MongoDatabase(Database):
    def save(self, data):
        print(f"Saving to MongoDB: {data}")

class UserService:
    def __init__(self, database: Database):  # Depende de abstracción
        self.db = database
    
    def save_user(self, user):
        self.db.save(user)

# Uso: fácil cambiar implementación
mysql_service = UserService(MySQLDatabase())
postgres_service = UserService(PostgreSQLDatabase())
mongo_service = UserService(MongoDatabase())