DRY, KISS, YAGNI и баланс

Три короткие заповеди, которые экономят недели — и одна ловушка, в которую они же заводят.

DRY, KISS, YAGNI — эвристики, которые подсказывают, сколько кода и абстракции писать: не дублируй знание, держи решение простым и не реализуй то, что ещё не нужно.

Если SOLID отвечает на вопрос «как разложить ответственность», то DRY/KISS/YAGNI отвечают на вопрос «сколько». Это не строгие правила, а компас. Их сила в том, что они тянут код в противоположные от типичных ошибок стороны: от копипасты, от усложнения и от работы впрок. Каждая из них короткая до лозунга — и именно поэтому опасна: лозунг легко довести до абсурда. Поэтому разбирать их мы будем парами «польза → перегиб».

Зачем это на практике

Две самые дорогие болезни кодовой базы — это размазанное дублирование (поправил в одном месте, забыл в трёх) и преждевременное усложнение (фреймворк там, где хватило бы функции). Эти эвристики — дешёвая прививка от обеих. Но применять их вслепую опасно: доведённый до абсурда DRY рождает монстров с десятком параметров-флагов, обслуживающих все случаи сразу; KISS превращается в оправдание небрежности («я же просто сделал»); а YAGNI — в повод не думать о дизайне вообще. Цель урока — не заучить три лозунга, а почувствовать, где проходит граница их разумного применения. Разберём каждую и её тёмную сторону.

DRY — Don't Repeat Yourself

Каждый кусочек знания должен иметь единственное authoritative-представление в системе.

«Плохо»: формула скидки повторяется в трёх местах. Маркетинг меняет ставку — и кто-нибудь обязательно забудет один из трёх кусков.

price1 = 100 - 100 * 0.1
price2 = 250 - 250 * 0.1
price3 = 80 - 80 * 0.1  # ставка 0.1 продублирована трижды

«Хорошо»: одно место правды.

DISCOUNT = 0.1

def with_discount(price):
    return price - price * DISCOUNT

print([with_discount(p) for p in (100, 250, 80)])

Вывод:

[90.0, 225.0, 72.0]

Важная тонкость: DRY — про знание, а не про похожий текст. Две функции, которые сегодня выглядят одинаково, но описывают разные бизнес-правила, — это не дублирование. Склеив их, вы создадите ложную связь, и завтра, когда одно правило изменится, придётся с болью разрывать.

KISS — Keep It Simple, Stupid

Из двух решений, делающих одно и то же, выбирай более простое и понятное.

«Плохо»: проверка чётности через побитовую магию и тернарники, потому что «так короче».

def is_even(n):
    return True if n & 1 == 0 else False

«Хорошо»: пишем прямо то, что имеем в виду.

def is_even(n):
    return n % 2 == 0

print(is_even(4), is_even(7))

Вывод:

True False

KISS — про читателя. Простой код не значит примитивный; он значит «понятный без расшифровки». Самый умный код в проде — обычно скучный код.

YAGNI — You Aren't Gonna Need It

Не реализуй функциональность, пока она реально не понадобилась.

«Плохо»: пишем универсальный загрузчик «из файла, из БД, из S3, из Kafka», хотя сегодня нужен только файл. Пять из шести веток никем не используются, но их надо поддерживать, тестировать и читать.

«Хорошо»: решаем сегодняшнюю задачу. Когда понадобится второй источник — добавим, и тогда же станет ясна правильная абстракция.

def load(path):
    with open(path) as f:
        return f.read()
# второй источник добавим, когда он действительно появится

YAGNI бьёт по соблазну «заложить на будущее». Будущее почти всегда оказывается не таким, как вы угадали, и заготовка устаревает раньше, чем пригодится. Любопытно, что YAGNI и DRY иногда конфликтуют: желание «не дублировать никогда» толкает к универсальному обобщению впрок, а это прямая работа на воображаемое будущее. Когда два принципа спорят, побеждает тот, что снижает реальную сегодняшнюю боль.

Когда «сухость» вредит

Самый коварный случай — over-DRY, преждевременное обобщение. Допустим, две функции расчёта (скидка покупателю и комиссия продавцу) сегодня формула в формулу совпадают. Соблазнительно склеить их в одну calc(rate). Но это разные бизнес-правила: завтра у скидки появится потолок, а у комиссии — ступенчатая шкала. Теперь общая функция обрастает условиями if is_discount, параметрами и ветками — она стала сложнее, чем две простые функции, которые вы «сэкономили». Это и есть цена ложной абстракции: вы связали то, что должно меняться независимо. Эмпирика проста — дублирование дешевле неправильной абстракции: дубль легко увидеть и поправить, а распутывать сросшийся код больно.

Как это работает под капотом

Эти три эвристики держат баланс между двумя силами. DRY тянет к обобщению (меньше копий — меньше рассинхрона). YAGNI и KISS тянут к конкретике (меньше абстракции — меньше веса). Здоровый код живёт в точке равновесия: дублирование знания убрано, но абстракции введены только под доказанную потребность. Полезное правило — «правило трёх»: первое появление кода пишем как есть, второе (дубль) терпим, на третьем — обобщаем. К этому моменту вы уже видите, что именно меняется, а что остаётся стабильным, и абстракция получится точной, а не угаданной. Тот же приём масштабируется: не проектируйте слой или фреймворк, пока у вас нет трёх реальных потребителей, чьи требования вы можете сравнить.

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

  • DRY поверх случайного совпадения. Склеили два куска, которые лишь похожи внешне; теперь любое расхождение бизнес-правил ломает общий код. Дублирование дешевле неверной абстракции.
  • Преждевременная абстракция. Generic-слой, фабрика фабрик и плагинная система «на вырост» — это нарушение YAGNI, маскирующееся под архитектуру.
  • KISS как оправдание. «Я сделал просто» иногда значит «я не подумал о краевых случаях». Простота — это про ясность, а не про пропуск требований.
  • Догматизм. Иногда сознательное дублирование (например, в двух независимых сервисах) проще и безопаснее, чем общая библиотека, связывающая их жёстко.

Итоги

  • DRY — убирай дублирование знания, но не склеивай случайно похожий код.
  • KISS — выбирай простое и читаемое решение; скучный код побеждает.
  • YAGNI — не реализуй впрок; добавишь, когда понадобится.
  • Правило трёх помогает не абстрагировать раньше времени.
  • Эти эвристики — компас, а не закон: их перегиб порождает свои антипаттерны.
Проверьте себя
1. Принцип DRY требует убирать дублирование. Чего именно?
AЛюбых строк кода, которые выглядят одинаково
BДублирования знания (одного бизнес-правила или факта), а не просто похожего текста
CВсех функций, у которых совпадает имя
DПовторяющихся комментариев
2. В чём суть принципа YAGNI?
AВсегда закладывай гибкость на будущее, чтобы потом не переписывать
BНе реализуй функциональность, пока она реально не понадобилась
CИспользуй как можно меньше внешних библиотек
DИмена переменных должны быть как можно короче
3. Что советует «правило трёх» применительно к абстракциям?
AСоздавать абстракцию сразу при первом написании кода
BТерпеть дублирование до третьего появления, и только тогда обобщать
CНикогда не допускать больше трёх классов в модуле
DКаждую функцию ограничивать тремя строками