В этой статье проводится фундаментальный анализ жизненного цикла разработки приложений, выявляются основные противоречия разработки программного обеспечения и предлагается решение.
Я начал изучать программирование еще в 2006 году, когда у моего старшего брата были занятия по информатике. В те времена в среднестатистической украинской школе учили программировать на Паскале, запуская «IDE» в режиме DOS на Windows XP. Я быстро понял синтаксис и начал решать простые задачи, такие как нахождение квадрата прямоугольников или длины гипотенузы. Шаблон был довольно прост: вы запрашиваете входные данные, делаете расчет и печатаете результат.
Это было первое ощущение силы, ты мог делать все, что хочешь, по крайней мере, я так думал в тот момент.
Позже, когда я перешел на PHP для создания простых веб-сайтов, я начал изучать новые концепции: циклы, функции, массивы и, наконец, классы. Это было примерно в то время, когда был выпущен PHP5, и он принес новую убойную функцию — поддержку ООП.
Для каждой концепции я помню путаницу вроде «зачем это кому-то нужно?», «зачем это нужно?». Зачем нам массивы, если мы можем определить несколько переменных? Зачем нам нужны функции, если мы можем писать встроенные?
Действительно, если решение вашей задачи умещается на экране, зачем вам разбивать его на несколько функций? Вы начинаете разбивать файлы и классы только в том случае, если решение превышает десяток функций. Только столкнувшись с более серьезной проблемой, я понял полезность этих концепций.
Вы можете увидеть это Motion of Complexity. Вы начинаете с экрана, затем переходите к функции, затем разбиваете функции на классы, классы на файлы и так далее. Сложность всегда увеличивается, но человеческий мозг имеет ограниченную способность одновременно удерживать контекст. Это постоянная борьба между написанием большего количества кода и поддержанием управляемости контекста.
Но если индустрия довольно четко определяет абстракции вплоть до классов, становится неясно, что будет дальше. Как правило, отрасль уже некоторое время предлагает MVC, надеясь, что этого будет достаточно. Но вы легко можете получить сотню плоских файлов в папкеapp/models. Другое популярное решение — использование микросервисов. Но для совместной работы 20 изолированных независимых сервисов требуются огромные накладные расходы.

Сегодня я разработал свой подход, чтобы справиться со сложностью и сохранить управляемость контекста. Это не что-то уникальное, а скорее объединение нескольких известных методов, таких как TDD, DDD и SOLID, в единую структуру. Этот набор статей — попытка систематизировать мой опыт и сделать его полезным для вас, читатель.
Мы можем начать с определения пары инвариантов, чтобы сделать процесс более формальным:
- сложность постоянно растет с размером кодовой базы
- контекст человеческого внимания постоянен и ограничен 5–7 объектами одновременно
Можно сделать вывод, что единственным решением в этой ситуации является композиция поверх абстракций. Весь процесс разработки можно проиллюстрировать на примере старой доброй игры «2048».

Сколько программных проектов умерло из-за растущей сложности? Он начинается медленно, увеличивая время, необходимое для реализации функции, количество неожиданных ошибок и 500 ошибок в производстве. И это всегда заканчивается одним и тем же: фича, на которую обычно уходит несколько дней, не может быть реализована за месяц; когда исправление вводит три новых ошибки; когда все боятся структурных изменений, потому что никто не понимает, как работает система. Это гибельная спираль, когда усилия по продолжению разработки только ухудшают ситуацию.
Подход, о котором я говорю, включает рекурсивную модульную структуру, в которой у вас есть ограниченный набор сущностей на каждом уровне. Это также предполагает постоянный рефакторинг. Все как в игре «2048». Когда мы начинаем с малого, у нас есть один модуль с дюжиной классов или функций. По мере роста мы разбиваем большие модули на подмодули и так далее.
Каждый модуль должен иметь свой логический интерфейс, входы и выходы, чтобы вы могли работать с ним независимо от остального приложения. Это как набор микросервисов, но без микросервисов, под единым колпаком, живущих под одной крышей.
Думайте о приложении как о машине. ChatGPT сказал мне, что современный автомобиль состоит примерно из 30 000 деталей. Можете ли вы думать о них всех сразу? Определенно нет. Но основные компоненты вы, наверное, могли бы назвать: двигатель, трансмиссия, рулевое управление, экстерьер, салон и подвеска. Они имеют четкий четко определенный интерфейс между собой. Все взаимодействия между двигателем и экстерьером - это всего лишь три опоры двигателя, которые у них есть между ними.

Вы определенно можете легко сказать, проблема ли это в двигателе или в треснутом бампере. Если вы работаете с двигателем, вам не нужно думать о рулевом колесе или о задней двери. Но в то же время вы расширяете контекст двигателя — теперь вам нужно думать о впуске, выпуске, впрыске топлива, охлаждении и электронике. Это пример отличного интерфейса, созданного для известной вещи за последние сотни лет. Но как разработчики мы работаем с более конкретными и уникальными продуктами в молодой отрасли. Именно мы должны определять модули и интерфейсы между ними, даже если завтра GPT возьмет на себя нашу работу по кодированию.
В следующих статьях я расскажу более конкретно о модулях на практике и покажу, как методы DDD, TDD и SOLID могут помочь вам управлять архитектурой.
Надеюсь, вам понравилось. Спасибо за ваше чтение.
P.S. Присоединяйтесь к моему каналу CuckooCode в Telegram, где я регулярно публикую свои идеи из повседневной работы.