Как вырастить программу из прототипа

в 8:57, , рубрики: agile, DRY, KISS, unite, выставки, говнокод, методологии разработки, Программирование, проектирование, прототип, прототипирование, управление проектами, метки: , , , , , , , , , ,

Каждую неделю на профильных блогах мы читаем как нужно использовать методологию X и фреймворк Y, чтобы написать хорошо спроектированный и легко поддерживаемый софт. Нам постоянно говорят, что, мол, говнокод — это плохо, рефакторинг — наше все, дают те или иные очень важные сферические советы в вакууме. В большинстве этих статей можно встретить абстрактные философские нравоучения, например, вот это я распечатаю и повешу при входе в офис:
Как вырастить программу из прототипа

А что, если я скажу, что не все проекты одинаковые, и некоторые из них не то что можно, а даже нужно тщательно выращивать из прототипа? Об этом я рассказывал на конференции Unite'12, а сейчас расскажу вам.

Вступление

Идея написать этот текст у меня пылилась в голове уже очень давно. Но реальным катализатором вдруг стала вчерашняя статья под заголовком «Почему нельзя превращать прототип в итоговую программу». Оказывается, мне вдруг нельзя делать то, что я успешно делаю уже два года и горжусь этим.

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

Отсюда и проявления яростного холивора в комментариях ко всякому подобному посту. Так что, позвольте сперва рассказать о специфике проектов, которыми я обычно занимаюсь.

Проекты

Я работаю в компании Interactive Lab. В основном, мы делаем оффлайн интерактивные инсталляции, создание софта для которых сильно отличается от обычного software development:

  • Нет четкого ТЗ (вот выставка, сделайте круто!),
  • Все проекты небольшие, но очень разные (совсем разные, а не «в том проекте мы юзали mysql, а в этом будем юзать mongodb — придется ЗАГУГЛИТЬ КОД!»),
  • Очень короткие дедлайны, которые нельзя перенести («извините, мы что-то не успеваем, перенесите пожалуйста Атом Экспо на месяцок-другой»),
  • Часто работают всего 1 день во время выставки, а потом выкидываются,
  • Комбинация софта и специального железа,
  • Полный контроль над железом (тормозит? оптимизировать? да вы что, пошли купим gtx 690).

Значит, сижу я в офисе, никого не трогаю, читаю Хабр, там, персик ем… И тут подходит ко мне босс и задает вопрос, который в общих чертах сводится к следующему: «Валентин Владимирович, а сколько у Вас займет сделать вот такую штуку, которую мы никогда не делали и смутно вообще представляем как можно сделать?.. Ммм, недели две, да?». Ну, и выясняется потом, что какое-то мероприятие через три недели, клиент «что именно хочет не знает, но хочет очень круто чтоб было все», никаких ассетов нет, идей тоже как-то не особо…

Процесс

Принцип итеративной разработки уже совсем не нов, и ему посвящены сотни статей, в том числе и на Хабре. Как там, евангелисты Agile говорят? Берем, значит, фичелист, смотрим, что сможем сделать за итерацию, скажем, в две недели. Делаем. Смотрим опять…

Так, стоп! Фичелист? У нас нет ни ТЗ, ни четкого представления что это вообще должно быть. Итерация в две недели? У нас времени всего 15 рабочих дней, какие две недели, вы что!?

Вот так мы пришли к следующему алгоритму:

  1. Брейншторм идеи.
  2. Максимально простой и быстрый прототип с тестовыми ассетами за день-два.
  3. Тест на целевом железе.
  4. Полнейший отстой? GOTO 1.
  5. Вроде неплохо, но нужно изменить/добавить кое-что.
  6. Апдейтим прототип за денёк.
  7. Если еще не дедлайн — GOTO 3.
  8. Отсыпаемся.

Как Вы видите, итерациями по 1-2 дня прототип растет в финальное приложение. И это отлично работает.

