Найти тему
Дед Мазай на Котлине

Правильное и неочень в процессе разработки

Про что

В основном, в статье будут описаны антипаттерны, применяемые при разработке программных продуктов. Антипаттерны собраны из разных аспектов разработки:

  • Управление разработкой
  • Архитектура
  • Разработка
  • Девопс
  • Тестирование
  • и т.д.

Иногда будут приводиться рекомендации как непопасть под дейсвие того или иного антипаттерна.

И так, поехали:

Применение технологий 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, а затем получают обратную связь, строят гипотезы, проводят эксперименты и определяют каким образом подтверждение гипотезы повлияет на развитие приложения.

Пример эксперимента: А/В-тестирование.