Playbook Architecture

Patterns & Anti-patterns Playbook

Proven design patterns and common pitfalls to avoid. Use these to make better architectural decisions.

# agent_prompt

Copy this prompt and give it to your AI agent:

"When writing code, apply these pattern guidelines:

1. Use composition over inheritance

2. Prefer small, focused functions over large, complex ones

3. Extract shared logic into utility functions

4. Use dependency injection for testability

5. Apply the strategy pattern for varying algorithms

6. Avoid: God objects, deep inheritance, global state, callback hell"

# useful_patterns

Factory Pattern

Create objects without specifying their exact class. Great for creating different implementations based on conditions.

// When to use: Creating different object types based on input

function createNotification(type, message) {
  const notifications = {
    email: () => new EmailNotification(message),
    sms: () => new SMSNotification(message),
    push: () => new PushNotification(message),
  };

  return notifications[type]?.() ?? new DefaultNotification(message);
}

// Usage
const notification = createNotification('email', 'Hello!');
notification.send();

Use when: Object creation logic is complex or varies by type.

Strategy Pattern

Define a family of algorithms and make them interchangeable. Replace conditionals with polymorphism.

// When to use: Multiple algorithms for the same task

const pricingStrategies = {
  standard: (price) => price,
  premium: (price) => price * 1.5,
  sale: (price) => price * 0.8,
  bulk: (price, quantity) => price * (quantity > 10 ? 0.9 : 1),
};

function calculatePrice(basePrice, strategy, ...args) {
  return pricingStrategies[strategy](basePrice, ...args);
}

// Usage
calculatePrice(100, 'premium');     // 150
calculatePrice(100, 'sale');        // 80
calculatePrice(100, 'bulk', 15);    // 90

Observer / Event Emitter Pattern

Allow objects to subscribe to and react to events. Decouples event producers from consumers.

class EventEmitter {
  constructor() {
    this.listeners = new Map();
  }

  on(event, callback) {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, []);
    }
    this.listeners.get(event).push(callback);
    return () => this.off(event, callback); // Return unsubscribe
  }

  emit(event, data) {
    this.listeners.get(event)?.forEach(cb => cb(data));
  }
}

// Usage
const events = new EventEmitter();
const unsubscribe = events.on('userCreated', user => {
  sendWelcomeEmail(user);
});
events.emit('userCreated', { name: 'Alice' });

Composition over Inheritance

Build complex functionality by combining simple functions or objects instead of deep inheritance hierarchies.

❌ Inheritance

class Animal { }
class Dog extends Animal { }
class SwimmingDog extends Dog { }
class FlyingDog extends Dog { }
// What about a swimming flying dog?

✅ Composition

const canSwim = { swim() { ... } };
const canFly = { fly() { ... } };
const canBark = { bark() { ... } };

const dog = { ...canBark };
const superDog = {
  ...canBark, ...canSwim, ...canFly
};

# anti_patterns

God Object / God Class

A class that knows too much or does too much. Violates single responsibility.

❌ God Object

class Application {
  handleLogin() { ... }
  sendEmail() { ... }
  processPayment() { ... }
  generateReport() { ... }
  validateForm() { ... }
  uploadFile() { ... }
  // 50 more methods...
}

✅ Separated concerns

class AuthService {
  login() { ... }
  logout() { ... }
}
class EmailService {
  send() { ... }
}
class PaymentService {
  process() { ... }
}

Callback Hell / Pyramid of Doom

Deeply nested callbacks that make code unreadable and hard to maintain.

❌ Nested callbacks

getUser(id, (user) => {
  getOrders(user.id, (orders) => {
    getProducts(orders[0].id, (products) => {
      getReviews(products[0].id, (reviews) => {
        // Now what?
      });
    });
  });
});

✅ Async/await

async function getData(id) {
  const user = await getUser(id);
  const orders = await getOrders(user.id);
  const products = await getProducts(orders[0].id);
  const reviews = await getReviews(products[0].id);
  return { user, orders, products, reviews };
}

Magic Numbers & Strings

Unexplained literal values scattered throughout code. Makes maintenance difficult.

❌ Magic values

if (user.role === 2) { ... }
if (retries > 3) { ... }
const tax = price * 0.0825;
setTimeout(fn, 86400000);

✅ Named constants

const ROLE_ADMIN = 2;
const MAX_RETRIES = 3;
const TAX_RATE = 0.0825;
const ONE_DAY_MS = 24 * 60 * 60 * 1000;

if (user.role === ROLE_ADMIN) { ... }
setTimeout(fn, ONE_DAY_MS);

Premature Optimization

Optimizing code before you know where the bottlenecks are. Results in complex code that may not even improve performance.

"Premature optimization is the root of all evil" — Donald Knuth

  • First: Make it work correctly
  • Then: Measure actual performance
  • Finally: Optimize the real bottlenecks

Copy-Paste Programming

Duplicating code instead of abstracting. Creates maintenance nightmares when bugs need fixing in multiple places.

Rule of Three: When you copy code a third time, extract it.

  • • Extract to a function if logic is duplicated
  • • Create a component if UI is duplicated
  • • Use configuration if only values differ
  • • Consider generics if types are duplicated

Stringly Typed

Using strings where enums, constants, or types would be better. No type safety, prone to typos.

❌ Stringly typed

user.status = "actve"; // Typo!
if (status === "pending") { ... }
sendEvent("user_singed_up"); // Typo!

✅ Type-safe

const Status = {
  ACTIVE: 'active',
  PENDING: 'pending',
} as const;

user.status = Status.ACTIVE;
if (status === Status.PENDING) { ... }