Страницы

понедельник, 7 мая 2012 г.

AssemblyVersion и git: маленькое дополнение.

  Я уже описывал вариант работы с git’ом для генерации файла SolutionVersion.cs. Этот файл добавляется в .gitignore, чтобы не коммитить его в контроль версий. А в проекты VIsual Studion добавлен BeforeBuild шаг генерации заглушки файла перед компиляцией, дабы работать  с проектом без дополнительных телодвижений. Основная идея – контроль версий не должен содержать сгенерированную AssemblyVersion и прочие атрибуты сборки, сохраняя удобство работы с кодом проекта.

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

git update-index –assume-unchanged SolutionVersion.cs

  Эта команда заставит git считать файл локально неизменённым, даже если в нём сделаны изменения. Случайно отправить в контроль версий непустой SolutionVersion.cs не получится. Эта команда влияет только на git-репозиторий, в котором выполнена. Если клонируем репозиторий заново – то и команду придётся выполнить еще раз.

  Какой из двух способов выбрать? Первый способ более надёжен, но требует больше манипуляций с файлами проектов, и вызывает большее количество обращений к диску при компиляции. Во втором случае проще начальная настройка, меньше обращений к диску, но есть риск всё-таки положить в контроль версий сгенерированный файл. Впрочем, с git’ом это не такая большая проблема, т.к. мы еще можем поработать над историей. Улыбка

  Лично я с текущего момент предпочитаю второй способ. Время компиляции (а оно вносит задержку перед запуском тестов) для меня важнее, а в случае проблем еще можно всё исправить.

четверг, 3 мая 2012 г.

Рефакторинг “выделить параметр” весёлым путём.

  Большое число параметров в методе – дурной запах в коде. Фаулер уже давно издал на эту тему рекомендацию – если параметров много, то надо выделить их в особый класс-параметр. И совсем недавно мне пришлось видеть код, который вызывал лёгкую ухмылку удивления:

public Person(string firstName, string secondName, string lastName)
{
    _firstName = firstName;
    _secondName = secondName;
    _lastName = lastName;
}

  Выделить параметр не так сложно, он напрашивается сам по себе. Но реализация повеселила:

public PersonName(string firstName, string secondName, string lastName)
{
    _firstName = firstName;
    _secondName = secondName;
    _lastName = lastName;
}
public Person(PersonName personName)
{
    //WTF???
    _firstName = personName.FirstName;
    _secondName = personName.SecondName;
    _lastName = personName.LastName;
}

  О да! Таскать строки по одной так весело и интересно, что лучше делать это дважды! Сначала затолкав их в PersonName и затем скопировав в Person. Бессмысленная и беспощадная трата человеко-часов. Правильная и очевидная реализация:

        public Person(PersonName personName)
        {
            _personName = personName;
        }

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

вторник, 1 мая 2012 г.

NHibernate Future – оптимизация сетевых издержек.

  Куда уходит время.

  Думаю многие знакомы с проблемами распределённых вычислений. Межпроцессные вызовы выполняются на 3-4 порядка медленнее внутрипроцессных. Межмашинные вызовы длятся на 5-6 порядков медленнее внутрипроцессных. Сетевые  издержки накладываются на каждый межмашинный вызов, приводят к бесполезным потерям времени ожидающими потоками. Напрасный расход машинных ресурсов. Эта проблема была изначальным стимулом к созданию DTO-объектов – вместо многих вызовов, запрашивающих по чуть-чуть, запросить сразу все необходимые данные.

  Эта проблема знакома и создателям NHibernate. NH старается как можно реже обращаться к базе данных, откладывая запросы на последний момент,  выполняет их в пакетном режиме по возможности. Впрочем, в некоторых случаях мы всё равно вынуждены брать контроль в свои руки.  Один из таких случаев – управление временем выполнения запроса (моментом, когда запрос необходимо отправить в базу данных). По понятным причинам, создать DTO-объект для работы с базой данных невозможно. Но и не нужно – NHibernate уже предоставляет хорошее API для этих целей.

  Запроси завтра то, что можно запросить сегодня.

  Цель – уменьшить количество вызовов  к базе данных. Для этого в NHibernate есть специальное API – Futures. В каждом API (IQuery, ISQLQuery, ICriteria, IQueryOver) есть два специальных метода Future и FutureValue:

var count = session.CreateQuery("select count(*) from Cat c").FutureValue<long>();
var peter = session.CreateQuery("from Cat c where c.Name = 'Peter'").Future<Cat>();

  Оба метода на самом деле не выполняют запрос, а лишь добавляют его к списку запросов, которые будут выполнены  потом. “Потом” наступает в момент перечисления коллекции (Future) или получения единственное значение (FutureValue). Все запросы будут отправлены в базу данных пачкой за один вызов.


  Несмотря на явные преимущества, в ответ на предложение использовать Future,  я слышал в ответ нечто вроде “А зачем? Не так уж дорого сделать еще один запрос в базу”. Насколько “дёшево” сходить еще раз в базу я и решил проверить численно. На этот раз мне было лень выдумывать “примеры из жизни”, поэтому будет тренироваться на абстрактных кошках, и сравнивать время выполнения двух методов:

private void QueryDirectly(string timeRegion)
{
    using (var session = _factory.OpenSession())
    using (var tx = session.BeginTransaction())
    {
        using (StopwatchManager.Start(timeRegion, elapsed => Console.WriteLine("{0} total time: {1}", timeRegion, elapsed.Total)))
        {
            var count = session.CreateQuery("select count(*) from Cat c").UniqueResult<long>();
            var peter = session.CreateQuery("from Cat c where c.Name = 'Peter'").List<Cat>();
            var bazil = session.CreateQuery("from Cat c where c.Name = 'Bazil'").UniqueResult<Cat>();
        }
        tx.Commit();
    }
}
private void QueryUsingFuture(string timeRegion)
{
    using (var session = _factory.OpenSession())
    using (var tx = session.BeginTransaction())
    {
        using (StopwatchManager.Start(timeRegion, elapsed => Console.WriteLine("{0} total time: {1}", timeRegion,  elapsed.Total)))
        {
            var count = session.CreateQuery("select count(*) from Cat c").FutureValue<long>();
            var peter = session.CreateQuery("from Cat c where c.Name = 'Peter'").Future<Cat>();
            var bazil = session.CreateQuery("from Cat c where c.Name = 'Bazil'").FutureValue<Cat>();
            var value = count.Value;
        }
        tx.Commit();
    }
}

  Перед запуском на время, каждый метод прогоняется отдельно, чтобы исключить влияние JIT-компилятора. После этого оба метода выполняются в цикле по 1000 раз, и c помощью Stopwatch замерялось время выполнения только самих запросов. Итак, результаты:


image


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


  Стоит отметить, что и в более ранних версиях существовал функционал для группировки запросов – IMultiCriteria и IMultiQuery. Но Future предоставляет более удобный и типобезопасный способ получения результатов.


  Итого, не игнорируйте возможность увеличения быстродействия своего приложения за счёт уменьшения издержек на сеть. С NHibernate Futures сделать это просто, а результат всегда гарантирован.