Files
gruperly/.agents/skills/clean-code-principles/rules/core-kiss-simplicity.md
2026-09-04 16:49:24 -03:00

6.5 KiB

id, title, category, priority, tags, related
id title category priority tags related
core-kiss-simplicity KISS - Simplicity core-principles critical
KISS
simplicity
over-engineering
maintainability
core-kiss-readability
core-yagni-abstractions
solid-srp-function

KISS Principle - Simplicity

Keep It Simple, Stupid. Choose the simplest solution that solves the problem. Avoid unnecessary complexity, over-engineering, and clever code that's hard to understand.

Bad Example

// Anti-pattern: Over-engineered solution for a simple problem

// Simple task: Check if a user can access a resource
// Over-engineered approach with unnecessary abstractions

interface AccessControlStrategy {
  evaluate(context: AccessContext): AccessDecision;
}

interface AccessContext {
  subject: Subject;
  resource: Resource;
  action: Action;
  environment: Environment;
}

interface Subject {
  id: string;
  attributes: Map<string, AttributeValue>;
}

interface Resource {
  id: string;
  type: string;
  attributes: Map<string, AttributeValue>;
}

interface Action {
  id: string;
  attributes: Map<string, AttributeValue>;
}

interface Environment {
  currentTime: Date;
  ipAddress: string;
  attributes: Map<string, AttributeValue>;
}

type AttributeValue = string | number | boolean | string[];

interface AccessDecision {
  decision: 'permit' | 'deny' | 'indeterminate' | 'not_applicable';
  obligations?: Obligation[];
  advice?: Advice[];
}

interface Obligation {
  id: string;
  fulfillOn: 'permit' | 'deny';
  attributes: Map<string, AttributeValue>;
}

interface Advice {
  id: string;
  appliesTo: 'permit' | 'deny';
  attributes: Map<string, AttributeValue>;
}

class AttributeBasedAccessControl {
  private strategies: AccessControlStrategy[] = [];
  private combiningAlgorithm: CombiningAlgorithm;

  constructor(combiningAlgorithm: CombiningAlgorithm) {
    this.combiningAlgorithm = combiningAlgorithm;
  }

  addStrategy(strategy: AccessControlStrategy): void {
    this.strategies.push(strategy);
  }

  evaluate(context: AccessContext): AccessDecision {
    const decisions = this.strategies.map(s => s.evaluate(context));
    return this.combiningAlgorithm.combine(decisions);
  }
}

// 500+ more lines of abstraction layers...

// Actually usage for a simple check:
const context: AccessContext = {
  subject: {
    id: user.id,
    attributes: new Map([['role', user.role]])
  },
  resource: {
    id: document.id,
    type: 'document',
    attributes: new Map([['ownerId', document.ownerId]])
  },
  action: {
    id: 'read',
    attributes: new Map()
  },
  environment: {
    currentTime: new Date(),
    ipAddress: request.ip,
    attributes: new Map()
  }
};

const decision = accessControl.evaluate(context);
if (decision.decision === 'permit') {
  // Allow access
}

Good Example

// Correct approach: Simple, direct solution

// Simple function that does what's needed
function canUserAccessDocument(user: User, document: Document, action: 'read' | 'write' | 'delete'): boolean {
  // Admin can do anything
  if (user.role === 'admin') {
    return true;
  }

  // Owner can do anything with their document
  if (document.ownerId === user.id) {
    return true;
  }

  // Check explicit permissions
  const permission = document.permissions.find(p => p.userId === user.id);
  if (!permission) {
    return false;
  }

  // Check if permission level is sufficient
  switch (action) {
    case 'read':
      return ['read', 'write', 'admin'].includes(permission.level);
    case 'write':
      return ['write', 'admin'].includes(permission.level);
    case 'delete':
      return permission.level === 'admin';
    default:
      return false;
  }
}

// Usage is straightforward
if (canUserAccessDocument(user, document, 'read')) {
  // Allow access
}

// If requirements grow, evolve the solution incrementally
// Add time-based access when actually needed
function canUserAccessDocument(
  user: User,
  document: Document,
  action: 'read' | 'write' | 'delete'
): boolean {
  // Admin can do anything
  if (user.role === 'admin') {
    return true;
  }

  // Owner can do anything with their document
  if (document.ownerId === user.id) {
    return true;
  }

  // Check explicit permissions
  const permission = document.permissions.find(p => p.userId === user.id);
  if (!permission) {
    return false;
  }

  // Check expiration (added when actually needed)
  if (permission.expiresAt && permission.expiresAt < new Date()) {
    return false;
  }

  // Check if permission level is sufficient
  const requiredLevel = getRequiredLevel(action);
  return hasRequiredLevel(permission.level, requiredLevel);
}

function getRequiredLevel(action: 'read' | 'write' | 'delete'): PermissionLevel {
  const levels: Record<string, PermissionLevel> = {
    read: 'read',
    write: 'write',
    delete: 'admin'
  };
  return levels[action];
}

function hasRequiredLevel(userLevel: PermissionLevel, required: PermissionLevel): boolean {
  const hierarchy: PermissionLevel[] = ['read', 'write', 'admin'];
  return hierarchy.indexOf(userLevel) >= hierarchy.indexOf(required);
}

// Still simple, still readable, handles new requirement

// For multiple resources, create focused helper
class DocumentAccessChecker {
  canRead(user: User, document: Document): boolean {
    return canUserAccessDocument(user, document, 'read');
  }

  canWrite(user: User, document: Document): boolean {
    return canUserAccessDocument(user, document, 'write');
  }

  canDelete(user: User, document: Document): boolean {
    return canUserAccessDocument(user, document, 'delete');
  }

  // Filter a list of documents to only accessible ones
  filterReadable(user: User, documents: Document[]): Document[] {
    return documents.filter(doc => this.canRead(user, doc));
  }
}

// Usage remains simple
const checker = new DocumentAccessChecker();
if (checker.canRead(user, document)) {
  // Show document
}
const accessibleDocs = checker.filterReadable(user, allDocuments);

Why

  1. Readability: Simple code can be understood in seconds. Complex abstractions require studying.

  2. Maintainability: New developers can work with simple code immediately. Complex frameworks need training.

  3. Debugging: When something breaks, simple code has obvious failure points.

  4. Performance: Simple code is often faster - fewer layers, fewer allocations, less indirection.

  5. Time to Market: Simple solutions are built and shipped faster.

  6. YAGNI Alignment: The complex solution solves problems you don't have (and may never have).

  7. Incremental Complexity: Start simple, add complexity only when real requirements demand it. The simple solution can evolve.