DRY / KISS / YAGNI
Назначение: три базовых принципа проектирования, фильтрующие избыточную сложность: DRY — единственное представление каждого знания; KISS — простота как ценность по умолчанию; YAGNI — отказ от функциональности «на будущее».
Аудитория: разработчики, техлиды, архитекторы.
Статус: устоявшиеся принципы (DRY — Эндрю Хант и Дэвид Томас, The Pragmatic Programmer, 1999; KISS — инженерная максима, ассоциируется с Келли Джонсоном, Lockheed Skunk Works; YAGNI — Extreme Programming, Кент Бек, фраза — Рон Джеффрис, c2 wiki, конец 1990-х).
Не путать с: SOLID — принципами структуры классов и модулей; DRY ≠ «в коде нет одинаковых строк»; KISS ≠ «делай примитивно»; YAGNI ≠ «никогда не расширяй систему».
Общее
DRY / KISS / YAGNI — триада самых узнаваемых базовых принципов проектирования программного обеспечения. Каждый из них адресует свою форму избыточной сложности: DRY — дублирование знания, KISS — неоправданную сложность решения, YAGNI — неоправданную функциональность. Вместе они образуют прагматический фильтр, через который пропускают проектные решения: что именно нужно построить, насколько просто оно должно быть устроено и где должно жить каждое знание о системе.
Все три принципа старше своих аббревиатур. KISS — классическая инженерная максима, известная задолго до программирования. DRY и YAGNI оформились в конце 1990-х в сообществе гибкой разработки: первый — в книге Эндрю Ханта и Дэвида Томаса The Pragmatic Programmer (1999), второй — в практике Extreme Programming Кента Бека, а сам афоризм «You Aren’t Gonna Need It» закрепился благодаря Рону Джеффрису на вики c2.com. В отличие от SOLID, сформулированного для классов и модулей, триада не привязана к парадигме: она применима к коду, данным, инфраструктуре, документации и процессам.
Ключевое различие между триадой и SOLID — уровень вопроса. SOLID отвечает на вопрос «как структурировать»: где проходит граница ответственности, куда направлена зависимость. Триада отвечает на вопросы «когда» и «сколько»: когда вводить абстракцию (DRY, Rule of Three), сколько сложности допустимо (KISS), что вообще не следует писать (YAGNI). Это делает триаду дополнением к SOLID, а не конкурентом: хорошо структурированный код может оказаться избыточно гибким (нарушение YAGNI) и наоборот.
Краткая сводка принципов:
| Принцип | Расшифровка | Источник | Суть |
|---|---|---|---|
| DRY | Don’t Repeat Yourself | Хант и Томас, The Pragmatic Programmer, 1999 | Каждое знание — единственное, однозначное представление в системе |
| KISS | Keep It Simple, Stupid | инженерная максима; Келли Джонсон, Skunk Works | Простота решения — ценность по умолчанию |
| YAGNI | You Aren’t Gonna Need It | XP, Кент Бек; Рон Джеффрис, c2 wiki | Не реализовывать то, что сейчас не нужно |
DRY (Don’t Repeat Yourself)
Формулировка и происхождение
Оригинальная формулировка. Every piece of knowledge must have a single, unambiguous, authoritative representation within a system. — Каждое знание должно иметь единственное, однозначное, авторитетное представление внутри системы. Эндрю Хант и Дэвид Томас, The Pragmatic Programmer (1999).
Принцип сформулирован явно про знание, а не про текст. «Знание» здесь — любое принятое в системе решение: бизнес-правило, формат данных, требование, алгоритм, значение константы. DRY требует, чтобы у каждого такого решения была ровно одна каноническая точка, где оно записано, — и запрещает дублировать не строки кода, а именно знания. Хант и Томас подчёркивали: нарушение DRY — это не «две одинаковые функции», а ситуация, когда изменение одного знания требует правок в нескольких местах, и часть из них неизбежно забывают.
Знание, а не текст кода
Различие между «дублированием знания» и «совпадением строк» — центральный нюанс принципа. Два фрагмента могут быть текстуально разными и при этом кодировать одно знание (SQL-запрос в репозитории и «тот же» фильтр в отчётном скрипте). И наоборот — два байт-в-байт одинаковых фрагмента могут выражать разные знания. Инструменты поиска клонов находят только первое, поэтому автоматическая проверка DRY невозможна: решение о том, что является дублированием знания, требует понимания предметной области.
Типичное нарушение — копипаст валидации между модулями:
// services/auth.ts
function isEmailValid(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
// services/marketing.ts
function checkEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); // скопировали из auth
}
Знание «email валиден, если соответствует шаблону» записано дважды. Когда продукт решит допускать адреса с +tag или домены на кириллице, правило поменяется — и с высокой вероятностью поправят только одно из двух мест: рассылка начнёт reject’ить адреса, которые регистрация приняла. Именно такие рассинхронизации, а не эстетика повторов, — главная цена нарушения DRY.
Не любой повтор — нарушение
Принцип не требует вычищать любое сходство. Два одинаковых фрагмента, кодирующие разные знания, имеют право существовать: у них разные причины изменяться и разные судьбы. Классический пример — одинаковое условие в двух предметных контекстах:
// модуль продажи алкоголя: требование закона о торговле
function canSellAlcohol(customer: Customer): boolean {
return customer.age >= 18;
}
// модуль финансовых договоров: требование ГК о дееспособности
function canSignContract(customer: Customer): boolean {
return customer.age >= 18;
}
Условия совпали сегодня, но источники требований разные: возраст продажи алкоголя может подняться до 21, а дееспособность останется с 18 — или наоборот. Если ради устранения повторения обе проверки «схлопнуть» в одну функцию isAdult(), изменения одного закона сломают другой контекст. Повтор текста здесь — случайность; дублирования знания нет.
DRY — это не цель «ноль одинаковых строк», а дисциплина: каждый раз, когда знание материализуется во втором месте, это осознанное и оправданное решение, а не случайность.
Классификация повторов по Ханту и Томасу
В The Pragmatic Programmer повторение классифицировано по причинам возникновения — от «неизбежного» до «недисциплинированного»:
| Тип | Причина | Отношение к DRY |
|---|---|---|
| Навязанное (imposed) | Ограничения среды: язык не позволяет выразить знание один раз, библиотека требует продублировать настройку | Минимизировать изоляцией повторов в одном модуле; устранять нельзя |
| Невольное (inadvertent) | Разработчики не знают, что знание уже записано | Устраняется коммуникацией и поиском перед написанием |
| Нетерпеливое (impatient) | «Быстрее скопировать, чем переиспользовать» | Основная мишень DRY: моментальная экономия оборачивается постоянной платой |
| Междуразработчиковое (interdeveloper) | Два человека дублируют усилия параллельно | Устраняется владельцем знания (модулем, библиотекой) и код-ревью |
Практический вывод классификации: DRY-долг в первую очередь погашается там, где повторение невольное или нетерпеливое — оно не имеет оправдания. Навязанное повторение честно локализуется: если среду нельзя заставить использовать знание из одного места, повтор изолируют в пограничном модуле и снабжают комментарием-ссылкой на источник, чтобы при изменении знания его можно было найти.
Дублирование между кодом и артефактами
Знание живёт не только в исходниках. Часто упускаемые носители дублирования:
- код и документация — правило описано в README и реализовано в коде; после первого изменения документация лжёт;
- код и миграции БД — ограничение (
VARCHAR(255),NOT NULL) зашито и в миграции, и строкой в приложении; - клиент и сервер — лимит размера файла скопирован числом и в браузерный валидатор, и в серверный обработчик;
- код и инфраструктура — порт сервиса прописан и в конфигурации деплоя, и константой в health-чеке.
// нарушение: знание о лимите продублировано числом в двух местах
// client/attachment-form.ts
if (file.size > 10_485_760) showError('Файл больше 10 МБ');
// api/upload.ts
if (request.body.length > 10_485_760) throw new PayloadTooLarge();
// DRY: одно знание — одно представление, обе стороны импортируют константу
export const MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024;
Для данных, конфигурации и инфраструктуры тот же принцип известен как SSOT (Single Source of Truth, единый источник истины): одно каноническое представление, всё остальное — производные, генерируемые или импортируемые, но не копируемые руками.
WET и цена дублирования
Антоним DRY иногда называют WET (Write Everything Twice — «пиши всё дважды»; в шутку расшифровывают и как We Enjoy Typing или Waste Everyone’s Time). Цена WET-кода растёт нелинейно: каждое изменение знания требует найти все его копии (поиск по кодовой базе ненадёжен), изменить согласованно, протестировать каждую копию и — самое дорогое — впредь помнить, что копий несколько. Кодовые базы, где одно правило живёт в восьми местах, со временем просто перестают меняться: страх что-то сломать парализует изменения.
KISS (Keep It Simple, Stupid)
Формулировка и происхождение
KISS (Keep It Simple, Stupid — «делай это простым, тупица») — инженерная максима, предписывающая стремиться к максимальной простоте решения и отвергать всё, что усложняет его без необходимости. Фраза связывается с Келли Джонсоном (Kelly Johnson), главным инженером секретного подразделения Lockheed Skunk Works (самолёты U-2, SR-71 Blackbird): по известной легенде, он вручал команде механиков простейший инструмент и предлагал посчитать, сколько его设计方案 можно починить подручными средствами в полевых условиях, под обстрелом, обычным армейским механиком со стандартным набором. Обращение «stupid» адресовано проектировщику, а не пользователю или исполнителю: это напоминание автору решения, что гениальность инженерии — в простоте, а не в сложности.
В программной инженерии максима распространилась с 1970-х годов и приобрела самостоятельное содержание: код читается на порядки чаще, чем пишется, поэтому сложность решения оплачивается временем каждого последующего читателя — включая самого автора через полгода.
Простота как ценность по умолчанию
KISS устанавливает простоту значением по умолчанию: бремя доказательства лежит на сложности. Любое усложнение — обобщение, слой косвенности, оптимизация, хитрый приём — должно оправдываться измеримой необходимостью, а не удовольствием от решения красивой задачи. Практические следствия:
- выбирать самое простое решение, которое удовлетворяет текущим требованиям, а не самым гибкое из возможных;
- предпочитать явное неявному: скучный линейный код понятнее изящной магии метапрограммирования;
- не вводить механизмы (кэши, пулы, очереди, конфигурационные фреймворки) без измеренной потребности в них.
Важная оговорка: простота — это не упрощение до потери сути. KISS требует минимальной сложности, достаточной для корректного решения задачи, а не выхолащивания задачи. Простой код, который молча не покрывает часть требований (обработка ошибок «чтобы не усложнять», округление граничных случаев), — не простота, а дефект. «Простота» системы, которая переносит сложность на пользователей, вынуждая их компенсировать её ручной работой, — тоже нарушение KISS: суммарная сложность не исчезла, она просто переехала.
«Умный» код как антиценность
Антипод KISS — clever code, «умный» код, написанный ради демонстрации мастерства или экономии строк. Типичные проявления:
// clever: компактно, автор доволен
const activeIds = users.reduce((acc, u) => (u.bannedAt ? acc : (acc.push(u.id), acc)), []);
// просто: на две строки длиннее, понятно каждому и отлаживаемо
const activeIds = users
.filter((user) => user.bannedAt === null)
.map((user) => user.id);
Разница между вариантами не в количестве символов, а в стоимости чтения: первый требует разобрать трюк с запятым и мутацией аккумулятора, второй читается как формулировка задачи. К этому же классу относятся избыточные обобщения и преждевременная оптимизация — их стоит разобрать отдельно.
Избыточные обобщения
Обобщение — самое респектабельное нарушение KISS: оно выглядит «профессионально» и маскируется под дальновидность. Признак — механизм, мощности которого не нужны ни одному реальному вызову:
// механизм «на все случаи» — при том, что в проекте ровно один сценарий:
// посчитать сумму заказа и округлить до копеек
class Pipeline<TIn, TOut, TCtx, TOpts> {
constructor(
private steps: Array<(i: TIn, c: TCtx, o: TOpts) => TIn | Promise<TIn>> = [],
) {}
use(step: (i: TIn, c: TCtx, o: TOpts) => TIn): this {
this.steps.push(step);
return this;
}
async run(input: TIn, ctx: TCtx, options: TOpts): Promise<TIn> {
let cur = input;
for (const step of this.steps) cur = await step(cur, ctx, options);
return cur;
}
}
// достаточно было одной функции
function orderTotal(order: Order): number {
const raw = order.items.reduce((sum, item) => sum + item.price * item.qty, 0);
return Math.round(raw * 100) / 100;
}
Конвейер с четырьмя параметрами типа, цепочками use() и семантикой контекста решает воображаемую задачу «гибкости», а реальную задачу делает невидимой: чтобы узнать, как считается сумма заказа, читатель обязан проследить конфигурацию конвейера в точке сборки. Простая функция отвечает на этот вопрос на нескольких строках. Эвристика: обобщение оправдано, когда оно обслуживает минимум два реальных сценария — в полном согласии с YAGNI и правилом трёх.
Преждевременная оптимизация
Частный случай избыточной сложности — оптимизация без измерений. Кэш с вытеснением, пул соединений, асинхронная обработка и небезопасные низкоуровневые трюки вводятся «для производительности» там, где профилировщик никогда не бывал. Каждая такая конструкция — источник трудноуловимых дефектов (гонки, устаревшие данные) и постоянная нагрузка на читателей. KISS требует обратного порядка: сначала корректное и простое решение, затем измерение, и лишь потом — точечная оптимизация узкого места, подтверждённого измерением. Почти всегда простое решение оказывается достаточно быстрым; если нет — оптимизировать приходится один процент кода, а не весь.
YAGNI (You Aren’t Gonna Need It)
Формулировка и происхождение
YAGNI (You Aren’t Gonna Need It — «это тебе не понадобится») — принцип Extreme Programming (XP), предписывающий не реализовывать функциональность, пока в ней нет актуальной подтверждённой потребности. Методологию XP создал Кент Бек (Kent Beck); сам афоризм закрепился благодаря Рону Джеффрису (Ron Jeffries) на вики c2.com в конце 1990-х. Каноническая формулировка Джеффриса: Always implement things when you actually need them, never when you just foresee that you need them — «реализуй вещи, когда они действительно нужны, и никогда — когда лишь предвидишь, что понадобятся».
Принцип направлен против главного двигателя over-engineering — спекулятивной гибкости: кода, написанного не под текущие требования, а под воображаемое будущее («а вдруг понадобится вторая реализация», «закладываемся на миллион пользователей», «сделаем сразу плагинную систему»). Опыт XP и последующих методологий устойчив: предсказания о будущем системы ошибочны чаще, чем точны, а написанный «на вырост» код почти никогда не угадывает форму будущей потребности.
Самый дешёвый код — ненаписанный
Экономика YAGNI проста: самый дешёвый код — тот, который не написан. У каждой строки кода есть пожизненная стоимость помимо момента написания: её нужно прочитать, чтобы понять окружающий код; протестировать и поддерживать тесты; отлаживать; документировать; не сломать при смежных изменениях; в конце концов — удалить, когда станет ясно, что она не нужна. Код, решающий несуществующую проблему, несёт все эти расходы, не создавая никакой ценности.
Типичный «задел на будущее»:
interface Notifier {
send(to: string, message: string): Promise<void>;
}
class EmailNotifier implements Notifier { /* единственная реализация */ }
class SmsNotifier implements Notifier { /* «скоро понадобится» — мёртвый код */ }
class TelegramNotifier implements Notifier { /* «маркетинг просил задел» — мёртвый код */ }
Интерфейс Notifier и «запасные» реализации написаны под фантазию о будущем. Расплата: интерфейс меняется под нужды почты и молча ломает неиспользуемые реализации; SMS- и Telegram-классы нужно поддерживать при каждом изменении контракта; читатель тратит время, выясняя, какая из трёх реализаций реально работает. Когда через год продуктовая задача действительно потребует Telegram, наличие старой заготовки почти наверняка окажется помехой: она написана под другой, уже изменившийся интерфейс.
«Мёртвый» и спекулятивный код — прямые источники технического долга: он не приносит ценности, но увеличивает стоимость каждого изменения.
Гибкость, которая не понадобилась
Кроме целых классов-заготовок, YAGNI нарушается «точечной» гибкостью: конфигурацией и флагами, которые навсегда остаются в одном значении.
const features = {
newCheckout: false, // «включим после релиза» — прошло два года, выключен
darkTheme: false, // «дизайнеры попросят» — не попросили
legacySearch: true, // «новый поиск будет» — не будет, флаг не снимается
};
Каждый мёртвый флаг — это ветка кода без пользователей: её никто не видит, никто не тестирует, но её проверяют в каждом ревью и обходят в каждом рефакторинге. То же происходит с параметрами функций («добавим параметр режима — пригодится»), значениями конфигурации и точками расширения: неиспользуемая гибкость не бесплатна — она лишь отложенная к уплате сложность. Обратная сторона YAGNI-дисциплины: когда развилка подтверждена и реализована, временное решение удаляют, а не оставляют «на всякий случай» рядом с новым.
Связь с Lean и XP
YAGNI — программное воплощение бережливого производства (Lean): не тратить усилия на работу, не создающую ценности прямо сейчас (eliminate waste), и проверять гипотезы минимальными порциями (MVP). В контексте XP принцип опирается на ключевое допущение методологии — низкую стоимость изменений: если рефакторинг дёшев, а тесты надёжны, «дописать нужное потом» безопасно, и резервировать гибкость заранее незачем. Отсюда практический вывод: готовить к написанию следует только подтверждённые потребности — те, что выражены в актуальных требованиях, пользовательских сценариях или измерениях, а не в прогнозах. Та же дисциплина составляет основу фазы Green в TDD: пишется ровно столько кода, сколько нужно, чтобы упа́л тест.
Взаимосвязь и баланс
Три фильтра избыточной сложности
Триада согласована и взаимно уравновешена: DRY убирает дублирование знания, KISS — сложность решения, YAGNI — избыточную функциональность. Каждый принцип ограничивает и остальных: YAGNI не даёт DRY абстрагировать «на будущее», KISS не даёт DRY строить сложные механизмы устранения повторов, а YAGNI и DRY вместе не дают KISS оправдывать беспорядок («простая» копипаст-архитектура нарушает DRY).
Практически триада работает как диагностическая таблица: по симптому в коде или ревью находится нарушенный принцип и противовес, который не даёт лечению перекинуться в противоположную крайность:
| Симптом | Нарушен | Противовес |
|---|---|---|
| Одно бизнес-правило найдено в N местах | DRY | YAGNI: абстрагировать после подтверждённых повторений |
| Код понятен только автору | KISS | Существенная сложность задачи может быть реальной |
| Механизм без реальных пользователей (флаг, параметр, класс) | YAGNI | Rule of Three: второй случай — ещё не повод |
| Изменение одной фичи ломает другую | DRY (wrong-DRY: объединено несвязанное) | Проверять общую причину изменений, а не сходство текста |
| «Простое» решение не покрывает требования | KISS (примитивизм) | Простота ≠ выхолащивание задачи |
Конфликт DRY и YAGNI
Центральное напряжение триады — DRY против YAGNI. Агрессивное применение DRY («увидел повторение — немедленно абстрагируй») ведёт к преждевременным абстракциям: обобщение строится по двум точкам, о которых неизвестно, совпадут ли их пути развития. Другое следствие агрессивного DRY — ранняя фиксация интерфейса, который ещё не успел «пожить» и почти наверняка спроектирован неверно: худший вид сложности — абстракция, угадавшая направление изменений неправильно. YAGNI выступает противовесом: абстракция — это тоже функциональность «на будущее» (механизм обобщения), и её следует вводить, когда потребность подтверждена повторениями, а не первым совпадением строк.
Rule of Three
Практический баланс между DRY и YAGNI даёт правило трёх (Rule of Three), сформулированное Мартином Фаулером в книге Refactoring (1999): «three strikes, then you refactor» — третий повтор — сигнал к рефакторингу. Схема:
- Первое вхождение знания — просто пишется, абстракции не нужно.
- Второе — допустимо повторить: два случая ещё не образуют закономерности, а преждевременное обобщение опаснее дублирования.
- Третье — закономерность подтверждена, знание точно дублируется; время вынести его в одно представление.
// было: три вхождения одного знания о валидном email
function validateOrderEmail(email: string) { /* regex */ }
function validateFeedbackEmail(email: string) { /* regex */ }
function validateSupportEmail(email: string) { /* regex */ }
// стало: одно знание — одно представление (после третьего повтора)
function isValidEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
Правило трёх не арифметический закон, а эвристика против двух симметричных ошибок: абстрагировать по первому совпадению (нарушение YAGNI) и копипастить бесконечно (нарушение DRY).
Чек-лист введения абстракции
Сведённый вместе баланс триады удобно применять как последовательность вопросов перед каждым «обобщить / упростить / добавить»:
- Подтверждена ли потребность? Есть ли реальный второй (третий) случай — в коде, требованиях, измерениях? Нет — YAGNI, не пишем.
- Одно ли это знание? Изменятся ли объединяемые фрагменты по одной причине? Нет — это wrong-DRY, повторение оставить.
- Проще ли станет решение? Уменьшит ли абстракция суммарную сложность или перенесёт её в косвенность? Осложнит — KISS против.
- Можно ли объяснить одним предложением? Название и контракт абстракции, требующие абзаца оговорок, — признак объединения разнородного.
- Обратим ли шаг? Дешевле ли откатить решение, чем жить с ним? Чем меньше уверенности, тем проще должно быть решение.
Положительные ответы на все пять вопросов — сигнал, что абстракция назрела; отрицательный ответ на любой — повод остановиться и оставить код простым и, при необходимости, повторённым.
Триада и SOLID
Триада и SOLID — взаимодополняющие наборы, отвечающие на разные вопросы. SOLID — про структуру: как разделить ответственности (SRP), куда направить зависимости (DIP), какими должны быть контракты (LSP, ISP). Триада — про решения «когда и сколько»: когда вводить абстракцию (DRY + Rule of Three), сколько сложности допустить (KISS), что не писать вовсе (YAGNI). SOLID без триады вырождается в догматическое переусложнение — интерфейсы с единственной реализацией и уровни косвенности «для расширяемости»; триада без SOLID не умеет структурировать даже минимально необходимую сложность. Конфликтные точки известны: OCP и DIP в буквальном прочтении подталкивают к ранним абстракциям «чтобы было легко расширять», и именно YAGNI с правилом трёх задают момент, когда эта гибкость оправдана (подробнее — в разделе «Критика» статьи SOLID).
Триада на уровне архитектуры
Хотя принципы чаще обсуждаются на уровне кода, их архитектурное прочтение — прямое и полезное:
- DRY между сервисами. Знание о формате события, схеме данных или бизнес-правиле, скопированное в пять микросервисов, — то же нарушение DRY, что и копипаст функции, только дороже: рассинхронизация обнаруживается в рантайме, а не в ревью. Средство — общий контракт (схема, контрактный тест, общая библиотека или сервис-владелец знания). При этом distributed-DRY имеет предел: жёсткая общая библиотека связывает релизные циклы команд, и иногда дублирование знания между контекстами — осознанная цена автономии (ср. «не любой повтор — нарушение»).
- KISS в архитектурных решениях. Выбор технологий и стилей — та же экономика простоты: Erlang-кластер и шина событий для корпоративного сайта нарушают KISS так же, как монада для форматирования строки. Вопрос ревью архитектуры: какая измеримая потребность делает это усложнение необходимым?
- YAGNI в масштабировании и интеграциях. «Заложим Kafka — вдруг вырастем», «добавим второй регион — отказоустойчивость» — типичные архитектурные YAGNI-нарушения: инфраструктурная гибкость, купленная заранее, оплачивается operational-нагрузкой с первого дня и почти всегда оказывается не той, какая нужна, когда потребность реально приходит.
Общий знаменатель: на уровне архитектуры цена ошибки выше (решения дороже менять), а предсказания будущего — не точнее, поэтому дисциплина «когда и сколько» важнее, а не наоборот.
Плюсы, минусы и антипаттерны «слепого» применения
Принципы триады — эвристики, а не законы. Их сила — в осознанном применении к конкретному решению; их слабость — в использовании как догмы, отменяющей мышление.
Плюсы осознанного применения
- Локализация изменений. DRY гарантирует: знание меняется в одном месте — риск рассинхронизации копий исчезает как класс.
- Дешёвое сопровождение. KISS-код читается и изменяется быстрее: простые решения короче входят в голову нового разработчика и реже ломаются.
- Отсутствие мёртвого груза. YAGNI исключает поддержку, тестирование и документирование кода, который не создаёт ценности.
- Быстрая обратная связь. Меньше кода — короче циклы «написал → проверил → узнал новое», что особенно важно в ранних итерациях продукта.
- Общий язык. Аббревиатуры служат компактной аргументацией в код-ревью: «это нарушение DRY» или «это YAGNI» — короткая ссылка на общеизвестное рассуждение.
Минусы «слепого» применения
- Догматизация. Принципы применяются без анализа контекста — как автоматические правила; любое повторение «наказывается», любая сложность «запрещается».
- Преждевременная фиксация структуры. Слепой DRY создаёт общие абстракции по случайным совпадениям — и связывает то, что должно меняться независимо.
- Потеря существенной функциональности. Слепой KISS/YAGNI отрезает действительно нужное под предлогом «упростим»: обработка ошибок, граничные случаи, безопасность.
- Скрытая сложность. Устраняя видимую сложность (код), слепое применение переносит её в неявные места: договорённости «в голове», магическое поведение, ручные процедуры.
- Ложная экономия. Экономия на «не нужном сейчас» оборачивается переделкой, если «потом» наступает раньше ожидаемого, а задел был дёшев именно сейчас.
Антипаттерн: wrong-DRY
Wrong-DRY (over-DRY) — устранение любого текстового сходства ценой объединения несвязанных знаний. Детектор повторов сработал — знания не проверили:
// НДС и скидка «похожи» — объединили в одну функцию с режимом
function applyRate(amount: number, rate: number, mode: 'vat' | 'discount'): number {
return mode === 'vat' ? amount * (1 + rate) : amount * (1 - rate);
}
Дальше знания расходятся: для НДС появляются правила округления до копейки и ставки по юрисдикциям, для скидок — стекирование промокодов и лимиты. Общая функция обрастает флагами и ветками, оба места вызова страдают от чужих изменений. Сэнди Мец (Sandi Metz) сформулировала цену этой ошибки: duplication is far cheaper than the wrong abstraction — дублирование значительно дешевле неправильной абстракции. Кент Доддс (Kent C. Dodds) предложил и позитивную альтернативу — AHA-программирование (Avoid Hasty Abstractions, «избегай поспешных абстракций»): предпочитать абстракции, которые просты и понятны уже сейчас, а не «идеально обобщённые на будущее». Правильный wrong-DRY-фильтр — вопрос не «похожи ли фрагменты», а «изменятся ли они по одной причине»; если нет, повторение — не враг, а защита контекстов друг от друга.
Антипаттерн: «простота» как маска выученной беспомощности
KISS легко превращается в оправдание остановки роста: «тесты — это сложно, у нас KISS», «типы/абстракции/автоматизация — лишняя сложность». Симптом — примитивизм, выдаваемый за простоту: ручное копирование данных вместо скрипта, отсутствие обработки ошибок, глобальные переменные «потому что так проще». Отличие принципиально: простота — результат освоения сложности, примитивизм — результат отказа от неё. KISS требует минимума сложности, достаточного для задачи; когда задача по существу сложна (многопоточность, консистентность данных, отчётность), «простое» решение, игнорирующее эту сложность, просто переносит её в production-инциденты.
Когда сложность оправдана
Различие сформулировал Фредерик Брукс в статье No Silver Bullet (1986) и популяризировал в инженерном обиходе Мартин Фаулер: сложность делится на существенную (essential — присущую самой задаче) и случайную (accidental — привнесённую инструментами и решениями). Налоговое законодательство, страховой актуарный расчёт, согласованность распределённых данных сложны по своей природе: никакой рефакторинг не сделает их проще самой задачи. KISS и YAGNI нацелены на случайную сложность — её следует минимизировать агрессивно. Существенную — честно моделировать, даже если код получается непростым: доменная сложность, «запрятанная» ради видимой простоты, искажает предметную область и стоит дороже всего. Зрелость инженера — умение отличить одно от другого: не проще ли решение потому, что случайная сложность устранена (хорошо), или потому, что существенная выкинута (катастрофа).
Связанные статьи
- Введение в архитектуру — место базовых принципов проектирования среди задач архитектуры ПО.
- SOLID — пять принципов структуры классов и модулей; триада отвечает на дополнительные вопросы «когда» и «сколько».
- Паттерны проектирования — каталог типовых решений; границы применимости паттернов определяются, в частности, YAGNI и KISS.
- Экономная архитектура — систематический взгляд на стоимость решений: YAGNI как частный случай экономии.
- TDD — Test Driven Development — фаза Green как ежедневное воплощение YAGNI: минимум кода для прохождения теста.
- Технический долг — судьба мёртвого и дублированного кода, не убранного вовремя.