Наводим порядок в разработке ПО вместе с maven. Часть 1

March 9, 2009Comments Off on Наводим порядок в разработке ПО вместе с maven. Часть 1

Разработка программного обеспечения не самая простая наука. В общем объеме времени, отданного на создание продукта, написание, непосредственно, программного кода занимает далеко не самую большую долю. По мере увеличения сложности создаваемого продукта, финансовых и временных затрат, опережающими темпами растут затраты на анализ требований, планирование и организацию коллективной работы, на повышение качества. Почти года назад я написал несколько серий статей посвященных не, собственно, программированию, а различным технологиям и инструментам, поддерживающим процесс разработки ПО. Это были статьи, рассказывающие об управлении версиями документов (SVN и perforce), ведения списка задачи и багов в JIRA, хоть и поверхностно, но я прошелся и по вопросам тестирования веб-проектов с помощью badboy и jmeter. Сегодня пришло время раскрыть еще один инструмент (maven), с помощью которого ход разработки ПО должен стать более управляемым.

Я не хотел бы начинать рассказ об maven и том, что это такое, с какой-то цитаты или описания maven взятого с wikipedia или сайта maven.apache.org. За сухой официальной формулировкой легко потерять главное – те конкретные плюсы, в реальных житейских ситуациях, которые вы получите, если будете использовать maven. Поэтому я расскажу об истории своих взаимоотношений с maven и о том, как он шаг за шагом завоевывал мое уважение. Хотя основная сфера моей деятельности связана с java и веб-технологиями и maven считается java-инструментом, но надеюсь что идеи, которые я расскажу, пригодятся и тем, кто работает с .net, php и другими языками и платформами (разработчики на flash/flex, внимание: maven уже идет к вам).

В далеком 2006 г я частенько посещал один из российских форумов, посвященных программированию и java в частности. Читая чужие вопросы и ответы, можно узнать много нового, а если уж попробовать отвечать на них, то рост знаний и навыков просто обеспечен. Единственная проблема была в том, что часто люди, у которых возникали затруднения с каким-то кодом, помимо словесного описания, что у них есть, что нужно получить, и что не работает, прикладывали к сообщениям форума и файлы с архивами своих проектов (точнее, каких-то выжимок из них). В этих архивах были файлы с исходными кодами на java и файлы проекта. Файлы проекта - это особые файлы, нужные для правильной работы IDE (intellij idea, eclipse, netbeans). В файлах проекта помимо всякой малополезной ерунды, вроде настроек шрифтов, списка открытых окон, были еще и сведения о библиотеках используемых для компиляции и запуска проекта. Я не открою большой тайны, если скажу что для java (да и для любого другого языка) характерно огромное количество библиотек, framework-ов, для web, для работы с базами данных, веб-сервисами. Библиотек этих много, много и их версий (ведь библиотеки развиваются). Теперь представим себе последовательность шагов, которые должен был бы выполнить я, чтобы просмотреть чей-то проект и быстро (ведь это все на голом энтузиазме) найти ошибку. Я загружаю архив, распаковываю его, смотрю содержимое и должен угадать на основании расширения файлов, то в какой ide его нужно открыть. Предположим, что это intellij idea, даже предположим, что у меня такая же версия intellij idea, как и та которой пользуется тот, кто отправил мне архив проекта. Если же проект сделан не в intellij idea, а, например, в eclipse, то я трачу время на то, чтобы создать проект и импортировать в него файлы. Импорт файлов не так прост, как кажется: давайте рассмотрим его на примере веб-приложения. Хотя есть sun-стандарт, описывающий то какая должна быть структура каталогов и файлов в конечном приложении (размещаемом на сервере), но вот стандарта того, как должны быть организованы (опять таки в каких каталогах и подкаталогах должны) исходные файлы проекта, такого стандарта нет. Если каталог с java-файлами худо-бедно, но во всех ide называется src, то каталог с выходными файлами может называться out, output, classes. По-разному могут называться и размещаться в каталоге проекта и папки, хранящие файлы графических ресурсов, css-файлов и файлов с javascript-ом и шаблоны html-страниц. Но сложности, связанные с необходимостью создания нового проекта и “раскладки” файлов из старого проекта по новым правилам, кажутся совсем незначительными, если мы подумаем о зависимостях проекта, т.е. о том какие библиотеки нужны для его работы.

