- PVSM.RU - https://www.pvsm.ru -

Рентабельный код

Жили-были в двух соседних деревушках Вилларибо и Виллабаджо две команды разработчиков. И те и другие делали ревью кода, писали тесты, приводили рефакторинг, но через год разработки в Вилларибо уже выпустили релиз и вышли в продакшн, а в Виллабаджо все еще проводят рефакторинг и чинят баги. В чем же дело?

Разработка ПО – область, подверженная рискам. В нашей сфере при наступлении одного или нескольких рисков, срок поставки рабочей версии может сдвинуться не на привычные и комфортные 10-20%, а на все 150-300%. И надо признаться, что это далеко не предел.
Мы можем либо скрестить пальцы и надеяться, что удача будет сопутствовать проекту во всем, либо признать, что по статистике большая часть проектов по разработке ПО «проваливается» и предпринять дополнительные усилия по ослаблению возможных рисков.
Моя практика показывает, что клиенты крайне неохотно работают по схеме T&M и чаще предпочитают Fixed Price. В условиях зафиксированной стоимости наступление рискового случая означает автоматическое снижение рентабельности проекта: сотрудники получают зарплату ежемесячно, а не за сданные проекты.

До Agile и XP вся ответственность за работу с рисками ложилась на менеджеров. В гибких методологиях разработчики гораздо больше вовлечены в процесс и делят ответственность с менеджерами. Однако, принципы XP и Agile – больше методологические, чем технологические. Я думаю, что с рисками эффективнее работать комплексно на всех уровнях, в том числе на самом низком уровне, т.е. во время проектирования и написания кода.

Почему об этом следует думать разработчику, если есть менеджер?

  1. Не секрет, что если факап случится, менеджмент примет единственное «супер-умное» решение: «давайте поработаем сверхурочно и в выходные»
  2. Премии сотрудники получают тоже обычно за в срок сданные, а не за проваленные проекты
  3. Чувство сделанного дела, в конце концов. Гораздо приятнее сдать проект во время и видеть улыбку клиента, чем с опозданием в пол года отвязаться от «трудного ребенка»

С моей точки зрения спокойная рабочая обстановка вместо авралов и бонусы – неплохая мотивация, чтобы начать заботиться об этом.

Итак, рассмотрим распространенные риски в разработке ПО.

Ошибка расписания

При оценке трудозатраты были оценены не верно.

Методологический метод ослабления риска

Команда оценивает трудоемкость задачи, используя Planning Poker [1].

Критика

В программировании мы сталкиваемся с большим количеством уникальных задач. Не смотря на предыдущий опыт можно промазать в разы из-за неучтенных деталей.

Дополнительные методы ослабления

Разработка Proof of concept [2] (прототипа)

У вас есть описание API клиента, но не понятно, как оно себя поведет в боевых условиях? Прототип на «коленке» поможет проверить ваши самые страшные гипотезы Код прототипа скорее всего придется просто выкинуть. В качестве бонуса, спроектировать систему со второго раза наверняка получится лучше, чем с первого.

Если есть опасения за нагрузку следует провести нагрузочное тестирование в самом начале на массиве сгенерированных данных, чтобы проверить жизнеспособность архитектуры под нагрузкой.

Появление новых требований или изменение существующих

В процессе работы появляется все больше требований или текущие требования меняются.

Методологический метод ослабления риска

Короткие итерации с фиксацией требований (scrum) или continuous delivery [3] (kanban).

Критика

Работая маленькими итерациями мы постоянно вынуждены проводить рефакторинг и переделывать уже существующий код. Команда располагает требованиями только на две недели вперед и не видит полной картины. Цена промаха снижается, но и КПД становится меньше, потому что не обладая полнотой информации не известно на сколько «навороченная» архитектура потребуется проекту.

Дополнительные методы ослабления

