В 2025 году компания GitClear разобрала 211 миллионов изменённых строк кода из репозиториев Google, Microsoft, Meta и корпоративных заказчиков за пять лет. Выяснилось, что доля скопированных строк выросла с 8,3 % в 2020 году до 12,3 % в 2024-м, а доля строк, которые кто-то переписал и упростил, рухнула с четверти до менее чем десятой части. Блоков-дублей длиннее пяти строк стало вчетверо больше. Впервые за всю историю наблюдений копипаста обогнала аккуратный перенос кода.
Проще говоря: кода стало больше, а порядка в нём меньше. И поэтому четыре старых аббревиатуры, о которых вы наверняка слышали на собеседовании, вдруг перестали быть теорией из учебника. Разберём KISS, DRY, YAGNI и SOLID на примерах уровня первого пет-проекта, то есть небольшой программы, которую вы пишете сами для себя. Разберём, что каждый принцип запрещает, как выглядит нарушение в живом коде и, главное, что делать, когда принципы противоречат друг другу.
Если вы только начинаете и ещё выбираете язык, сначала загляните в наш разбор «Программирование с нуля: с чего начать». Эта статья пригодится чуть позже, когда у вас уже есть первые сто строк собственного кода.
Материал написан для новичков. Все примеры на Python, короткие и без фреймворков, но сами принципы работают одинаково в JavaScript, Java, C#, Go и даже в 1С. Если язык ещё не выбран, у нас есть отдельный текст про то, какой язык программирования выбрать первым.
А если статей мало и хочется программы, где живой наставник читает ваш код и объясняет, что и почему стоит переписать (это называется код-ревью), в нашем каталоге собраны курсы программирования: от двухнедельных интенсивов до годовых профессий с трудоустройством.
Зачем принципы, если код и так работает

