YAGNI Principle

You Aren't Gonna Need It - a principle to avoid over-engineering by only implementing what is currently needed.

best-practices
yagnibest-practicesagileclean-codesimplicity

What is YAGNI?

"Don't implement something until you actually need it."

YAGNI = You Aren't Gonna Need It

✓ Build only what's required now
✓ Avoid speculative features
✓ Reduce complexity
✓ Ship faster

Avoid Premature Abstraction

// ❌ Bad - over-engineered for "future" needs
interface DataSource {
  connect(): void;
  query(sql: string): any;
  disconnect(): void;
}

class MySQLDataSource implements DataSource { /* ... */ }
class PostgresDataSource implements DataSource { /* ... */ }
class MongoDataSource implements DataSource { /* ... */ }
class OracleDataSource implements DataSource { /* ... */ }

class DataSourceFactory {
  static create(type: string): DataSource {
    // Complex factory for databases we don't use
  }
}

// ✅ Good - only what we need now
class Database {
  private connection: MySQLConnection;
  
  query(sql: string) {
    return this.connection.execute(sql);
  }
}
// Add abstraction later IF we need multiple databases

Don't Add Unused Parameters

// ❌ Bad - parameters "we might need later"
function createUser(
  name: string,
  email: string,
  phone?: string,      // Not used yet
  address?: string,    // Not used yet
  timezone?: string,   // Not used yet
  locale?: string,     // Not used yet
  preferences?: object // Not used yet
) {
  return { name, email };
}

// ✅ Good - only required parameters
function createUser(name: string, email: string) {
  return { name, email };
}
// Add parameters when features require them

Skip Speculative Features

// ❌ Bad - features nobody asked for
class ShoppingCart {
  items: Item[] = [];
  
  addItem(item: Item) { /* ... */ }
  removeItem(id: string) { /* ... */ }
  
  // "Might be useful someday"
  saveForLater(id: string) { /* ... */ }
  shareCart(email: string) { /* ... */ }
  exportToPDF() { /* ... */ }
  compareWithPreviousCart() { /* ... */ }
  predictNextPurchase() { /* ... */ }
}

// ✅ Good - only requested features
class ShoppingCart {
  items: Item[] = [];
  
  addItem(item: Item) { /* ... */ }
  removeItem(id: string) { /* ... */ }
  getTotal(): number { /* ... */ }
}

Avoid Complex Configurations

// ❌ Bad - configurable everything
const config = {
  database: {
    type: 'mysql',
    host: 'localhost',
    port: 3306,
    pool: {
      min: 5,
      max: 100,
      idleTimeout: 30000,
      acquireTimeout: 60000,
      reapInterval: 1000,
      // 50 more options...
    },
    replication: { /* ... */ },
    sharding: { /* ... */ },
  }
};

// ✅ Good - sensible defaults, minimal config
const config = {
  database: {
    host: 'localhost',
    port: 3306,
  }
};
// Add configuration options when needed

Simple Error Handling

// ❌ Bad - elaborate error handling for unlikely cases
class ApiError extends Error {
  constructor(
    message: string,
    public code: string,
    public statusCode: number,
    public retryable: boolean,
    public context: object,
    public originalError?: Error,
    public requestId?: string,
    public timestamp?: Date,
  ) {
    super(message);
  }
}

// Custom errors for every possible scenario
class NetworkTimeoutError extends ApiError { }
class RateLimitError extends ApiError { }
class AuthenticationError extends ApiError { }
// ... 20 more error classes

// ✅ Good - handle what you need
class ApiError extends Error {
  constructor(message: string, public statusCode: number) {
    super(message);
  }
}
// Add specific error types when requirements demand them

Avoid Unused Generics

// ❌ Bad - generic everything "for flexibility"
class Repository<T, K, Q extends Query<T>, R extends Result<T>> {
  find(query: Q): R { /* ... */ }
  save(entity: T): R { /* ... */ }
  delete(key: K): R { /* ... */ }
}

// ✅ Good - specific implementation
class UserRepository {
  find(id: string): User | null { /* ... */ }
  save(user: User): User { /* ... */ }
  delete(id: string): boolean { /* ... */ }
}
// Introduce generics when you have multiple similar repositories

Skip "Just In Case" Code

// ❌ Bad - defensive code for impossible scenarios
function processOrder(order: Order) {
  // "Just in case" checks
  if (!order) throw new Error('Order is null');
  if (!order.id) throw new Error('Order has no id');
  if (order.items === undefined) order.items = [];
  if (order.items === null) order.items = [];
  if (!Array.isArray(order.items)) order.items = [];
  if (typeof order.total !== 'number') order.total = 0;
  
  // Actual logic...
}

// ✅ Good - trust your types and validation
function processOrder(order: Order) {
  // TypeScript ensures order shape
  // Validation happens at API boundary
  return calculateTotal(order.items);
}

YAGNI vs Planning Ahead

YAGNI does NOT mean:
✗ Never plan or design
✗ Ignore scalability entirely
✗ Write hacky code
✗ Skip basic abstractions

YAGNI DOES mean:
✓ Don't build features before they're needed
✓ Don't optimize before measuring
✓ Don't abstract before patterns emerge
✓ Keep it simple until complexity is required

Quick Reference

Instead of...Do this...
Building for every databaseSupport the one you use
Adding unused parametersAdd them when needed
Creating deep inheritanceStart with simple classes
Complex configurationSensible defaults
Many error typesBasic error handling
Generic everythingSpecific implementations
YAGNI works well with:

• KISS - Keep It Simple, Stupid
• DRY - Don't Repeat Yourself  
• Agile - Iterative development
• Lean - Eliminate waste

"Simplicity is the ultimate sophistication" - Leonardo da Vinci