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 | Когда клиентов мало и схема стабильна |
Частые ошибки
- Путать простоту и примитивность. KISS не значит «пиши плохой код». Простота — это результат продуманного дизайна
- Предвосхищать требования. Не создавайте абстракции «на будущее» — см. YAGNI
- Использовать паттерн ради паттерна. Singleton, Factory, Observer — используйте, когда решают конкретную проблему
- Путать «просто написать» и «просто читать». Цель — чтобы код было просто читать, даже если писать чуть дольше
Практика
- Возьмите сложную функцию (>20 строк) и упростите её:
- Вынесите вложенные
ifв guard clauses - Разбейте на 2-3 маленькие функции
- Замените непонятные переменные на говорящие имена
- Вынесите вложенные
- Откажитесь от одной зависимости в проекте, заменив простым кодом
- При code review спрашивайте: «Можно ли решить проще?»
Связанные темы
Ресурсы
- A Philosophy of Software Design (John Ousterhout)
- Simple Made Easy — Rich Hickey (доклад)
- refactoring.guru — рефакторинг к простоте