Итак, я загрузил файл проекта, запустил его на компиляцию и получаю множество ошибок вида: “не найдена библиотека X, не известен класс Y”. Когда создавался проект в IDE, то программист определил в составе проекта некоторую логическую абстракцию – библиотеку, то как ее понимает IDE. В состав этой библиотеки входит множество файлов с расширениями jar (реальные архивы библиотек java). Эти файлы загружены программистом на свой компьютер, например, в папку “c:\java_libs”. Таким образом, библиотека в понятии IDE – это множество путей вида: “c:\java_libs\hibernate.jar ”. А у меня на компьютере нет этих библиотек. Даже если я открыл настройки чужого проекта и увидел там файл с именем hibernate.jar, то это мне ничем не поможет т.к. библиотека hibernate имеет множество версий, и я просто не знаю, какой файл добавить к проекту чтобы он скомпилировался и запустился. Напоминаю, что я решил потратить не больше 10 минут на то, чтобы помочь кому-то разобраться с его ошибкой, я не будут тратить свое время на совершение рутинных подготовительных операций. Даже если забыть об истории с форумом и моим желанием разобраться в чужом коде, то представьте ту же ситуацию, когда на работе вы создаете в коллективе некоторый java-продукт. Т.к. над проектом работает несколько человек и у каждого из них своя предпочитаемая среда разработки (одному нравится eclipse, другому idea) с разными настройками, с разными путями к файлам библиотек и проектов. Следовательно, мы не можем хранить в CVS файлы проекта – они слишком “личные”. Но ведь этот проект должен регулярно извлекаться из репозитория, компилироваться, развертываться на тестовом сервере, чтобы затем команда тестеров могла регулярно проверять сделанную вами за день работу и завести в jira десяток-другой багов. Следовательно, в репозитории должна храниться информация о проекте, о том какие модули входят в его состав, какой модуль зависит от какого – и все это в максимально абстрактном виде. Т.е. нам нужен такой стандарт представления проекта, который бы не зависел от среды разработки (читай, поддерживался бы ими всеми). Та же беда и с библиотеками, нужными для компиляции проекта: где их хранить? Единственное приемлемое решение – поместить их также внутрь cvs-репозитория. Таким образом, java-проект представляет в репозитории максимально самодостаточную единицу: он содержит и файлы с исходным кодом и библиотеки нужные для их компиляции и некий супер-скрипт, который выполняет компиляцию проекта и подготовку его к развертыванию на “боевом” веб-сервере.

Возникает естественный вопрос: неужели до сих пор не появился какой-то инструмент решающий эту задачу. Задачу представления проекта, и составляющих его модулей в форме не зависимой от конкретной среды разработки, инструмента содержащего средства записать сценарий как нужно компилировать и собирать проект. Инструмент позволяющего вынести все используемые для разработки библиотеки в отдельное хранилище (например, отдельный сервер) и позволяющего условно сказать: “для сборки проекта нужна библиотека hibernate версии 3.2.1”. Инструмент, известный, популярный, такой чтобы, если у вас возникнет какая-то проблема, связанная с его применением, то можно было бы положиться на развитое community. Желательно, чтобы проект был opensource, чтобы включал в себя api, позволяющее создавать собственные расширения, плагины. Итак, первое лицо maven – это инструмент для декларативного описания структуры проекта и нужных для его работы зависимостей (библиотек). Таким образом, после миграции проекта на maven, набор шагов по компиляции проекта будет следующим: извлечь из cvs исходные коды проекта и файл проекта maven, запуск проекта на сборку, в ходе которой maven найдет правильный порядок сборки модулей образующих проект (по ходу их зависимостей друг от друга), загрузит со специального сервера нужные для компиляции библиотеки (если их еще нет на вашем компьютере) и завершит компиляцию проекта. Важно, что один и тот же файл будет использоваться всеми участниками команды, как программистами, так и тестерами. Тестеры ни вообще ничего не знают о структуре проекта и его зависимостях, они знают только то, что если запустить вот этот maven-файл на выполнение то, само собой получится исполняемый файл, который можно и проверять на предмет ошибок.

