воскресенье, 29 мая 2011 г.

Впечатления от конференции DevCon

На этой неделе проходила, пожалуй, самая крупная Microsoft-oriented российская конференция - DevCon.

Два дня - четверг и пятница - получились для меня очень насыщенными. Четверг начался с 4-х часового полета из Томска в Москву. Затем от Внуково мы с коллегой еще несколько часов добирались на такси до дома отдыха, где проходила конференция. И лишь благодаря часовым поясам (-3) успели к началу.

Организаторы (Microsoft Россия) постарались на славу. Было все: приглашенные звезды из Америки, отличные выступления, концерт вечером первого дня, отличное проживание и питание и т. д. Вообще программа была очень насыщенной и вечером первого дня (особенно на фоне перелета, переезда, да еще и болезни) голова просто раскалывалась.

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

Например, был показан CSS3 тест, который в IE10 Preview 1 работал быстрее, чем в текущей версии Google Chrome (в котором, вероятно, была отключена GPU Acceleration). Непонятно, зачем такие дешевые приемчики обыгрывать на профессиональной аудитории.

Или еще одна сценка. Вызывают на сцену парня. Он в течение двух минут рассказывает, что их компания занимается разработкой игр, причем в IE 9 их игры работают в 4 раза быстрее, чем в Google Chrome - и уходит. Опять какая-то желтизна. Я бы понял, если бы это был пресс-релиз для журналистов, но на профессиональной конференции разработчиков подобные вещи вызывают недоумение.

К счастью все эти бестолковые выступления уместились в первые два пленарных заседания. К сожалению, это были самые длинные заседания общей длительностью в 3 часа. И если бы не ноутбук и желание дописать код, который я не закончил в самолете, я бы, наверное, уснул, как и несколько людей рядом со мной в зале.

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

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

Из интересного еще были стенды разных компаний. Из присутствовавших стендов меня заинтересовал лишь стенд компании DevExpress, но зато я смог вдоволь наговориться с ребятами из этой чудной (ударение поставьте на свой вкус :) ) компании.

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

В целом, я остался доволен, а негативные моменты спишем на мою вредность и придирчивость.

По результатам конференции, вероятность того, что после окончания учебы (защищаюсь через 2 недели) я уеду из Томска, повысилась до 90 процентов.

To Tuple Or Not To Tuple

Tuple (кортеж) - последовательность из конечного числа элементов.

