🧑💻 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
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:
- ❌ Funciones >20 líneas → Extraer métodos
- ❌ Más de 3 parámetros → Objeto de parámetros
- ❌ Números mágicos → Constantes nombradas
- ❌ Código duplicado → DRY principle
- ❌ Comentarios obvios → Nombres descriptivos
- ❌ Clase “Dios” (hace todo) → Separar responsabilidades
- ❌ Feature Envy (usa más datos de otra clase) → Mover lógica
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())