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) и наоборот.

Краткая сводка принципов:

ПринципРасшифровкаИсточникСуть
DRYDon’t Repeat YourselfХант и Томас, The Pragmatic Programmer, 1999Каждое знание — единственное, однозначное представление в системе
KISSKeep It Simple, Stupidинженерная максима; Келли Джонсон, Skunk WorksПростота решения — ценность по умолчанию
YAGNIYou Aren’t Gonna Need ItXP, Кент Бек; Рон Джеффрис, 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 местахDRYYAGNI: абстрагировать после подтверждённых повторений
Код понятен только авторуKISSСущественная сложность задачи может быть реальной
Механизм без реальных пользователей (флаг, параметр, класс)YAGNIRule 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» — третий повтор — сигнал к рефакторингу. Схема:

  1. Первое вхождение знания — просто пишется, абстракции не нужно.
  2. Второе — допустимо повторить: два случая ещё не образуют закономерности, а преждевременное обобщение опаснее дублирования.
  3. Третье — закономерность подтверждена, знание точно дублируется; время вынести его в одно представление.
// было: три вхождения одного знания о валидном 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).

Чек-лист введения абстракции

Сведённый вместе баланс триады удобно применять как последовательность вопросов перед каждым «обобщить / упростить / добавить»:

  1. Подтверждена ли потребность? Есть ли реальный второй (третий) случай — в коде, требованиях, измерениях? Нет — YAGNI, не пишем.
  2. Одно ли это знание? Изменятся ли объединяемые фрагменты по одной причине? Нет — это wrong-DRY, повторение оставить.
  3. Проще ли станет решение? Уменьшит ли абстракция суммарную сложность или перенесёт её в косвенность? Осложнит — KISS против.
  4. Можно ли объяснить одним предложением? Название и контракт абстракции, требующие абзаца оговорок, — признак объединения разнородного.
  5. Обратим ли шаг? Дешевле ли откатить решение, чем жить с ним? Чем меньше уверенности, тем проще должно быть решение.

Положительные ответы на все пять вопросов — сигнал, что абстракция назрела; отрицательный ответ на любой — повод остановиться и оставить код простым и, при необходимости, повторённым.

Триада и 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: минимум кода для прохождения теста.
  • Технический долг — судьба мёртвого и дублированного кода, не убранного вовремя.