среда, 31 декабря 2008 г.

Дао Scrum

Много бумаги исписано и и мегабайтов перекачано в процессе, когда одни пытаются понять, что же такое Scrum, а другие пытаются это объяснить. Главное достоинство Scrum в этом смысле заключается в том, что для того, чтобы начать его применять не нужно многого – бери готовые несложные правила, минимум артефактов, и колесики закрутились. Но понимание глубокой сути этого процесса наступает далеко не сразу.

Как известно, центром любого scrum-проекта является product backlog. Это место, куда product owner записывает свои пожелания, оценивая их значимость и вместе с командой планирует итерации. Казалось бы, что может быть проще? Но чем же этот подход оказался лучше других? Чем он отличается, скажем, от случая, когда заказчик просто заводит тикеты, например, в JIRA? Что делает этот проект столь эффективным?

Для того, чтобы это понять, достаточно взглянуть на перевод термина “backlog” (ну, или в толковый словарь для носителей языка):

backlog

  1. долг, задолженность
  2. невыполненные заказы
  3. резервы (товаров, материалов и т. п.)

На мой взгляд, значение этого термина – квинтэссенция всего процесса. Попробуем разобрать его по пунктам.

Backlog – это не список задач для разработчиков

Нет, конечно, в конечном счете разработчик работает со списком конкретных задач. Но изначально, с точки зрения product owner, список конкретных задач – это скучные подробности. В первую очередь из перевода следует, что backlog – это не список задач и даже не список user stories, которые нужно выполнить, а список недостатков системы. Под недостатками подразумеваются недостающая функциональность, недостаточное качество реализации, недостаточное соответствие ожиданиям бизнеса и так далее. Тут видно замечательное свойство термина “недостаток”, которое заключается в том, что оно обобщает артефакты нескольких подпроцессов: фича, улучшение, дефет.

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

У такого подхода, среди прочих достоинств, есть одно важное преимущество: он построен на позитивном мышлении творцов, стремящихся в своей работе к совершенству, подогревая творческий потенциал участников процесса. Ведь даже Микеланджело был приверженцем такого принципа: на вопрос о том, как он создает столь совершенные скульптуры, он ответил “Я просто беру резец и отсекаю от мрамора все лишнее”. Правда похоже? :)

Подразумевается, что приложение существует на этапе планирования

Хорошо известный постулат любого agile-проесса напрямую следует из предыдущего пункта. Ведь прежде чем определить недостатки системы, да еще и определить их степень важности, нужно иметь некую текущую версию, глядя на которую и можно сказать, чего ей не достает. Нужен тот самый кусок мрамора. Ведь сложно искать недостатки в белизне чистого листа бумаги. Поэтому и существуют правило о доступе product-owner-а к актуальной рабочей версии приложения пополам с особым акцентом на коммуникации между заказчиком и разработчикам.

Выживают важнейшие требования

Получается, что процесс управления недостатками подчеркивает эволюционную природу Scrum. Планирование каждого спринта – это, по сути, естественный отбор задач, где критерием отбора является степень ее важности для приложения на данном этапе эволюции. А механизм отбора универсален – борьба за ресурсы: мы просто ограничиваем продолжительность спринта. Ну и важно спланировать спринт так, чтобы по его окончанию мы получили полноценный экземпляр приложения, который должен быть как минимум не хуже предыдущего.

К чему это все?

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

Всех с Новым годом! :)

понедельник, 15 декабря 2008 г.

DSL, валидация и jQuery

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

Введение

Итак, напомню, что мы реализовали на данный момент:

  1. Объектная модель DSL валидации;
  2. Удобный механизм конструирования синтаксического дерева;
  3. Интерпретатор, который транслирует синтаксическую структуру дерева в логику валидации.

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

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

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

Исходный код валидатора и модификации в DSL

В качестве подопытного кролика на сей раз выберем класс Person:

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public string Comments { get; set; }
}

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

Email Constraint

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

Таким образом, пусть код валидатора будет таким:

var v = DefineValidator.For<Person>()

   .WhereProperty(p => p.FirstName).SatisfiedAs(Should.NotBeNullOrEmpty)
        .WithReason("Введите имя").AsError()
   .WhereProperty(p => p.FirstName).SatisfiedAs(Should.NotBeLongerThan(10))
        .WithReason("Слишком длинное имя")

   .WhereProperty(p => p.LastName).SatisfiedAs(Should.NotBeNullOrEmpty)
        .WithReason("Введите фамилию").AsError()
   .WhereProperty(p => p.LastName).SatisfiedAs(Should.NotBeLongerThan(10))
        .WithReason("Слишком длинная фамилия")

   .WhereProperty(p => p.Email).SatisfiedAs(Should.MatchEmailTemplate)
        .WithReason("Введите правильный емейл");