Пройдя глазами по алгоритму, проследовав по циклу пару раз и выйдя из него, можно сразу представить себе очевидные его плюсы:

  • Концепт создается итеративно с обратной связью от прототипа. Нет необходимости писать исчерпывающее ТЗ перед началом работы, которое еще и никак не гарантирует успешность первоначальной идеи.
  • Насколько концепт хороший видно в первые же два дня. Нет ничего неприятнее, чем понять за день перед дедлайном, что вся идея полнейший отстой.
  • Ближе к дедлайну всегда есть готовое приложение. В нем не все фичи, оно не достаточно красивое, но его уже можно ставить на выставку. Такого, что у нас просто ничего не готово, не может быть в принципе.
  • Все нюансы связки софт+железо можно выяснить сразу же на этапе первых итераций. Если вы думаете, что на 9ти экранах и 5ти видеокартах с общим разрешением 4098х2304 ваше приложение заработает так же, как и на офисном FullHD мониторе, вы сильно ошибаетесь.
  • Поигравшись с прототипом, нет необходимости все переписать *более правильным кодом* в финальное приложение. Сам прототип будет приложением.

Прототип

Почему-то в большинстве статей о прототипах говорят как о чем-то временном, мерзком, липком и неприятном. Мол, на небрежный прототип недобросовестные разработчики накручивают все новые и новые фичи, превращая его в монстра Франкенштейна. А главный вывод — нужно все обязательно переписать, и наступит счастье!

Как вырастить программу из прототипа

Мне иногда задают укоризненный вопрос: «А Вам не стыдно за код своего приложения, который получился постепенным апгрейдом прототипа?». Мне, честно говоря, стыдно за весь свой код, который я писал больше месяца назад. Почему? Потому что за прошедший месяц, в том числе и пока писался этот код, лично я вырос как разработчик. И сегодняшний Я написал бы все уже совсем по-другому. Ну а завтрашнему Я очень бы хотелось все это переписать к чертям! Поэтому, я каждый раз грязно улыбаюсь, когда слышу предложения взять и переписать все заново.

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

Вообще, сходимость написанного кода к говнокоду при количестве строк n > N сильно зависит от конкретного человека. Если ваш программист лепит к дереву-прототипу щупальца, не особо заботясь о его будущем, почему вы думаете, что заново все переписав, он в конце асимптотически не сойдется опять к подобному результату?

Как вырастить программу из прототипа

Делать, просто, нужно все правильно. Хорошо себе представлять, что это не прототип на выброс, а живой организм, который от ваших действий на этапе ростка сильно изменится, выросши в финальную свою форму. И форма эта может СИЛЬНО отличаться от того, что вы закладывали в его генотип при старте.

И как ваша бабушка на своем небольшом огородике за городом выдергивает траву вокруг ростков, подрезает их, прививает одни сорта на другие, — так же и вы должны аккуратно следить за своим прототипом по мере его роста. Этим и отличается хороший программист от плохого. Хороший программист видит куда растет его код, что нужно сделать уже сейчас, чтобы потом не пришлось рубить мертвые ветки; понимает как аккуратно избавиться от жуков, терроризирующих дерево; знает когда нужно его огородить так, чтобы оно росло в нужном направлении.

KISS, DRY, OOP, проектирование и другие экстремумы

Если бы мне давали 10 рублей за каждую статью, в которой упоминаются KISS или DRY, я бы давно купил BMW 3, о которой мечтаю. Эти баззворды уже заткнули за пояс страшное OOP, на котором сидят The Gang of Four и хлестают его паттернами проектирования (та еще картина, конечно).

Все, что имеет значение — может ли человек вырастить из прототипа здоровое дерево.

Конкретно в наших реалиях всё, к тому же, нужно сделать ещё и очень быстро. Итерации-то день-два, не больше. Вот и начинаешь бороться с самовольными интерпретациями KISS, DRY и всякими OOP головного мозга. Что, честно признаюсь, не всегда получается.

Это как, простите, с РПЦ. Опытные священники, теологи и историки религии, поди, знают почему вот этот конкретный обряд именно такой, хотя со стороны выглядит весьма странно. Но спроси любого из 97% остальных верующих — получишь ответ, мол, делай как сказали и будет тебе спасение. И хорошо, если не побьют.

И KISS, и DRY, OOP… что там еще? Проектирование всякое. Все это хорошие концепции, но дрожь берет, когда видишь, как они в жизни применяются. И понимаешь, что такое применение с выращиванием прототипа совместимо чуть менее, чем никак. Отсюда и страшные хромые деревья с цветками-людоедами.