Теперь перейдем к “немножко попрограммировать” и я опишу процесс создания проекта с помощью maven. Предполагается, что вы загрузили с сайта http://maven.apache.org архив с maven-ом. Т.к. maven появился уже достаточно давно, то в настоящий момент есть две ветви его разработки: 1x (больше не развивается) и 2x. Прогресс при переходе от версий 1x к 2x был очень большой так, что я не вижу ни какой причины, почему вам следует использовать maven 1. Сам я уже довольно давно “сижу” на maven 2.0.9 и единственная причина, почему я не перешел недавно вышедшую 2.0.10 так это моя лень и то, что в девятке меня все устраивает. Распаковав архив с maven, нужно создать пару переменных среды окружения для удобства последующей работы с maven из командной строки. Сначала создадим переменную M2_HOME, указывающую на каталог с maven. Еще в самый конец переменной PATH я добавил путь к каталогу %M2_HOME%/bin. Проверьте работоспособность maven, набрав в командной строке “m2 -v”, и в ответ вы должны получить строку “Maven version: 2.0.9.”. Значит, maven установлен и работает, а мы идем дальше. Проект в терминологии maven – это каталог со стандартизированной структурой. Хоть, путь и де де-факто, но maven предлагает стандарт именования каталогов с исходными кодами, каталога, где находятся ресурсы, и каталога, куда будут помещены результаты компиляции проекта. В корне каталога проекта находится файл pom.xml. За счет того, что maven создан в соответствии идеологии “соглашения превыше конфигурирования”, то размер файла pom.xml может быть совсем маленьким. Например, если вы решили изменить имена и структуру каталогов проекта, то это придется указать в pom-файле, а если решили следовать правилам maven, то ничего делать не нужно. Более того, в maven есть понятие archetypes (или шаблонов проектов). Количество различных программ, которые можно написать на java бесконечно, однако количество типов этих приложений довольно ограничено: обычные java, web-приложения, ejb-модули, ear-приложения – это первое что приходит на память. Более того, maven настолько популярен, что разработчики различных известных и не очень framework-ов, например, spring, jsf, tapestry, seam создали для maven новые виды archetypes. Следовательно, вы получаете возможность “быстрого старта” с maven и archetype. Более подробно об archetypes, какие они бывают и откуда их брать я расскажу попозже, сейчас давайте создадим “ручками” maven-style структуру каталогов. Пологая, что мой проект называется testartifact1 (почему в названии присутствует слово artifact опять требует уточнений – но позже), я создал каталог testartifact1, а в нем подкаталоги src и target. В первом из них будут храниться исходные коды проекта, а во второй будет помещаться результат компиляции. Т.к. современная разработка ПО не мыслима без тестов, то в каталоге src были созданы еще два подкаталога: main и test. В первом из них хранится код приложения, а во втором тесты для него. Это еще не все: кроме исходного кода в виде файлов java, проект часто включает ресурсы: это могут быть файлы properties с различным настройками и конфигурационными переменными нужным для запуска создаваемой программы, или это могут быть файлы картинок для интерфейса. В любом случае каталог main состоит из двух подкаталогов: java и resources. Такое же деление характерно и для каталога test. В конечном счете, я получил дерево каталогов, показанное на рис. 1.



Теперь нужно наполнить эти каталоги содержимым. Так я создал простенький класс HelloBean, единственным назначением которого будет “приветствовать пользователя” (имя пользователя передается как параметр конструктора класса). Файл размещен в каталоге “src/main/java/blackzorro”:
  1. package blackzorro;
  2. public class HelloBean {
  3.  
  4.   private String name;
  5.  
  6.   public HelloBean(String name) {
  7.       this.name = name;
  8.   }
  9.  
  10.   public String sayHello() {
  11.       return "Hello, " + name;
  12.   } 
  13.  
  14. }
Затем я создал два еще два класса, использующих HelloBean, первый из них HelloMaven разместился в одном каталоге с HelloBean:
  1. public class HelloMaven {
  2.  
  3.   public static void main(String [] args) {
  4.      System.out.println(new HelloBean("Maven").sayHello());
  5.   } 
  6.  
  7. }
А второй находится в каталоге “src/test/java/blckzorro” и представляет собой тест junit:
  1. public class TestHelloBean extends TestCase {
  2.  
  3.   public void testSimpleMessage() {
  4.      String message = new HelloBean("Maven 2").sayHello();
  5.      Assert.assertEquals("Test Hello Machine", "Hello, Maven 2", message);
  6.   } 
  7.  
  8. }