Кортежи особенно популярны в функциональных языках программирования, где поддерживается их удобное создание, декомпозиция, pattern matching и т. д. (подробнее на примере F#). В .NET 4 появился тип Tuple, тем самым сделав массовым (до этого те, кто были в теме, писали/генерировали Tuple самостоятельно) использование кортежей и в императивных языках программирования. Однако ввиду отсутствия поддержки на уровне языка Tuple в C# превращается в простое хранилище разнородных элементов.

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

public void Process(Tuple<int, int> tuple)
{
...
var item = Retrieve(tuple.Item1);
...
}

или с

public void Process(IdAndValue idAndValue)
{
...
var item = Retrieve(idAndValue.Id);
...
}
?
Я бы предпочел второй вариант: у свойств есть имена, несущие некоторую семантику, что снижает сложность поддержки и уменьшает вероятность допустить ошибку. Согласитесь, по ошибке вызвать tuple.Item2 вместо tuple.Item1 гораздо проще, нежели idAndValue.Value вместо idAndValue.Id. Именно такой банальной ошибкой, которая не была отловлена тестами (потому что в тестах она была с легкостью повторена) и навеян этот пост.

Использование Tuple в C# вполне себе может быть уместно, но злоупотреблять этим не стоит.

понедельник, 23 мая 2011 г.

Следуете ли вы принципам SOLID при объектно-ориентированном дизайне и программировании?

Создал на Хабре опрос "Следуете ли вы принципам SOLID при объектно-ориентированном дизайне и программировании?". Дорогой читатель, поддержи плюсом: очень хочу, чтобы этот опрос добрался до главной.

вторник, 17 мая 2011 г.

Linq To Entities vs. Linq To Objects на примере группировки

LINQ - удобная, красивая, но при этом довольно коварная абстракция. Самые неожиданные вещи обычно происходят на стыке какой-либо реализации LINQ и LINQ To Objects. Сегодня на одном примере я рассмотрю совместную работу LINQ To Entities (Entity Framework) и LINQ To Objects.

За основу возьмем метод репозитория, который принимает на вход список идентификаторов клиентов и возвращает сгруппированный по этим идентификаторам набор заказов (таблица Orders содержит поля OrderId, OrderDate и CustomerId):

public IDictionary<long, List<Order>> GetOrdersByCustomersIds(IList<long> customersIds)
{
using (var ctx = new RepositoryContext())
{
return ctx.Orders.
Where(o => customersIds.Contains(o.Id)).
GroupBy(o => o.CustomerId).
ToDictionary(o => o.Key, o => o.ToList());
}
}


Минуточку! А как это работает? Ведь при выполнении GROUP BY запроса мы можем выбрать лишь поля, по которым происходит группировка, а также агрегированные значения. Стандартным решением этой проблемы является JOIN данных таблицы и результатов группировки. Примерно так:

SELECT o1.*, MinTotal
FROM Orders as o1
INNER JOIN
(SELECT o2.CustomerId, Min(o2.Total) as MinTotal
FROM Orders o2
GROUP BY o2.CustomerId) as o3
ON o1.CustomerId = o3.CustomerId
Where o1.CustomerId in (1, 2, 3, 4, 5)

Что-то в этом духе и должен сгенерировать EF-провайдер. Давайте убедимся в этом. У меня под рукой был MySQL .NET Connector (официальный ADO.NET-провайдер для MySQL), поэтому я воспользовался им и получил следующий сгенерированный запрос (передав на вход список из идентификаторов от 1 до 5):

SELECT `Project2`.`C1`,
`Project2`.`CustomerId`,
`Project2`.`C2`,
`Project2`.`CustomerId1`,
`Project2`.`Id`,
`Project2`.`OrderDate`
FROM
(SELECT `Distinct1`.`CustomerId`,
1 AS `C1`,
`Extent2`.`CustomerId` AS `CustomerId1`,
`Extent2`.`Id`,
`Extent2`.`OrderDate`,
CASE WHEN (`Extent2`.`CustomerId` IS NULL) THEN (NULL) ELSE (1) END AS `C2`
FROM
(SELECT DISTINCT `Extent1`.`CustomerId`
FROM `orders` AS `Extent1`
WHERE ((1 = `Extent1`.`Id`) OR (2 = `Extent1`.`Id`)) OR (((3 = `Extent1`.`Id`) OR (4 = `Extent1`.`Id`)) OR (5 = `Extent1`.`Id`))) AS `Distinct1`
LEFT OUTER JOIN `orders` AS `Extent2`
ON (((1 = `Extent2`.`Id`) OR (2 = `Extent2`.`Id`)) OR (((3 = `Extent2`.`Id`) OR (4 = `Extent2`.`Id`)) OR (5 = `Extent2`.`Id`))) AND (`Distinct1`.`CustomerId` = `Extent2`.`CustomerId`)) AS `Project2`
ORDER BY `CustomerId` ASC, `C2` ASC

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

Стоп! А зачем мы используем группировку на уровне базы данных? Группировка оправдана в случае использования функций агреграции (как в приведенной выше ручной реализации запроса). В нашем же случае группировка - лишь удобное представление полученных данных. Давайте слегка модифицируем метод репозитория и перенесем процесс группировки на уровень LINQ To Objects:

public IDictionary<long, List<Order>> GetOrdersByCustomersIds(IList<long> customersIds)
{
using (var ctx = new RepositoryContext())
{
return ctx.Orders.
Where(o => customersIds.Contains(o.Id)).
AsEnumerable().
GroupBy(o => o.CustomerId).
ToDictionary(o => o.Key, o => o.ToList());
}
}

Для полноты картины посмотрим, какой запрос сгенерирует EF-провайдер:

SELECT `Extent1`.`CustomerId`,
`Extent1`.`Id`,
`Extent1`.`OrderDate`
FROM `orders` AS `Extent1`
WHERE ((1 = `Extent1`.`Id`) OR (2 = `Extent1`.`Id`)) OR (((3 = `Extent1`.`Id`) OR (4 = `Extent1`.`Id`)) OR (5 = `Extent1`.`Id`))

Определенно этот запрос эффективнее предыдущего.

Вот, собственно, и все. Ничего особенного - лишь хотел заострить ваше внимание на коварности перехода от LINQ To X к LINQ To Objects после того, как сам попал в эту ловушку. Будьте бдительны!

P. S. Несмотря на то, что я использовал MySQL .NET Connector, категорически не рекомендую применять этот провайдер в продакшене: это не провайдер, а коцентрированный сгусток багов, которые не фиксятся годами.

PP. S. Кросспост на Хабре: http://habrahabr.ru/blogs/net/119624/

Entity Framework и MySQL

Если вы вдруг соберетесь использовать Entity Framework в связке MySQL, ни за что, на при каких обстоятельствах не используйте родной провайдер MySQL .NET Connector. Это не ADO.NET-провайдер, а кишащее критичными багами, которые не фиксятся годами, недоразумение (по крайней мере в области поддержки Entity Framework).

Из сторонних альтернатив я бы посоветовал продукт dotConnect for MySQL от компании DevArt. Он платный, но стоит вполне адекватных денег.

Я некоторое время назад сделал неправильный выбор, остановившись на стандартном провайдере. Надеюсь, вы не повторите моей ошибки.

вторник, 10 мая 2011 г.

Как не нужно прятать вещи

Для получения допуска к защите магистрской диссертации мне нужно выполнить одно задание. Задание туповатое, нудноватое, да к тому же еще и добровольно-принудительное. Неудивительно, что я отложил его на последний момент.

Итак, глубокая ночь. Я таки собрался сделать это несчастное задание. Начинаю искать учебное пособие, необходимое для его выполнения. Оно было где-то здесь... Ну вот прям здесь... Я же точно помню, как клал его на стол...

Обыскал всю квартиру. На несколько раз. Посмотрел везде, даже в самых невероятных местах: в холодильнике, микроволновой печи и т. д. :) Начал строить разные догадки. Может, сквозняком унесло? Или вместе с мусором выбросил? Да нет, ну бред же!