Особенно, когда человека бросает на экстремальные ситуации. Если OOP, то обязательно 100500 абстрактных классов и по интерфейсу на каждого, да еще, чтобы что-то конкретное создать нужно делать это через специальную фабрику фабрик билдеров. Зато полнейший DRY. Все мегаабстрактно и никакой код не повторяется. И никого не смущает, что, чтобы всего этого добиться, потребовалось написать в 20 раз больше кода. А ведь еще и IoC с DI надо вфигачить, чтоб совсем никто не мог понять где эта программа начинается вообще.

А программисты же люди такие, им дай только все усложнить. Думаешь вырастить прототип кипятильника, а через неделю ловишь себя на мысли, что сидишь ядерный реактор пишешь. И как, блин, с кипятильника на реактор мысль перескочила, уже не понятно.

А KISS, в основном, сваливается в противоположный экстремум. Где в середине разработки понимаешь, что все настолько Simple, что ветка, вообще говоря, у дерева может быть только одна, и ничего с этим уже не поделаешь. Выкидываем прототип, говорим, что прототипирование отстой и начинаем заново.

Так а что делать-то?

Как это обычно в фильмах говорят. «Забудь все, что ты знаешь!», «Чтобы поймать кошку, тебе нужно стать кошкой!» и тд. Но это не про нас. Не нужно ничего забывать. Наоборот, хорошо было бы помнить примеры экстремумов применения всяких методик, чтобы в минутку мимолетной рефлексии вовремя осознать, что сносит вас куда-то не туда.

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

Итак, что нужно делать, чтобы дерево-прототип не завяло? Несколько на первый взгляд простых принципов:

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

Целостность
В наше время принято, что над одним проектом работают сразу несколько программистов. Каждый делает какой-то свой модуль. Так вот, важные модули должны разрабатываться параллельно, синхронизация разработчиков проходить как можно чаще. В идеале, раз в итерацию. Цель — как можно раньше собрать все работающие модули вместе и посмотреть что получилось.

Не важно на какой стадии разработки сейчас модуль. А если (как обычно бывает) подошло время интеграции, а код в таком состоянии, что ничего из прошлого УЖЕ не работает, а из нового ЕЩЕ не работает, возможно, вас это шокирует, но неработающие места МОЖНО ЗАКОММЕНТИРОВАТЬ!

Решение текущих проблем
Допустим, есть простая задача на час-два. Но, программисты же люди такие, как у шахматиста, в голове сразу же разворачивается партия на 64 хода вперед. Становится понятно, что потом придется сделать одно, а чтобы была расширяемость, второе. К тому же, ходу на 32м уже сейчас видно, что для пущей фотореалистичности балансировки нагрузки просчета физики нужно начинать делать третье прямо сейчас. Да и вообще, напишем-ка фреймворк!

ЛИНЕЙКОЙ ПО РУКАМ!!! А потом ногами-ногами! Сейчас решаем только текущие проблемы. Самым быстрым способом. Как показывает практика, у таких размашистых фичей есть большая вероятность сильно измениться или вообще отпасть полностью.

Аккуратно следить за экстремумами
Хорошо, все делаем быстро и тупо… НЕТ! Ну что ж вы опять! Очень старайтесь не впадать в крайности и делать все прямо совсем тупо. Вы же видите куда вас все это дело приведет — оставьте себе моменты для маневра: аккуратное OOP без излишеств, впилите в компоненты state machines, сделайте чуть больше настроек, чем нужно сейчас, отрефакторите немного разросшийся класс. Делать все просто и только то, что нужно, не значит обязательно плодить говнокод. Просто, у некоторых по-другому не получается.

Максимальная приближенность ассетов к финальным
Все ассеты с самого начала по основным параметрам нужно подгонять максимально близко к ожидаемым. Пусть в первом прототипе вместо картинок будут lolcats из личного архива разработчика, а вместо планируемых FullHD видео скачанныйкупленный Iron Man 2 в BluRay качестве.

Если вы планируете показывать 100 картинок, нагенерите их ровно 100 (а лучше 150 для надежности), вместо того, чтобы всю жизнь прототипа проверять его на плавно скролящихся трех картинках. А потом с удивлением обнаружить, что 100 картинок лагают, виснут и в память не влазят.

***

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

А я лишь надеюсь, что из этой статьи каждый возьмет что-то полезное для себя. И что деревья-прототипы будут расти, а само слово «прототип» потеряет свой негативный оттенок.

Автор: valyard

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js