La Secuencia Principal: El Equilibrio A/I
Los componentes deben equilibrar Abstracción (A) e Inestabilidad (I) — la Zona de Dolor (estable+concreto) y la Zona de Inutilidad (abstracto+inestable) son antipatrones a evitar.
Por qué importa
La Secuencia Principal es la línea ideal en un gráfico bidimensional donde los ejes son Abstracción (A) e Inestabilidad (I), ambos de 0 a 1. La Secuencia Principal es la línea A + I = 1. Los componentes en esta línea están perfectamente equilibrados: o estables y abstractos, o volátiles y concretos.
Zona de Dolor (A≈0, I≈0): Estable y concreto. Un componente del que dependen muchos módulos, pero que contiene solo implementación concreta. No puede extenderse sin modificación. Cada nuevo requisito requiere editarlo, lo que se propaga a todos los dependientes. Esta es la zona más peligrosa — el componente resiste el cambio pero no puede evolucionar. Piensa en una clase dios o un módulo utilitario para todo.
Zona de Inutilidad (A≈1, I≈1): Abstracto e inestable. Un componente de interfaces puras del que nadie depende. Quizás fue creado en anticipación de uso futuro, o fue muy dependido en su momento pero todos esos dependientes fueron refactorizados. Es abstracto pero vestigial — presente sin propósito.
Distancia de la Secuencia Principal: D = |A + I − 1|. D ideal = 0. Cualquier componente con D > 0.3 vale la pena investigarlo. Puedes medir esto automáticamente con herramientas de análisis estático e incluir D en los criterios de calidad del CI.
✗El problema
A DatabaseManager class highly depended upon (I≈0) but entirely concrete (A≈0). D = 1.0 — maximum distance from the Main Sequence. Every new requirement forces editing this stable component.
✗ Bad — Pain Zone (A≈0, I≈0)
# database_manager.py ← imported by 15 modules (stable, I≈0)
import sqlite3
class DatabaseManager:
def __init__(self, db_path: str):
self._conn = sqlite3.connect(db_path) # concrete: SQLite
def get_user(self, user_id: str) -> dict:
cursor = self._conn.execute(
"SELECT * FROM users WHERE id = ?", (user_id,)
)
return dict(cursor.fetchone())
def save_order(self, order: dict) -> None:
self._conn.execute(
"INSERT INTO orders (id, total) VALUES (?, ?)",
(order["id"], order["total"])
)
# D = |0 + 0 - 1| = 1.0 ← maximum distance from Main Sequence
# Adding PostgreSQL support: rewrite everything. Pain Zone confirmed.
// DatabaseManager.ts ← imported by 15 modules (stable, I≈0)
import { Pool } from "pg";
export class DatabaseManager {
private pool = new Pool({ connectionString: process.env.DATABASE_URL });
async getUser(userId: string): Promise> {
const result = await this.pool.query(
"SELECT * FROM users WHERE id = $1", [userId]
);
return result.rows[0];
}
async saveOrder(order: { id: string; total: number }): Promise {
await this.pool.query(
"INSERT INTO orders (id, total) VALUES ($1, $2)", [order.id, order.total]
);
}
}
// D = |0 + 0 - 1| = 1.0 ← maximum distance from Main Sequence
// Adding MySQL support: rewrite everything. Pain Zone confirmed.
✓La solución
Split into DatabasePort (stable, abstract, D=0) and PostgresDatabaseAdapter (volatile, concrete, D=0). Each sits exactly on the Main Sequence.
✓ Good — On the Main Sequence
# database_port.py ← stable (I≈0), abstract (A≈1)
from abc import ABC, abstractmethod
from dataclasses import dataclass
@dataclass
class UserRecord:
id: str
email: str
@dataclass
class OrderRecord:
id: str
total: float
class DatabasePort(ABC):
@abstractmethod
def get_user(self, user_id: str) -> UserRecord: ...
@abstractmethod
def save_order(self, order: OrderRecord) -> None: ...
# D(DatabasePort) = |1 + 0 - 1| = 0 ← on the Main Sequence
# postgres_adapter.py ← volatile (I≈1), concrete (A≈0)
import psycopg2
class PostgresDatabaseAdapter(DatabasePort):
def __init__(self, dsn: str):
self._conn = psycopg2.connect(dsn)
def get_user(self, user_id: str) -> UserRecord:
cur = self._conn.cursor()
cur.execute("SELECT id, email FROM users WHERE id = %s", (user_id,))
row = cur.fetchone()
return UserRecord(id=row[0], email=row[1])
def save_order(self, order: OrderRecord) -> None:
cur = self._conn.cursor()
cur.execute("INSERT INTO orders VALUES (%s, %s)", (order.id, order.total))
self._conn.commit()
# D(PostgresDatabaseAdapter) = |0 + 1 - 1| = 0 ← on the Main Sequence
// DatabasePort.ts ← stable (I≈0), abstract (A≈1)
export interface UserRecord { id: string; email: string; }
export interface OrderRecord { id: string; total: number; }
export interface DatabasePort {
getUser(userId: string): Promise;
saveOrder(order: OrderRecord): Promise;
}
// D(DatabasePort) = |1 + 0 - 1| = 0 ← on the Main Sequence
// PostgresDatabaseAdapter.ts ← volatile (I≈1), concrete (A≈0)
import { Pool } from "pg";
import { DatabasePort, UserRecord, OrderRecord } from "./DatabasePort";
export class PostgresDatabaseAdapter implements DatabasePort {
private pool = new Pool({ connectionString: process.env.DATABASE_URL });
async getUser(userId: string): Promise {
const result = await this.pool.query(
"SELECT id, email FROM users WHERE id = $1", [userId]
);
return result.rows[0] as UserRecord;
}
async saveOrder(order: OrderRecord): Promise {
await this.pool.query(
"INSERT INTO orders (id, total) VALUES ($1, $2)", [order.id, order.total]
);
}
}
// D(PostgresDatabaseAdapter) = |0 + 1 - 1| = 0 ← on the Main Sequence
💡Conclusión clave
Calcula D = |A + I − 1| para tus componentes clave. Cualquier valor por encima de 0.3 es un olor de diseño que merece investigación. Los componentes en la Zona de Dolor (estable+concreto) necesitan que se extraigan interfaces de ellos. Los componentes en la Zona de Inutilidad (abstracto+inestable) necesitan nuevos dependientes o eliminación. La Secuencia Principal es el óptimo arquitectónico — equilibra abstracción con estabilidad.
🔧 Algunos ejercicios pueden tener errores. Si algo parece incorrecto, usa el botón Feedback (abajo a la derecha) para reportarlo — nos ayuda a corregirlo rápido.
Pista: Calcula D = |A + I - 1| para tus componentes clave. Cualquier valor superior a 0,3 es un mal olor de diseño que merece investigación.
✗ Tu versión