Самое частое возражение новичка звучит так: программа запускается, тесты зелёные, заказчик доволен. Зачем тогда правила?
Затем, что код пишут один раз, а читают десятки раз. Через месяц вы вернётесь в собственный проект и не вспомните, почему функция называется process2 и что она делает с третьим аргументом. Через полгода в проект придёт другой человек. Через год окажется, что нужно поменять одно правило расчёта, а оно почему-то записано в четырёх местах, и три из них вы нашли, а четвёртое нашёл пользователь.
Всё, что накапливается из таких мест, называют техническим долгом: работающий код, который с каждым месяцем всё дороже менять. Через год-другой он превращается в легаси, то есть в код, который страшно трогать, потому что никто уже не помнит, почему он написан именно так. Рефакторинг легаси стоит в разы дороже, чем аккуратность на старте.
Все четыре принципа отвечают на один вопрос: как писать так, чтобы завтрашние изменения были дешёвыми. Дешёвыми по времени и нервам. Красота кода тут вторична. Побочный бонус тоже приятный: у кода, написанного по этим правилам, заметно выше тестируемость, потому что каждый кусок можно проверить отдельно от остальных.
Важно сразу снять два страха. Первый: принципы придуманы для больших команд и корпораций. Неправда. В пет-проекте на 500 строк они экономят время быстрее всего, потому что вы сами и есть тот человек, который через месяц будет разбираться в своём коде. Второй страх: если делать «по правилам», сроки поедут. На старте уходит чуть больше времени, но окупается это на первой же правке.
Ориентир для новичка. Принципы стоит вспоминать не в момент написания первой версии, а в момент, когда вы возвращаетесь к коду во второй раз. Первый черновик может быть каким угодно.
KISS: делайте проще, но не примитивнее
KISS расшифровывается как Keep It Simple, Stupid, что можно перевести как «сделай просто, не умничай». Принцип пришёл из авиации: в 1960 году его сформулировали в ВМС США под влиянием авиаконструктора Кларенса Джонсона, который работал в подразделении Skunk Works компании Lockheed. Джонсон требовал, чтобы самолёт можно было починить в полевых условиях обычным набором инструментов, а не силами инженера с высшим образованием. В программировании требование то же самое: решение должно чиниться человеком, который видит код впервые.
Суть принципа KISS в том, что из двух работающих решений выбирают то, которое проще объяснить вслух. Не то, которое короче, и не то, которое умнее.
Вот скрипт, который считает сумму заказов из списка. Первый вариант написан «красиво»:
total = sum(map(lambda o: o["price"] * o["qty"],
filter(lambda o: o["status"] in ("paid", "shipped"), orders)))
Второй вариант делает то же самое:
total = 0
for order in orders:
if order["status"] in ("paid", "shipped"):
total += order["price"] * order["qty"]
Первый короче на три строки. Второй читается вслух за пять секунд и правится без риска, когда завтра к статусам добавится третий. KISS выбирает второй.
Чего KISS не требует
Принцип KISS часто понимают как призыв писать примитивно и никогда не выносить логику в отдельные функции. Это ошибка. Простота измеряется числом сущностей, которые нужно держать в голове одновременно, чтобы понять кусок кода. Длина файла тут ни при чём.
На курсах обычно дают такие ориентиры: функция помещается на экран без прокрутки, у неё не больше трёх-четырёх аргументов, вложенность условий не глубже двух уровней. Имя функции честно описывает то, что она делает. Если имя получается вроде save_and_notify_and_log, функция делает три дела и её пора разделить.
DRY: не повторяйтесь, но и не склеивайте что попало
DRY расшифровывается как Don’t Repeat Yourself. Принцип DRY требует, чтобы каждое правило вашей системы было записано в коде ровно один раз.
Классический пример: форма регистрации. Проверка email нужна при регистрации, при смене почты в профиле и при подписке на рассылку. Новичок копирует три строки в три места:
if "@" not in email or "." not in email.split("@")[-1]:
return "Некорректный email"
Работает. И будет работать до того дня, когда придёт требование запретить одноразовые почтовые ящики. Вы поправите две проверки из трёх, а третья продолжит пускать кого попало. И найдёт это пользователь, раньше любого тестировщика.
Лечится выносом в одно место:
def is_valid_email(email):
if "@" not in email:
return False
domain = email.split("@")[-1]
return "." in domain and domain not in DISPOSABLE_DOMAINS
Теперь правило живёт в одной функции, и правка автоматически доезжает во все три места.
Главная ловушка DRY
Новички читают DRY как «два одинаковых куска кода запрещены» и начинают вычищать любые совпадения. Так рождается общая функция на пятнадцать аргументов и семь флагов (переключателей, каждый из которых меняет её поведение), которая обслуживает три экрана сразу, и трогать её боятся все.
DRY запрещает дублирование знания, а не дублирование символов. Если код валидации пароля и код валидации промокода сейчас выглядят одинаково, это совпадение: завтра правила пароля поменяются, а правила промокода останутся. Склеив их, вы получите функцию, которая обслуживает две разные жизни.
Правило трёх. На первое повторение не реагируйте. На второе присмотритесь. Выносите в общее место на третьем, когда уже видно, где общее знание, а где случайное совпадение.
YAGNI: не пишите код про запас
YAGNI расшифровывается как You Aren’t Gonna Need It: «вам это не понадобится». Принцип запрещает писать функциональность впрок, под гипотетическое будущее требование.
Как это выглядит на практике. Вы делаете калькулятор доставки для маленького магазина. Сейчас нужна одна служба доставки. Но вы думаете наперёд и закладываете абстрактный класс DeliveryProvider (заготовку, от которой потом наследуются конкретные службы), три таких наследника под службы, которые магазин когда-нибудь подключит, фабрику для их создания (отдельный класс, занятый только тем, что создаёт нужный объект) и конфиг с настройками каждой.
# Реальность: подключена одна служба.
# В коде: 4 класса, фабрика, конфиг на 40 строк.
provider = DeliveryProviderFactory.create(config["delivery"]["provider"])
price = provider.calculate(weight, city)
Через год магазин подключает вторую службу, и оказывается, что считает она по объёму и почтовому индексу вместо веса и города. Ваша заготовка не подходит, её нужно ломать. Вы заплатили за абстракцию дважды: когда писали и когда сносили. Абстракцией здесь называется общая заготовка, под которую потом подставляют конкретные случаи.
Вариант по YAGNI на старте выглядит так:
def calculate_delivery(weight, city):
return BASE_PRICE + weight * RATE_PER_KG + CITY_SURCHARGE.get(city, 0)
Когда придёт вторая служба, вы уже будете знать её реальные требования и сделаете абстракцию под факты, а не под догадки.
Оговорка, о которой обычно забывают: YAGNI не запрещает думать об архитектуре. Он запрещает писать код под неподтверждённые требования. Продумать, куда система будет расти, полезно всегда. Реализовывать этот рост заранее вредно.
SOLID: пять букв, которые спрашивают на собеседовании
SOLID — это набор из пяти принципов проектирования классов, который в начале двухтысячных собрал Роберт Мартин. В отличие от предыдущих трёх, SOLID заточен под объектно-ориентированное программирование: язык, где код организован вокруг классов и объектов, а не вокруг отдельных функций.
По ходу встретятся три слова, которые стоит расшифровать сразу. Методом называют функцию внутри класса. Наследование позволяет собрать новый класс на основе существующего. Интерфейс задаёт список методов, которые класс обязуется реализовать.
Для новичка SOLID звучит страшнее, чем есть. Проще всего держать в голове кухонную аналогию: чайник кипятит воду, тостер жарит хлеб, кофеварка варит кофе. Сломался тостер, меняете тостер, а не перестраиваете кухню. Все пять букв обслуживают эту мысль с разных сторон.
S: у класса одна причина меняться
Single Responsibility Principle, принцип единственной ответственности. Класс должен отвечать за что-то одно.
Типичное нарушение: класс Report, который сам считает цифры, сам верстает HTML и сам отправляет письмо. Три ответственности означают три независимых повода его править. Поменялся шаблон письма, и вы лезете в файл, где живёт математика. Разделите на три класса, и каждая правка будет трогать одно место.
O: открыт для расширения, закрыт для правок
Open/Closed Principle, принцип открытости и закрытости. Новое поведение добавляется новым кодом, а не переписыванием старого, который уже работает и уже протестирован.
Признак нарушения выглядит так: длинная цепочка if тип == "A" ... elif тип == "B" ..., в которую вы дописываете новую ветку каждый раз при появлении нового типа. Каждое такое дописывание рискует сломать соседние ветки. Лечится это полиморфизмом: вы описываете общий метод, каждый тип реализует его по-своему, и цепочка условий исчезает.
L: наследник не должен ломать ожидания
Liskov Substitution Principle, принцип подстановки Барбары Лисков. Самая непонятная буква из пяти, хотя смысл бытовой: если код работает с родительским классом, он должен без сюрпризов работать и с любым наследником.
Хрестоматийный пример: класс «Прямоугольник» с методами задать ширину и задать высоту, и класс «Квадрат», который от него наследуется. Логика подсказывает, что квадрат — это частный случай прямоугольника. Но у квадрата стороны равны, поэтому при установке ширины он вынужден менять и высоту. Код, который честно поставил ширину 5 и высоту 3 и ждёт площадь 15, получит 9. Формально всё компилируется, фактически наследник обманул ожидания.
Проверить легко: если в документации наследника приходится писать «работает так же, кроме случаев, когда…», принцип нарушен. Чинят это обычно отказом от наследования: общее выносят в отдельный объект и держат внутри, такой приём называется композицией.
I: не заставляйте реализовывать лишнее
Interface Segregation Principle, принцип разделения интерфейса. Лучше несколько узких интерфейсов, чем один широкий.
Нарушение: интерфейс Animal с методами «бежать», «плавать» и «летать». Пингвин обязан реализовать «летать», страус обязан реализовать «плавать», и оба вынуждены писать заглушки, которые бросают ошибку. Разбейте на отдельные интерфейсы, и каждый класс возьмёт только то, что умеет.
D: зависьте от абстракций
Dependency Inversion Principle, принцип инверсии зависимостей. Класс с бизнес-логикой не должен сам создавать внутри себя конкретную базу данных, конкретный почтовый сервис или конкретный платёжный шлюз.
# Нарушение: OrderService намертво привязан к MySQL и SMTP
class OrderService:
def __init__(self):
self.db = MySQLDatabase()
self.mailer = SMTPMailer()
# По DIP: зависимости передают снаружи
class OrderService:
def __init__(self, storage, notifier):
self.storage = storage
self.notifier = notifier
Во втором варианте тот же OrderService можно протестировать с фальшивым хранилищем в памяти, не поднимая настоящую базу. Передача зависимостей снаружи называется внедрением зависимостей и в реальных проектах встречается чаще, чем все остальные буквы SOLID вместе взятые.
Два соседних правила, которые обычно идут в комплекте
В подборках рядом с четвёркой почти всегда упоминают ещё два правила, и оба про то же самое. Бритва Оккама в переложении на код звучит так: не плодите сущностей сверх необходимого, лишний класс должен доказать своё право на существование. Преждевременная оптимизация (её ещё называют APO, avoid premature optimization) запрещает ускорять то, что вы не замерили: девять раз из десяти узкое место окажется совсем не там, где вы его подозревали, а код после ускорения станет нечитаемым зря.
Что каждый принцип запрещает и как поймать нарушение
Собрали всё в одну таблицу. Колонка «как выглядит нарушение» полезнее определений: именно по этим симптомам принцип и находят в чужом коде.
| Принцип | Что запрещает | Как выглядит нарушение | Как чинить |
|---|---|---|---|
| KISS | Умные решения там, где хватит скучных | Строку кода нельзя пересказать вслух за 5 секунд; вложенность в 4 уровня | Развернуть в цикл, разбить на именованные шаги |
| DRY | Одно правило системы, записанное дважды | Правка требования тянет за собой поиск по проекту | Вынести правило в одну функцию или константу |
| YAGNI | Код под требования, которых ещё нет | Классы и флаги, которые никто не вызывает | Удалить; вернуть, когда требование появится |
| SOLID / S | Больше одной ответственности в классе | «И считает, и рисует, и отправляет» | Разделить по причинам изменения |
| SOLID / O | Правку работающего кода ради нового случая | Растущая цепочка if по типам | Полиморфизм или словарь обработчиков |
| SOLID / L | Наследника, который ведёт себя иначе | «Работает так же, кроме случаев, когда…» | Убрать наследование, вынести общее в композицию |
| SOLID / I | Широкие интерфейсы с ненужными методами | Заглушки, которые бросают ошибку | Разбить на узкие интерфейсы |
| SOLID / D | Жёсткую привязку логики к инструменту | Класс сам создаёт внутри себя базу или почту | Передавать зависимости в конструктор |
Где принципы дерутся друг с другом

