Рентабельный код
Жили-были в двух соседних деревушках Вилларибо и Виллабаджо две команды разработчиков. И те и другие делали ревью кода, писали тесты, приводили рефакторинг, но через год разработки в Вилларибо уже выпустили релиз и вышли в продакшн, а в Виллабаджо все еще проводят рефакторинг и чинят баги. В чем же дело?
Разработка ПО – область, подверженная рискам. В нашей сфере при наступлении одного или нескольких рисков, срок поставки рабочей версии может сдвинуться не на привычные и комфортные 10-20%, а на все 150-300%. И надо признаться, что это далеко не предел.
Мы можем либо скрестить пальцы и надеяться, что удача будет сопутствовать проекту во всем, либо признать, что по статистике большая часть проектов по разработке ПО «проваливается» и предпринять дополнительные усилия по ослаблению возможных рисков.
Моя практика показывает, что клиенты крайне неохотно работают по схеме T&M и чаще предпочитают Fixed Price. В условиях зафиксированной стоимости наступление рискового случая означает автоматическое снижение рентабельности проекта: сотрудники получают зарплату ежемесячно, а не за сданные проекты.
До Agile и XP вся ответственность за работу с рисками ложилась на менеджеров. В гибких методологиях разработчики гораздо больше вовлечены в процесс и делят ответственность с менеджерами. Однако, принципы XP и Agile – больше методологические, чем технологические. Я думаю, что с рисками эффективнее работать комплексно на всех уровнях, в том числе на самом низком уровне, т.е. во время проектирования и написания кода.
Почему об этом следует думать разработчику, если есть менеджер?
- Не секрет, что если факап случится, менеджмент примет единственное «супер-умное» решение: «давайте поработаем сверхурочно и в выходные»
- Премии сотрудники получают тоже обычно за в срок сданные, а не за проваленные проекты
- Чувство сделанного дела, в конце концов. Гораздо приятнее сдать проект во время и видеть улыбку клиента, чем с опозданием в пол года отвязаться от «трудного ребенка»
С моей точки зрения спокойная рабочая обстановка вместо авралов и бонусы – неплохая мотивация, чтобы начать заботиться об этом.
Итак, рассмотрим распространенные риски в разработке ПО.
Ошибка расписания
При оценке трудозатраты были оценены не верно.
Методологический метод ослабления риска
Команда оценивает трудоемкость задачи, используя Planning Poker.
Критика
В программировании мы сталкиваемся с большим количеством уникальных задач. Не смотря на предыдущий опыт можно промазать в разы из-за неучтенных деталей.
Дополнительные методы ослабления
Разработка Proof of concept (прототипа)
У вас есть описание API клиента, но не понятно, как оно себя поведет в боевых условиях? Прототип на «коленке» поможет проверить ваши самые страшные гипотезы Код прототипа скорее всего придется просто выкинуть. В качестве бонуса, спроектировать систему со второго раза наверняка получится лучше, чем с первого.
Если есть опасения за нагрузку следует провести нагрузочное тестирование в самом начале на массиве сгенерированных данных, чтобы проверить жизнеспособность архитектуры под нагрузкой.
Появление новых требований или изменение существующих
В процессе работы появляется все больше требований или текущие требования меняются.
Методологический метод ослабления риска
Короткие итерации с фиксацией требований (scrum) или continuous delivery (kanban).
Критика
Работая маленькими итерациями мы постоянно вынуждены проводить рефакторинг и переделывать уже существующий код. Команда располагает требованиями только на две недели вперед и не видит полной картины. Цена промаха снижается, но и КПД становится меньше, потому что не обладая полнотой информации не известно на сколько «навороченная» архитектура потребуется проекту.
Дополнительные методы ослабления
Изменение требований – это риск с почти 100% вероятностью наступления, поэтому стоит считать, что он уже наступил до начала проекта и заложить его в календарный план и оценку трудозатрат. Однако именно он наиболее болезненно воспринимается программистами. Психологически сложно выкидывать код, который хорошо работал, потому что, видите ли, бизнес-модель не сработала. К сожалению надо просто принять тот факт, что мы регулярно будем отправлять часть кода на помойку. Более того, чем больше старого кода и костылей мы выкинем, тем лучше.
Lean и метод прогрессивного jpeg’а
В первую очередь сосредоточьтесь на самом важном функционале. Все «прибамбасы», без которых можно обойтись должны реализовываться в конце проекта. Нужно показывать и скрывать панель? Для начала пойдет простой hide и show. Сложную анимацию можно добавить потом.
Слабая связанность и модульное проектирование, onion-архитектура
К счастью на сегодняшний день существует множество готовых решений, обеспечивающих слабую связанность. Пойте мантру S.O.L.I.D, ежедневно на работе. Мыслите интерфейсами, а не реализациями. Потенциально, любая реализация может отправиться на помойку. Заведите за правило использовать принцип IOC/DI. Если у вас много JavaScript-кода обязательно пользуйтесь RequireJS или чем-то аналогичным.
Откажитесь от идеи спроектировать все приложение в едином стиле. Делите его на подсистемы. Вам придется заплатить за это некоторым дублированием кода, но возможность выкинуть и переписать с нуля целую подсистему приложения, не разломав при этом другие части гораздо важнее.
Эволюционный рефакторинг совместно с классическим проектирование сверху-вниз
Людей, которых действительно можно назвать System Architect очень-очень мало. Настоящий архитектор должен поучаствовать в трех-четырех действительно больших проектах, парочку из них завалить, написать и выбросить вагон и маленькую тележку кода. Если у вас есть такой человек — честь ему и хвала. Беда в том, что у него не будет времени писать код. Его работой на полную ставку станет проектирование на уровне крупных блоков и контекстов системы. Проектирование, порой, целых подсистем уйдет на откуп Team Lead’ам и страшим разработчикам. Кто-то из них справится с задачей, а кто-то – нет, поэтому важно, чтобы все модули были независимы.
Устанавливайте взаимоотношения разработчик между смежными командами, например команда UI – клиент команды Backend.
Такой подход дает следующие преимущества:
- Вы избавляетесь от священного идола и готовы постоянно проводить рефакторинг и улучшать качество кода. Могут появиться «пахнующие» куски, но только в рамках определенного модуля. Общий уровень качества кода будет постоянно поддерживаться на высокой отметке
- Вам проще взаимодействовать с клиентом и менеджментом, ведь объяснить, что за эту неделю у нас +3 новых фичи гораздо проще, чем у нас +100500 новых классов
- Вы экономите время, в том числе на согласования и интеграцию. Я не однократно наблюдал пьесу в трех актах:
Акт 1 Команде UI не нравится API и надо все сделать иначе и вообще поля у нас названы совершенно иначе.
Акт 2 Команда бекенда считает, что команда UI — балбесы и вообще не понимают, как работает ядро
Акт 3 Менеджеры обеих команд уговаривают сделать хоть как-нибудь, чтобы работало, иначе уволят всех на фиг. Занавес.
Если коммуникация между командами налажена, то каждая из них получает ценную обратную связь. Наверняка существует способ немного спецификацию с обеих сторон, что позволит методом проб и ошибок получить архитектуру, которая работает во всех смыслах этого слова.
Действительно переиспользуемый код следует вынести в ядро. Каждый модуль (подсистема) может зависеть от функций ядра, но сами модули не должны ничего знать друг о друге. Если требуется обеспечить взаимодействие двух модулей, используйте событийно-ориентированное программирование. Подписывайтесь на события только через ядро. Если вам приходится дублировать какой-то функционал в двух-трех модулях – это сигнал для включения его в ядро или выделение в отдельную внешнюю зависимость. Такая организация кода позволит выкинуть любой модуль или переписать с нуля, возможно даже, используя другой язык программирования.
Graceful Degradation для кода с низкой ценностью, хитрое YAGNI
Ценность (business value) разрабатываемого приложения не распределена равномерно по всей кодовой базе. Это утверждение справедливо и для количества усилий на разработку. До недавнего времени поддержка IE6 была примером колоссального перерасхода ресурсов на код с потенциально низкой ценностью.
Мы не можем ликвидировать затраты на написание кода с низким показателем ценности, но можем снизить их: не писать юнит-тестов, не проводить рефакторинга, забить на качество кода в определенных частях системы. За это нам придется заплатить поддержкой в будущем. Если конечно именно этот кусок систему действительно необходимо будет поддерживать и развивать в будущем.
А если нет? А если нет, то мы молодцы и смогли сэкономить. Один из секретов команды Вилларибо в том, что при прочих равных (а мы условились, что квалификация программистов и стеки у них аналогичные), они смогли направить основные свои усилия на разработку действительно ценного кода, а в Виллабаджо использовался унифицированный подход ко всей кодовой базе.
Пока меня не закидали помидорами в комментариях, поспешу раскрыть идею с отсутствием тестов и «костыльными» частями системы.
Ни один человек на свете не может знать достоверно, какая часть системы останется неизменной, а что придется переписывать с нуля или вообще выкидывать. Однако, существует вероятность изменения того или иного требования. Например, обработка финансовых транзакций с высокой вероятностью не будет меняться в течение длительного срока. Стоимость ошибки в этой части ПО экстремально велика. В этой части приложения есть место DDD, TDD, BDD и любым другим крутым *DD, которые вам помогают в решении задач.
А вот если компания решила устроить рекламную акцию и вас попросили сделать landing page для вашего онлайн-банка – это совсем другая история. Скорее всего, проще отдать этот функционал на аутсорс и сделать страничку на PHP. Отгораживайтесь от такого кода с душком с помощью паттерна Facade. Оставляйте ядро системы чистым.
Направление слишком большого количества усилий в часто-изменяемую часть кодовой базы не рентабельно
Подробно этот подход описан в статье The Good, the Bad and the Ugly code.
механизм обновления приложения
Если вы разрабатываете десктопное приложение, позаботьтесь о своих пользователях и предусмотрите механизм авто-обновлений из интернета. Для win существует, например ClickOnce.
Смена сотрудников
Ключевые сотрудники могут покинуть компанию, уйти в отпуск или даже умереть, унося с собой важные знания как из предметной области, так и о коде приложения.
Методологический метод ослабления риска
Совместное владение кодом, парное программирование, код-ревью.
Дополнительные методы ослабления
Используйте общеизвестные паттерны проектирования, именуйте классы очевидным образом (Repository, Specification, DAL, DTO, ValueObject, Entity).
Не лишним будет оставить ссылки на ресурсы в сети прямо в коде, если какая-то концепция не слишком известна, а материал удачно ее раскрывает.
Минимизируйте количество «велосипедов». Если велосипеды появляются, добавьте комментарий, объясняющий наличие не очевидного кода и почему была выбрана именно такая реализация. Если велосипед вам все-таки очень нужен, выложите его в open-source или оформите отдельным продуктом. Пусть кто-то будет поддерживать новую технологию.
Для сложных предметных областей очень желательно использовать концепции DDD, особенно Ubiquitous Language. Это поможет новым сотрудникам быстрее «врубиться» в проект. Гораздо проще разбираться в коде, если он похож на естественный язык из спецификации, которую ты только что изучил.
Используйте аннотации/атрибуты, помогающие intellisense и анализаторам кода.
Используйте стандартные методы разворачивания приложений на сколько это возможно. Современные фреймворки идут в комплекте с механизмом миграции БД. Если конфигурация очень сложная есть смысл написать скрипт развертывания. Для особо-тяжелых случаев может быть целесообразно использовать штуки вроде vagrant.
Упрощайте dev-конфигурацию на сколько это возможно. Если вы используете Memcached для ускорения работы приложения, предусмотрите реализацию MemcachedDummy. Чем быстрее новый разработчик может развернуть работающую, пусть возможно и не целиком, версию, тем лучше.
Старайтесь использовать общепринятые методы специфицирования и описания багов: user stories, use cases, UML, expected/actual behavior, закрепите основные бизнес-правила системы в unit-тестах.
Создайте поддерживайте тесты хотя-бы на основные бизнес-правила Используйте bdd-нотацию в названиях тестов, чтобы сделать их назначение очевидными. Подробные рекомендации об организации unit-тестирования на проекте можно почитать в статье Unit-тестирование для чайников.
Декомпозиция спецификации
Спецификация неполная и/или содержит конфликтные требования
Методологический метод ослабления риска
Использование методологии SpecByExample, возможность исключить требования с низким business value из плана спринта, формальная проверка на не противоречивость до начала работ.
Критика
Обычно риск ходит в паре с «изменением требований». Если наступил первый, то второй наступает почти автоматически. Для больших систем формальная задача проверки не противоречивости требований по меньшей мере не тривиальна.
Дополнительные методы ослабления
Здравый смысл. Если в ТЗ написан очевидный бред или очередной change request безумен, то лучше всего поднять этот вопрос на самой ранней стадии и внести исправления. Пока не принято решение нужно просто отложить эту задачу и взять из беклога следующую наиболее важную, но не связанную проблемной задачу.
Если вам не хватает каких-то материалов для начала работы и вы точно знаете, что к сроку они не появятся, не нужно играть в Counter Strike. Попробуйте начать работать без необходимых материалов. Нет графики? Используйте квадратики и кружочки. Нет визуального дизайна, но есть прототипы? Возьмите Bootstrap. Даже медленное движение лучше, чем полная остановка.
Наличие тестов на бизнес-логику отлично помогает ослабить и этот риск: вы узнаете о конфликте требований из упавшего теста, а не плавающего бага на продакшне.
Технологические риски
Удовлетворит ли технологический стек задаче. Не придется ли менять язык программирования, СУБД из-за нагрузки или недостаточно интероперабельности?
Методы ослабления
Закладывайте возможность горизонтального масштабирования на раннем этапе, Обеспечьте Persistence Ignorance.
Используйте Data Mapper или определите место, где он может появиться в процессе рефакторинга – это поможет гибко изменить источник данных в случае необходимости. Современные мапперы снабжены Assert’ами, которые помогут не забыть замапить все поля правильно, независимо от источника данных.
Предпочитайте QueryObject паттерну Repository. Проблемы Repository в long-run отлично расписаны в этой статье. У QueryObject нет недостатков по сравнению с Repository, но есть преимущество – возможность быстро переехать на CQRS и шину данных для обеспечения полноценного горизонтального масштабирования. Подробно эволюция архитектуры проекта от монолитной к распределенной описана здесь.
Низкая продуктивность
Интенсивность работы прямо-пропорциональна близости дедлайна. Пока сроки поставки далеки, есть соблазн валять дурака и всячески придаваться прокарстинации.
Методологический метод ослабления риска
Короткие итерации, stand-up миттинги
Дополнительные методы ослабления
Инвестиции времен в начале проекта в инфраструктуру и мета-программирование. Если в вашем проекте много форм, задумайтесь о формо-генераторе, вместо клепания однотипных View-файлов. Если вам нужен CRUD-функционал для большого количества сущностей, один хорошо-спроектированный контроллер со всеми виртуальными методами поможет избавиться от написания тонн рутинного кода. Виртуальные методы позволят вам переопределить поведение, если это потребуется. В этом случае контроллер нужно будет пометить не наследуемым (final/sealed), чтобы исключить нарушение принципа OCP.
Проблемы с производительностью мета-программирования (в особенности reflection) можно решить за счет динамической компиляции или кодо-генераторов.
Чем меньше кода, особенно однотипного рутинного, вы напишете, тем проще будет поддержка. Кроме этого, рутинный код – самый противный. Клиенту в принципе не важно, интересно вам было в процессе решения его проблемы или скучно. Вы как разработчик можете поставить себе задачу оптимизировать свою рутинную работу и решить задачу более элегантно. Новые задачи всегда интереснее, чем унылая копипаста.
Важно понимать, что увлекаться абстрактными фабриками абстрактных фабрик тоже не стоит. Если игра не стоит свеч, то лучше остановиться и воспользоваться проверенным способом. Новые не проверенные технологии и подходы могут очень пригодиться проекту. Но они же и привносят дополнительные риски. Поэтому эксперименты следует проводить строго в начале проекта, а никак не за неделю до дедлайна.
Начало проекта — отличный момент для настройки Continuous Integration, автоматизации рутинных операций и анализа рынка на предмет новых средств, упрощающих жизнь разработчику. Если вы давно слышите, что штука N крутая, но вы до сих пор ее не пробовали, потому что времени как-то не было — попробуйте. Кто знает, как она может улучшить вашу продуктивность.
В продолжении темы рекомендую этот скринкаст.
Автор: marshinov