Разработка
Трудности проектирования
С точки зрения бизнеса, постоянные неудачи, срывы сроков и превышения бюджетов в области разработки не могло быть оправдано молодостью этой отрасли. Бизнес требовал, и разработчики искали способы более точно предсказывать цену, сроки и риски разработки.
Приветствую вас, коллеги!
В предыдущей колонке я рассказал о кошельке Миллера — закономерности, которая правдоподобно объясняет причины возникновения сложности в программных продуктах. В этой колонке я попробую показать, как из этого знания можно извлечь практическую пользу и упростить один из сложнейших этапов разработки — предварительное проектирование.
Большинство разработчиков через несколько лет коммерческой разработки так или иначе сталкиваются с ситуацией, когда очередное требование заказчика (или собственная идея) встает перпендикулярно существующей архитектуре, и, чтобы сделать желаемое, нужно либо переписывать все с нуля, либо использовать эпических пропорций костыли, грозящие в будущем серьезными проблемами в развитии продукта.
В соответствии с уже рассказанными теоретическими выкладками, такие ситуации происходят из-за комбинации трех факторов:
- Молодость отрасли и отсутствие устоявшихся, проверенных временем решений приводит к тому, что заказчик программного обеспечения чаще всего слабо представляет, что должно получиться в конце. И трудно его в этом винить — если вспомнить зарю автомобилестроения и посмотреть старые фотографии, то ясно видно, что перед тем, как автомобили стабилизировались в том облике, который мы сейчас видим на улицах, много лет разработчики экспериментировали с внутренним устройством и внешним обликом автомобилей. Первые десятилетия автомобили походили то на карету, то на капсулу, то на неведомую зверушку с креативным расположением всего. Сейчас то же самое происходит в разработке программного обеспечения — все экспериментируют с формой, содержанием, технологии меняются с огромной скоростью и нельзя при предварительном проектировании точно сказать, как именно будет выглядеть разрабатываемый проект через год. Постоянно изменение требований — одна из фундаментальных сложностей разработки, и я еще не раз вернусь к этому вопросу.
- Кошелек Миллера обеспечивает невозможность при разработке держать в памяти все варианты дальнейшего развития программы и менять ее всегда правильным и непротиворечивым способом. Даже если разработчик старается ограничить количество одновременно используемых сущностей магической цифрой «7», добавляя слои абстракции и активно используя композицию, реальность вносит свои коррективы в виде «протекающих абстракций», предельного количества уровней абстракций и накапливающегося технического долга (о нем я расскажу в следующей колонке). В том же автомобилестроении кошелек Миллера компенсируется наработанной теоретической базой — инженерам нужно удерживать в фокусе внимания только ключевые отличия от того, что они уже знают.
- Многие изменения взаимоисключающие — машина может быть либо спорткаром, либо на гусеничном ходу, нельзя сделать шасси, адекватно совместимое с обоими способами передвижения. При разработке программы постоянно возникают ситуации, когда требования заказчика требуют развития в определенном направлении, которое меняет архитектуру программы и делает ее несовместимой с тем, что свойственно противоположному направлению развития. И если через полгода выяснится, что выбранное направление было неверным — то быстро поменять танковую броню и гусеницы на легкий пластик спорткара и шины уже не получится.
Борьба с архитектурой
Столкнувшись с архитектурными проблемами несколько раз, разработчики, руководствуясь здравым смыслом, начинают придумывать способы борьбы с проблемой. Из наиболее интересных могу рассказать о следующих:
- Полное проектирование продукта перед разработкой. Очень популярный среди начинающих способ, когда перед тем, как писать код, все поведение и внутреннее устройство программы описывается в текстовых документах. Проводя частные консультации, я много раз видел компании, где перед началом разработки команда месяцами работала только в Microsoft Word и каждый день устраивала совещания, бесконечно обсуждая созданную в уме программу. Как нетрудно догадаться, способ абсолютно не рабочий, потому что проблема сложности будет распространяться на текстовую документацию точно так же, как и на создаваемый код. Хуже того, в отличии от кода, текстовое описание программы не позволяет использовать дополнительные инструменты компенсации сложности — тесты, continuous integration, рефакторинг, DSL и многое другое (хотя адепты Rational со мной не согласятся). Чаще всего меня звали как раз к тому моменту, когда после нескольких месяцев планирования, команда начинала тонуть в производимой документации, так и не написав ни строчки кода.
- Использование максимально общих архитектурных решений, которые по необходимости можно менять в любую сторону. Как правило, такой подход знаменует второй этап борьбы с проектированием — после нескольких лет неудачных попыток целиком описать программу на бумаге, разработчики переходят к попыткам создать программу с максимально гибким внутренним устройством. Но, вооружившись знанием о природе сложности, нетрудно предсказать, чем окончатся такие попытки. Как только количество абстракций (а именно они дают гибкость) в какой-либо точке превысит волшебное число «7», сложность этой точки начнет неуклонно возрастать, и через некоторое время разработчики с удивлением увидят stacktrace своего java приложения, в котором более полусотни вызовов небольших, простых элементов. Именно эта цепочка из многих десятков уровней абстракции становится той сложностью, которая не дает разработчику понять, что же происходит в коде и эффективно убивает возможность дальнейшего развития программы.
- Полный отказ от предварительного проектирования. Такой подход я видел у опытных команд, уже успевших побороться со сложностью проектирования и решивших, что овчинка категорически не стоит выделки. По их мнению, проще в случае проблем полностью переписать часть кода, чем пытаться предсказать будущие изменения. Опыт показывает, что такой подход тоже порождает проблемы — не попытавшись заранее спланировать внутреннее устройство нашей программы, мы ограничиваем себя кошельком Миллера уже в процессе разработки, развивая программу, как садовник выращивает сад — примерно ясно, что должно получиться, а дальше как вырастет. Иногда вырастает хорошо, чаще — разработка уходит «немного не в ту сторону» и разработчики тратят много времени на создание и последующее героическое решение архитектурных проблем, которые сами же себе и придумали.
Agile спешит на помощь
Шестая по счету колонка — оптимальное, на мой взгляд, место чтобы более серьезно рассказать о методологии Agile. Я уже рассказал про специфику разработки как постоянное создание нового, про проблему сложности с кошельком Миллера, и, наконец, про трудности проектирования, вызванные этими занимательными фактами. Такое положение дел не могло устраивать крупный бизнес, автоматизация которого является одной из крупнейших областей разработки программного обеспечения (на самом деле, статистики по «областям разработки программного обеспечения» у меня нет, и как ее собрать я не представляю, если кто из читателей обладает этой информацией — буду благодарен за наводку). С точки зрения бизнеса, постоянные неудачи, срывы сроков и превышения бюджетов в области разработки не могло быть оправдано молодостью этой отрасли. Бизнес требовал, и разработчики искали способы более точно предсказывать цену, сроки и риски разработки.
В начале 2001 года инициативная группа известных специалистов по разработке выдвинула концепцию «гибкой модели разработки», представленную в Agile Manifesto. Основная идея Agile состоит в том, что нужно балансировать проектирование и непосредственно разработку, для чего вводится понятие «итерации», являющейся в Agile методологи ключевой.
Сила итераций
Итеративный подход противопоставляется классическому Waterfall подходу устоявшихся отраслей, при котором заказчик пишет четкое техническое задание, по нему проводится проектирование и затем — разработка. Agile методология предлагает разбить разработку на небольшие, изолированные друг от друга итерации длиной от одной о четырех недель. В начале каждой итерации заказчик совместно с командой решает, что они хотят видеть в конце итерации — после чего команда принимается за работу. По окончании итерации команда показывает заказчику, что получилось, а что нет — и они вместе согласовывают дальнейшее развитие проекта и задачи на следующую итерацию. Подход не лишен недостатков, но позволяет эффективно бороться с упомянутыми в начале статьи проблемами:
Проблема непонимания заказчиком конечного результата решается тем, что программа создается небольшими частями, и заказчик может постоянно корректировать развитие по мере того, как ему становятся понятны ограничения и специфика.
С кошельком Миллера частично борется то, что итерации небольшие и изолированные друг от друга — разработчики вполне в состоянии удерживать в памяти ограниченный набор изменений, который вносится на протяжении двухнедельной итерации.
Проблема взаимоисключающих изменений также частично решается вовлеченностью заказчика — на каждом этапе он корректирует направление развития и шансы промахнуться на 180 градусов гораздо ниже, чем когда заказчик общается с командой только один раз на этапе согласования технического задания.
И их слабость
Безусловно, Agile методологии — это далеко не серебряная пуля. У них есть множество недостатков, углубляться в которые я пока не буду — это и необходимость вовлекать заказчика в процесс разработки, и невозможность заранее спланировать бюджет и сроки, трудности распиливания сложной функциональности на двухнедельные итерации, неприменимость к ряду областей, в которых разработка без полного предварительного проектирования невозможна — и многое другое. Тем не менее, правильное применение гибких методологий способно сэкономить огромное количество времени, денег и понизить шансы на провал проекта — что, в целом, подтверждается ростом их популярности в последнее десятилетие. Главное, на мой взгляд — это понимать причины возникновения Agile, знать проблемы, которые решает эта методология, и использовать ее тогда, когда это полезно. А когда не полезно — не использовать.