Изменение требований – это риск с почти 100% вероятностью наступления, поэтому стоит считать, что он уже наступил до начала проекта и заложить его в календарный план и оценку трудозатрат. Однако именно он наиболее болезненно воспринимается программистами. Психологически сложно выкидывать код, который хорошо работал, потому что, видите ли, бизнес-модель не сработала. К сожалению надо просто принять тот факт, что мы регулярно будем отправлять часть кода на помойку. Более того, чем больше старого кода и костылей мы выкинем, тем лучше.

Слабая связанность и модульное проектирование, onion-архитектура [4]

К счастью на сегодняшний день существует множество готовых решений, обеспечивающих слабую связанность. Пойте мантру S.O.L.I.D [5], ежедневно на работе. Мыслите интерфейсами, а не реализациями. Потенциально, любая реализация может отправиться на помойку. Заведите за правило использовать принцип IOC/DI. Если у вас много JavaScript-кода обязательно пользуйтесь RequireJS [6] или чем-то аналогичным.

Откажитесь от идеи спроектировать все приложение в едином стиле. Делите его на подсистемы. Вам придется заплатить за это некоторым дублированием кода, но возможность выкинуть и переписать с нуля целую подсистему приложения, не разломав при этом другие части гораздо важнее.

Эволюционный рефакторинг совместно с классическим проектирование сверху-вниз

Людей, которых действительно можно назвать System Architect очень-очень мало. Настоящий архитектор должен поучаствовать в трех-четырех действительно больших проектах, парочку из них завалить, написать и выбросить вагон и маленькую тележку кода. Если у вас есть такой человек — честь ему и хвала. Беда в том, что у него не будет времени писать код. Его работой на полную ставку станет проектирование на уровне крупных блоков и контекстов системы. Проектирование, порой, целых подсистем уйдет на откуп Team Lead’ам и страшим разработчикам. Кто-то из них справится с задачей, а кто-то – нет, поэтому важно, чтобы все модули были независимы.
Устанавливайте взаимоотношения разработчик между смежными командами, например команда UI – клиент команды Backend.

Такой подход дает следующие преимущества:

  1. Вы избавляетесь от священного идола и готовы постоянно проводить рефакторинг и улучшать качество кода. Могут появиться «пахнующие» куски, но только в рамках определенного модуля. Общий уровень качества кода будет постоянно поддерживаться на высокой отметке
  2. Вам проще взаимодействовать с клиентом и менеджментом, ведь объяснить, что за эту неделю у нас +3 новых фичи гораздо проще, чем у нас +100500 новых классов
  3. Вы экономите время, в том числе на согласования и интеграцию. Я не однократно наблюдал пьесу в трех актах:
    Акт 1 Команде UI не нравится API и надо все сделать иначе и вообще поля у нас названы совершенно иначе.
    Акт 2 Команда бекенда считает, что команда UI — балбесы и вообще не понимают, как работает ядро
    Акт 3 Менеджеры обеих команд уговаривают сделать хоть как-нибудь, чтобы работало, иначе уволят всех на фиг. Занавес.

Если коммуникация между командами налажена, то каждая из них получает ценную обратную связь. Наверняка существует способ немного спецификацию с обеих сторон, что позволит методом проб и ошибок получить архитектуру, которая работает во всех смыслах этого слова.

Действительно переиспользуемый код следует вынести в ядро. Каждый модуль (подсистема) может зависеть от функций ядра, но сами модули не должны ничего знать друг о друге. Если требуется обеспечить взаимодействие двух модулей, используйте событийно-ориентированное программирование. Подписывайтесь на события только через ядро. Если вам приходится дублировать какой-то функционал в двух-трех модулях – это сигнал для включения его в ядро или выделение в отдельную внешнюю зависимость. Такая организация кода позволит выкинуть любой модуль или переписать с нуля, возможно даже, используя другой язык программирования.

Graceful Degradation [7] для кода с низкой ценностью, хитрое YAGNI [8]

Ценность (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/