В свое время средства разработки программного обеспечения — компиляторы, линковщики и сборщики — создавались для использования отдельным программистом на персональном компьютере. По мере развития инструментов росла их ресурсоемкость — требовались все более мощные машины. Затем инструментарий был адаптирован для командной разработки на небольшом вычислительном кластере, в котором выделенные серверы каждый раз выполняют сборку с нуля. Еще недавно эта парадигма прекрасно отвечала текущему моменту, но оставляла за бортом преимущества работы с облаками.

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

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

Системы сборки

Сердце любого проекта разработки ПО — система сборки, преобразующая один вид данных в другой. Сам процесс сборки состоит из: этапов компиляции кода на языках вроде Си, C++, Java или C# в машинный или промежуточный код; тестирования модулей и статического анализа; упаковки и развертывания приложений. Время сборки — важнейший фактор жизненного цикла разработки ПО, поэтому эффективность системы сборки становится одним из главных двигателей продуктивности разработки [1].

Системы сборки с годами эволюционировали, пройдя путь от скриптов на основе утилиты make 70-х годов к инструментам на основе заданий вроде Ant и MSBuild в 90-х, интегрированным средам разработки наподобие Visual Studio и Eclipse в 2000-х и современным встроенным предметно-ориентированным языкам, таким как Gradle, SCons, Rake и Shake [2]. Однако, несмотря на высокий уровень развития систем сборки, инструменты для больших программных проектов остаются сложными в понимании, имеют ненужные или незадокументированные функции и ограничения, привязаны к среде и не масштабируются, лимитируя численность команды программистов или количество машин. В частности, у большинства платформ сборки очень слабые механизмы абстрагирования: у make — правила, а у MSBuild — макросы. Языкам сборки обычно недостает механизмов задания области видимости — например, пространства имен. Планирование нередко выполняется не машиной, а пользователем (Ant, MSBuild и другие предметно-ориентированные языки). В большинстве систем инкрементальная сборка реализована с использованием отметок времени, которые в распределенных системах ненадежны ввиду различий в ходе часов на разных машинах.

Для повышения надежности во многих крупных программных проектах предпочитают полные, иногда последовательные сборки. Но при этом падает скорость и неоптимально расходуются ресурсы. Как показал опыт Microsoft, когда программисты работают над одной и той же кодовой базой, например Windows или SQL Server, 70–90% используемых ими настроек идентичны, поэтому при сборках их вполне можно было бы использовать совместно внутри команд. Более того, когда файлы исходного кода Windows или SQL Server передаются в систему управления версиями, то в 80% случаев спецификация сборки не меняется, что позволяет выполнять инкрементальную сборку. В некоторых крупных компаниях, например в Google, перешли на новую распределенную систему сборки [3], но многие разработчики по-прежнему предпочитают консервативный, затратный подход.

Сегодня разработчикам ПО доступен широкий круг коммерческих и созданных собственными силами инструментов, постоянно появляются новые средства, но большинство из них не рассчитаны на перенос в облачную среду.

Инструменты разработки в облаках должны соответствовать следующим ключевым требованиям:

  • детерминированность и идемпотентность (неизменяемость при повторном действии), что необходимо для обеспечения отказоустойчивости;
  • независимость от среды (для распределения и мультиарендности);
  • согласованность (для простоты в использовании);
  • сочетаемость и самодокументируемость (для интеграции потоков работ и управления ими);
  • масштабируемость (для практической пользы);
  • измеримость и контролируемость (для обратной связи и оптимизации системы).

Детерминированность и идемпотентность

Любой инструмент, исполняемый в распределенной среде, должен быть детерминированным и идемпотентным.

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

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

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

Независимость от среды

Переносимость настроек и кодов между различными средами — важнейшее свойство, необходимое для распределения приложения и обеспечения мультиарендности. Нужна независимость как от вычислительной среды (одиночный компьютер, серверный кластер или облако), так и от пользовательской (пути, переменные окружения, настройки реестра и т. д.).

Инструменты разработки исполняются по-разному в различных вычислительных средах, инкапсулируя и абстрагируя предоставляемые сервисы. К примеру, многие инструменты полагаются на отметки времени, чтобы определить, устарели ли сгенерированные настройки. На одиночном компьютере это разумно, но в распределенных системах, для которых свойственны расхождения в ходе часов на разных машинах, полагаться на отметки времени ненадежно. Для решения этой проблемы в системах версионного контроля наподобие Git [4] различия в файлах идентифицируются по хэшам содержания.

Многие инструменты внедряют информацию о среде (время, абсолютные пути и глобальные уникальные идентификаторы) в свои выходные параметры, привязываясь тем самым к среде и затрудняя перенос результатов своей работы между средами разных пользователей и машин. Типичный пример — информация об отладочных символах в Visual Studio. Кроме того, использование абсолютных путей файловой системы вместо относительных затрудняет мультиарендное исполнение.