Динамическое добавление серверных валидаторов

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

<form runat="server">
    Имя:<br />
    <cc:ValidableTextBox ID="FirstName" runat="server" Type="Product"></cc:ValidableTextBox><br />
    Фамилия:<br />
    <cc:ValidableTextBox ID="LastName" runat="server" Type="Product"></cc:ValidableTextBox><br />
    Емейл:<br />
    <cc:ValidableTextBox ID="Email" runat="server" Type="Product"></cc:ValidableTextBox><br />
    Комментарии:<br />
    <cc:ValidableTextBox ID="Comments" runat="server" Type="Product"></cc:ValidableTextBox><br />
    <br />
    <asp:Button runat="server" Text="Submit" />
</form>

В приведенном фрагменте вместо стандартного контрола TextBox мы использовали его расширенную версию ValidableTextBox, в котором появилось поле Type. Для чего это нужно? Дело в том, что мы должны связать контрол на форме с конкретным свойством конкретного класса. В простейшем случае имя свойства можно использовать в качестве идентификатора для контрола, а свойство Type будет использоваться для указания типа, к которому это свойство принадлежит.

В WebForms валидаторы - это обычные серверные контролы, поэтому добавление валидаторов на форму - это обычная манипуляция коллекцией контролов:

protected void Page_Init(object sender, EventArgs e)
{
    const string maxLengthPattern = @"[\s\S]{{0,{0}}}";
    const string emailPattern = @"\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*";

    AddValidator<Person>(p => p.FirstName,
        new RequiredFieldValidator(),
        "Введите имя");

    AddValidator<Person>(p => p.FirstName,
        new RegularExpressionValidator { ValidationExpression = string.Format(maxLengthPattern, 10) },
        "Слишком длинное имя");

    AddValidator<Person>(p => p.LastName,
        new RequiredFieldValidator(),
        "Введите фамилию");

    AddValidator<Person>(p => p.LastName,
        new RegularExpressionValidator { ValidationExpression = string.Format(maxLengthPattern, 10) },
        "Слишком длинная фамилия");

    AddValidator<Person>(p => p.Email,
        new RegularExpressionValidator { ValidationExpression = emailPattern },
        "Введите правильный емейл");
}

void AddValidator<T>(Expression<Func<T, object>> property, BaseValidator validator, string message)
{
    var fildName = ((MemberExpression)property.Body).Member.Name;

    var validableControl = FindControl(fildName) as IValidableControl;

    if (validableControl != null)
    {
        validator.ErrorMessage = message;
        validator.Display = ValidatorDisplay.Dynamic;
        validator.ControlToValidate = ((Control) validableControl).ID;

        var container = ((Control)validableControl).Parent.Controls;
        container.AddAt(container.IndexOf((Control) validableControl) + 1, validator);
    }
}

В ходе экспериментов с динамическим добавлением контролов мы увидели, что констрейнт StringMaxLengthConstraint разумно реализовать на базе RegularExpressionConstraint, потому что ограничение по длине поля с использованием серверных валидаторов в WebForms реализуется с помощью RegularExpressionValidator.

Показательным является то, что перекроив логику работы StringMaxLengthConstraint мы никак не повлияли на синтаксис нашего DSL.

Динамическая генерация javascript-кода для валидации

Раз уж речь пошла о том, что второй подход у нас будет ориентирован на использование ASP.NET MVC, то логично будет развить эту мысль и сказать, что код клиентской валидации мы будем писать, используя jQuery. И раз уж мы решили использовать jQuery, то стоит взглянуть на плагин Validation. Главным плюсом этого плагина является то, что вся логика описывается в декларативном стиле и может быть локализована в одном месте.

Код нашей формы будет выглядеть так:

<form action="ValidateHtmlForm.aspx" method="post" id="PersonForm">
    Имя:<br />
    <input type="text" name="Person$FirstName" /><br />
    Фамилия:<br />
    <input type="text" name="Person$LastName" /><br />
    Емейл:<br />
    <input type="text" name="Person$Email" /><br />
    Комментарии:<br />
    <input type="text" name="Person$Comments" /><br />
    <br />
    <input type="submit" value="Submit" />
</form>

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

И в этом случае код валидации будет таким:

