Оптимизация агрегатов без ленивой загрузки
Мы привыкли к тому, что содержимое коллекций вложенных объектов одинаково в БД и памяти PHP процесса. Например:
Имея такой код, мы ожидаем, что после загрузки юзера из базы, память PHP процесса содержит все объекты
Максимальное отклонение от этой ситуации, которое мы обычно готовы принять, это ленивая загрузка. При ленивой загрузке поле
Но ленивая загрузка не ломает того факта, что состав объектов
💡 Однако, при проектировании доменных агрегатов бывает полезным перешагнуть через этот шаблон.
Давайте посмотрим пару примеров.
Согласно DDD, чендж лог карты является неотъемлемой ее частью. Код написан так, чтобы было невозможно совершить действия с картой, минуя запись в историю.
Аналогично, при загрузке
❓ Но вот вопрос: зачем?
Если на чендж лог карты не опирается никакая логика, то зачем нам таскать в памяти всю коллекцию
🍀 Загруженная из БД карта всегда будет с пустым логом, но по бизнес-логике нам это неважно.
🍀 Все новые события, произошедшие в рамках PHP процесса, сохраняются в базу вместе с картой.
🍀 Лог в базе растет инкрементно.
🍀 Лог карты в памяти хранит только последние события и только с целью сохранения их в БД.
🍀 Это не мешает, например, выводить историю карты в интерфейсе с навигацией и поиском.
🧠 Другой пример.
Когда на бонусный счет начисляется какая-то сумма, эта сумма представлена объектом
Так вот: необязательно держать в памяти и базе одинаковый набор грантов для счета. Можно загружать из базы только гранты с положительным балансом, а сохранять в базу все гранты счета (которые есть в памяти).
🍀 БД будет содержать всю историю грантов с первоначальными суммами и датами сгорания.
🍀 По полному списку грантов можно строить историю счета: есть даты создания, первоначальные суммы и даты сгорания.
🍀 Память содержит только ненулевые гранты, которые можно потратить. Этого достаточно для методов
Такое мышление частично заимствовано из архитектурного подхода Event Sourcing, где каждая сущность представлена не набором полей, а набором событий. Сам Event Sourcing в чистом виде довольно экзотичен. Но иногда экзотичные подходы помогают сломать шаблоны для достижения практических результатов.
Мы привыкли к тому, что содержимое коллекций вложенных объектов одинаково в БД и памяти PHP процесса. Например:
class User
{
/** @var Tag[] */
private array $tags;
}
Имея такой код, мы ожидаем, что после загрузки юзера из базы, память PHP процесса содержит все объекты
Tag, связанные с этим юзером. И наоборот: если в процессе выполнения программы состав тегов поменяется, то изменения будут сохранены в базу.Максимальное отклонение от этой ситуации, которое мы обычно готовы принять, это ленивая загрузка. При ленивой загрузке поле
$tags держит специальный объект коллекции, который в памяти PHP хранит только id вложенных объектов. Все поля конкретных объектов Tag загружаются из базы по потребности.Но ленивая загрузка не ломает того факта, что состав объектов
Tag, связанных с User, одинаков как в памяти PHP, так и в БД.Давайте посмотрим пару примеров.
class LoyaltyCard
{
private ChangeLog $changeLog;
public function accrue(int $amount): void
{
//...
$this->changeLog->accrue();
}
}
class ChangeLog
{
/** @var Entry[] */
private array $entries = [];
public function accrue(): void
{
$this->entries[] = new Entry();
}
}
Согласно DDD, чендж лог карты является неотъемлемой ее частью. Код написан так, чтобы было невозможно совершить действия с картой, минуя запись в историю.
LoyaltyCard является доменным агрегатом, а значит при сохранении его в базу, сохраняются и все вложенные в него объекты Entry.Аналогично, при загрузке
LoyaltyCard из базы мы, по идее, должны выгрузить в память все связанные Entry.Если на чендж лог карты не опирается никакая логика, то зачем нам таскать в памяти всю коллекцию
Entry? В данном случае можно вообще не загружать Entry из базы.🧠 Другой пример.
class BonusAccount
{
/** @var BonusGrant[] */
private array $grants = [];
public function accrue(int $amount, ?\DateInterval $burnPeriod): void
{
$this->grants[] = new BonusGrant($amount, $burnPeriod);
}
public function writeOff(int $amount): void
{
//Идем циклом по грантам и списываем с них, пока не наберется $amount
}
}
Когда на бонусный счет начисляется какая-то сумма, эта сумма представлена объектом
Grant с индивидуальной датой сгорания. То есть, каждый начисленный бонус сгорит в свой черед. При этом тратить бонусы можно как единый бонусный счет. Это значит, что при списании бонусов какие-то гранты обнулятся полностью, а у одного из них уменьшится сумма (но останется прежняя дата сгорания).Так вот: необязательно держать в памяти и базе одинаковый набор грантов для счета. Можно загружать из базы только гранты с положительным балансом, а сохранять в базу все гранты счета (которые есть в памяти).
getBalance() и writeOff($amount).Такое мышление частично заимствовано из архитектурного подхода Event Sourcing, где каждая сущность представлена не набором полей, а набором событий. Сам Event Sourcing в чистом виде довольно экзотичен. Но иногда экзотичные подходы помогают сломать шаблоны для достижения практических результатов.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2😴2🗿1
Отключили с утра интернет. Смотрю свои задачи в работе по найму: вся бизнес-логика описана, на ближайшие дни одни Application команды да контроллеры предстоит штамповать. Думаю, не буду это без AI вручную делать: долго и нудно. Неэффективно.
Открываю пет-проект: на этой неделе запуск, идут последние причесывания по фронту. Фронтом я вообще не занимаюсь и даже не ревьюю тот код, который мне агент генерирует.
Решил, что нет смысла без доступа к AI заниматься разработкой и ушел писать статью. Мир изменился.
Открываю пет-проект: на этой неделе запуск, идут последние причесывания по фронту. Фронтом я вообще не занимаюсь и даже не ревьюю тот код, который мне агент генерирует.
Решил, что нет смысла без доступа к AI заниматься разработкой и ушел писать статью. Мир изменился.
💯4🔥3😢2🤔1🙉1
Архитектурный подход - это как думать, а не как делать
На днях у меня состоялась переписка с одним из студентов моего курса по Symfony. Она показалась мне интересной, и я решил опубликовать ее фрагмент.
Вам не нужен аналог этой библиотеки в домене. Тут важно понимать различия между валидацией пользовательского ввода и проверкой бизнес-правил. Пытаясь описывать бизнес-правила через набор готовых ассертов вы неизбежно ставите себя в неудобные рамки. Отказываясь от этого, вы даете себе свободу писать бизнес-логику как угодно. Это становится легко и удобно как на старте проекта, так и на всем сроке его жизни.
А ассерты вы используете для валидации пользовательского ввода.
Посмотрите на вот эти два правила:
1️⃣ Номер дома в адресе не может быть отрицательным.
2️⃣ Пользователь не может иметь больше одного адреса проживания.
Задайте себе такие вопросы:
1️⃣ Нарушение какого из этих двух правил может приводить к непредсказуемому поведению приложения?
2️⃣ Какое из этих двух правил будет удобно описать через ассерты, а какое - нет.
Поразмышляв над этим, вы поймете, что отрицательный номер дома не может сломать бизнес-логику, поскольку почти невозможно представить себе бизнес-правило, опирающееся на номер дома. А вот если вы допустите два адреса, то у вас возникнет проблема, на какой адрес опираться при реализации бизнес-логики.
Это значит, что отрицательный номер дома - ошибка ввода, не мешающая бизнес-логике. А двойной адрес - недопустимое состояние доменного объекта.
Первое вы решаете в инфраструктурном слое, второе - в доменном и при желании дублируете в инфраструктурном, хотя чаще всего, вам это просто не потребуется.
Первое вы легко опишите ассертом
Вот так вы используете определенный набор оптики для взгляда на свои объекты и отношения между ними для ответов на вопрос, где чья ответственность и куда положить код.
Это дает вам структуру, порядок и понятный путь. Это НЕ дает вам истины в последней инстанции или единственно верного решения.
Можно еще так на это посмотреть.
✅ Правильно: "Я использую стейт-машину на проекте X способом Y для решения задачи Z. Это решит мне проблемы 1, 2 и 3, но породит проблемы 4, 5 и 6. Я проанализировал предполагаемый вектор развития проекта, поговорив с бизнес-заказчиком и счел такое решение оправданным, потому что А и Б".
❌ Неправильно: "Я супер-буквально понимаю принцип DRY и моей основной целью является недопущение появления двух внешне похожих кусков кода на проекте. Для этого я стремлюсь использовать вендорный код как можно чаще и больше".
Ну а вывод из этой переписки следует такой: любая связка архитектура + методология - это не более, чем набор оптики, через которую вы смотрите на свои объекты, их свойства и отношения между ними. Нет единственно верного взгляда. Есть накопленный опыт, который позволяет нам осознанно принимать решения, понимая цену и выгоду.
На днях у меня состоялась переписка с одним из студентов моего курса по Symfony. Она показалась мне интересной, и я решил опубликовать ее фрагмент.
Есть библиотека webmozart/assert. Видел примеры ее использования для валидации Value Object (например) при их создании (чтобы объект мог находиться только в валидном состоянии). Эту библиотеку допустимо использовать на слое домена или нужно писать свой аналог? В случае толстой модели объект должен сам обеспечивать свое валидное состояние. это предполагает наличие каких-то типовых проверок. Мы их пишем сами или используем готовые библиотеки. Разве нельзя использовать такие проверки внутри объекта доменной области?
Вам не нужен аналог этой библиотеки в домене. Тут важно понимать различия между валидацией пользовательского ввода и проверкой бизнес-правил. Пытаясь описывать бизнес-правила через набор готовых ассертов вы неизбежно ставите себя в неудобные рамки. Отказываясь от этого, вы даете себе свободу писать бизнес-логику как угодно. Это становится легко и удобно как на старте проекта, так и на всем сроке его жизни.
А ассерты вы используете для валидации пользовательского ввода.
Посмотрите на вот эти два правила:
Задайте себе такие вопросы:
Поразмышляв над этим, вы поймете, что отрицательный номер дома не может сломать бизнес-логику, поскольку почти невозможно представить себе бизнес-правило, опирающееся на номер дома. А вот если вы допустите два адреса, то у вас возникнет проблема, на какой адрес опираться при реализации бизнес-логики.
Это значит, что отрицательный номер дома - ошибка ввода, не мешающая бизнес-логике. А двойной адрес - недопустимое состояние доменного объекта.
Первое вы решаете в инфраструктурном слое, второе - в доменном и при желании дублируете в инфраструктурном, хотя чаще всего, вам это просто не потребуется.
Первое вы легко опишите ассертом
Positive, под второе вам пришлось бы писать кастомный валидатор (а зачем?)Вот так вы используете определенный набор оптики для взгляда на свои объекты и отношения между ними для ответов на вопрос, где чья ответственность и куда положить код.
Это дает вам структуру, порядок и понятный путь. Это НЕ дает вам истины в последней инстанции или единственно верного решения.
Можно еще так на это посмотреть.
Ну а вывод из этой переписки следует такой: любая связка архитектура + методология - это не более, чем набор оптики, через которую вы смотрите на свои объекты, их свойства и отношения между ними. Нет единственно верного взгляда. Есть накопленный опыт, который позволяет нам осознанно принимать решения, понимая цену и выгоду.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12❤1
Как думать о перформансе на небольших и средних проектах
Большая часть разговоров о перформансе на не-highload проектах базируется на мифах, а не реальных расчетах. Это касается как совсем абсурдных заявлений типа "рефлексия медленная", так и типичной боязни вытащить в память PHP процесса несколько тысяч объектов и что-то с ними поделать.
Давайте разберемся, всегда ли эти страхи оправданы.
🐢 Про медленную рефлексию
Нужно понимать, что рефлексия везде: в любом фреймворке, в Doctrine, в вендорном коде. Вы используете рефлексию гораздо больше, чем вам может показаться, каждый день. И если разработчик не хочет применить в своем коде рефлексию однократно, потому что "она медленная", то ему стоит в первую очередь отказаться от фреймворков.
Я много раз слышал про медленную рефлексию, но ни разу о том - кто и как это замерил.
🤪 Про избыточную оптимизацию
Иногда разработчики пытаются усложнить свое решение, объясняя это "перформансом". Часто речь идет об отказе использовать перебор тысяч объектов в цикле и намерении сгородить вместо этого 8-этажный SQL с получением сырых данных в 8-этажный же массив и последующие пляски с этим по всей кодовой базе.
Иногда это действительно бывает оправданно. Но на небольших и даже средних проектах нередко проще купить гигабайт оперативки, чем оплачивать дополнительное время разработки. И об этом нужно помнить, а не идти в "перформанс" по дефолту.
❓ А как же с реальными замедлениями на небольших и средних проектах?
Да, на не-highload проектах тоже случаются слишком медленные HTTP запросы или очень долгие cron-задачи. Это не удивительно. Удивительно то, что, сталкиваясь с подобным, разработчики почти всегда начинают рассуждать о логе медленных запросов SQL сервера или, еще круче, "переходе на golang".🌈
На самом деле, частая причина таких затыков - что-нибудь вроде
❓ Так нужно ли заморачиваться перформансом на не-highload проектах?
По дефолту - никогда. По факту возникновения проблемы - сначала смотрим код профайлером xdebug, а потом умничаем про golang.
😱 "А вдруг у нас будет highload?"
Такое тоже можно нередко услышать как аргумент. Чтобы понять, "будет ли хайлоад" как правило, достаточно довольно простой логики. Я приведу пример такой логики для проекта по предоставлению микрозаймов, на котором мне как-то случилось поработать.
Гуглим "сколько людей берут микрозаймы" и видим грубо 15 млн. чел. Предположим, что наш проект "захватит мир" и каждый второй в России начнет брать займ именно у нас. Получаем 7-8 миллионов юзеров. Отсюда можно увидеть максимальные объемы таблиц в БД: пользователи, займы, транзакции. И понять, что никаких больших объемов и больших нагрузок на проекте не будет никогда: в нашем проекте юзер ходит на сайт нечасто и не генерит много запросов.
А значит, все разговоры о нагрузках для этого проекта лишены оснований. Если в процессе разработки возникнет какой-то медленный кусок, с ним нужно будет разбираться отдельно.
Конечно, не нужно впадать и в обратную крайность и быть расточительным без причины. Если вам нужно обработать два поля для десятков тысяч записей, и написание нативного SQL вам ничего не стоит, то напишите его и работайте с массивом. Здесь, как и везде: требуется адекватный взгляд на задачу и понимание дальнейшего развития проекта, а не догматизм.
Большая часть разговоров о перформансе на не-highload проектах базируется на мифах, а не реальных расчетах. Это касается как совсем абсурдных заявлений типа "рефлексия медленная", так и типичной боязни вытащить в память PHP процесса несколько тысяч объектов и что-то с ними поделать.
Давайте разберемся, всегда ли эти страхи оправданы.
🐢 Про медленную рефлексию
Нужно понимать, что рефлексия везде: в любом фреймворке, в Doctrine, в вендорном коде. Вы используете рефлексию гораздо больше, чем вам может показаться, каждый день. И если разработчик не хочет применить в своем коде рефлексию однократно, потому что "она медленная", то ему стоит в первую очередь отказаться от фреймворков.
Я много раз слышал про медленную рефлексию, но ни разу о том - кто и как это замерил.
Иногда разработчики пытаются усложнить свое решение, объясняя это "перформансом". Часто речь идет об отказе использовать перебор тысяч объектов в цикле и намерении сгородить вместо этого 8-этажный SQL с получением сырых данных в 8-этажный же массив и последующие пляски с этим по всей кодовой базе.
Иногда это действительно бывает оправданно. Но на небольших и даже средних проектах нередко проще купить гигабайт оперативки, чем оплачивать дополнительное время разработки. И об этом нужно помнить, а не идти в "перформанс" по дефолту.
Да, на не-highload проектах тоже случаются слишком медленные HTTP запросы или очень долгие cron-задачи. Это не удивительно. Удивительно то, что, сталкиваясь с подобным, разработчики почти всегда начинают рассуждать о логе медленных запросов SQL сервера или, еще круче, "переходе на golang".
На самом деле, частая причина таких затыков - что-нибудь вроде
ActiveRecord::findOne, выполняющегося десятки тысяч раз внутри цикла. И, как правило, в подавляющем большинстве случаев достаточно найти медленный код профайлером xdebug и убрать цикл, заменив дополнительно при необходимости тяжелые ActiveRecord объекты простыми массивами.По дефолту - никогда. По факту возникновения проблемы - сначала смотрим код профайлером xdebug, а потом умничаем про golang.
😱 "А вдруг у нас будет highload?"
Такое тоже можно нередко услышать как аргумент. Чтобы понять, "будет ли хайлоад" как правило, достаточно довольно простой логики. Я приведу пример такой логики для проекта по предоставлению микрозаймов, на котором мне как-то случилось поработать.
Гуглим "сколько людей берут микрозаймы" и видим грубо 15 млн. чел. Предположим, что наш проект "захватит мир" и каждый второй в России начнет брать займ именно у нас. Получаем 7-8 миллионов юзеров. Отсюда можно увидеть максимальные объемы таблиц в БД: пользователи, займы, транзакции. И понять, что никаких больших объемов и больших нагрузок на проекте не будет никогда: в нашем проекте юзер ходит на сайт нечасто и не генерит много запросов.
А значит, все разговоры о нагрузках для этого проекта лишены оснований. Если в процессе разработки возникнет какой-то медленный кусок, с ним нужно будет разбираться отдельно.
Конечно, не нужно впадать и в обратную крайность и быть расточительным без причины. Если вам нужно обработать два поля для десятков тысяч записей, и написание нативного SQL вам ничего не стоит, то напишите его и работайте с массивом. Здесь, как и везде: требуется адекватный взгляд на задачу и понимание дальнейшего развития проекта, а не догматизм.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10💯4🔥3
Лог, метрика или профайлер?
Почти любую проблему в проде пытаются решать одним из трёх инструментов:
⚙️ логирование,
⚙️ профилирование,
⚙️ сбор метрик.
Тема огромная и интересная, в данном посте я дам очень краткий сравнительный обзор всех трех инструментов и приложу к посту табличку, которую я делал для внутреннего митапа пару лет назад.
📝 Логирование
☘️ Как правило, собирает информацию о событиях.
☘️ Логи хранятся в файлах, их агрегация ресурсоемка (как, впрочем и результатов профилирования).
☘️ Хорошо подходит для ретроспективного расследования инцидентов.
☘️ Изначально не предназначены для агрегации таких метрик, как время выполнения запроса или подсчет количества запросов.
Любое приложение генерирует огромное количество логов. Другое дело, насколько они полезны? Самое частое их использование - это выяснение причин ошибки на проде, ведь дебажить прод дебаггером не получится. В остальном, в большом количестве проектов основная масса логов никогда не анализируется.
🔬 Профилирование
☘️ Позволяет построить дерево вызовов с указанием ресурсоемкости каждого элемента дерева.
☘️ Возможна агрегация узлов дерева и показателей затрат ресурсов.
☘️ Некоторые профайлеры позволяют агрегировать статистику.
☘️ Позволяет исследовать только отдельные ветки выполнения программы.
☘️ Замедляет работу приложения, что затрудняет применение на проде.
☘️ Профайлер пишет в файл или в реляционную БД, объем сохраняемых данных может быть огромным.
Основная задача, которая решается профайлером - это поиск медленных или жрущих память участков кода (кроме времени выполнения и памяти профайлер больше ничего и не меряет).
Вторая очень полезная функция профайлера - это отслеживание пути выполнения программы при обработке конкретного запроса. Построив графическое дерево вызовов по результату профилирования можно узнать много интересного.
Если логи используются повсеместно, то профилирование, к моему огромному сожалению, не используется почти никем. А зря. Регулярное использование профайлера для наблюдения за тем, как строится дерево вызовов в вашем приложении, меняет ваше мышление, как разработчика.
📊 Сбор метрик
☘️ Метрики приложения – это измерители, представляющие уже сжатую информацию.
☘️ Метрики не дают возможности «провалиться в детали».
☘️ Хорошо подходят для обнаружения изменений и сдвигов в работе приложения.
☘️ Подходят для замера бизнесовых показателей (количество заказов, средний чек, конверсия).
☘️ Хорошо сочетаются с алертами.
☘️ Занимают мало места в хранилище приложения.
Основное отличие метрик состоит в том, что они сохраняются в базе данных временных рядов. Тот, кто работал на мало-мальски крупных проектах, наверняка сталкивался с Prometheus. Для тех, кто не сталкивался, опишу очень коротко и упрощенно, что такое метрика в базе данных временных рядов:
⚙️ Ваше приложение держит счетчик. Он может быть в памяти, key-value хранилище или в РБД.
⚙️ При наступлении определенного события (обращение к роуту, определенный вид эксцепшена, создание заказа клиентом) счетчик увеличивается.
⚙️ Раз в период, называемый scrape interval, Prometheus обращается к вашему приложению, чтобы получить значение счетчика.
⚙️ Значение сохраняется в базе данных временных рядов с текущей меткой времени.
В итоге накапливаются данные о том, как меняется с течением времени значение метрики. При длинных временных рядах это позволяет, например, наблюдать за тем, с какой скоростью меняется метрика и сигнализировать об отклонениях (ошибок стало больше, чем обычно, упала посещаемость такой-то страницы и т. д.)
К слову, табличку, которую я прикладываю к посту, я в свое время приготовил из-за попытки CTO с помощью логов и метрик решить задачу, которая решается профайлером (поиск медленных роутов и причин замедления). Это я к тому, что ни один из этих трех инструментов не заменяет другой. Грубо говоря:
☘️ логи - для расследования ("что произошло?");
☘️ метрики - для наблюдения ("насколько массово?", "где отклонения?");
☘️ профайлер - для поиска узких мест ("почему это медленно?") и трассировки.
Почти любую проблему в проде пытаются решать одним из трёх инструментов:
⚙️ логирование,
⚙️ профилирование,
⚙️ сбор метрик.
Тема огромная и интересная, в данном посте я дам очень краткий сравнительный обзор всех трех инструментов и приложу к посту табличку, которую я делал для внутреннего митапа пару лет назад.
📝 Логирование
☘️ Как правило, собирает информацию о событиях.
☘️ Логи хранятся в файлах, их агрегация ресурсоемка (как, впрочем и результатов профилирования).
☘️ Хорошо подходит для ретроспективного расследования инцидентов.
☘️ Изначально не предназначены для агрегации таких метрик, как время выполнения запроса или подсчет количества запросов.
Любое приложение генерирует огромное количество логов. Другое дело, насколько они полезны? Самое частое их использование - это выяснение причин ошибки на проде, ведь дебажить прод дебаггером не получится. В остальном, в большом количестве проектов основная масса логов никогда не анализируется.
🔬 Профилирование
☘️ Позволяет построить дерево вызовов с указанием ресурсоемкости каждого элемента дерева.
☘️ Возможна агрегация узлов дерева и показателей затрат ресурсов.
☘️ Некоторые профайлеры позволяют агрегировать статистику.
☘️ Позволяет исследовать только отдельные ветки выполнения программы.
☘️ Замедляет работу приложения, что затрудняет применение на проде.
☘️ Профайлер пишет в файл или в реляционную БД, объем сохраняемых данных может быть огромным.
Основная задача, которая решается профайлером - это поиск медленных или жрущих память участков кода (кроме времени выполнения и памяти профайлер больше ничего и не меряет).
Вторая очень полезная функция профайлера - это отслеживание пути выполнения программы при обработке конкретного запроса. Построив графическое дерево вызовов по результату профилирования можно узнать много интересного.
Если логи используются повсеместно, то профилирование, к моему огромному сожалению, не используется почти никем. А зря. Регулярное использование профайлера для наблюдения за тем, как строится дерево вызовов в вашем приложении, меняет ваше мышление, как разработчика.
📊 Сбор метрик
☘️ Метрики приложения – это измерители, представляющие уже сжатую информацию.
☘️ Метрики не дают возможности «провалиться в детали».
☘️ Хорошо подходят для обнаружения изменений и сдвигов в работе приложения.
☘️ Подходят для замера бизнесовых показателей (количество заказов, средний чек, конверсия).
☘️ Хорошо сочетаются с алертами.
☘️ Занимают мало места в хранилище приложения.
Основное отличие метрик состоит в том, что они сохраняются в базе данных временных рядов. Тот, кто работал на мало-мальски крупных проектах, наверняка сталкивался с Prometheus. Для тех, кто не сталкивался, опишу очень коротко и упрощенно, что такое метрика в базе данных временных рядов:
⚙️ Ваше приложение держит счетчик. Он может быть в памяти, key-value хранилище или в РБД.
⚙️ При наступлении определенного события (обращение к роуту, определенный вид эксцепшена, создание заказа клиентом) счетчик увеличивается.
⚙️ Раз в период, называемый scrape interval, Prometheus обращается к вашему приложению, чтобы получить значение счетчика.
⚙️ Значение сохраняется в базе данных временных рядов с текущей меткой времени.
В итоге накапливаются данные о том, как меняется с течением времени значение метрики. При длинных временных рядах это позволяет, например, наблюдать за тем, с какой скоростью меняется метрика и сигнализировать об отклонениях (ошибок стало больше, чем обычно, упала посещаемость такой-то страницы и т. д.)
К слову, табличку, которую я прикладываю к посту, я в свое время приготовил из-за попытки CTO с помощью логов и метрик решить задачу, которая решается профайлером (поиск медленных роутов и причин замедления). Это я к тому, что ни один из этих трех инструментов не заменяет другой. Грубо говоря:
☘️ логи - для расследования ("что произошло?");
☘️ метрики - для наблюдения ("насколько массово?", "где отклонения?");
☘️ профайлер - для поиска узких мест ("почему это медленно?") и трассировки.
👍9🔥3❤1
Хочу поделиться новостями о жизни и планах канала
1️⃣ На платформе LevelUp Club появился каталог постов этого канала.
Давно хотел систематизировать материалы по темам: архитектура, паттерны, PHP, Фреймворки и т. д. Теперь можно быстро посмотреть список всех публикаций и найти посты по интересующему направлению.
🔗 https://level-up-club.ru/posts
2️⃣ В ближайшее время выйдут новые лонгриды на Хабре.
Планирую серию материалов про:
🍀 Symfony Messenger в слоистой архитектуре и DDD.
🍀 ID и первичные ключи (казалось бы все просто, но нет).
🍀 Работу с таймзонами в коде и БД (источник многих болей).
🍀 О «медленной рефлексии» с реальными замерами (которые по замыслу мы сделаем вместе с участниками LevelUp Club).
3️⃣ Помимо постов и статей постепенно добавляется ещё один формат: видео-разборы с примерами кода реальных проектов и практикой.
Видео-разборы доступны на платформе LevelUp Club.
В целом стараюсь собрать всё в более системную структуру, чтобы материалы не терялись в ленте и не существовали отдельно друг от друга.
Давно хотел систематизировать материалы по темам: архитектура, паттерны, PHP, Фреймворки и т. д. Теперь можно быстро посмотреть список всех публикаций и найти посты по интересующему направлению.
🔗 https://level-up-club.ru/posts
Планирую серию материалов про:
Видео-разборы доступны на платформе LevelUp Club.
В целом стараюсь собрать всё в более системную структуру, чтобы материалы не терялись в ленте и не существовали отдельно друг от друга.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11🔥1
Почём сортировка? Или бинарный поиск против хешмапы.
Я уже как-то сравнивал хеш-таблицы с простыми массивами. Но на днях у меня случился спор, в ходе которого коллеги выдвинули точку зрения, что индексация массива может быть дорогой по памяти, и для больших массивов бинарный поиск может оказаться эффективнее.
Коротко освежу для тех, кто подзабыл тему алгоритмов:
1️⃣ Поиск в хеш-таблице имеет сложность O(1), то есть это самый быстрый вариант. Но чтобы им воспользоваться, массив нужно сначала проиндексировать.
2️⃣ Бинарный поиск имеет сложность O(log n), что медленнее, но тоже хорошо. Но чтобы воспользоваться бинарным поиском, массив нужно сначала отсортировать.
3️⃣ Сортировка массива имеет сложность O(n log n), а построение индекса O(n). То есть, индексация быстрее.
Я решил это проверить. Итак:
⚙️ Массив на 100 тыс. элементов (вложенные ассоциативные массивы).
⚙️ Искать будем 1 тыс. (+-) случайных элементов по ключу id.
⚙️ Проверять будем три варианта, измеряя профайлером xdebug.
Первый вариант:
⚙️ Сортируем массив через
⚙️ Пользуемся бинарным поиском.
Второй вариант:
⚙️ Индексируем массив через
⚙️ Достаем элементы по
🥊 Тут коллеги снова выдвинули гипотезу:
Третий вариант:
⚙️ Индексируем массив через
⚙️ Достаем элементы по
Результаты вы видите в таблице. Ну а выводы такие:
☘️ PHP прекрасно работает с хеш-мапами, и никаких дополнительных манипуляций особо не требуется:
☘️ Бинарный поиск потребовал 41ms времени на тысячу операций поиска в 100-тысячном массиве. Для фоновых операций это ничто, а вот для HTTP запроса уже ощутимо.
☘️ Ну и главное: сортировка оказалась ни то что в разы, а на порядки дороже индексации как по времени, так и по памяти.
Код, замеры которого выполнялись, размещу в комментариях к этому посту.
Ну а напоследок хочется в который раз порекомендовать как можно чаще пользоваться Xdebug: отладчиком ежедневно при каждом прогоне своего кода, а профайлером просто почаще, когда станет любопытно, как распределяется потребление времени и памяти по дереву вызовов.
Такое регулярное использование не даст вам схоластических аргументов в споре типа "Я знаю, как память реаллоцируется в стеке" (с этим ко мне пришли коллеги😄 ), но со временем наделит вас интуитивным ощущением того, где на самом деле могут быть просадки в перформансе, а где это просто непроверенный стереотип. Который, впрочем, всегда можно проверить. 💪
Я уже как-то сравнивал хеш-таблицы с простыми массивами. Но на днях у меня случился спор, в ходе которого коллеги выдвинули точку зрения, что индексация массива может быть дорогой по памяти, и для больших массивов бинарный поиск может оказаться эффективнее.
Коротко освежу для тех, кто подзабыл тему алгоритмов:
Таким образом, была выдвинута идея, что индексирование и хеш-мапа, конечно, быстрее, но сортировка в PHP будет эффективнее индексации по памяти. Причем, заметно эффективнее.
Я решил это проверить. Итак:
⚙️ Массив на 100 тыс. элементов (вложенные ассоциативные массивы).
⚙️ Искать будем 1 тыс. (+-) случайных элементов по ключу id.
⚙️ Проверять будем три варианта, измеряя профайлером xdebug.
Первый вариант:
⚙️ Сортируем массив через
usort().⚙️ Пользуемся бинарным поиском.
Второй вариант:
⚙️ Индексируем массив через
array_combine(array_column()).⚙️ Достаем элементы по
$array[$id].🥊 Тут коллеги снова выдвинули гипотезу:
array_combine() создает копию массива и жрет память, поэтому:Третий вариант:
⚙️ Индексируем массив через
array_pop() (перетаскиваем по 1 элементу).⚙️ Достаем элементы по
$array[$id], как и во 2 варианте.Результаты вы видите в таблице. Ну а выводы такие:
☘️ PHP прекрасно работает с хеш-мапами, и никаких дополнительных манипуляций особо не требуется:
array_pop() не дал выигрыша по времени по сравнению с array_combine() и сэкономил всего 7MB памяти на массив из 100 тыс. элементов, что по сегодняшним временам - мелочь.☘️ Бинарный поиск потребовал 41ms времени на тысячу операций поиска в 100-тысячном массиве. Для фоновых операций это ничто, а вот для HTTP запроса уже ощутимо.
☘️ Ну и главное: сортировка оказалась ни то что в разы, а на порядки дороже индексации как по времени, так и по памяти.
Код, замеры которого выполнялись, размещу в комментариях к этому посту.
Ну а напоследок хочется в который раз порекомендовать как можно чаще пользоваться Xdebug: отладчиком ежедневно при каждом прогоне своего кода, а профайлером просто почаще, когда станет любопытно, как распределяется потребление времени и памяти по дереву вызовов.
Такое регулярное использование не даст вам схоластических аргументов в споре типа "Я знаю, как память реаллоцируется в стеке" (с этим ко мне пришли коллеги
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11👍3
Кодовая база существует для того, чтобы деградировать
Как бы это не было грустно, но у кода нет иной судьбы. Каждая новая фича или доработка практически неизбежно приводит к падению качества кода.
В идеальных условиях снижение качества кодовой базы происходит крайне медленно. Как и накопление техдолга. Но в реальных проектах бизнес требует выкатывать фичи как можно скорее, тестирует продуктовые гипотезы, делает прототипы и MVP. А это все не делает разработку качественней.
Важно понимать, что для абсолютно любой задачи помимо привычных всем сроков и рисков, существует дополнительная, неочевидная метрика цены, о которой редко задумываются.
❗️ Это степень деградации кодовой базы.
Эта метрика определяет стоимость всех будущих изменений, напрямую влияя на скорость развития продукта. В меньшей степени она влияет на риск незаметной поломки уже существующего функционала.
Влияние на эту метрику определяется следующими факторами:
⚙️ Степень архитектурной проработки задачи.
⚙️ Степень архитектурной автономности разработчика.
⚙️ Сложность изменяемого модуля или модулей.
⚙️ Используемость изменяемого модуля.
⚙️ Давление по срокам.
🧩 Степень архитектурной проработки задачи
Означает, есть ли в описании задачи помимо бизнес-требований технические рекомендации, как и - что гораздо важнее - где вносить изменения. Сравните бизнесовое описание:
И архитектурное описание:
Такое описание может быть как совсем коротким, задающим стартовую рамку разработки, так и исчерпывающим, описывающим все нюансы.
🧠 Степень архитектурной автономности разработчика
Способность принимать корректные архитектурные решения без внешнего контроля. Определяется опытом и арсеналом доступных средств программирования и проектирования. Поясню на примерах:
💀 Разработчик, не знающий, какие функции работы с массивами доступны в PHP, будет городить циклы, в том числе трехэтажные, по любому поводу, делая код громоздким и нечитаемым.
💀 Разработчик, не знающий, что такое сериалайзер, будет писать длинные методы
💀 Разработчик, не понимающий композиции и наследования, будет городить кучу связанных классов, или писать все в один гигантский класс.
И так далее. Этот фактор работает в связке со сложностью задачи: чем технически сложнее задача и чем меньше арсенал разработчика, тем более низкого качества код будет получаться.
⚙️ Сложность изменяемого модуля
Тут все просто. Сложность определяется бизнес-логикой (запутанная и объемная или скудная и прямолинейная), качеством кодовой базы модуля (чем хуже качество кода, тем сложнее с ним работать) и архитектурной сложностью: модуль может быть написан чисто и аккуратно, но быть сложно организован, иметь сложные отношения между классами и использовать неочевидные или редко применяемые паттерны.
🔗 Используемость изменяемого модуля
Определяется количеством связей модуля с остальными частями приложения. Например, в любом проекте есть центральные модули (заказ, пользователь и т. п.) и периферийные интеграции с внешними сервисами.
⏳ Давление по срокам
Тут все просто. Даже опытный разработчик, если на него давить по срокам, может начать понижать качество кода и плодить техдолг. Про неопытного и говорить нечего.
Понимание этих факторов позволит управлять деградацией кода более осознанно. В следующем посте расскажу подробнее, как сочетание этих факторов влияет на степень деградации кода и приведу конкретный реальный пример из практики.
Как бы это не было грустно, но у кода нет иной судьбы. Каждая новая фича или доработка практически неизбежно приводит к падению качества кода.
В идеальных условиях снижение качества кодовой базы происходит крайне медленно. Как и накопление техдолга. Но в реальных проектах бизнес требует выкатывать фичи как можно скорее, тестирует продуктовые гипотезы, делает прототипы и MVP. А это все не делает разработку качественней.
Важно понимать, что для абсолютно любой задачи помимо привычных всем сроков и рисков, существует дополнительная, неочевидная метрика цены, о которой редко задумываются.
Эта метрика определяет стоимость всех будущих изменений, напрямую влияя на скорость развития продукта. В меньшей степени она влияет на риск незаметной поломки уже существующего функционала.
Влияние на эту метрику определяется следующими факторами:
⚙️ Степень архитектурной проработки задачи.
⚙️ Степень архитектурной автономности разработчика.
⚙️ Сложность изменяемого модуля или модулей.
⚙️ Используемость изменяемого модуля.
⚙️ Давление по срокам.
🧩 Степень архитектурной проработки задачи
Означает, есть ли в описании задачи помимо бизнес-требований технические рекомендации, как и - что гораздо важнее - где вносить изменения. Сравните бизнесовое описание:
В дополнение к применению скидки на каждый товар реализовать функционал применения скидки на весь заказ.
И архитектурное описание:
Написать для класса Discount новые реализации стратегий DiscountValidity и DiscountApplicability. Применяемость скидки ко всему заказу выразить через булево поле. Необходимость изменения модуля расчета цен, если она возникнет, согласовывать отдельно!
Такое описание может быть как совсем коротким, задающим стартовую рамку разработки, так и исчерпывающим, описывающим все нюансы.
🧠 Степень архитектурной автономности разработчика
Способность принимать корректные архитектурные решения без внешнего контроля. Определяется опытом и арсеналом доступных средств программирования и проектирования. Поясню на примерах:
toArray(), вручную набирая индексы ассоциативных массивов.И так далее. Этот фактор работает в связке со сложностью задачи: чем технически сложнее задача и чем меньше арсенал разработчика, тем более низкого качества код будет получаться.
Тут все просто. Сложность определяется бизнес-логикой (запутанная и объемная или скудная и прямолинейная), качеством кодовой базы модуля (чем хуже качество кода, тем сложнее с ним работать) и архитектурной сложностью: модуль может быть написан чисто и аккуратно, но быть сложно организован, иметь сложные отношения между классами и использовать неочевидные или редко применяемые паттерны.
🔗 Используемость изменяемого модуля
Определяется количеством связей модуля с остальными частями приложения. Например, в любом проекте есть центральные модули (заказ, пользователь и т. п.) и периферийные интеграции с внешними сервисами.
Тут все просто. Даже опытный разработчик, если на него давить по срокам, может начать понижать качество кода и плодить техдолг. Про неопытного и говорить нечего.
Понимание этих факторов позволит управлять деградацией кода более осознанно. В следующем посте расскажу подробнее, как сочетание этих факторов влияет на степень деградации кода и приведу конкретный реальный пример из практики.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10💯2
Как управлять деградацией кода
В предыдущем посте я перечислил факторы, определяющие степень деградации кода - скрытой метрикой цены любой задачи.
Понимание этих факторов можно использовать не только для борьбы с деградацией, но и для осознанного управления ею.
Разберем каждый фактор на реальном примере. Есть задача внести небольшую доработку в сложную логику применения скидок к цене заказа.
⚙️ Сложность изменяемого модуля
Модуль расчета цен состоит из множества компонентов:
⚙️ Подборщик базовых цен
⚙️ Скидки со сложной логикой
⚙️ Подборщик скидок, с правилами сочетаемости скидок
⚙️ Другие компоненты, с логикой изменения цены
⚙️ Калькулятор цен, как точка входа в модуль и пайплайн применения всех его компонентов.
Код модуля чистый и хорошо структурирован. Но бизнес-логика довольно сложна, а сам код активно использует композицию, наследование и паттерны Стратегия, Визитер и Спецификация.
Итого, сложность модуля очень высокая.
🔗 Используемость изменяемого модуля
Калькулятор цен является одним из самых используемых модулей в проекте и ниточки к нему тянутся из десятков мест.
По этим двум факторам уже можно сделать предположение о цене задачи: риск деградации кода крайне высок, а последствия будут аффектить весь проект. Плохо написанный в этом модуле код будет также активно влиять на скорость и качество разработки многих других задач.
Сравните это с ситуацией, когда сложность высокая, но используемость низкая: риск деградации высок, но последствия несущественны: если код работает, то никому нет дела до его качества, т. к. никто не будет его дорабатывать или читать.
Дальше начинается самое интересное. Факторами сложности и используемости изменяемого модуля мы не управляем. Как, в большинстве случаев, и давлением сроков.
Но мы можем управлять двумя оставшимися факторами:
🧠 Степень архитектурной автономности разработчика.
🧩 Степень архитектурной проработки задачи.
Описанную задачу требовалось решить срочно, а все опытные разработчики были заняты. И задачу получил разработчик, чей арсенал ограничивался, по сути, одним только условным оператором.
Для решения задачи в классе скидки были заложены точки расширения: две стратегии, отвечавшие за применимость скидки к виду услуги и дате заказа. Этого было достаточно для описания новой логики. Хватило бы по 15 строк на новые стратегии и еще столько же для правок в калькуляторе цен.
Но разработчик, выполнявший задачу, не увидел этих точек расширения и не понял, как устроен модуль цен. В общей сложности он внес более 800 строк кода в 7 классов, намертво связав все компоненты калькулятора цен.
Все последующие доработки этого модуля были вынуждены опираться на эти 800 строк высокосвязанного кода, что неизбежно понижало их скорость и качество.
История закончилась рефакторингом всего модуля после нескольких жалоб на некорректную работу логики применения скидок и осознания, что код модуля достиг состояния, не позволявшего дебаг и точечные правки.
После рефакторинга код модуля без потерь в бизнес-логике был избавлен от этих 800 строк кода, а описанная выше задача была решена повторно за 40-50 строк.
Мораль этой истории в том, что при осознанном управлении деградацией менеджмент мог бы выбирать не только из вариантов "дать задачу неопытному сейчас" или "поручить опытному попозже". Хотя и такой выбор может быть неочевиден, если неясна сложность и используемость изменяемого модуля.
Но на самом деле, менеджмент мог бы решить, например, отвлечь сеньора на 3 дня, чтобы тот повысил архитектурную проработанность задачи. Это немного увеличило бы срок задачи, с которой отвлекли сеньора, но существенно сократило бы срок задачи по скидке и понизило степень деградации кода одного из центральных модулей. А это, между прочим, большой выигрыш по срокам будущих задач, связанных с этим модулем.
В идеальном мире неопытный разработчик вообще не должен прикасаться к сложным и центральным модулям. Но в реальности менеджмент может решить "допустить говнокод", но зато быстро выкатить фичу. Главный вопрос в том, делается ли это с полным осознанием цены решения и доступных альтернатив.
В предыдущем посте я перечислил факторы, определяющие степень деградации кода - скрытой метрикой цены любой задачи.
Понимание этих факторов можно использовать не только для борьбы с деградацией, но и для осознанного управления ею.
Разберем каждый фактор на реальном примере. Есть задача внести небольшую доработку в сложную логику применения скидок к цене заказа.
Модуль расчета цен состоит из множества компонентов:
⚙️ Подборщик базовых цен
⚙️ Скидки со сложной логикой
⚙️ Подборщик скидок, с правилами сочетаемости скидок
⚙️ Другие компоненты, с логикой изменения цены
⚙️ Калькулятор цен, как точка входа в модуль и пайплайн применения всех его компонентов.
Код модуля чистый и хорошо структурирован. Но бизнес-логика довольно сложна, а сам код активно использует композицию, наследование и паттерны Стратегия, Визитер и Спецификация.
Итого, сложность модуля очень высокая.
🔗 Используемость изменяемого модуля
Калькулятор цен является одним из самых используемых модулей в проекте и ниточки к нему тянутся из десятков мест.
По этим двум факторам уже можно сделать предположение о цене задачи: риск деградации кода крайне высок, а последствия будут аффектить весь проект. Плохо написанный в этом модуле код будет также активно влиять на скорость и качество разработки многих других задач.
Сравните это с ситуацией, когда сложность высокая, но используемость низкая: риск деградации высок, но последствия несущественны: если код работает, то никому нет дела до его качества, т. к. никто не будет его дорабатывать или читать.
Дальше начинается самое интересное. Факторами сложности и используемости изменяемого модуля мы не управляем. Как, в большинстве случаев, и давлением сроков.
Но мы можем управлять двумя оставшимися факторами:
🧠 Степень архитектурной автономности разработчика.
🧩 Степень архитектурной проработки задачи.
Описанную задачу требовалось решить срочно, а все опытные разработчики были заняты. И задачу получил разработчик, чей арсенал ограничивался, по сути, одним только условным оператором.
Для решения задачи в классе скидки были заложены точки расширения: две стратегии, отвечавшие за применимость скидки к виду услуги и дате заказа. Этого было достаточно для описания новой логики. Хватило бы по 15 строк на новые стратегии и еще столько же для правок в калькуляторе цен.
Но разработчик, выполнявший задачу, не увидел этих точек расширения и не понял, как устроен модуль цен. В общей сложности он внес более 800 строк кода в 7 классов, намертво связав все компоненты калькулятора цен.
Все последующие доработки этого модуля были вынуждены опираться на эти 800 строк высокосвязанного кода, что неизбежно понижало их скорость и качество.
История закончилась рефакторингом всего модуля после нескольких жалоб на некорректную работу логики применения скидок и осознания, что код модуля достиг состояния, не позволявшего дебаг и точечные правки.
После рефакторинга код модуля без потерь в бизнес-логике был избавлен от этих 800 строк кода, а описанная выше задача была решена повторно за 40-50 строк.
Мораль этой истории в том, что при осознанном управлении деградацией менеджмент мог бы выбирать не только из вариантов "дать задачу неопытному сейчас" или "поручить опытному попозже". Хотя и такой выбор может быть неочевиден, если неясна сложность и используемость изменяемого модуля.
Но на самом деле, менеджмент мог бы решить, например, отвлечь сеньора на 3 дня, чтобы тот повысил архитектурную проработанность задачи. Это немного увеличило бы срок задачи, с которой отвлекли сеньора, но существенно сократило бы срок задачи по скидке и понизило степень деградации кода одного из центральных модулей. А это, между прочим, большой выигрыш по срокам будущих задач, связанных с этим модулем.
В идеальном мире неопытный разработчик вообще не должен прикасаться к сложным и центральным модулям. Но в реальности менеджмент может решить "допустить говнокод", но зато быстро выкатить фичу. Главный вопрос в том, делается ли это с полным осознанием цены решения и доступных альтернатив.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7
Куда расти из мидла: смотрим через функции, а не названия ролей
Пообщавшись с подписчиками, я с удивлением обнаружил, что возможные траектории профессионального роста для многих не так очевидны. Отсюда и родился этот пост.
Чтобы понять, куда расти, важно сначала разобраться с функциями, которые существуют в командах.
💻 Разработка
⚙️ разработка фичей
⚙️ баги, саппорт
⚙️ разработка эпиков
⚙️ рефакторинг
⚙️ ревью кода
📐 Технический менеджмент
⚙️ архитектурные решения
⚙️ стандарты (кодстайл, практики)
⚙️ управление техдолгом
⚙️ выбор технологий
🔥 Операционка (прод)
⚙️ тушение пожаров
⚙️ наблюдение за продом
⚙️ on-call
⚙️ разбор инцидентов
⚙️ мониторинг и алерты
⚙️ CI/CD
🔄 Процессы
⚙️ управление разработкой (скрам, канбан)
⚙️ планирование
⚙️ приоритизация
⚙️ оценка задач
⚙️ контроль сроков и рисков
⚙️ коммуникация с бизнесом
👥 Люди
⚙️ пипл-менеджмент
⚙️ найм
⚙️ менторство
Список упрощен, но даже по нему видно, что не все сводится к написанию кода и архитектуре. Поэтому, выбирая путь роста, важно ответить себе, что из этого вам интересно, что готовы терпеть, а что категорически не хотите делать. И уже через это смотреть на роли.
👨💻 Senior разработчик
50-100% времени занимается разработкой.
В идеале:
🍀 крупные эпики
🍀 рефакторинг
🍀 сложные задачи
Сеньор сам разбирается в задаче, уточняет требования, принимает решения и не дергает лидов по каждому шагу.
Дополнительно может брать:
⚙️ ревью
⚙️ менторство
⚙️ участие в найме
⚙️ инфраструктурные задачи
Это самая "тихая" роль: максимум разработки, минимум обязательных коммуникаций.
🧠 Техлид
Фокус - технический менеджмент + операционка.
Типичный день:
💀 прод упал - разбираешься
💀 сеньоры приходят утверждать технические решения
💀 мидлы приходят за помощью в генерации технических решений
💀 джуны приходят, потому что джуны 😄
💀 бизнес приходит за оценками и рисками
Плюсом к этому:
⚙️ архитектура
⚙️ стабильность системы
⚙️ часто CI/CD и прод
Это уже не "разработчик+", а технический координатор системы. Много контекста, переключений, ответственности и часто - текучки.
🗣 Тимлид
Самая вариативная роль. Тут есть два крайних полюса:
1️⃣ Маленькая команда (2–3 человека). До 80% разработки + немного процессов и people management.
2️⃣ Большая команда (7+ человек). 0% разработки, максимум процессов, коммуникаций, управления людьми
Между этими полюсами есть большое количество вариаций. Часто бывает гибрид с техлидом.
Так что, рост в эту роль не про хард скиллы, а про работу с людьми и процессами. Из рассматриваемых ролей тимлид предполагает наибольший объем текучки и коммуникаций.
💡 Итого
Важно думать не столько о желаемой роли, сколько о наборе функций, который для одной и той же роли может сильно варьироваться от команды к команде.
И главный вопрос не "хочу ли я быть техлидом", а "хочу ли я жить в этом наборе задач?"
Пообщавшись с подписчиками, я с удивлением обнаружил, что возможные траектории профессионального роста для многих не так очевидны. Отсюда и родился этот пост.
Чтобы понять, куда расти, важно сначала разобраться с функциями, которые существуют в командах.
💻 Разработка
⚙️ разработка фичей
⚙️ баги, саппорт
⚙️ разработка эпиков
⚙️ рефакторинг
⚙️ ревью кода
📐 Технический менеджмент
⚙️ архитектурные решения
⚙️ стандарты (кодстайл, практики)
⚙️ управление техдолгом
⚙️ выбор технологий
⚙️ тушение пожаров
⚙️ наблюдение за продом
⚙️ on-call
⚙️ разбор инцидентов
⚙️ мониторинг и алерты
⚙️ CI/CD
🔄 Процессы
⚙️ управление разработкой (скрам, канбан)
⚙️ планирование
⚙️ приоритизация
⚙️ оценка задач
⚙️ контроль сроков и рисков
⚙️ коммуникация с бизнесом
👥 Люди
⚙️ пипл-менеджмент
⚙️ найм
⚙️ менторство
Список упрощен, но даже по нему видно, что не все сводится к написанию кода и архитектуре. Поэтому, выбирая путь роста, важно ответить себе, что из этого вам интересно, что готовы терпеть, а что категорически не хотите делать. И уже через это смотреть на роли.
50-100% времени занимается разработкой.
В идеале:
Ключевое отличие сеньора от мидла - самостоятельность.
Сеньор сам разбирается в задаче, уточняет требования, принимает решения и не дергает лидов по каждому шагу.
Дополнительно может брать:
⚙️ ревью
⚙️ менторство
⚙️ участие в найме
⚙️ инфраструктурные задачи
Это самая "тихая" роль: максимум разработки, минимум обязательных коммуникаций.
🧠 Техлид
Фокус - технический менеджмент + операционка.
Типичный день:
Плюсом к этому:
⚙️ архитектура
⚙️ стабильность системы
⚙️ часто CI/CD и прод
Это уже не "разработчик+", а технический координатор системы. Много контекста, переключений, ответственности и часто - текучки.
🗣 Тимлид
Самая вариативная роль. Тут есть два крайних полюса:
Между этими полюсами есть большое количество вариаций. Часто бывает гибрид с техлидом.
Большинство тимлидов вырастают прямиком из middle и middle+ разработчиков.
Так что, рост в эту роль не про хард скиллы, а про работу с людьми и процессами. Из рассматриваемых ролей тимлид предполагает наибольший объем текучки и коммуникаций.
Важно думать не столько о желаемой роли, сколько о наборе функций, который для одной и той же роли может сильно варьироваться от команды к команде.
И главный вопрос не "хочу ли я быть техлидом", а "хочу ли я жить в этом наборе задач?"
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8🔥8🤔1
Кривой код начинается с кривой постановки задачи
Прежде чем писать код, нужно спроектировать систему. Но прежде, чем вы начнете проектировать, нужно убедиться, что вы собрали все бизнес-требования, и что они являются исчерпывающими и непротиворечивыми.
Умение работать с бизнес-требованиями – важная составляющая системного мышления для любого разработчика.
Поэтому я попросил свою коллегу Анну Ковалеву – бизнес-аналитика со стажем – провести с вами встречу и поделиться своим опытом.
Что будет на встрече
🍀 Почему модель “бизнес придумал → аналитик написал → разработчик сделал” ломается.
🍀 Как разработчику подключаться к работе с требованиями заранее.
🍀 Какие артефакты порождает работа с бизнес-аналитиком.
🍀 В какие моменты разработчик должен вмешиваться.
🍀 И многое другое.
🍒 В эту субботу, 11 апреля, в 10:00 МСК
🍒 Участие бесплатное
🍒 Формат: живая встреча
🍒 Длительность: ~60 мин
🍒 После доклада сможете задать свои вопросы
📽️ Кто не смог посетить вживую, доступна запись
Прежде чем писать код, нужно спроектировать систему. Но прежде, чем вы начнете проектировать, нужно убедиться, что вы собрали все бизнес-требования, и что они являются исчерпывающими и непротиворечивыми.
Умение работать с бизнес-требованиями – важная составляющая системного мышления для любого разработчика.
Поэтому я попросил свою коллегу Анну Ковалеву – бизнес-аналитика со стажем – провести с вами встречу и поделиться своим опытом.
Что будет на встрече
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥9
Типовые ошибки мышления при проектировании модуля
За последние недели я провел несколько разборов решений с разработчиками. Разбирали задачу на проектирование нотифаера.
Требования к нотифаеру такие:
☘️ Приложение должно отправлять уведомления по разным поводам: изменение статуса заказа, рекламные рассылки и т. д.
☘️ Нужны разные каналы доставки: email, telegram, sms.
☘️ Пользователь в настройках должен выбирать, какие уведомления каким способом он хочет получать, либо отключать уведомления совсем.
В разборах регулярно повторялись одни и те же проблемы:
⚙️ Смешение ответственностей компонентов.
⚙️ Смешение ответственности кода и инфраструктуры.
⚙️ Упускалась из виду необходимость рендеринга сообщений.
⚙️ Игнорировался вопрос, в каком месте должно приниматься решение о выборе транспорта (а это один из ключевых вопросов в этом кейсе).
⚙️ Пропускались точки расширения там, где они реально нужны.
⚙️ И, наоборот, лишние точки расширения создавались там, где без них можно обойтись.
Что важно, источник этих проблем обычно не в том, что человек “не знает паттерны” или “плохо знает ООП”.
Чаще проблема в другом: решение собирается от знакомых инструментов и локальных ходов, а не от целостной модели задачи. В итоге человек пишет не от архитектуры модуля, а от инерции мышления.
Почти всем я давал примерно одну и ту же последовательность шагов:
1️⃣ Выписать из требований все данные, которые данные нужны для работы модуля.
2️⃣ Определить, какие операции будут выполняться в модуле.
3️⃣ На основе требований и предыдущих двух пунктов определить, из каких компонентов состоит модуль.
4️⃣ Определить ответственность каждого компонента.
5️⃣ Определить связи между компонентами, стремясь их минимизировать.
6️⃣ Написать псевдокодом сквозной пример использования модуля.
7️⃣ Определить места принятия всех ключевых решений.
8️⃣ Определить точку входа в модуль.
И вот это уже давало сдвиг.
Когда человек проходил по этим шагам, у него обычно начинала появляться более комплексная картина решения. И вместе с этим становилось понятнее, какие важные моменты он упускал.
Если хотите проверить себя, возьмите любую нетривиальную задачу со своей работы (или кейс с нотифаером из этого поста) и попробуйте пройти по этим шагам до написания кода.
Это помогает превентивно обнаруживать проблемы и генерировать более структурированный код.
За последние недели я провел несколько разборов решений с разработчиками. Разбирали задачу на проектирование нотифаера.
Требования к нотифаеру такие:
☘️ Приложение должно отправлять уведомления по разным поводам: изменение статуса заказа, рекламные рассылки и т. д.
☘️ Нужны разные каналы доставки: email, telegram, sms.
☘️ Пользователь в настройках должен выбирать, какие уведомления каким способом он хочет получать, либо отключать уведомления совсем.
В разборах регулярно повторялись одни и те же проблемы:
⚙️ Смешение ответственностей компонентов.
⚙️ Смешение ответственности кода и инфраструктуры.
⚙️ Упускалась из виду необходимость рендеринга сообщений.
⚙️ Игнорировался вопрос, в каком месте должно приниматься решение о выборе транспорта (а это один из ключевых вопросов в этом кейсе).
⚙️ Пропускались точки расширения там, где они реально нужны.
⚙️ И, наоборот, лишние точки расширения создавались там, где без них можно обойтись.
Что важно, источник этих проблем обычно не в том, что человек “не знает паттерны” или “плохо знает ООП”.
Чаще проблема в другом: решение собирается от знакомых инструментов и локальных ходов, а не от целостной модели задачи. В итоге человек пишет не от архитектуры модуля, а от инерции мышления.
Почти всем я давал примерно одну и ту же последовательность шагов:
И вот это уже давало сдвиг.
Когда человек проходил по этим шагам, у него обычно начинала появляться более комплексная картина решения. И вместе с этим становилось понятнее, какие важные моменты он упускал.
Если хотите проверить себя, возьмите любую нетривиальную задачу со своей работы (или кейс с нотифаером из этого поста) и попробуйте пройти по этим шагам до написания кода.
Это помогает превентивно обнаруживать проблемы и генерировать более структурированный код.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍18❤1