Об этом почти не пишут. Между тем именно здесь новичок и застревает: он честно выучил четыре правила, попытался применить все сразу и получил код хуже исходного.
Так и должно быть. Принципы противоречат друг другу постоянно, и умение выбирать между ними отличает джуна от мидла сильнее, чем знание определений.
KISS против DRY
Самый частый конфликт. Чтобы убрать дублирование, приходится вводить дополнительный слой: общую функцию, базовый класс, конфиг. Каждый такой слой делает код менее очевидным.
Ориентир такой: если вынесение общего кода добавляет больше одного нового понятия, которое читателю придётся держать в голове, дублирование дешевле. Два похожих метода по десять строк честнее одного универсального метода с четырьмя флагами.
DRY против SOLID
DRY тянет код в одно место, принцип единственной ответственности тянет его в разные. Типичный тупик: два модуля, то есть два независимых куска программы, используют одну и ту же формулу, но по разным причинам. Склеите по DRY, получите класс, у которого два повода меняться, и нарушите S.
Развязка простая: смотрите, кто заказчик правил. Если формулу меняет один и тот же человек по одной и той же причине, знание общее и его выносят. Если два разных отдела просят изменить её независимо, это два разных правила, которые сегодня случайно совпали.
YAGNI против SOLID
SOLID подталкивает заранее развести систему на классы и интерфейсы, YAGNI требует не делать ничего, пока нет требования. Компромисс: вводите абстракцию на второй реализации, а не на первой. Первый платёжный шлюз пишите прямо. Когда появится второй, вы увидите, что у них общего на самом деле, и вынесете интерфейс по фактам.
Порядок разрешения споров. Когда принципы тянут в разные стороны, побеждает тот, который в этом месте делает будущую правку дешевле. Не тот, что красивее звучит на собеседовании.
Оверинжиниринг: когда «правильно» получается хуже, чем было
Оверинжиниринг — это переусложнение системы ради гибкости, которая никому не нужна. Обычно он появляется у человека, который только что прочитал про SOLID и хочет применить всё сразу.
Как его узнать в своём коде:
- Интерфейс с единственной реализацией, которая никогда не менялась. Абстракция ради абстракции.
- Фабрика, которая создаёт один-единственный класс. Лишний слой между вызовом и объектом.
- Настройки в конфиге, которые никто никогда не менял. Каждая такая настройка удваивает число сценариев, которые нужно проверять.
- Слой абстракции над библиотекой «на случай, если мы её заменим». Библиотеки меняют примерно никогда, а слой поддерживать приходится всегда.
- Класс, который называется
Manager,HelperилиUtils. Такое имя обычно означает, что автор сам не смог объяснить ответственность.
Попробуйте объяснить каждый слой архитектуры одним предложением, начинающимся со слов «он нужен, потому что сегодня…». Если предложение получается только в будущем времени, слой пока лишний.
Принципы и AI-кодинг: что показало исследование 211 млн строк