$(document).ready ( 
    function() {
        $("#PersonForm").validate(
            {
                rules: {
                    Person$FirstName: {
                        required: true,
                        maxlength: 10
                    },
                    Person$LastName: {
                        required: true,
                        maxlength: 10
                    },
                    Person$Email: {
                        email: true
                    }
                },
                messages: {
                    Person$FirstName: {
                        required: "Введите имя",
                        maxlength: "Слишком длинное имя"
                    },
                    Person$LastName: {
                        required: "Введите фамилию",
                        maxlength: "Слишком длинная фамилия"
                    },
                    Person$Email: {
                        email: "Введите правильный емейл"
                    }
                }
            }
        )
    }
);

Как видим, в отличии от серверных контролов для проверки длины поля и его соответствия шаблону используются готовые спецификаторы maxLength и email. Далее мы увидим, как констрейнты в нашем DSL спроектированны таким образом, что они могут без труда интерпретироваться и как RegularExpressionValidator с конкретным паттерном, и как спецификаторы maxlength и email.

Автоматизация генерации кода

Стандартным решением для пододбных задач являтся использование паттерн Visitor. Именно реализацию этого паттерна все желающие могут найти в недрах исходников LINQ в виде абстрактного intenal-класса ExpressionVisitor. Итак, напомним, в нашем примере синтаксическая структура DSL описывается иерархией констрейнтов. Поэтому разработаем абстрактный Visitor с реализациями метода Visit для каждого из типов констрейнтов и добавим две реализации для каждого из типов валидации:

2_Visitors

"Кирпичиком" наших валидаторов является инетрфейс IRule. Модифицируем его так, чтобы он выполнял роль Element для паттерна Visitor:

3_ValidatorElement 

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

public class StringNotNullOrEmptyConstraint : IConstraint
{
    ....
    public void Accept(IValidatorVisitor visitor)
    {
        visitor.Visit(this);
    }
}

Т.е. единственная польза от этого метода - полиморфная диспетчеризация вызовов.

Точкой входа для всей конструкции является метод VisitValidator. Этот метод получает на вход список валидаторов, которые необходимо "посетить", перебирая их, вызывает метод Accept. А сами констрейнты "возвращают" вызовы в Visitor через метод Visit, как было показано выше.

Реализация метода VisitValidator для WebFormValidatorVisitor:

public void VisitValidator(IEnumerable<IRulesGroup> rulesGroups)
{
    foreach (var validator in rulesGroups)
        foreach (var rule in validator.Rules)
            rule.Accept(this);
}

Обернув логику связывания в некие классы-хелперы, на примере JQueryValidatorVisitor код использования будет выглядеть так:

<head runat="server">
    
    <script type="text/javascript" src="jquery-1.2.6.js"></script>
    <script type="text/javascript" src="jquery.validate.js"></script>

    <%= ValidateForm("PersonForm") %>
    
</head>

Реализация метода ValidateForm для случая с использованием JQueryValidatorVisitor:

public string ValidateForm(string fortmId)
{
    var script = new StringBuilder();

    IValidatorVisitor visitor = new JQueryValidatorVisitor(script, fortmId);
    visitor.VisitValidator(CreateValidators());

    return string.Format("<script type=\"text/javascript\">\n{0}</script>", script);
}

Методы Visit в свою очередь обрабатывают каждый конкретный констрейнт, "накапливая" и возвращая результат инерпретации клиентскому коду. Например, вот так вот выглядит код одного из методов класса WebFormValidatorVisitor:

public void Visit(StringNotNullOrEmptyConstraint constraint)
{
    var validator = new RequiredFieldValidator
                        {
                            ErrorMessage = _currentRule.Message,
                            Display = ValidatorDisplay.Dynamic,
                            ControlToValidate = _controlToValidate.ID
                        };
    var container = _controlToValidate.Parent.Controls;
    container.AddAt(_controlIndex + 1, validator);
}

Заключение

В последних двух постах мы увидели лишь верхушку огромного айсберга, под названием DSL. Эта тема сейчас активно разрабатывается многими крупными игроками на рынке средств разработки. Главное направление этих изысканий - упрощение разработки и внедрения DSL в приложения. Например, Microsoft на последней PDC анонсировала интересный продукт, под названием Oslo. Хороший обзор написал Мартин Фаулер. Кроме этого, старина Мартин работает над книгой по DSL. Как обычно, это будет каталог паттернов, с которым уже можно познакомиться у него на сайте. А еще, совсем недавно JetBrains выпустили бета-релиз своего нового продукта Meta Programming System, который решает схожие задачи с Microsoft Oslo. И что-то мне подсказывает, впереди нас ждет еще много интересного.

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