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 — не реализуй впрок; добавишь, когда понадобится.
- Правило трёх помогает не абстрагировать раньше времени.
- Эти эвристики — компас, а не закон: их перегиб порождает свои антипаттерны.