- PVSM.RU - https://www.pvsm.ru -
Жили-были в двух соседних деревушках Вилларибо и Виллабаджо две команды разработчиков. И те и другие делали ревью кода, писали тесты, приводили рефакторинг, но через год разработки в Вилларибо уже выпустили релиз и вышли в продакшн, а в Виллабаджо все еще проводят рефакторинг и чинят баги. В чем же дело?
Разработка ПО – область, подверженная рискам. В нашей сфере при наступлении одного или нескольких рисков, срок поставки рабочей версии может сдвинуться не на привычные и комфортные 10-20%, а на все 150-300%. И надо признаться, что это далеко не предел.
Мы можем либо скрестить пальцы и надеяться, что удача будет сопутствовать проекту во всем, либо признать, что по статистике большая часть проектов по разработке ПО «проваливается» и предпринять дополнительные усилия по ослаблению возможных рисков.
Моя практика показывает, что клиенты крайне неохотно работают по схеме T&M и чаще предпочитают Fixed Price. В условиях зафиксированной стоимости наступление рискового случая означает автоматическое снижение рентабельности проекта: сотрудники получают зарплату ежемесячно, а не за сданные проекты.
До Agile и XP вся ответственность за работу с рисками ложилась на менеджеров. В гибких методологиях разработчики гораздо больше вовлечены в процесс и делят ответственность с менеджерами. Однако, принципы XP и Agile – больше методологические, чем технологические. Я думаю, что с рисками эффективнее работать комплексно на всех уровнях, в том числе на самом низком уровне, т.е. во время проектирования и написания кода.
Почему об этом следует думать разработчику, если есть менеджер?
С моей точки зрения спокойная рабочая обстановка вместо авралов и бонусы – неплохая мотивация, чтобы начать заботиться об этом.
Итак, рассмотрим распространенные риски в разработке ПО.
При оценке трудозатраты были оценены не верно.
Команда оценивает трудоемкость задачи, используя Planning Poker [1].
В программировании мы сталкиваемся с большим количеством уникальных задач. Не смотря на предыдущий опыт можно промазать в разы из-за неучтенных деталей.
У вас есть описание API клиента, но не понятно, как оно себя поведет в боевых условиях? Прототип на «коленке» поможет проверить ваши самые страшные гипотезы Код прототипа скорее всего придется просто выкинуть. В качестве бонуса, спроектировать систему со второго раза наверняка получится лучше, чем с первого.
Если есть опасения за нагрузку следует провести нагрузочное тестирование в самом начале на массиве сгенерированных данных, чтобы проверить жизнеспособность архитектуры под нагрузкой.
В процессе работы появляется все больше требований или текущие требования меняются.
Короткие итерации с фиксацией требований (scrum) или continuous delivery [3] (kanban).
Работая маленькими итерациями мы постоянно вынуждены проводить рефакторинг и переделывать уже существующий код. Команда располагает требованиями только на две недели вперед и не видит полной картины. Цена промаха снижается, но и КПД становится меньше, потому что не обладая полнотой информации не известно на сколько «навороченная» архитектура потребуется проекту.
Изменение требований – это риск с почти 100% вероятностью наступления, поэтому стоит считать, что он уже наступил до начала проекта и заложить его в календарный план и оценку трудозатрат. Однако именно он наиболее болезненно воспринимается программистами. Психологически сложно выкидывать код, который хорошо работал, потому что, видите ли, бизнес-модель не сработала. К сожалению надо просто принять тот факт, что мы регулярно будем отправлять часть кода на помойку. Более того, чем больше старого кода и костылей мы выкинем, тем лучше.
К счастью на сегодняшний день существует множество готовых решений, обеспечивающих слабую связанность. Пойте мантру S.O.L.I.D [5], ежедневно на работе. Мыслите интерфейсами, а не реализациями. Потенциально, любая реализация может отправиться на помойку. Заведите за правило использовать принцип IOC/DI. Если у вас много JavaScript-кода обязательно пользуйтесь RequireJS [6] или чем-то аналогичным.
Откажитесь от идеи спроектировать все приложение в едином стиле. Делите его на подсистемы. Вам придется заплатить за это некоторым дублированием кода, но возможность выкинуть и переписать с нуля целую подсистему приложения, не разломав при этом другие части гораздо важнее.
Людей, которых действительно можно назвать System Architect очень-очень мало. Настоящий архитектор должен поучаствовать в трех-четырех действительно больших проектах, парочку из них завалить, написать и выбросить вагон и маленькую тележку кода. Если у вас есть такой человек — честь ему и хвала. Беда в том, что у него не будет времени писать код. Его работой на полную ставку станет проектирование на уровне крупных блоков и контекстов системы. Проектирование, порой, целых подсистем уйдет на откуп Team Lead’ам и страшим разработчикам. Кто-то из них справится с задачей, а кто-то – нет, поэтому важно, чтобы все модули были независимы.
Устанавливайте взаимоотношения разработчик между смежными командами, например команда UI – клиент команды Backend.
Такой подход дает следующие преимущества:
Если коммуникация между командами налажена, то каждая из них получает ценную обратную связь. Наверняка существует способ немного спецификацию с обеих сторон, что позволит методом проб и ошибок получить архитектуру, которая работает во всех смыслах этого слова.
Действительно переиспользуемый код следует вынести в ядро. Каждый модуль (подсистема) может зависеть от функций ядра, но сами модули не должны ничего знать друг о друге. Если требуется обеспечить взаимодействие двух модулей, используйте событийно-ориентированное программирование. Подписывайтесь на события только через ядро. Если вам приходится дублировать какой-то функционал в двух-трех модулях – это сигнал для включения его в ядро или выделение в отдельную внешнюю зависимость. Такая организация кода позволит выкинуть любой модуль или переписать с нуля, возможно даже, используя другой язык программирования.
Ценность (business value) разрабатываемого приложения не распределена равномерно по всей кодовой базе. Это утверждение справедливо и для количества усилий на разработку. До недавнего времени поддержка IE6 была примером колоссального перерасхода ресурсов на код с потенциально низкой ценностью.
Мы не можем ликвидировать затраты на написание кода с низким показателем ценности, но можем снизить их: не писать юнит-тестов, не проводить рефакторинга, забить на качество кода в определенных частях системы. За это нам придется заплатить поддержкой в будущем. Если конечно именно этот кусок систему действительно необходимо будет поддерживать и развивать в будущем.
А если нет? А если нет, то мы молодцы и смогли сэкономить. Один из секретов команды Вилларибо в том, что при прочих равных (а мы условились, что квалификация программистов и стеки у них аналогичные), они смогли направить основные свои усилия на разработку действительно ценного кода, а в Виллабаджо использовался унифицированный подход ко всей кодовой базе.
Пока меня не закидали помидорами в комментариях, поспешу раскрыть идею с отсутствием тестов и «костыльными» частями системы.
Ни один человек на свете не может знать достоверно, какая часть системы останется неизменной, а что придется переписывать с нуля или вообще выкидывать. Однако, существует вероятность изменения того или иного требования. Например, обработка финансовых транзакций с высокой вероятностью не будет меняться в течение длительного срока. Стоимость ошибки в этой части ПО экстремально велика. В этой части приложения есть место DDD, TDD, BDD и любым другим крутым *DD, которые вам помогают в решении задач.
А вот если компания решила устроить рекламную акцию и вас попросили сделать landing page для вашего онлайн-банка – это совсем другая история. Скорее всего, проще отдать этот функционал на аутсорс и сделать страничку на PHP. Отгораживайтесь от такого кода с душком с помощью паттерна Facade [9]. Оставляйте ядро системы чистым.
Направление слишком большого количества усилий в часто-изменяемую часть кодовой базы не рентабельно
Подробно этот подход описан в статье The Good, the Bad and the Ugly code [8].
Если вы разрабатываете десктопное приложение, позаботьтесь о своих пользователях и предусмотрите механизм авто-обновлений из интернета. Для win существует, например ClickOnce [10].
Ключевые сотрудники могут покинуть компанию, уйти в отпуск или даже умереть, унося с собой важные знания как из предметной области, так и о коде приложения.
Совместное владение кодом, парное программирование, код-ревью.
Используйте общеизвестные паттерны проектирования, именуйте классы очевидным образом (Repository, Specification, DAL, DTO, ValueObject, Entity).
Не лишним будет оставить ссылки на ресурсы в сети прямо в коде, если какая-то концепция не слишком известна, а материал удачно ее раскрывает.
Минимизируйте количество «велосипедов». Если велосипеды появляются, добавьте комментарий, объясняющий наличие не очевидного кода и почему была выбрана именно такая реализация. Если велосипед вам все-таки очень нужен, выложите его в open-source или оформите отдельным продуктом. Пусть кто-то будет поддерживать новую технологию.
Для сложных предметных областей очень желательно использовать концепции DDD [11], особенно Ubiquitous Language [12]. Это поможет новым сотрудникам быстрее «врубиться» в проект. Гораздо проще разбираться в коде, если он поход на естественный язык из спецификации, которую ты только что изучил.
Используйте аннотации/атрибуты, помогающие intellisense и анализаторам кода.
Используйте стандартные методы разворачивания приложений на сколько это возможно. Современные фреймворки идут в комплекте с механизмом миграции БД. Если конфигурация очень сложная есть смысл написать скрипт развертывания. Для особо-тяжелых случаев может быть целесообразно использовать штуки вроде vagrant [13].
Упрощайте dev-конфигурацию на сколько это возможно. Если вы используете Memcached для ускорения работы, предусмотрите реализацию MemcachedDummy. Чем быстрее новый разработчик может развернуть работающую, пусть возможно и не целиком, версию, тем лучше.
Старайтесь использовать общепринятые методы специфицирования и описания багов: user stories, use cases, UML, expected/actual behavior, закрепите основные бизнес-правила системы в unit-тестах.
Создайте поддерживайте тесты хотя-бы на основные бизнес-правила Используйте bdd-нотацию в названиях тестов, чтобы сделать их назначение очевидными. Подробные рекомендации об организации unit-тестирования на проекте можно почитать в статье Unit-тестирование для чайников [14].
Спецификация неполная и/или содержит конфликтные требования
Использование методологии SpecByExample [15], возможность исключить требования с низким business value из плана спринта, формальная проверка на не противоречивость до начала работ.
Обычно риск ходит в паре с «изменением требований». Если наступил первый, то второй наступает почти автоматически. Для больших систем формальная задача проверки не противоречивости требований по меньшей мере не тривиальна.
Здравый смысл. Если в ТЗ написан очевидный бред или очередной change request безумен, то лучше всего поднять этот вопрос на самой ранней стадии и внести исправления. Пока не принято решение нужно просто отложить эту задачу и взять из беклога следующую наиболее важную, но не связанную проблемной задачу.
Если вам не хватает каких-то материалов для начала работы и вы точно знаете, что к сроку они не появятся, не нужно играть в Counter Strike. Попробуйте начать работать без необходимых материалов. Нет графики? Используйте квадратики и кружочки. Нет визуального дизайна, но есть прототипы? Возьмите Bootstrap [16]. Даже медленное движение лучше, чем полная остановка.
Наличие тестов на бизнес-логику отлично помогает ослабить и этот риск: вы узнаете о конфликте требований из упавшего теста, а не плавающего бага на продакшне.
Удовлетворит ли технологический стек задаче. Не придется ли менять язык программирования, СУБД из-за нагрузки или недостаточно интероперабельности?
Закладывайте возможность горизонтального масштабирования на раннем этапе, Обеспечьте Persistence Ignorance [17].
Используйте Data Mapper или определите место, где он может появиться в процессе рефакторинга – это поможет гибко изменить источник данных в случае необходимости. Современные мапперы снабжены Assert’ами, которые помогут не забыть замапить все поля правильно, независимо от источника данных.
Предпочитайте QueryObject паттерну Repository. Проблемы Repository в long-run отлично расписаны в этой статье [18]. У QueryObject нет недостатков по сравнению с Repository, но есть преимущество – возможность быстро переехать на CQRS и шину данных для обеспечения полноценного горизонтального масштабирования. Подробно эволюция архитектуры проекта от монолитной к распределенной описана здесь [19].
Интенсивность работы прямо-пропорциональна близости дедлайна. Пока сроки поставки далеки, есть соблазн валять дурака и всячески придаваться прокарстинации.
Короткие итерации, stand-up миттинги
Инвестиции времен в начале проекта в инфраструктуру и мета-программирование. Если в вашем проекте много форм, задумайтесь о формо-генераторе, вместо клепания однотипных View-файлов. Если вам нужен CRUD-функционал для большого количества сущностей, один хорошо-спроектированный контроллер со всеми виртуальными методами поможет избавиться от написания тонн рутинного кода. Виртуальные методы позволят вам переопределить поведение, если это потребуется. В этом случае контроллер нужно будет пометить не наследуемым (final/sealed), чтобы исключить нарушение принципа OCP.
Проблемы с производительностью мета-программирования (в особенности reflection) можно решить за счет динамической компиляции или кодо-генераторов.
Чем меньше кода, особенно однотипного рутинного, вы напишете, тем проще будет поддержка. Кроме этого, рутинный код – самый противный. Клиенту в принципе не важно, интересно вам было в процессе решения его проблемы или скучно. Вы как разработчик можете поставить себе задачу оптимизировать свою рутинную работу и решить задачу более элегантно. Новые задачи всегда интереснее, чем унылая копипаста.
Важно понимать, что увлекаться абстрактными фабриками абстрактных фабрик тоже не стоит. Если игра не стоит свеч, то лучше остановиться и воспользоваться проверенным способом. Новые не проверенные технологии и подходы могут очень пригодиться проекту. Но они же и привносят дополнительные риски. Поэтому эксперименты следует проводить строго в начале проекта, а никак не за неделю до дедлайна.
Начало проекта — отличный момент для настройки Continuous Integration [20], автоматизации рутинных операций и анализа рынка на предмет новых средств, упрощающих жизнь разработчику. Если вы давно слышите, что штука N крутая, но вы до сих пор ее не пробовали, потому что времени как-то не было — попробуйте. Кто знает, как она может улучшить вашу продуктивность.
Автор: marshinov
Источник [21]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/razrabotka/67418
Ссылки в тексте:
[1] Planning Poker: https://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D0%BA%D0%B5%D1%80_%D0%BF%D0%BB%D0%B0%D0%BD%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F
[2] Proof of concept: http://en.wikipedia.org/wiki/Proof_of_concept
[3] continuous delivery: http://en.wikipedia.org/wiki/Continuous_delivery
[4] onion-архитектура: http://jeffreypalermo.com/blog/the-onion-architecture-part-1/
[5] S.O.L.I.D: https://ru.wikipedia.org/wiki/SOLID_(%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BD%D0%BE-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)
[6] RequireJS: http://requirejs.org/
[7] Graceful Degradation: http://habrahabr.ru/post/157115/
[8] хитрое YAGNI: http://habrahabr.ru/post/195732/
[9] Facade: https://ru.wikipedia.org/wiki/%D0%A4%D0%B0%D1%81%D0%B0%D0%B4_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)
[10] ClickOnce: https://ru.wikipedia.org/wiki/ClickOnce
[11] DDD: http://habrahabr.ru/post/61524/
[12] Ubiquitous Language: http://habrahabr.ru/post/232881/
[13] vagrant: http://www.vagrantup.com/
[14] Unit-тестирование для чайников: http://habrahabr.ru/company/etnasoft/blog/169381/
[15] SpecByExample: http://habrahabr.ru/company/etnasoft/blog/166747/
[16] Bootstrap: http://getbootstrap.com/
[17] Persistence Ignorance: http://msdn.microsoft.com/en-us/magazine/dd882510.aspx
[18] в этой статье: http://blog.byndyu.ru/2011/08/repository.html
[19] здесь: http://blog.byndyu.ru/2014/07/command-and-query-responsibility.html
[20] Continuous Integration: http://habrahabr.ru/post/190412/
[21] Источник: http://habrahabr.ru/post/233221/
Нажмите здесь для печати.