Завершающим штрихом будет создание файла проекта pom.xml:
  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  2.   <modelVersion>4.0.0</modelVersion>
  3.   <groupId>blackzorro</groupId>
  4.   <artifactId>testartifact1</artifactId>
  5.   <packaging>jar</packaging>
  6.   <version>1.0</version>
  7.   <name>Simple Maven 2 Artifact</name>
  8.   <url>http://maven.apache.org</url>
  9.   <dependencies>
  10.     <dependency>
  11.      <groupId>junit</groupId>
  12.      <artifactId>junit</artifactId>
  13.      <version>3.8.2</version>
  14.      <scope>test</scope>
  15.    </dependency>
  16.   </dependencies>
  17. </project>
Начало xml файла тривиально: в корневом элементе (с вполне ожидаемым названием project) я декларирую то, какие пространства имен xml я хочу использовать. Таким образом, файл pom.xml будет построен в соответствии с требованиями, описанными в файле правил http://maven.apache.org/POM/4.0.0. К счастью, большинство умных java и xml редакторов знает об такой схеме. А если и не знает, то всегда можно загрузить файл схемы xsd с адреса http://maven.apache.org/maven-v4_0_0.xsd и пользоваться удобными подсказками при наборе. Элемент modelVersion пропускаем как не представляющий для нас интереса. А вот на элементы groupid, artifactid, packaging и version следует обратить самое пристальное внимание. Наиболее часто используемое понятие в maven – артефакт. Артефакт – это результат некоторой работы, которую можно использовать в ходе разработки программ, или который является конечной целью разработки. Звучит довольно абстрактно, но в практике под артефактами понимают библиотеки, нужные для разработки проекта. Модули, образующие проект - это тоже артефакты; и сам проект в скомпилированном и готовом к использованию виде - это тоже артефакт. Maven предлагает создать универсальный репозиторий, хранилище артефактов. В котором каждый артефакт подобно книжному каталогу имеет свою карточку, по которой можно его легко найти. К примеру, если вы компания veniki ltd и ваш флагманский продукт “super bookreader”, то вы можете считать что группа артефактов groupId это “veniki ”, а сам продукт имеет имя “bookreader”. Т.к. ваш продукт развивается во времени и выходят новые версии, то каждый новый файл pom, описывая ваш проект, будет задавать соответствующие номера артефактов внутри элемента version. Атрибут packaging нужен для того, чтобы указать какое представление имеет данный проект-артефакт. Так для java мира исполняемые файлы или библиотеки поставляются в виде архивов jar. Веб-приложения поставляются в виде архивов с расширением war и т.д. Значение атрибута packaging не сводится только к расширению, под которым будет сохранен файл артефакта. Packaging оказывает прямое влияние на жизненный цикл артефакта: и то по каким правилам будут обрабатываться, составляющие ваш проект, файлы на различных этапах разработки. Звучит снова не слишком понятно, но прошу набраться терпения и ждать подробного рассказа про жизненный цикл проекта в maven. Элементы name и url носят декоративный характер, не оказывая прямого воздействия на ваш проект, и служат только для того, чтобы указать название проекта, его адрес в internet. Если делать все красиво, то кроме элементов name и url, можно задать еще описание проекта внутри элемента description. Внутри элемента organization задаются сведения об организации занимающейся разработкой данного проекта-артефакта (я буду часто повторять эти два слова вместе, чтобы вы побыстрее привыкли рассматривать и проект, и составляющие его модули и библиотеки как разновидности общего понятия артефакта). Элемент inceptionYear позволяет задать год когда был начат проект, это пригодится нам когда maven будет автоматически генерировать документацию для проекта, чтобы правильно вывести надписи copyright-ов. Стоп… я снова забегаю вперед, но это понятно т.к. трудно не удержаться от восхваления maven-а и его ориентации на полный охват различных задач связанных с управлением проектом (хотя чего греха таить, у maven-а хватает недоработок). Для opensource продуктов будет полезным задать сведения о разработчиках, поместив их имена внутрь элемента developers/developer. А сведения об mail-листах, в которых ведется обсуждение проекта, перечисляются внутри элемента mailingLists/mailingList. Если проект хранится в системе управления версиями документов (CVS, SVN), то адрес подключения с репозиторию задается внутри элемента scm.

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