Feel Good.

среда, 24 октября 2012 г.

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

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

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

среда, 28 марта 2012 г.

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

Эта статья о том, как я наладил семантическое версионирование (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:


  
    
  
Все готово!

Заключение
  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

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

среда, 23 ноября 2011 г.

Adapter pattern. Понимание важности.

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

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

Итак, пришло время вспомнить о существовании такого шаблона проектирования как "Адаптер" (Adapter pattern). Я не буду здесь рассказывать про этот шаблон проектирования, а просто постараюсь выделить главную идею:

Всегда оборачивайте стороннюю компоненту своим интерфейсом и адаптируйте ее под себя.

Первая причина, почему это стоит делать очевидна - это устранение жестких зависимостей на стороннюю компоненту, а учитывая, что у зависимостей есть один неприятный момент: с ростом проекта они тоже разрастаются и чем дальше тем сложнее от них избавиться, и в конце концов можно стать "заложником" сторонней компоненты. Казалось бы простое правило, но на практике периодически сталкиваешься с обратным, да и сам порой забываешь обернуть, например, какой-нибудь logger, serializer(json, xml, yaml) или DI/IoC-контейнер, в надежде, что этот инструмент ты не сменишь никогда в проекте.

Вторая причина - это адаптация компоненты "под себя". Адаптируете компоненту таким образом, чтобы Вам было удобно ее использовать.

среда, 31 августа 2011 г.

Mock для ICloneable типов

Буквально недавно я столкнулся с задачей, суть которой заключалась в том, чтобы создать mock объект реализующий интерфейс ICloneable, например средствами Moq.
В моей ситуации, было все просто: метод Clone должен возвращать объект (которого на самом деле нет), с наперед известными данными (которые не подлежали бы изменениям). Для наглядности лучше привести исходный код.
Пусть это будет следующий интерфейс:

public interface ISome : ICloneable

{

    // некое бизнес-поле

    int Value { get; }

}

И для этого интерфейса, построим mock объект реализующий его.
Итак, тесты вперед, поэтому напишем простой unit-test для метода Clone:

[TestMethod]

public void CloneTest()

{

    var original = MakeMockCloneableSome();

 

    // покажем, что и у клонированого объекта

    // метод Clone работает как надо

    var clone = (ISome)((ISome)((ISome)original.Clone()).Clone()).Clone();

 

    // проверим, что бизнес поле совпадает

    Assert.AreEqual(original.Value, clone.Value);

 

    // проверим, что это 2 отдельных объекта

    Assert.IsFalse(Object.ReferenceEquals(original, clone));

}

В тесте я трижды вызывают метод Clone для того, чтобы продемонстрировать, что и у клонированного объекта метод Clone тоже работает, это важно.
Осталось реализовать метод MakeMockCloneableSome():

ISome MakeMockCloneableSome()

{

    var mock = new Mock<ISome>();

 

    // Здесь привычный нам mock бизнес логики...

    mock

        .Setup(c => c.Value)

        .Returns(123);

 

    // Искомый mock метода ICloneable.Clone()

    mock

        .Setup(c => c.Clone())

        .Returns(MakeMockCloneableSome);

 

    return mock.Object;

}

четверг, 12 мая 2011 г.

Проверяем входные данные

Все программисты знают, что пришедшие из внешнего источника данные всегда надо проверять. Например в качестве входного параметра для метода (свойства, конструктора), может приходить ссылка, которую нужно проверить на null, число, которое должно быть из определенного диапазона, или определенного вида строка. Несмотря на то, что по этому поводу даже существует методика разработки через Code Contracts, по-прежнему, с высокой вероятностью в проекте можно встретить код проверки на null аналогичный следующему:

class Bar {}

 

class Foo

{

    Bar _bar;

 

    public Foo(Bar bar)

    {

        // bar может быть null

        if (bar == null)

            throw new ArgumentNullException("bar");

 

        _bar = bar;

    }

}

четверг, 14 апреля 2011 г.

AOP средствами Unity 2.0

В этой статье рассмотрим простой пример реализации AOP (Aspect-Oriented Programming) средствами Unity 2.0. Итак, Unity представляет собой DI/IoC контейнер, с возможностью вносить в него новую функциональность или поведение через стандартные или пользовательские расширения (Extentions).Благодаря этой возможности Unity, мы можем написать свое или воспользоваться уже готовым расширением и реализовать AOP в нашем .NET приложении. Стандартный подход при реализации AOP средствами Unity является использование перехватчика (Microsoft.Practices.Unity.Interception) вызова.
Unity 2.0 предоставляет нам три вида перехвата:
  1. InterfaceInterceptor для перехвата методов интерфейса
  2. VirtualMethodInterceptor для перехвата виртуальных методов
  3. TransparentProxyInterceptor для перехвата методов у объектов типа MarshalByRefObject
Принцип работы перехватчика очень простой. Когда мы делаем resolve-запрос к Unity контейнеру мы получаем динамически сгенерированный прокси-объект, делегирующий искомый тип. Функционально прокси устроен следующим образом: ПЕРЕД тем и ПОСЛЕ того, как прокси передаст вызов на искомый объект, он выполнит код перехватчика (на практике обычно это pipe из нескольких обработчиков, реализующих интерфейс ICallHandler), передав ему все необходимые параметры вызова. Этого вполне достаточно для реализации AOP поведения.