Capítulo 1: Los Diferentes Estilos de Pair Programming
Capítulo 1: Los Diferentes Estilos de Pair Programming
El pair programming ha evolucionado significativamente desde sus inicios en los años 90 con Extreme Programming. Lo que comenzó como una práctica simple de “dos programadores, una computadora” se ha transformado en un conjunto rico de técnicas especializadas, cada una diseñada para maximizar diferentes aspectos de la colaboración y el aprendizaje.
En este capítulo exploraremos los seis estilos principales de pair programming, entendiendo no solo cómo funcionan, sino también cuándo aplicarlos y cómo transicionar fluidamente entre ellos según las necesidades del momento.
La Evolución del Pair Programming
El pair programming surgió como una práctica fundamental de Extreme Programming (XP) en la década de 1990. Kent Beck y Ward Cunningham observaron que cuando dos programadores trabajaban juntos en el mismo código, la calidad mejoraba dramáticamente mientras que el tiempo de desarrollo apenas aumentaba. Esta observación contradecía la intuición gerencial tradicional de que dos personas trabajando juntas serían la mitad de productivas.
Con el tiempo, diferentes equipos y organizaciones desarrollaron variaciones de la técnica original, adaptándola a sus contextos específicos. Empresas como Pivotal Labs, ThoughtWorks y Spotify han sido pioneras en refinar y documentar estos diferentes estilos, contribuyendo a la rica taxonomía que tenemos hoy.
Los Seis Estilos Fundamentales
1. Driver-Navigator Clásico
El estilo Driver-Navigator es el más conocido y practicado en la industria. Su popularidad se debe a su simplicidad conceptual y la clara división de responsabilidades entre los participantes.
En este estilo, el Driver tiene control físico del teclado y mouse. Su responsabilidad principal es traducir las ideas en código, manejando los aspectos tácticos de la implementación. Mientras escribe, el Driver debe mantener un diálogo constante con su pareja, verbalizando sus decisiones y preguntando cuando tiene dudas.
El Navigator, por otro lado, observa el trabajo del Driver desde una perspectiva más estratégica. Sin la presión de escribir código, puede pensar en el diseño general, identificar potenciales problemas, buscar información en la documentación y considerar casos edge que el Driver podría pasar por alto.
La rotación de roles típicamente ocurre cada 15-30 minutos, aunque algunos equipos prefieren intervalos más cortos (10 minutos) para mantener alta energía, o más largos (hasta una hora) cuando trabajan en problemas complejos que requieren contexto profundo.
Ventajas del Driver-Navigator
- Separación clara de responsabilidades: Cada persona sabe exactamente cuál es su rol
- Balance entre táctica y estrategia: Mientras uno se enfoca en los detalles, otro mantiene la visión general
- Facilidad de adopción: Es intuitivo para equipos nuevos en pair programming
- Flexibilidad cognitiva: Permite al Navigator investigar mientras el Driver implementa
Desventajas y Riesgos
- Desbalance de participación: El Driver puede dominar la sesión si el Navigator es pasivo
- Fatiga del Driver: Escribir constantemente puede ser agotador
- Desconexión del Navigator: Sin tareas activas, el Navigator puede distraerse
- Jerarquías implícitas: Puede reforzar dinámicas de poder existentes
Código Ejemplo: Implementación con Driver-Navigator
// Contexto: Implementando un sistema de autenticación
// El Navigator guía estratégicamente mientras el Driver implementa
class AuthenticationService {
private tokenStore: Map<string, TokenData> = new Map();
// Navigator: "Necesitamos un método para generar tokens seguros"
// Driver: "Voy a usar crypto para generar bytes aleatorios"
generateSecureToken(userId: string): string {
// Driver implementa con conocimiento técnico
const buffer = crypto.randomBytes(32);
const token = buffer.toString('base64url');
// Navigator: "No olvides almacenar la metadata del token"
// Driver: "Cierto, agrego la expiración y el userId"
const tokenData: TokenData = {
userId,
token,
createdAt: new Date(),
expiresAt: new Date(Date.now() + 3600000) // 1 hora
};
this.tokenStore.set(token, tokenData);
return token;
}
// Navigator: "Ahora necesitamos validación del token"
// Driver: "Creo un método que verifique existencia y expiración"
validateToken(token: string): ValidationResult {
const tokenData = this.tokenStore.get(token);
// Navigator: "Considera el caso cuando el token no existe"
if (!tokenData) {
return { valid: false, reason: 'Token not found' };
}
// Navigator: "Y también cuando está expirado"
if (tokenData.expiresAt < new Date()) {
this.tokenStore.delete(token); // Driver agrega limpieza
return { valid: false, reason: 'Token expired' };
}
return { valid: true, userId: tokenData.userId };
}
}
2. Strong-Style Pairing
Strong-Style Pairing representa un cambio radical en la filosofía del pair programming. Llewellyn Falco lo describió con la regla de oro: “Para que una idea llegue a la computadora, debe pasar por las manos de otra persona.”
Este estilo invierte completamente la dinámica tradicional. El Navigator es quien tiene las ideas y dirige la implementación, mientras que el Driver se convierte en una extensión inteligente del Navigator, traduciendo intenciones en código.
La genialidad de este enfoque radica en que fuerza la transferencia de conocimiento. El Navigator no puede simplemente tomar el teclado cuando se frustra; debe aprender a comunicar sus ideas claramente. El Driver no puede implementar sus propias ideas sin consultarlas primero; debe aprender a entender y ejecutar las visiones de otros.
Los Cuatro Niveles de Comunicación
Strong-Style Pairing define cuatro niveles de comunicación, y los participantes deben esforzarse por comunicarse en el nivel más alto posible:
- Nivel de Sintaxis: “Escribe ‘function’, abre paréntesis…”
- Nivel de Implementación: “Crea una función que reciba dos parámetros”
- Nivel de Intención: “Necesitamos validar los datos del usuario”
- Nivel de Estrategia: “Mejoremos la experiencia del usuario en el onboarding”
Beneficios Transformadores
- Transferencia garantizada de conocimiento: El conocimiento debe fluir del Navigator al Driver
- Desarrollo de habilidades de comunicación: Ambos aprenden a articular y entender ideas
- Reducción del ego: El código pertenece verdaderamente al equipo
- Inclusión activa: Nivela el campo de juego para diferentes niveles de experiencia
Código Ejemplo: Sesión de Strong-Style
// Navigator (Senior): Vamos a implementar un rate limiter para nuestra API
// Driver (Junior): Ok, ¿por dónde empiezo?
// Navigator: Primero crea una clase RateLimiter
class RateLimiter {
// Navigator: Necesitamos trackear intentos por IP
// Driver: ¿Uso un Map?
// Navigator: Exacto, pero con timestamps
private attempts: Map<string, AttemptRecord[]> = new Map();
// Navigator: Agrega una configuración para el límite y ventana de tiempo
constructor(
private maxAttempts: number = 100,
private windowMs: number = 60000 // 1 minuto
) {}
// Navigator: Ahora necesitamos un método para verificar si una IP puede hacer request
// Driver: Lo llamo checkLimit?
// Navigator: Mejor isAllowed, es más claro
isAllowed(ipAddress: string): boolean {
const now = Date.now();
const userAttempts = this.attempts.get(ipAddress) || [];
// Navigator: Filtra solo los intentos dentro de la ventana de tiempo
// Driver implementa con su conocimiento de JavaScript
const recentAttempts = userAttempts.filter(
attempt => now - attempt.timestamp < this.windowMs
);
// Navigator: Si hay menos intentos que el límite, permite y registra
if (recentAttempts.length < this.maxAttempts) {
// Driver: Debo agregar el nuevo intento?
// Navigator: Sí, y actualiza el Map
recentAttempts.push({ timestamp: now });
this.attempts.set(ipAddress, recentAttempts);
return true;
}
// Navigator: Si llegamos aquí, bloqueamos
return false;
}
// Navigator: Agreguemos un método para limpiar registros viejos
// Driver: Para no acumular memoria infinitamente?
// Navigator: Exacto! Muy bien pensado
cleanup(): void {
const now = Date.now();
for (const [ip, attempts] of this.attempts.entries()) {
const recentAttempts = attempts.filter(
a => now - a.timestamp < this.windowMs
);
if (recentAttempts.length === 0) {
this.attempts.delete(ip);
} else {
this.attempts.set(ip, recentAttempts);
}
}
}
}
3. Ping-Pong Programming
Ping-Pong Programming surge naturalmente de la práctica de Test-Driven Development (TDD). Este estilo convierte el ciclo Red-Green-Refactor en un juego colaborativo donde cada participante tiene un turno claro y definido.
La belleza de este estilo radica en su ritmo natural. No hay necesidad de timers o negociación sobre cuándo rotar; el flujo de TDD dicta automáticamente los cambios de rol. Esto crea una sensación de momentum y progreso constante que puede ser altamente motivante.
El Ritmo del Ping-Pong
- Alice escribe un test que falla (Red)
- Bob escribe el código mínimo para pasar el test (Green)
- Ambos refactorizan juntos si es necesario (Refactor)
- Bob escribe el siguiente test que falla
- Alice implementa la solución
- El ciclo continúa…
Ventajas Únicas
- Participación perfectamente equilibrada: Cada persona tiene roles claros y alternantes
- TDD incorporado: Es imposible hacer Ping-Pong sin practicar TDD
- Ritmo energizante: Los cambios frecuentes mantienen alta la energía
- Aprendizaje de testing: Ambos mejoran sus habilidades de escribir tests
Código Ejemplo: Sesión de Ping-Pong
// ========== RONDA 1: Alice escribe test ==========
describe('ShoppingCart', () => {
it('should calculate total with single item', () => {
const cart = new ShoppingCart();
cart.addItem({ id: '1', name: 'Laptop', price: 999.99, quantity: 1 });
expect(cart.getTotal()).toBe(999.99);
});
});
// Test falla ❌ - ShoppingCart no existe
// ========== Bob implementa ==========
class ShoppingCart {
private items: CartItem[] = [];
addItem(item: CartItem): void {
this.items.push(item);
}
getTotal(): number {
return this.items.reduce((sum, item) =>
sum + (item.price * item.quantity), 0
);
}
}
// Test pasa ✅
// ========== RONDA 2: Bob escribe test ==========
describe('ShoppingCart', () => {
it('should apply percentage discount', () => {
const cart = new ShoppingCart();
cart.addItem({ id: '1', name: 'Laptop', price: 1000, quantity: 1 });
cart.applyDiscount(10); // 10% discount
expect(cart.getTotal()).toBe(900);
});
});
// Test falla ❌ - applyDiscount no existe
// ========== Alice implementa ==========
class ShoppingCart {
private items: CartItem[] = [];
private discountPercentage: number = 0;
addItem(item: CartItem): void {
this.items.push(item);
}
applyDiscount(percentage: number): void {
if (percentage < 0 || percentage > 100) {
throw new Error('Invalid discount percentage');
}
this.discountPercentage = percentage;
}
getTotal(): number {
const subtotal = this.items.reduce((sum, item) =>
sum + (item.price * item.quantity), 0
);
const discount = subtotal * (this.discountPercentage / 100);
return subtotal - discount;
}
}
// Test pasa ✅
// ========== RONDA 3: Alice escribe test ==========
describe('ShoppingCart', () => {
it('should handle multiple items with different quantities', () => {
const cart = new ShoppingCart();
cart.addItem({ id: '1', name: 'Mouse', price: 25, quantity: 2 });
cart.addItem({ id: '2', name: 'Keyboard', price: 75, quantity: 1 });
expect(cart.getTotal()).toBe(125); // (25*2) + (75*1)
});
});
// Test pasa ✅ - Ya funciona!
// ========== Alice escribe test más complejo ==========
describe('ShoppingCart', () => {
it('should not allow negative quantities', () => {
const cart = new ShoppingCart();
expect(() => {
cart.addItem({ id: '1', name: 'Invalid', price: 10, quantity: -1 });
}).toThrow('Quantity must be positive');
});
});
// Test falla ❌
// ========== Bob implementa validación ==========
class ShoppingCart {
// ... código anterior ...
addItem(item: CartItem): void {
if (item.quantity <= 0) {
throw new Error('Quantity must be positive');
}
if (item.price < 0) {
throw new Error('Price cannot be negative');
}
this.items.push(item);
}
}
// Test pasa ✅
4. Backseat Navigator
El estilo Backseat Navigator es una técnica pedagógica diseñada específicamente para situaciones donde existe una diferencia significativa de experiencia entre los participantes. A diferencia del mentoring tradicional, este estilo mantiene al aprendiz en el asiento del conductor, literalmente.
El nombre proviene de la analogía de enseñar a alguien a conducir: el instructor está en el asiento del pasajero (backseat), guiando pero no tomando el volante. Esta restricción física fuerza al experto a desarrollar habilidades de enseñanza y al novato a aprender haciendo.
Filosofía del Backseat Navigator
El experto debe resistir el impulso de tomar el control, incluso cuando el novato lucha. En lugar de dictar soluciones, el experto hace preguntas socráticas que guían al aprendiz hacia el descubrimiento. Este proceso, aunque más lento inicialmente, resulta en un aprendizaje más profundo y duradero.
Técnicas del Experto
- Preguntas socráticas: “¿Qué crees que pasaría si…?”
- Scaffolding gradual: Proveer menos ayuda con el tiempo
- Permitir errores seguros: Dejar que el novato experimente consecuencias
- Celebrar descubrimientos: Reforzar cuando el novato encuentra soluciones
Código Ejemplo: Sesión de Backseat Navigator
// Contexto: Junior aprendiendo React hooks con un Senior
// Senior (Navigator): Vamos a crear un componente de búsqueda con debounce
// Junior (Driver): Ok, empiezo con una función?
function SearchComponent() {
// Senior: ¿Qué necesitamos guardar en el estado?
// Junior: El término de búsqueda?
// Senior: Bien! ¿Algo más?
// Junior: Mmm... los resultados?
// Senior: Exacto! Implementa eso
const [searchTerm, setSearchTerm] = useState('');
const [results, setResults] = useState([]);
// Senior: ¿Cómo harías para no llamar la API en cada keystroke?
// Junior: Podría esperar a que el usuario deje de escribir?
// Senior: Perfecto! ¿Conoces alguna técnica para eso?
// Junior: He oído de algo llamado debounce?
// Senior: Sí! ¿Cómo crees que funciona?
// Junior piensa y experimenta...
const [debouncedTerm, setDebouncedTerm] = useState('');
// Senior: Necesitarás useEffect para el timer
// Junior: Ah, para esperar antes de actualizar?
useEffect(() => {
// Junior: Creo un timer aquí?
const timer = setTimeout(() => {
setDebouncedTerm(searchTerm);
}, 500);
// Senior: ¿Qué pasa si el usuario escribe antes de los 500ms?
// Junior: Oh! Necesito cancelar el timer anterior!
// Senior: ¿Cómo harías eso en React?
// Junior: Con el cleanup del useEffect?
return () => clearTimeout(timer);
}, [searchTerm]);
// Senior: Genial! Ahora, ¿cuándo deberías hacer la búsqueda?
// Junior: Cuando debouncedTerm cambie?
useEffect(() => {
if (debouncedTerm) {
// Junior: Aquí llamo a la API
searchAPI(debouncedTerm).then(setResults);
}
}, [debouncedTerm]);
// Senior: ¿Qué pasa si el componente se desmonta durante la búsqueda?
// Junior: Eh... podría causar un warning?
// Senior: Exacto! ¿Cómo lo solucionarías?
useEffect(() => {
let cancelled = false;
if (debouncedTerm) {
searchAPI(debouncedTerm).then(data => {
if (!cancelled) {
setResults(data);
}
});
}
return () => { cancelled = true; };
}, [debouncedTerm]);
// Senior: Excelente! Has implementado debounce y evitado memory leaks!
return (
<div>
<input
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Buscar..."
/>
<ul>
{results.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
5. Tour Guide Style
Tour Guide Style es especialmente valioso cuando se trabaja con sistemas legacy o cuando nuevos miembros se unen a un equipo con una base de código establecida. A diferencia de otros estilos donde ambos participantes están creando algo nuevo, aquí uno actúa como guía a través de territorio conocido.
Este estilo reconoce que entender código existente requiere diferentes habilidades que escribir código nuevo. El guía no solo muestra el “qué” sino también el “por qué” - las decisiones históricas, los compromisos técnicos, y las razones detrás de aparentes inconsistencias.
El Arte de Ser un Buen Guía
Un buen guía turístico no solo señala monumentos; cuenta historias, explica contexto histórico, y responde preguntas. Similarmente, un buen guía de código no solo navega por archivos; explica decisiones de arquitectura, comparte anécdotas de bugs memorables, y conecta diferentes partes del sistema.
Progresión Natural
Tour Guide Style típicamente evoluciona hacia Driver-Navigator tradicional a medida que el “turista” se familiariza con el código. Esta transición debe ser gradual y natural, con el turista tomando cada vez más iniciativa.
Código Ejemplo: Sesión de Tour Guide
// Guía (Driver): Te voy a mostrar nuestro sistema de procesamiento de pagos
// Turista (Navigator): Genial, tengo muchas preguntas
// Guía navega a payment-processor.ts
class PaymentProcessor {
// Guía: Esta es nuestra clase principal, maneja múltiples gateways
private gateways: Map<string, PaymentGateway> = new Map();
// Turista: ¿Por qué un Map y no un objeto simple?
// Guía: Buena pregunta! Necesitábamos agregar/remover gateways dinámicamente
// Guía: Los merchants pueden cambiar de proveedor sin reiniciar
constructor(private config: PaymentConfig) {
// Guía: Aquí inicializamos los gateways basados en la config
this.initializeGateways();
// Turista: Veo que no está await, ¿es sincrónico?
// Guía: Sí, fue una decisión consciente. Los gateways se conectan lazy
// Guía: Solo cuando se usa por primera vez establece la conexión real
}
async processPayment(request: PaymentRequest): Promise<PaymentResult> {
// Guía: Este es el flujo principal, bastante complejo
// Turista: ¿Por qué validamos dos veces?
this.validateRequest(request); // Validación de negocio
const gateway = this.selectGateway(request);
// Guía: Seleccionamos gateway basado en país, monto, y tipo de tarjeta
// Guía: Stripe para US/EU, MercadoPago para LATAM
try {
// Guía: Aquí está la parte interesante - retry logic
return await this.executeWithRetry(async () => {
// Turista: Veo que transforman el request, ¿por qué?
// Guía: Cada gateway tiene su propio formato
const gatewayRequest = this.transformRequest(request, gateway.type);
// Guía: Esta línea ha causado muchos problemas
const response = await gateway.charge(gatewayRequest);
// Turista: ¿Qué tipo de problemas?
// Guía: Timeouts, respuestas parciales, doble cobro...
// Guía: Por eso agregamos el idempotency key aquí arriba
return this.transformResponse(response);
});
} catch (error) {
// Guía: El error handling es crítico en pagos
// Turista: ¿Registran todo?
// Guía: Todo. Incluso tenemos un modo "paranoid" que guarda en 3 lugares
await this.logPaymentError(error, request);
// Turista: Veo que no lanzan el error original
// Guía: Correcto, nunca exponemos detalles internos del gateway
throw new PaymentFailedError(
'Payment processing failed',
error.code
);
}
}
// Guía: Ahora te muestro la parte más complicada - reconciliación
async reconcilePayments(): Promise<ReconciliationReport> {
// Turista: ¿Esto corre manualmente o está programado?
// Guía: Ambos. Cron cada hora, pero pueden forzarlo si hay problemas
// Guía: Este código es legacy, funciona pero necesita refactoring
// Turista: ¿Por qué no lo han refactorizado?
// Guía: Procesa millones al día. El riesgo vs beneficio no vale la pena... aún
// ... código de reconciliación ...
}
}
// Turista: ¿Puedo ver los tests?
// Guía: Claro! Pero prepárate, algunos son... creativos
// [Navegan a payment-processor.test.ts]
// Guía: Este test simula un "thunder herd" - 1000 pagos simultáneos
// Turista: ¿Han tenido ese problema en producción?
// Guía: Black Friday 2021. Nunca lo olvidaremos.
6. Distributed Pairing
La pandemia de COVID-19 aceleró dramáticamente la adopción del Distributed Pairing. Lo que antes era una práctica de nicho para equipos remotos se convirtió en una necesidad para toda la industria. Este cambio forzado reveló tanto los desafíos como las oportunidades únicas del pairing remoto.
Herramientas que Hacen la Diferencia
La calidad de la experiencia en Distributed Pairing depende enormemente de las herramientas. VS Code Live Share revolucionó este espacio al permitir colaboración en tiempo real sin compartir pantalla. Tuple, diseñado específicamente para pair programming, ofrece baja latencia y control compartido del mouse.
Compensando la Distancia
La distancia física requiere over-comunicación intencional. Los pairers remotos exitosos narran sus acciones (“Voy a abrir el archivo de configuración”), confirman entendimiento frecuentemente (“¿Te hace sentido?”), y usan cámaras cuando es posible para mantener conexión humana.
Código Ejemplo: Configuración para Distributed Pairing
// Configuración y mejores prácticas para sesión remota
interface DistributedPairingSetup {
// Herramientas esenciales
tools: {
ide: 'VS Code with Live Share' | 'IntelliJ with Code With Me';
communication: 'Zoom' | 'Discord' | 'Tuple';
timer: 'Mobtime' | 'Pomodoro.io';
whiteboard?: 'Miro' | 'Excalidraw';
};
// Configuración del ambiente
environment: {
// Ambos deben tener el mismo setup
dockerCompose: string;
envVariables: Record<string, string>;
database: 'shared-dev-db' | 'local-with-same-seed';
};
// Protocolo de comunicación
protocol: {
// Narración explícita de acciones
narration: {
example: "Voy a refactorizar este método";
frequency: 'continuous';
};
// Confirmación de entendimiento
confirmation: {
phrases: [
"¿Te hace sentido?",
"¿Alguna pregunta hasta aquí?",
"¿Cómo ves este approach?"
];
frequency: 'every-5-minutes';
};
// Manejo de interrupciones
interruptions: {
mute: 'when-not-speaking';
notifications: 'disabled';
slack: 'do-not-disturb';
};
};
}
// Ejemplo de sesión remota con narración
class RemotePairingSession {
// Alice: "Voy a crear una clase para manejar las conexiones WebSocket"
// Bob: "Perfecto, yo voy mirando la documentación de Socket.io"
constructor(private config: WebSocketConfig) {
// Alice: "Inicializo con la configuración que pasamos"
this.initialize();
}
private initialize(): void {
// Alice: "Estoy configurando los event listeners básicos"
// Alice: [Escribe código]
// Bob: "Espera, ¿no deberíamos validar la config primero?"
// Alice: "Tienes razón, agrego validación"
this.validateConfig();
this.setupEventListeners();
// Alice: "Listo, ¿cómo ves si agregamos reconnection logic?"
// Bob: "Sí, y podríamos usar exponential backoff"
// Alice: "Genial, ¿quieres tomar el control para implementarlo?"
// Bob: "Dale, comparto mi pantalla"
// [Cambio de control]
}
// Bob: "Voy a implementar el reconnection con exponential backoff"
private setupReconnection(): void {
let attempts = 0;
const maxAttempts = 5;
// Bob: "Uso una función recursiva para los reintentos"
const attemptReconnection = async () => {
// Bob: "Calculo el delay exponencial"
const delay = Math.min(1000 * Math.pow(2, attempts), 30000);
// Alice: "Me gusta el cap de 30 segundos"
// Bob: "Sí, evita esperas ridículas"
attempts++;
// Bob: "¿Te parece si agregamos logging aquí?"
// Alice: "Definitivamente, es crítico para debugging"
console.log(`Reconnection attempt ${attempts}/${maxAttempts} in ${delay}ms`);
await this.sleep(delay);
try {
await this.connect();
// Bob: "Si conecta, reseteo los intentos"
attempts = 0;
} catch (error) {
if (attempts < maxAttempts) {
// Alice: "¿No deberíamos notificar al usuario aquí?"
// Bob: "Cierto, emito un evento"
this.emit('reconnecting', { attempt: attempts, maxAttempts });
attemptReconnection();
} else {
// Bob: "Después de max intentos, me rindo"
this.emit('connection-failed', { reason: 'Max attempts reached' });
}
}
};
// Bob: "¿Qué te parece la implementación?"
// Alice: "Se ve bien, pero agreguemos tests"
// Bob: "¿Hacemos ping-pong para los tests?"
// Alice: "¡Perfect! Yo escribo el primero"
}
}
Matriz de Decisión: ¿Qué Estilo Usar?
La selección del estilo apropiado no debe ser arbitraria. Factores como la diferencia de experiencia, el tipo de tarea, y el objetivo de la sesión deben informar esta decisión.
Factores de Decisión
interface StyleSelectionCriteria {
// Diferencia de experiencia entre participantes
experienceGap: 'none' | 'small' | 'medium' | 'large';
// Tipo de trabajo
taskType: 'new-feature' | 'bug-fix' | 'refactoring' |
'exploration' | 'learning' | 'review';
// Objetivo principal
primaryGoal: 'deliver' | 'learn' | 'teach' |
'explore' | 'quality' | 'speed';
// Familiaridad con el código
codeFamiliarity: {
person1: 'none' | 'some' | 'expert';
person2: 'none' | 'some' | 'expert';
};
// Restricciones
constraints: {
timeLimit?: number;
remote?: boolean;
tooling?: string[];
};
}
// Función para recomendar estilo
function recommendPairingStyle(criteria: StyleSelectionCriteria): string {
const { experienceGap, taskType, primaryGoal, codeFamiliarity } = criteria;
// Lógica de recomendación
if (experienceGap === 'large' && primaryGoal === 'teach') {
return 'Backseat Navigator';
}
if (taskType === 'exploration' &&
(codeFamiliarity.person1 === 'expert' || codeFamiliarity.person2 === 'expert')) {
return 'Tour Guide → Driver-Navigator';
}
if (primaryGoal === 'learn' && experienceGap !== 'none') {
return 'Strong-Style Pairing';
}
if (taskType === 'new-feature' && primaryGoal === 'quality') {
return 'Ping-Pong (con TDD)';
}
// Default
return 'Driver-Navigator Clásico';
}
Antipatrones y Cómo Evitarlos
El Síndrome del Teclado Pegajoso
Síntoma: Una persona monopoliza el teclado y nunca rota.
Solución: Usar un timer visible y acordar rotaciones estrictas. En casos extremos, usar Strong-Style donde el que tiene ideas no puede tocar el teclado.
El Navegante Fantasma
Síntoma: El Navigator se desconecta, revisa el teléfono, o se distrae.
Solución: Asignar tareas activas al Navigator como buscar documentación, pensar en test cases, o revisar código relacionado.
La Batalla de Egos
Síntoma: Discusiones improductivas sobre “la mejor manera” de hacer algo.
Solución: Acordar experimentar con ambos approaches por tiempo limitado, medir resultados objetivamente, o consultar con el equipo.
El Dictador de Sintaxis
Síntoma: Navigator dictando cada carácter en lugar de comunicar intenciones.
Solución: Practicar comunicación en niveles más altos. Hacer ejercicios donde está prohibido mencionar palabras clave del lenguaje.
Ejercicios Prácticos
Ejercicio 1: Rotación de Estilos
Con un compañero, implementen una calculadora simple rotando por todos los estilos:
- 10 minutos en Driver-Navigator
- 10 minutos en Strong-Style
- 10 minutos en Ping-Pong
- Reflexionen sobre cada experiencia
Ejercicio 2: Comunicación por Niveles
Practiquen Strong-Style Pairing enfocándose en subir el nivel de comunicación:
- Ronda 1: Solo nivel 3-4 permitido (Intención/Estrategia)
- Ronda 2: Mezcla de todos los niveles según necesidad
- Comparen la eficiencia y claridad
Ejercicio 3: Simulación de Escenarios
Simulen estos escenarios y elijan el estilo apropiado:
- Onboarding de un junior al equipo
- Bug crítico en producción a las 3am
- Refactoring de código legacy de 5 años
- Implementación de un algoritmo complejo nuevo
- Review de seguridad de código sensible
Resumen del Capítulo
El pair programming no es una técnica única sino un conjunto rico de prácticas que pueden adaptarse a diferentes contextos. Dominar estos seis estilos - Driver-Navigator, Strong-Style, Ping-Pong, Backseat Navigator, Tour Guide, y Distributed Pairing - te permite elegir la herramienta correcta para cada situación.
La clave está en la flexibilidad y la comunicación. Los mejores pair programmers pueden transicionar suavemente entre estilos según las necesidades del momento, siempre manteniendo el foco en el aprendizaje mutuo y la calidad del código.
Próximo Capítulo
Ahora que conoces los diferentes estilos, profundizaremos en el más transformador de todos: Strong-Style Pairing con el patrón Driver-Navigator. Exploraremos los cuatro niveles de comunicación, técnicas avanzadas, y cómo este estilo puede revolucionar la dinámica de tu equipo.
Continuar con Capítulo 2: Strong-Style Pairing con Driver-Navigator →