Вернёмся к цифрам из начала статьи. Исследование GitClear охватило период с января 2020 по декабрь 2024 года, то есть тот самый отрезок, когда ассистенты вроде GitHub Copilot, а затем нейросети для кода вроде Claude Opus 5 и
GPT-5.6 Sol стали массовым инструментом. Три числа из отчёта стоит запомнить:
- Доля клонированных строк выросла с 8,3 % до 12,3 % за пять лет.
- Блоков-дублей длиной от пяти строк стало вчетверо больше только за 2024 год.
- Доля строк, связанных с рефакторингом (переписыванием работающего кода ради читаемости, без изменения поведения), упала примерно с 25 % до менее чем 10 %.
Логика понятная. Модель отлично генерирует работающий фрагмент под конкретный запрос, но она не знает, что похожий фрагмент уже лежит в соседнем файле. Попросили функцию отправки письма в трёх местах диалога, получили три независимые функции отправки письма. Каждая работает. Все три придётся править, когда сменится почтовый сервис.
Что из этого следует для тех, кто пишет с ассистентом или занимается вайбкодингом: принципы переезжают из момента написания в момент проверки. Код пишет модель, а роль DRY и SOLID достаётся вам на этапе ревью. Три вопроса, которые стоит задавать любому сгенерированному куску:
- Нет ли такого же куска в проекте? Поиск по характерной строке занимает десять секунд.
- Не притащила ли модель абстракцию, о которой я не просил? Лишний менеджер или фабрика удаляются сразу.
- Смогу ли я объяснить этот код вслух? Если нет, просите переписать проще: с этим модели справляются охотно.
Чек-лист: 8 вопросов к своему коду перед коммитом
Держите короткий список, который закрывает все четыре принципа. Он занимает пару минут и ловит большую часть проблем, о которых вам напишут на код-ревью.
- Смогу ли я пересказать эту функцию вслух одним предложением? Если в предложении появляется «и», функция делает два дела.
- Есть ли в проекте такой же кусок? Поищите по самой характерной строке.
- Если завтра поменяется одно бизнес-правило, сколько файлов я открою? Ответ «больше одного» означает нарушение DRY.
- Есть ли здесь код, который сейчас никто не вызывает? Удаляйте, история в git всё помнит.
- Сколько причин у этого класса меняться? Перечислите вслух, честно.
- Растёт ли где-то цепочка if по типам? Третья ветка означает, что пора менять подход.
- Создаёт ли класс внутри себя базу, почту или платёжку? Если да, передайте их снаружи.
- Каждый слой абстракции нужен уже сегодня? Объяснение в будущем времени не считается.
Отдельно про наименования: половина проблем с читаемостью лечится честными именами переменных и функций, безо всякой архитектуры. days_until_expiry экономит читателю больше времени, чем любой паттерн проектирования.
С чего начать и где научиться
Самостоятельный путь выглядит так. Возьмите свой текущий проект, пройдите по чек-листу выше и исправьте один-два пункта, которые бросаются в глаза сильнее всего. Не переписывайте всё сразу: принципы усваиваются на маленьких правках. Большой рефакторинг за выходные обычно заканчивается сломанным проектом и испорченным настроением. Когда захочется теории поглубже, читайте «Чистый код» Роберта Мартина и «Рефакторинг» Мартина Фаулера, но уже после того, как набьёте первые шишки на своём коде.
Быстрее всего эти вещи усваиваются на код-ревью, когда живой человек показывает пальцем на конкретную строку и объясняет, почему через полгода она обернётся проблемой. Статьи такой обратной связи не дают, поэтому в какой-то момент имеет смысл пойти на курс. О том, как выбрать курс программирования, мы писали отдельно, а собранные и отсортированные по рейтингу программы лежат ниже.
| Курс | Школа | Стоимость со скидкой | В рассрочку | Длительность | Обзор курса от Checkroi |
|---|---|---|---|---|---|
| Нейросети для рабочих задач Перейти на сайт курса | 31 290 ₽ | 2608 ₽/мес. | 1 месяц | Обзор курса | |
| Нейросети. Практический курс Перейти на сайт курса | 74 900 ₽ | 6242 ₽/мес. | 3 месяца | Обзор курса | |
| Нейросети для каждого: как решать рабочие задачи быстрее Перейти на сайт курса | 57 000 ₽ | 2763 ₽/мес. | 6 недель | Обзор курса | |
| Программирование для анализа данных | 134 640 ₽ | 5500 ₽/мес. | 12 месяцев | Обзор курса | |
| Профессия «Python-разработчик» Перейти на сайт курса | 157 335 ₽ | 5987 ₽/мес. | 10 месяцев | Обзор курса | |
| Профессия «Fullstack-разработчик на PHP» Перейти на сайт курса | 166 715 ₽ | 5378 ₽/мес. | 12 месяцев | Обзор курса | |
| Frontend-разработчик с нуля Перейти на сайт курса | 123 700 ₽ | 5385 ₽/мес. | 10 месяцев | Обзор курса | |
| Fullstack-разработчик на Python Перейти на сайт курса | 161 200 ₽ | 7125 ₽/мес. | 21 месяц | Обзор курса | |
| Профессия «Разработчик игр на Unity с нуля» Перейти на сайт курса | 130 521 ₽ | 3679 ₽/мес. | 10 месяцев | Обзор курса | |
| PHP-разработчик с нуля до PRO Перейти на сайт курса | 114 876 ₽ | 4176 ₽/мес. | 7 месяцев | Обзор курса |
Больше программ — в полном каталоге курсов по программированию и IT
Онлайн-курсы по программированию удобны как раз тем, что домашние задания на них проверяет человек, и все восемь пунктов чек-листа выше вам подсветят на чужом опыте. Начинать обучение программированию имеет смысл с бесплатных вводных модулей: почти у каждой школы первые уроки открыты, и этого хватает, чтобы понять, ваш это формат или нет. Если направление уже выбрано, посмотрите специализированные подборки: курсы backend-разработки, где SOLID нужен ежедневно, или курсы Python для тех, кто начинает с примеров вроде разобранных выше. А тем, кто хочет понять, куда этот путь ведёт через несколько лет, пригодится статья «Как стать архитектором ПО»: там принципы проектирования превращаются в основную работу.