Я уже был готов строить конспирологические теории - но мой взгляд упал на красивую коробочку D-Link. И тут все встало на свои места. Давеча я купил рутер D-Link и разложил его содержимое на диване, а после установки и настройки сложил документацию рутера обратно в коробку. И, как вы понимаете, в эту документацию затесалось то самое учебное пособие. И если бы коробка не была такой яркой, или если бы она лежала не на видном месте, никогда бы я не нашел пропажу, и не успел бы сделать задание вовремя. Мир бы от этого не перевернулся, но наши отношения с деканатом на фоне моего повального непосещения учебы и так держатся на "добром" слове.

Happy end.

Дорогой читатель, если тебе нужно будет что-нибудь спрятать (оружие, наркотики...), ты знаешь к кому обратиться.

Новый шаблон

Я таки преодолел свою лень и поставил новый, более широкий шаблон. Надеюсь, теперь читать исходники будет удобнее.

P. S. Перешел на темную сторону :)

среда, 4 мая 2011 г.

Приходилось ли вам нарушать GPL?

Запустил на Хабре опрос "Приходилось ли вам нарушать GPL?"

Мой вариант - "да". Стыдно ли мне? Да.

вторник, 3 мая 2011 г.

C# и синтаксический сахар

Вот смотрю я сейчас на код своего проекта и вижу десятки тестов, содержащих строчки вроде этой:

DictionaryAssert.AreMultiEquivalent(
new Dictionary<long, IList<long>>()
{
{
ConstantParameterIds.BookFormat,
new List<long>() {ConstantBookFormatValueIds.Paperback}
}
},
valuesDictionary);

Очень много лишних символов, не находите? На 5 минут представлю себя в роли архитектора C# и немного пофантазирую.

Сначала добавим вывод generic-типов для конструкторов:

DictionaryAssert.AreMultiEquivalent(
new Dictionary()
{
{
ConstantParameterIds.BookFormat,
new List() {ConstantBookFormatValueIds.Paperback}
}
},
valuesDictionary);

Я уже недавно сетовал на отсутсвие этой фичи в C#, и упоминал статическую фабрику в качестве workaround'а (new Tuple(5, 5) vs. Tuple.Create(5, 5)). Однако этот workaround не распространяется на случай использования инициализации через фигурные скобочки (как такая инициализация по-умному называется?).

Теперь введем специальный синтаксис для создания словарей и списков:

DictionaryAssert.AreMultiEquivalent(
{
ConstantParameterIds.BookFormat: {ConstantBookFormatValueIds.Paperback}
},
valuesDictionary);

Ну и заодно офтопом введем специальный синтаксис для кортежей:

var tuple = (5, 5); // syntax sugar for "new Tuple<int, int&пt;(5, 5)"

Закругляюсь, пока Хейлсберг не заметил :)

Как по-вашему, код стал лучше или хуже?

Update
В комментариях Shaddix предложил интересный вариант:

DictionaryAssert.AreEqual(
new []
{
Tuple.Create(ConstantParameterIds.BookFormat, new[] {ConstantBookFormatValueIds.Paperback})}
},
valuesDictionary.ToMultiArrayOfTuples());
, где ToMultiArrayOfTuples - метод расширения:

public Tuple<T, K>[] ToMultiArrayOfTuples(this IDictionary<T, List<K>> dictionary)
{
return dictionary.Select(x => Tuple.Create(x.Key, x.Value.ToArray())).ToArray();
}
Лично мне на этот тест приятнее смотреть: ничто не не отвлекает взгляд от того, какие данные мы ожидаем. Причем, в отличие от моих вышеизложенных предложений, этот код не является фантазией, а решает задачу минимизации синтаксической избыточности в рамках существующих возможностей C#.