Про что
В основном, в статье будут описаны антипаттерны, применяемые при разработке программных продуктов. Антипаттерны собраны из разных аспектов разработки:
- Управление разработкой
- Архитектура
- Разработка
- Девопс
- Тестирование
- и т.д.
Иногда будут приводиться рекомендации как непопасть под дейсвие того или иного антипаттерна.
И так, поехали:
Применение технологий COTS
COTS - commercial off-the-shelf - готовая к коммерческому использованию “коробочная” технология (инструмент, программный продукт).
Разработчики не являются владельцами таких элементов экосистемы.
В какой-то мере COST-инструменты упрощают разработку.
Но могут создавать сложности с внесением изменений, автоматизацией развёртывания и тестирования.
Велосипедостроение
Есть продукт с открытым исходным кодом. Но мы делаем свою реализацию.
Плюсы:
- контроль над продуктом,
- красивое резюме.
Минусы:
- может уходить много сил на развитие и поддержку велосипеда,
- может блокировать использование инноваций для создания чего-то лучшего.
Неизменяемая инфраструктура
Инфраструктура приложения должна быть одинаковой везде:
- на ПК разработчика
- на всех стендах “промышленной” эксплуатации (dev, demo, stage, smoke, prod)
- в интеграционных тестах
Плюсы:
- везде работает или не работает одинаково
Минусы:
- требует дополнительных усилий
Обратимые изменения
Применяется так называемое синие-зелёное развёртывание (blue/green deployment):
- Развёртываются два одинаковых компонента “синий” и “зелёный”
- Синий – рабочий компонент
- Зелёный – подготовка к следующему релизу
- Когда зелёный будет готов, часть пользователей переключают на него
- Если всё ОК, то зелёный становится рабочим компонентом, а синий – подготовительным к следующему релизу
- Если не ОК, то синий остаётся рабочим
Защитный слой
Компоненты системы обращаются к API какого-то внешнего компонента не напрямую, а через компонента-посредника.
В случае изменения API внешнего компонента, компонент-посредник становится адаптером и все изменения, связанные с адаптацией к новому API, нужно будет сделать только в нём.
Если не делать защитный слой, то изменения нужно будет вносить по всему коду приложения, где использовался доступ к внешнему API.
Практика жертвоприношений
Жертвой называют первый опытный образец (MVP).
Если приложение создаётся впервые, то вопрос о том, будет оно жертвой или нет, не стоит.
Правильный вопрос заключается в том, когда мы будем готовы принести его в жертву – сделать заново, с учётом полученного опыта.
Суть этого подхода заключается в том, что только после создания первой версии приложения становятся известны ранее неизвестные обстоятельства и складывается понимание того, какие решения были приняты правильно, а какие – нет.
При этом наиболее выгодным считается не поддержка и доработка MVP, а создание нового, лучшего приложения.
Проактивное управление зависимостями
Реактивное управление – реагировать на изменения.
Проактивное управление – быть готовым к ним заранее.
Рекомендуется создавать своё хранилище внешних компонентов, от которых зависит система.
В случае изменения таких компонентов во “внешнем мире” – добавлять их в своё хранилище только после проверки.
Более радикальным вариантом является хранение внешних зависимостей в виде репозиториев с исходным кодом, которые имеют свои пайплайны сборки. В этом случае изменения внешних зависимостей оформляются в виде мерж-реквестов из “внешних репозиториев” и принимаются только после проверки кода. Мерж-реквесты с вредными изменениями отклоняются.
Обновление библиотек и фреймворков
Библиотеки заменить легче, фреймворки – сложнее.
Подходы к обновлению:
- Пассивный – обновлять, когда появляется нужный функционал или исправление ошибки. Рекомендуется для библиотек.
- Агрессивный – обновлять как только новая версия станет стабильной. Рекомендуется для фреймворков. Фреймворк – это основа всего приложения. Чем раньше провести обновление, тем меньше времени и усилий оно займёт.
Версионирование
Кроме ведения версии приложения (семантическое версионирование), рекомендуют вести версионирование его API.
Версионироваие API применяется в системах с большим числом интеграций.
Старый API продолжает какое-то время существовать прараллельно с новым для того, чтобы дать клиентам время перейти на новый API.
Ловушка последних 10%
Ловушка последних 10% проявляется в решениях, призванных устранить сложность разработки и обеспечить полнофункциональную разработку с предсказуемыми результатами.
Примеры таких решений:
- Low Code,
- No Code,
- AI
- Повторно переиспользуемые собственные решения
Как правило, такие решения, из коробки, способны удовлетворить до 80% потребностей пользователей.
Следующие 10% - при небольшой доработке или за счёт отдельной разработки “сателлитных” решений (“Персонализация продукта”).
Последние 10% - никогда, без несоразмерных затрат.
Король-поставщик (vendor lock)
Архитектура строится полностью вокруг продукта одного поставщика.
Организация становится патологически зависимой от этого инструмента.
Поставщик диктует будущие архитектруные решения.
Инструмент тянет за собой антипаттерны “Ловушка последних 10%” и “Персонализация продукта”.
Примеры:
- - ERP (enterprise resource planning) системы
- - MS Excel
Дырявые абстракции
Абстракция – упрощение чего-то значительно более сложного, происходящего “под капотом”.
Плата за абстракции – их несовершенство. Ошибки в абстракциях называются “дырами”.
Чем ближе к пользователю (выше по стеку, дальше от железа), тем абстракции сложнее, тем больше в них ошибок.
Чем дальше абстракция от пользователя (ниже по стеку, ближе к железу), тем непредвиденнее будет на верхних уровнях реакция на возникшие в ней сбои.
Последствия дыр в абстракциях называются “утечками абстракций”:
- - ухудшение производительности
- - неожиданные результаты работы
Как с этим бороться? Знать, что скрывается под абстракциями.
Молчание ягнят
Замалчивание проблемы, откладывание её решения "на потом".
Отложенные проблемы копятся.
Каждая отложенная проблема рапространяется по стеку (вокруг неё наворачивается новая логика и создаются новые абстракции).
Это затрудняет устранение проблем.
Выход: открыто говорить о проблемах и не копить технический долг.
Недостаточная скорость выпуска
Возможность эволюции продукта сильно зависит от способности команд своевременно выпускать релизы.
Измеряется временем цикла – временем от начала работы над новой функцией до запуска её на продакшен.
Сокращение времени цикла – одна из главных целей – позволяет продукту эволючионировать быстрее.
Один стек для всего
Стек должен определяться потребностями, а не способностями.
Возможность выбора стека позволяет выпускать наиболее оптимальные продукты.
Но, потенциально, способно замедлить разработку и осложнить сопровождение.
Чтобы не допускать ни застоя, ни хаоса, хорошей практикой считается иметь несколько стеков для приложений разной сложности.
Пример из книги (стек назван по уровню сложности приложения):
- Малый стек - Ruby on Rails и MySQL,
- Средний стек - GoLang, а в качестве бэкенда Cassandra, MongoDB или MySQL
- Большой стек - Java и Oracle.
Персонализация продукта
Уникальная сборка для каждого клиента
Переключатели функций
Персонализация через настройки продукта
Хорошо:
- продукт продаётся
Плохо:
- возрастает нагрузка на тестирование
- может осложнить изменение продукта
Отчёты поверх регистрации
В одном приложении и/или на одной базе данных выполняются: и бизнес-логика, и севис формирования отчётов, и анализ данных.
Одна база данных не может быть оптимизирована одновременно и для хранения данных и для их анализа.
Невозможно вносить изменения в схему базы данных, не затрагивая отчетов.
Архитектура многоуровневого приложения, содержащего бизнес-логику, препятствует быстрому выполнению задач анализа данных – маршрутизация запросов по разным уровням увеличивает задержку.
Слишком широкий горизонт планирования
Составление бюджетов и планирование вынуждают делать допущения и принимать решения на их основе.
Чем меньше возможности пересмотреть план на стадии исполнения, тем больше решений принимается на основе минимального количества информации.
Тем больше вероятность “поворота не туда” и дороже будут его последствия.
Ловушка невозвратных затрат
Перед разработкой проводятся исследования, выявляются “лучшие практики”.
Продукт разрабатывается с учётом предположений, основанных на Ловушка невозвратных затрат”
Перед разработкой проводятся исследования, выявляются “лучшие практики”.
Продукт разрабатывается с учётом предположений, основанных на результатах таких исследований.
Если через какое-то время окажется, что предположения были неверными, от них тем сложнее будет отказаться, чем больше усилий и времени было вложено в их реализацию.
Чем больше времени и сил вы вкладываете в планирование, тем с большей вероятностью вы будете защищать его, даже осознавая, что оно неточно или устарело.
Это же касается решений, в которые были вложены эмоции.
В разработке это проявляется в иррациональной привязанности к артефактам.
Как не попасть в Ловушку невозвратных затрат
- Остерегаться длительных циклов планирования, которые вынуждают принимать необратимые решения.
- Сохранить возможность выбора на стадии реализации плана.
- Разбивать большие задачи на более мелкие.
- Использовать быстрый цикл поставки.
- Избегать технологий, требующих значительных инвестиций (крупных лицензий и контрактов на поддержку) до того, как будет создан продукт и отзывы конечных пользователей подтвердят, что выбранная технология действительно подходит для решения проблемы.
Закон Конвея
Структура проектов копирует структуру команд. Есть отдельные команды:
- аналитики
- фронтенд
- бэкенд
- базы данных
- тестирование
В монолитной архитектуре такая организация команд может работать эффективно.
В микросервисной – благодаря ей, увеличиваются затраты на коммуникации и согласования и замедляется разработка.
Выход: кроссфункциональные команды (“Обратный закон Конвея”) – должны быть похожи на архитектуру создаваемых систем.
Проектный подход
Работа организуется вокруг проекта.
По заврешении проекта продукт передаётся службе эксплуатации, а команда переходит на следующий проект.
Последствия проектного подхода:
Команда переключается на другие задачи – сложно контролировать исправление ошибок и доработки.
Команде не важны эксплуатационые характеристики своего кода – проблемы с качеством продукта.
Проект – что-то временное, что рано или поздно заканчивается. Это влияет на решения, принимаемые на проекте.
Продуктовый подход
Работа организуется вокруг продукта.
Продукты – это “навсегда”. Команды остаются связанными со своим продуктом и ответственнее относятся к его качеству.
У продукта есть владелец, который защищает его использование в экосистеме и управляет требованиями.
Проще вносить изменения и исправлять ошибки.
Слишком большая команда
Большие команды работают неэффективно из-за быстро растущего количества связей.
Больше людей в команде – больше коммуникаций (совещаний), дольше процесс согласования, меньше времени на разработку.
Правило компании Амазон – команда не больше, чем на 2 пиццы (около 10 человек).
Эксперименты
Для достижения успеха нужны эксперименты – тестирование новых идей, продуктов, подходов и интеграция успешных решений в существующую систему.
При слишком сильной концентрации на выполнении планов, на эксперименты не остаётся достаточно времени и средств.
Как начать проводить эксперименты:
- Заимствовать идеи из вне (конференции, учёба).
- Поощрять совершенствования.
Пример: культура кайдзен в компании Тойота. - Работать методом всплеска и стабилизации.
Сгенерированная идея применяется на продукте. После стабилизации – выводится в продакшен. - Выделять время на инновации.
Примеры: практика “20%” в Google, хакатоны. - Создавать прототипы, использующие конкурирующие решения.
- Сокращать время на получение разработчиками обратной всязи от пользователей.
Разработка на основе гипотез и данных
Используется научный подход, вместо сбора формальных требований.
Команды создают MVP, а затем получают обратную связь, строят гипотезы, проводят эксперименты и определяют каким образом подтверждение гипотезы повлияет на развитие приложения.
Пример эксперимента: А/В-тестирование.