В этой статье я расскажу об основных заповедях программирования простыми и понятными словами, а также попробую предостеречь от слепого следования этим заповедям.
Идея написания этой статьи у меня возникла после публикации статьи «Путь инженера» — кажется, будет полезным развить эту тему и поговорить более детально обо всём, что пересекается с основной мыслью.
Вступление
Программирование часто сравнивают со строительством: вы проектируете, возводите структуры, а затем поддерживаете их в рабочем состоянии. Но в отличие от зданий, код постоянно меняется: новые требования, баги, обновления — всё это требует гибкости. Однако, гибкость без ориентиров может привести к хаосу.
Заповеди
Программирование, как и любая дисциплина, опирается на принципы, выработанные десятилетиями проб и ошибок. Эти принципы, а, скорее, даже заповеди — это не свод законов, а уроки, извлечённые из реальных проектов. Они существуют не для того, чтобы ограничивать, а для того, чтобы направлять.
Boy Scout Rule
Этот принцип пришёл в программирование из мира скаутов, где главное правило звучит: «Оставь поляну чище, чем нашёл». Впервые его адаптировал Роберт Мартин в контексте разработки, подчеркнув: «Каждый раз, когда ты касаешься кода, обязан улучшить его хоть чуть-чуть».
Суть не в глобальном рефакторинге, а в малых шагах: исправить опечатку в комментарии, упростить запутанное условие, удалить мёртвый код. Это как уборка крошек со стола после обеда — незаметно, но предотвращает хаос. Следование этому принципу превращает поддержку кода из рутины в коллективную ответственность: даже если проблема не твоя, её исправление — вклад в будущее проекта.
Технический долг не исчезает — он ждёт, пока его заметят.
Principle of Least Astonishment
Этот принцип, известный также как «Принцип наименьшего удивления», зародился в 1980-х годах в среде разработчиков UNIX-систем, где интуитивность интерфейсов была ключевой ценностью. Его авторство приписывают сообществу инженеров, стремившихся создать системы, которые «не удивляют» пользователя.
Суть этого принципа в том, что любой элемент кода должен работать именно так, как предполагает его название или контекст. Например, метод save() обязан сохранять данные, а не отправлять их в космос, а кнопка «Отмена» обязана отменять действие, а не запускать фоновые процессы. Это правило о снижении когнитивной нагрузки: если код ведёт себя предсказуемо, его легче читать, поддерживать и доверять ему.
Хороший код — как хороший друг: он не подкидывает сюрпризов, когда вы отворачиваетесь
Avoid Premature Optimization
Этот принцип стал крылатой фразой благодаря Дональду Кнуту, который в 1974 году написал: «Преждевременная оптимизация — корень всех зол». Позже сообщество разработчиков адаптировало идею как предостережение: не усложняйте код ради гипотетического «прироста производительности», пока не доказана необходимость.
Суть этого принципа в том, что оптимизация без данных — как ремонт двигателя машины, которая ещё не собрана. Сначала сделайте код работающим, читаемым и тестируемым, а потом ищите узкие места с помощью профилирования, иначе вы рискуете потратить часы на микрооптимизацию или написать «умный» код, который никто не сможет понять.
Если оно работает, не трогай — пока не узнаешь, что именно трогать
Composition over Inheritance
Этот принцип стал ключевым элементом объектно-ориентированного дизайна благодаря книге «Паттерны объектно-ориентированного проектирования» банды четырёх, где авторы подчеркнули «Предпочитайте композицию наследованию».
Суть этого принципа в том, чтобы объединять объекты, делегируя им функциональность, вместо построения жёстких иерархий классов. Если у вас есть класс Птица, не стоит наследовать от него классы Утка, Пингвин и Страус, ведь не все птицы летают или плавают, лучше создать отдельные компоненты ЛетающееПоведение и ПлавающееПоведение, которые можно гибко подключать, а если у вас есть класс ТранспортноеСредство, то классы Автомобиль и Грузовик могут и должны быть наследованы от него.Некорректное наследование зачастую приводит к хрупкости: изменение базового класса ломает всех потомков. Композиция же позволяет пересобирать объекты, как конструктор.
Law of Demeter
Этот принцип, известный как «Принцип наименьшего знания», впервые предложен Яном Холландом в рамках проекта «Деметра».
Суть этого принципа в том, что объект должен взаимодействовать только с ближайшими соседями — теми, которые доступны ему напрямую. Например: цепочки вызовов $user->getAccount()->getBalance()->update() превращают код в хрупкую паутину, изменение класса Account может разорвать всю цепочку. Закон Деметры предлагает вместо этого делегировать действия добавить метод $user->updateBalance(), который инкапсулирует логику внутри класса User.
Однако, слепое следование принципу может породить десятки методов-прослоек.
Принцип наименьшего знания — это когда ты не можешь позвать друга друга, даже если он в соседней комнате.
Command-Query Separation
Этот принцип предложил Бертран Мейер в 1988 году в книге «Объектно-ориентированное программирование». Он стал основой для языков вроде Eiffel, где чёткое разделение методов на команды и запросы встроено в синтаксис.
Суть этого принципа в том, чтобы разделять методы на два типа: команды и запросы. Команды изменяют состояние системы (например, сохраняют данные), но не возвращают значений. Запросы возвращают данные (например, читают из базы), но не изменяют состояние.
Однако, если слепо следовать принципу, можно наплодить лишних методов, когда одному нужно и читать, и писать. Тут важно не переборщить, иначе код станет похож на бюрократическую машину.
Если метод и командует, и спрашивает, он просто болтливый генерал.
Separation of Concerns
Этот принцип, который позже стал основой для архитектурных шаблонов, предложил Эдсгер Дейкстрой в 1974 году в статье «О роли научного мышления».
Суть этого принципа заключается в том, что система должна быть разделена на независимые компоненты, каждый из которых решает одну задачу.
Однако, чрезмерное дробление может превратить код в лоскутное одеяло из мелких модулей, где никто не понимает, как всё работает вместе.
Separation of Concerns — это когда каждый класс делает так мало, что вместе они создают слишком много шума.
YAGNI
Этот принцип стал неотъемлемой частью экстремального программирования благодаря его идеологам — Кенту Беку и Рону Джеффрису.
Суть этого принципа в том, что не нужно реализовывать то, что не требуется прямо сейчас. YAGNI — это бунт против «программирования гадалок», когда разработчики добавляют функции «на будущее», оправдывая это гипотетическими сценариями. Например, писать универсальный парсер для всех возможных форматов данных, когда текущий проект работает только с JSON. Такой подход не только тратит время, но и усложняет код, превращая его в музей ненужных абстракций. YAGNI учит дисциплине: сначала сделайте минимально рабочую версию, а расширяйте её только тогда, когда потребности станут явными. Ведь, как показывает практика, 80% «потенциально полезного» кода так и остаётся невостребованным, но успешно замедляет разработку.
Лучший код для будущего — тот, которого нет.
KISS
Этот принцип родился в 1960-х годах в аэрокосмической индустрии благодаря инженеру Кларенсу Джонсону, создателю самолётов Lockheed. Его идея была проста: «Система должна быть настолько простой, насколько это возможно — но не проще».
Суть этого принципа в том, что код должен решать задачу без избыточных усложнений. Например, вместо многослойных абстракций для обработки данных можно использовать прямолинейный алгоритм, если он делает код понятным и поддерживаемым. Суть не в примитивизме, а в устранении «архитектурного нарциссизма»: когда разработчик добавляет паттерны, фреймворки или «умные» решения только чтобы блеснуть навыками. KISS напоминает, что элегантность — это не количество строк, а ясность замысла.
Сложность — это мусор, который программисты производят, пытаясь сделать код идеальным.
DRY
Этот принцип был впервые описан в книге «Программист-прагматик» Энди Ханта и Дэйва Томаса.
Суть этого принципа в том, что «Каждое знание в системе должно иметь единственное, однозначное представление». DRY борется с дублированием кода, данных и даже документации: если один и тот же алгоритм встречается в двух местах, его стоит вынести в отдельную функцию или модуль. Например, вместо копирования проверки адреса электронной почты в трёх местах лучше создать метод validateEmail().
Однако, слепое следование DRY опасно: попытка объединить логически разную функциональность ради «сухости» кода рождает монстров-абстракций. Как говорят авторы: «Дублирование — это не когда код похож, а когда он меняется по одним и тем же причинам». DRY — это не про лень, а про предсказуемость: если какой-то кусок кода изменится, его не придётся искать по всему проекту.
GRASP
Это общие шаблоны распределения ответственностей (General Responsibility Assignment Software Patterns), которые появились в конце 1990-х годов благодаря книге Крэга Лармана «Применение UML и шаблонов проектирования».
Эти шаблоны — это не строгие правила, а рекомендации по распределению ответственностей между классами, которые помогают избежать хаоса в архитектуре.
Ключевые принципы GRASP:
- Информационный эксперт (Information Expert): Отдай задачу тому классу, который владеет данными для её решения.
- Создатель (Creator): Поручай создание объекта тому, кто его использует.
- Контроллер (Controller): Выдели посредника для обработки действий.
- Слабое зацепление (Low Coupling): Минимизируй зависимости между классами.
- Сильная связность (High Cohesion): Собери в классе то, что работает на одну цель.
- Полиморфизм (Polymorphism): Позволь объектам выполнять одно действие по-разному.
- Чистая выдумка (Pure Fabrication): Создавай классы для удобства, даже если их нет в реальности.
- Перенаправление (Indirection): Добавь прослойку, чтобы классы не общались напрямую.
- Устойчивость к изменениям (Protected Variations): Спрячь нестабильные части за стабильным интерфейсом.
Каждый принцип GRASP — это подсказка, как распределить ответственности в коде, чтобы он оставался гибким и понятным.
Однако, слепое следование GRASP может привести к избыточным абстракциям, поэтому важно применять принципы там, где они действительно снижают сложность, а не увеличивают её.
GRASP — это когда ты создаёшь 10 классов там, где хватило бы одного, но зато потом не страшно их менять.
SOLID
Термин SOLID ввёл Роберт Мартин в начале 2000-х годов, объединив пять принципов, которые превращают код из хрупкой конструкции в адаптируемую систему.
Каждая буква аббревиатуры — это шаг от хаоса к порядку:
- Принцип единственной ответственности (Single Responsibility): Каждый класс должен решать только одну задачу.
- Принцип открытости/закрытости (Open/Closed): Код должен быть открыт для расширения, но закрыт для модификации.
- Принцип подстановки Барбары Лисков (Liskov Substitution): Наследники должны полностью заменять родительские классы без нарушения работы программы.
- Принцип разделения интерфейсов (Interface Segregation): Интерфейсы должны быть узкоспециализированными.
- Принцип инверсии зависимостей (Dependency Inversion): Зависимости должны строиться на абстракциях, а не на конкретных реализациях.
Принципы учат дробить ответственность, как стеклянную вазу: осколки сами по себе бесполезны, но вместе создают форму, которую можно менять без трещин.
Однако, слепое следование SOLID порождает десять классов вместо одного, поэтому автор напоминает: «Принципы существуют, чтобы их нарушать — но только если вы точно знаете, зачем». Ведь цель SOLID — не идеал, а контроль над сложностью.
SOLID — это когда ты делаешь код настолько гибким, что он гнётся под любым углом, кроме нужного.
Итог
Заповеди — это компас, а не железная дорога.
Слепое следование заповедям — это тупик.
Однако, новичкам будет полезно строго следовать всем заповедям, чтобы не наступать на грабли, но со временем важно научиться чувствовать, когда можно отступить от них ради скорости или простоты.
Правила созданы, чтобы их нарушать — но только если вы понимаете, что делаете.
P.S. Как вы решаете, когда стоит отступить от заповедей ради упрощения кода или ускорения разработки? — Напишите об этом в комментариях.