Согласованность

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

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

Сборка сложных проектов, таких как SQL Server или Windows, требует от десятков до сотен различных инструментов. Различия в аргументах командной строки, форматах вывода и возвращаемых значениях сильно усложняют координацию работы разных компонентов. Иногда подобные проблемы возникают из-за недоработок — например, Microsoft не предлагает стандартного анализатора командной строки для платформы. NET, увеличивая тем самым многообразие командных строк. В новой же системе сборки Microsoft применяются типизированные методы-обертки, позволяющие придерживаться единых стилевых рекомендаций, что упрощает использование инструмента.

Сочетаемость и самодокументируемость

Средства разработки должны быть сочетаемыми — то есть допускать возможность выстраивания конвейера, в котором результат работы одного инструмента служит входными данными для следующего. Сочетаемость имеется у большинства утилит командной строки Unix и объектов Windows PowerShell. Составляя цепочку из относительно простых инструментов, решающих одну задачу, можно создавать сложные сценарии. Когда весь поток операций реализован в одном инструменте, потенциал многократного использования ухудшается. Инструменты, в которые втиснуто множество функций, нельзя оптимизировать по быстродействию извне или расширять.

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

Масштабируемость

Масштабируемость — главное свойство, ради которого средства разработки переносят в облачную среду.

Существует немало инструментов, оптимизирующих быстродействие путем исполнения нескольких задач (с разными входными и выходными данными) внутри одного процесса. Таким образом снижаются непроизводительные затраты на обмен информацией и запуск процессов, поэтому данный подход можно рекомендовать для повышения быстродействия на одноядерных машинах. Но механизму оркестровки он будет мешать распределять задачи между несколькими процессами. Соответственно, последовательный запуск оптимизированных задач будет гораздо медленнее параллельного исполнения тех же задач без оптимизации. Разработчикам инструментов стоит помнить о законе Амдала, согласно которому параллельная программа не может выполняться быстрее, чем ее нераспараллеленные фрагменты. Поэтому объем нераспараллеливаемых участков процесса сборки лучше свести к минимуму, даже если вырастет общий объем затраченных ресурсов.

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

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

Измеримость и контролируемость

Ключевая функция эффективной системы сборки — способность тщательно следить за различными характеристиками системы и анализировать показатели при различных условиях, чтобы оптимально развертывать инструменты и управлять ими.

В системе сборки Microsoft делается акцент на знании критических путей — например, наихудшего времени сборки при условно неограниченном объеме ресурсов; времени, затраченного на разных этапах выполнения системы; распределении нагрузки между набором машин. Изучение этих показателей помогает лучше оценить производительность конфигурации (в частности, выяснить, где и как долго происходит обмен информацией, какие и как используются ресурсы, на каких этапах задания ожидают доступа к ресурсам), а также характеристики конкретных сборок (в том числе лучшее и худшее время выполнения, частоту попаданий в кэш и узкие места, например нераспараллеленные фрагменты кода).

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

***

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

Детерминированность и идемпотентность; независимость от среды; согласованность; сочетаемость и самодокументированность; масштабирование; а также измеримость и контролируемость одинаково важны для современной облачной системы разработки — ни одним из этих принципов нельзя жертвовать в пользу других. Даже если системы высокооптимизированы для своей конкретной среды, нужно сохранять их независимость, поскольку инструменты становятся обузой, когда среда, породившая их, больше не существует. Следование этим принципам поможет повысить продуктивность, обеспечив инструментам разработки действенность, производительность и возможность многократного использования.

Литература

  1. P.M. Duvall, S. Matyas, A. Glover. Continuous Integration: Improving Software Quality and Reducing Risk. Addison-Wesley, 2007.
  2. B. Smith. Ant, Maven and Gradle — A Side by Side Comparison. Blog, 11 May 2012. URL: http://xpanxionsoftware.wordpress.com/2012/05/11/ant-maven-and-gradle-a-side-by-side-comparison (дата обращения: 11.03.2014).
  3. N. York. Build in the Cloud: Accessing Source Code. Blog, 21 June 2011. URL: http://google-engtools.blogspot.com/2011/06/build-in-cloud-accessing-source-code.html (дата обращения: 11.03.2014).
  4. J. Loeliger, M. McCullough. Version Control with Git: Powerful Tools and Techniques for Collaborative Software Development. 2nd ed., O’Reilly Media, 2012.

Чандра Прасад (chandra.prasad@microsoft.com) — руководитель разработки отдела Tools for Software Engineers, Вольфрам Шульте (schulte@microsoft.com) — генеральный менеджер по разработке, компания Microsoft.

Chandra Prasad, Wolfram Schulte, Taking Control of Your Engineering Tools. IEEE Computer, November 2013, IEEE Computer Society. All rights reserved. Reprinted with permission.