Feel Good.

24 октября 2012

Инфраструктурный код

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

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

28 марта 2012

Семантическое версионирование в DVCS Mercurial

Эта статья о том, как я наладил семантическое версионирование (semantic versioning) в своем проекте на базе DVCS Mercurial (можно читать как Git). На хабре есть отличный перевод про Семантическое управление версиями, советую начать с него.

В качестве хранилища кода, я выбрал mercurial. Помимо функции хранилища кода, mercurial будет выступать еще и как провайдер версий, это означает, что запрашивать текущую семантическую версию (Major.Minor.Patch) мы будем именно у него.

Итак, рассмотрим пару Major.Minor. Обычно эта пара всегда задается вручную в момент релиза, и самый простой способ это сделать - пометить руками нужный сhangeset тегом, содержащим версию релиза (например: 1.4, или v1.4, но не 1.4.3, так как path мы договоримся вычислять).

С Patch все намного сложнее. Наша цель, добиться чтобы при каждой фиксации(commit) изменений кодовой базы автоматически бы инкрементировался path-номер. Это примерно означало бы, что path будет равен числу фиксаций в текущем релизе (например: 1.4.17 - 17-ая фиксация в релизе 1.4, но в то же время 1.5.0 уже новый релиз, с новой path-нумерацией). Но здесь не стоит забывать один факт: мы ведь работаем с DVCS и у нас нет единого "брокера" path-номеров...

В интернете я нашел несколько вариантов решения этой задачи (SO, RSDN), где в основном предлагалось использовать число, полученное как {latesttagdistance}, но можно заметить, что {latesttagdistance} перестает работать в общем случае, когда тег может содержать произвольный текст. Но эта идея мне очень понравилась, и я начал искать универсальное решение. И вскоре решение было найдено.

Я предлагаю в качестве path-номера брать разницу между номером ревизии текущего changeset-а (замечу, что это не всегда tip) и номером ревизии changeset-а, имеющего тэг с релизной версией и являющегося ближайшим предком по отношению к текущему changeset-у.

На текущий момент, при таком подходе мне удалось достичь желаемого семантического версионирования своих проектов.

Теперь осталось все это как-то оформить. В конечном же счете, я преследовал цель автоматической генерации файла AssemblyInfo.cs на основе номера версии полученной от mercurial до начала сборки проекта, и с последующей сборкой. Очевидным решением здесь было сделать Custom MSBuild Task/Target, и в итоге появился проект Devme.MSBuildTasks.


Коротко о Devme.MSBuildTasks (текущая версия 0.3.0)

Q: Какие системы контроля версий поддерживает библиотека?

A: Пока только для mercurial.

Q: Как я могу начать использовать это в своем проекте?

A: Необходимо выполнить 3 простых шага:

Шаг 1. Используя Nuget, установить/скачать библиотеку к себе в проект.

Шаг 2. Создать в каталоге Properties (если использовали nuget, то файл шаблон будет создан автоматически) файл шаблон следующего содержания (за основу можно взять Ваш текущий файл AssemblyInfo.cs):

using System.Reflection;
 
[assembly: AssemblyTitle("todo")]
[assembly: AssemblyDescription("todo")]
[assembly: AssemblyCompany("todo")]
[assembly: AssemblyProduct("todo")]
[assembly: AssemblyCopyright("Copyright © todo")]
 
[assembly: AssemblyVersion("${major}.${minor}.${path}")]
[assembly: AssemblyFileVersion("${major}.${minor}.${path}")]
[assembly: AssemblyInformationalVersionAttribute("${major}.${minor}.${path}-${branch} ${hash|short}")]
Формат шаблона думаю Вам понятен, здесь в качестве ${some} будут подставлены соответствующие значения взятые из mercurial (см wiki). У себя я называю этот файл AssemblyInfo.cs.template.

Шаг 3. Открыть в блокноте файл проекта (*.csproj) и добавить в самом конце пару тэгов (или расширить существующую цель BeforeBuild), указав правильный путь к Devme.MSBuildTasks.dll:

<UsingTask 
    TaskName="SemanticVersioningTask" 
    AssemblyFile="ПУТЬ К ФАЙЛУ Devme.MSBuildTasks.dll" />
<Target Name="BeforeBuild">
    <semanticversioningtask 
        TemplateFilePath="Properties/AssemblyInfo.cs.template" 
        OutputFilePath="Properties/AssemblyInfo.cs" />
</Target>
Все готово!

Заключение
  1. Следует помнить, что каждый раз, во время сборки проекта, файл AssemblyInfo.cs будет генерироваться автоматически, поэтому его можно(надо) исключить из кодовой базы добавив в .hgignore.
  2. Так же не рекомендуется вносить в файл AssemblyInfo.cs любые изменения. Все изменения надо делать в шаблоне AssemblyInfo.cs.template.
  3. Исходники
  4. Wiki
Ссылки:
  1. Semantic Versioning
  2. AssemblyVersion и git. Давайте жить дружно.
  3. Полуавтоматическое выставление номера версии с помощью git

Всем приятного использования!

13 марта 2012

Stopwatch. Новые идеи.

Как-то давно я написал статью "Пишем обертку для Stopwatch", в которой рассказал как удобно было бы использовать стандартный класс Stopwatch из System.Diagnostics, сделав для него IDisposable обертку. И вот наконец, у меня появились новые идеи и свободное время и я решил вернуться к этой теме и немного ее доработать.

1. API. Я решил сделать единую точку входа - статический класс StopwatchManager со статическим методом Start. Все очень просто и понятно:

using (StopwatchManager.Start(elapsed => Console.WriteLine("Elapsed: {0}", elapsed)))
{
Thread.Sleep(2345);
}

2. Регионы (new!). Как часто Вам требовалось узнать не только текущее время выполнения блока, а например, еще и общее или суммарное время выполнения, либо узнать сколько раз блок выполнился, либо узнать минимальное, максимальное или среднее время выполнения? Теперь это стало проще благодаря именованным регионам:

using (StopwatchManager.Start("region", elapsed => 
Console.WriteLine("Third. " +
"Elapsed: {0}, " +
"Total: {1}, " +
"Count: {2}, " +
"Min: {3}, " +
"Max: {4}, " +
"Avg: {5}",
elapsed.Current,
elapsed.Total,
elapsed.Count,
elapsed.Min,
elapsed.Max,
elapsed.Avg
)))
{
Thread.Sleep(500);
}

Здесь “region” это идентификатор региона, задав его мы указываем, что хотим собирать статистику для помеченного блока кода. В статистику входят: Current – текущее, Avg - среднее, Min – минимальное, Max – максимальное, Total – общее время выполнения, Count – количество прогонов.


3. NuGet package (new!). Для удобства использования, я собрал все это в NuGet пакет, который можно скачать здесь, или через консоль:

PM> Install-Package Devme.Diagnostics


Исходники
Wiki

Всем приятного использования!