KISS (Keep It Simple, Stupid)

KISS — принцип, утверждающий, что большинство систем работают лучше всего, если остаются простыми, а не усложняются. Простота — главная цель проектирования.

Зачем нужно

Сложный код труднее читать, отлаживать, тестировать и поддерживать. Каждая лишняя абстракция — это потенциальный баг и когнитивная нагрузка для будущего разработчика (включая вас через 3 месяца).

Где используется

  • Проектирование архитектуры приложений
  • Написание функций и модулей
  • Выбор технологий и инструментов
  • Code review
  • Принятие решений «делать сейчас или нет»

Предпосылки

Примеры over-engineering

Пример 1: излишняя абстракция

// ПЛОХО: паттерн ради паттерна
class UserRepositoryFactory {
  static createRepository(type) {
    switch (type) {
      case 'memory':
        return new InMemoryUserRepository();
      case 'database':
        return new DatabaseUserRepository();
      case 'api':
        return new ApiUserRepository();
      default:
        throw new Error(`Unknown type: ${type}`);
    }
  }
}

class InMemoryUserRepository {
  constructor { this.users = ; }
  getAll { return this.users; }
  getById(id) { return this.users.find(u => u.id === id); }
  create(user) { this.users.push(user); return user; }
}
// ... ещё 2 класса по 20 строк каждый

const repo = UserRepositoryFactory.createRepository('memory');
const user = repo.getById(1);

// ХОРОШО: если вам нужен только массив — используйте массив
const users = ;

function getUserById(id) {
  return users.find(u => u.id === id);
}

function addUser(user) {
  users.push(user);
  return user;
}

const user = getUserById(1);

Пример 2: преждевременная оптимизация

// ПЛОХО: оптимизация, которая не нужна
// "А вдруг будет миллион элементов!"
class OptimizedUserLookup {
  constructor {
    this.byId = new Map();
    this.byEmail = new Map();
    this.byName = new Map();
    this.sortedByDate = ;
  }

  add(user) {
    this.byId.set(user.id, user);
    this.byEmail.set(user.email, user);
    this.byName.set(user.name, user);
    this._insertSorted(user);
  }

  _insertSorted(user) {
    // Бинарный поиск для вставки в отсортированный массив...
    // 30 строк кода для "оптимизации"
  }
}

// ХОРОШО: начните просто, оптимизируйте когда появится проблема
const users = ;

// Когда users.length < 10000, find достаточно быстрый
const user = users.find(u => u.email === email);

Пример 3: сложный однострочник

// ПЛОХО: "умный" код, который никто не поймёт
const result = data.reduce((a, c) => ({...a, [c.type]: [...(a[c.type]||), c]}), {});

// ХОРОШО: читаемый код с понятными именами
function groupByType(items) {
  const groups = {};

  for (const item of items) {
    if (!groups[item.type]) {
      groups[item.type] = ;
    }
    groups[item.type].push(item);
  }

  return groups;
}

const result = groupByType(data);

Читаемый код

// ПЛОХО: сокращения, неочевидные имена
const u = getU;
const d = new Date();
const r = u.filter(x => x.a && x.d < d);

// ХОРОШО: самодокументирующийся код
const users = getActiveUsers;
const now = new Date();
const overdueUsers = users.filter(user =>
  user.isActive && user.dueDate < now
);
// ПЛОХО: вложенные тернарники
const label = age < 13 ? 'ребёнок' : age < 18 ? 'подросток' : age < 65 ? 'взрослый' : 'пенсионер';

// ХОРОШО: явные условия
function getAgeLabel(age) {
  if (age < 13) return 'ребёнок';
  if (age < 18) return 'подросток';
  if (age < 65) return 'взрослый';
  return 'пенсионер';
}
// ПЛОХО: глубокая вложенность
function processOrder(order) {
  if (order) {
    if (order.items.length > 0) {
      if (order.status === 'pending') {
        if (order.total > 0) {
          // бизнес-логика
        }
      }
    }
  }
}

// ХОРОШО: ранний выход (early return / guard clauses)
function processOrder(order) {
  if (!order) return;
  if (order.items.length === 0) return;
  if (order.status !== 'pending') return;
  if (order.total <= 0) return;

  // бизнес-логика — на одном уровне вложенности
}

Выбор простого решения

Сложное решение Простая альтернатива Когда переходить
Микросервисы Монолит Когда команда > 10 человек
Redux useState/useContext Когда состояние действительно глобальное
TypeORM Прямые SQL-запросы Когда запросы сложные и ORM мешает
WebSocket Polling / SSE Когда реал-тайм не критичен
GraphQL REST API Когда клиентов мало и схема стабильна

Частые ошибки

  1. Путать простоту и примитивность. KISS не значит «пиши плохой код». Простота — это результат продуманного дизайна
  2. Предвосхищать требования. Не создавайте абстракции «на будущее» — см. YAGNI
  3. Использовать паттерн ради паттерна. Singleton, Factory, Observer — используйте, когда решают конкретную проблему
  4. Путать «просто написать» и «просто читать». Цель — чтобы код было просто читать, даже если писать чуть дольше

Практика

  1. Возьмите сложную функцию (>20 строк) и упростите её:
    • Вынесите вложенные if в guard clauses
    • Разбейте на 2-3 маленькие функции
    • Замените непонятные переменные на говорящие имена
  2. Откажитесь от одной зависимости в проекте, заменив простым кодом
  3. При code review спрашивайте: «Можно ли решить проще?»

Связанные темы

Ресурсы

  • A Philosophy of Software Design (John Ousterhout)
  • Simple Made Easy — Rich Hickey (доклад)
  • refactoring.guru — рефакторинг к простоте