Monday, November 10, 2008

Сравнение производительности .NET ORM: Часть 1. Выборки данных

Сегодня я хотел бы начать серию заметок о сравнении производительности Entity Framework, LINQ to SQL, Active Record (NHibernate) и классического ADO.NET. Я еще не знаю, во что выльется эта идея, и к чему мы придем в результате, но основными целями этого сравнения я бы назвал:

  1. получение адекватных результатов сравнения и попытка их объяснить
  2. обсуждения различных вариантов увеличения производительности вышеуказанных способов доступа к базе данных
  3. получение понимания, какие варианты доступа к базе данных являются наиболее приемлемыми в той или иной ситуации с точки зрения производительности

Сразу скажу, что, несмотря на то, что я уже почти год использую Entity Framework, я не буду стараться его выгораживать. Откровенно говоря, сейчас мы в нашем проекте подошли к той границе, за которой bottleneck начинается уже в самом Entity Framework, а я пока и близко не в восторге от производительности нашей системы. Поэтому я постараюсь быть максимально объективным в данном вопросе, а если все же где-нибудь буду говорить чушь, вы меня поправите.

Изначально я хотел выяснить производительность EF и L2S, а также сравнить их с классическим ADO.NET, т.к. последний на данный момент является наиболее быстрым способом доступа к базе данных. Естественно, любая ORM будет медленнее. Потом меня эта идея настолько увлекла, что я для полноты картины добавил еще и Active Record, который, как известно, базируется на NHibernate. Еще один важный момент: я вижу NHibernate впервые в жизни, поэтому: а) не судите строго тот код, который я с горем пополам в нем написал, б) если у вас есть советы и комментарии, как по коду, так и по улучшению производительности – буду счастлив услышать :)

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

Используемые тесты

Использовал я всеми любимую базу Northwind, в частности таблицы Customers, Orders, Order Details и Products. Для каждого из способов доступа к данным я сделал 8 простых тестов:

Тест Описание Цель
Инициализация Создание контекста, инициализация, чтение строки подключения Получить время инициализации, возможно, оптимизировать его
Получение всех заказов Простая выборка всех строк из таблицы Orders Самый простой запрос, в то же время можно сравнить время материализации, т.к. там больше 800 записей
Многократное получение всех заказов Выборка всех строк из таблицы Orders 3 раза Сравнить степень кеширования запросов и их результатов
Получение всех продуктов одного заказчика Один сложный запрос через 3 таблицы Сравнить эффективность генерации SQL-скриптов, материализация минимальна
Многократное получение всех продуктов одного заказчика Тот же запрос 3 раза То же самое + кеширование
Получение всех продуктов одного заказчика (сложная версия) Пошаговое получение того же результата, что и в предыдущем случае. Берем коллекцию, проходимся foreach'ем, берем связанные записи и т.д. Сравнить данный способ обращения к данным с предыдущим
Многократное получение всех продуктов одного заказчика (сложная версия) То же самое 3 раза То же самое + кеширование
Микс: получение заказов + продуктов заказчика Синтетический тест, эмулирующий разные запросы Посмотреть, как влияют результаты выполнения одного запроса на следующий
Тест более серьезной нагрузкой Все предыдущие запросы последовательно за один присест Попробовать нагрузить базу большим количеством последовательных запросов, и посмотреть, что получится

Код я здесь приводить не буду, а лучше дам вам ссылочку, где можно скачать приложение, чтобы глянуть запросы и код и надавать автору по голове за него посоветовать мне, что и как можно улучшить :) Для запуска приложения вам понадобится база Northwind, VS2008 SP1 (там есть EF и L2S), а также установленный Active Record (скачать можно здесь). Не забудьте сконфигурировать строки подключения к базе и параметры Active Record в классе ActiveRecordProvider (да, знаю, что за такое нужно отбивать руки, но для текущей задачи такой реализации с головой).

Пара слов перед тем, как показать результаты. Тестировал я достаточно просто, вручную запуская один тест за другим, но в то же время я старался учесть кеширование запросов в SQL Server'е. Не знаю, насколько мне это удалось, но я старался запускать каждый тест отдельно вразнобой, и с некоторыми временными интервалами. К сожалению, тесты с базой данных – это такая вещь, которую бывает трудно воспроизвести, результаты варьируются, но, надеюсь, показанные числа дадут хотя бы какое-то общее представление о порядках. В любом случае, вы можете скачать исходники, установить L2S, EF, AR (если еще не установлены) и поиграться сами. Машинка у меня дома относительно небыстрая (Athlon 64 3200+ с 3 гигами мозгов), поэтому делайте скидку и на это.

Анализ результатов

Итак, результаты в миллисекундах:

Test Classic L2S EF NH/AR
Initialization 15 78 101 410
Orders 125 172 1078 1062
OrdersMultiple 187 218 1093 1187
CustomerProducts 109 234 1328 828
CustomerProductsMultiple 125 281 1344 1156
CustomerProductsComplex 140 464 1453 937
CustomerProductsComplexMultiple 203 511 1515 1109
Mixed 140 296 1265 1187
All 187 515 1656 1546

Что сразу бросается в глаза, так это тотальное отставание EF и AR от L2S (в 3-6 раза), а также то, насколько быстро L2S работает с базой – в среднем всего лишь где-то в 2 раза медленнее. При ближайшем рассмотрении можно сделать вывод, что AR работает быстрее, чем EF, по крайней мере, в запросах, возвращающих небольшое количество данных. У меня есть подозрение, что AR медленнее материализует объекты, чем EF (запрос по Orders – самый простой, но в то же время самый большой по количеству данных), но это нужно взять выборку побольше, чтобы проверить. При этом инициализация контекста у EF происходить почти так же быстро, как и у L2S, а AR тут немного отстает, хотя это и не страшно. Что еще важно: все ORM отлично справляются с кешированием повторных запросов: 3 запроса выполняются ненамного дольше, чем один. Связано это, прежде всего, с тем, что они кешируют не только материализованные объекты, чем существенно экономят время, но и, в случае EF – деревья промежуточных запросов (CQT, Canonical Query Tree). Так что одинаковые и даже похожие запросы выполняются намного быстрее. Также показателен последний тест: он дает возможность увидеть, что каждый запускаемый поодиночке тест все-таки несет определенный накладные расходы, потому что один общий тест лишь очень ненамного превышает самый медленный из своих компонентов. А это говорит о том, что НЕ НУЖНО создавать контексты на каждый запрос (это касается, в первую очередь L2S и EF), как бы вас этому ни учили в умных статьях. Если хотите добиться хорошей производительности – старайтесь использовать контексты как можно дольше, но в разумных пределах. Мы вот у себя нашли этот самый разумный предел – контекст живет в пределах обработки одной страницы, то есть контекст создается на каждый HttpRequest и умирает вместе с ним. В сложных случаях многостраничных диалогов мы продлеваем время жизни контекста, перемещая его временно в сессию.

Теперь пара слов о том, почему же мы видим такие результаты. Ну, во-первых, L2S у нас завязан лишь на один SQL Server и, если я не ошибаюсь, не строит никаких промежуточных CQT, чтобы общаться с тысячей различных провайдеров, как это делает EF. Во-вторых, EF – это не просто ORM, способная работать с разными базами данных и на лету подменять их без изменения кода. EF также обладает различными продвинутыми фичами маппинга, которых как минимум нет в L2S (не буду говорить про NH, ибо не знаю всех его возможностей). В-третьих, судя по проанализированным мною сгенерированным SQL-запросам, здесь особой разницы между ORM нет (подробнее об этом мы поговорим в следующей заметке), поэтому сюда особо рыть не стоит. И, в-четвертых, EF еще только-только зарелизился, думаю, мы еще увидим более быстро работающие версии.

Советы по использованию подопытных

Итак, определенные советы по использованию этих ORM (в широком смысле, L2S – это тоже ORM) можно дать уже сейчас. Если вам нужно написать простенькое приложение – используйте L2S – это САМЫЙ быстрый способ. Накидали табличек в базу, сгенерировали модель 2-мя кликами мыши – и вуаля, можно работать. Для более серьезных приложений уже нужно смотреть внимательнее. С точки зрения скорости написания кода равных L2S и EF нет. При всем уважении, AR/NH пока медленнее за счет того, что нет тула по созданию и поддержке модели. Если вам нужны продвинутые возможности маппинга, вы делаете разработку в рамках DDD или нужна поддержка различных баз данных – смотрите в сторону AR/NH и EF. Тут уже что больше по душе. Если же у вас на первом месте производительность и вы работаете лишь с MS SQL Server – тогда вам, наоборот, нужно смотреть либо в сторону L2S, либо вообще в сторону классического ADO.NET. Однако, в случае с классическим ADO.NET время разработки, а также последующей поддержки Data Access Layer увеличивается не просто в разы, а на порядки. Да, и пусть вас не смущают результаты запросов секунда и выше – как можно заметить, реально занимают время первые запросы, а остальные идут очень быстро и по накатанной. Более того, могу сказать по опыту, что где-то для 50-60% приложений производительности EF и AR/NH должно хватить с головой. Так что, если у вас не rocket science, не стоит заморачиваться.

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

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

Полезные ссылки:

Тестовый проект для сравнения производительности L2S, EF и AR/NH между собой и с классическим ADO.NET

Exploring the Performance of the ADO.NET Entity Framework - Part 1
Exploring the Performance of the ADO.NET Entity Framework - Part 2
ADO.NET Entity Framework Performance Comparison
Entity Framework Instantiation Times with a 120-Table Database
Linq to SQL vs NHibernate Part 1: What do they have in common?

Saturday, November 1, 2008

Будущее LINQ to SQL и Entity Framework

В одном из своих постов я имел неосторожность высказать мнение, что Entity Framework будет активно развиваться, а L2S (LINQ to SQL) – вряд ли, и что акцент разработки будет переведен на Entity Framework. К сожалению, мои опасения, похоже, становятся реальностью: Tim Mallalieu, Product Manager LINQ to SQL and LINQ to Entities сначала написал в блоге ADO.NET, что, цитирую:

«We’re making significant investments in the Entity Framework such that as of .NET 4.0 the Entity Framework will be our recommended data access solution for LINQ to relational scenarios. We are listening to customers regarding LINQ to SQL and will continue to evolve the product based on feedback we receive from the community as well»

А потом, в ответ на кучу комментариев о том, что «WTF? LINQ to SQL is dead?» он сделал дополнительный пост с разъяснениями, в которых повторил свои предыдущие слова, но постарался смягчить выражения:

«As mentioned, we have been working on this decision for the last few months. When we made the decision, we felt that it was important to immediately to let the community know. We knew that being open about this would result in a lot of feedback from the community, but it was important to be transparent about what we are doing as early as possible. We want to get this information out to developers so that you know where we’re headed and can factor that in when you’re deciding how to build future applications on .NET»

Так что, да, L2S будет жить и даже немного развиваться, но не с такой скоростью и не с такими инвестициями, как Entity Framework. Похоже, Microsoft сделала ставку на более тяжеловесный, но функциональный ORM. В свете последнего опроса на сайте того же Скотта Хенселмана, где оказалось, что L2S пользуется в 3 раза больше людей, чем EF, и по популярности он сравнялся с каличными датасетами, это решение выглядит немножко странным. С другой стороны, не стоит забывать, что L2S с нами уже почти год, на EF зарелизился только месяц назад. Я же считаю, что Microsoft нужно бы продолжать развивать L2S с прицелом на легковесность и производительность. Entity Framework – это, конечно, здорово, но все дело в том, что есть целый класс веб-приложений, в которых производительность превыше всего. Это, в первую очередь, сайты для массового обслуживания пользователей: электронные магазины, социальные сети, форумы и др. А здесь Entity Framework, увы, совсем не рулит. Кроме того, L2S можно рассматривать как реальную альтернативу датасетам, которые до сих пор используют многие. На деле же получается, что своим решением и маркетинговой политикой Microsoft может просто оставить L2S без существенной поддержки, а нас с вами – без мощного инструмента для быстрой работы с базой. Неужели придется возвращаться к DataReader'ам? :)

PS. Присмотритесь внимательнее к чарту с результатами опроса: ASP.NET MVC, который еще в бете, УЖЕ пользуется внушительное количество людей. Конечно, выборка не репрезентативна, так как Скотт работает в MVC тиме, и на его блог люди приходят целенаправленно, чтобы что-то узнать об этой технологии, но все же уже неплохо :)

Upd. Nov 9. Джули Лерман (Julie Lerman) дала дополнительные комментарии по этой теме здесь. В частности, она рассказала, откуда у Microsoft появилось 2 похожих ORM. Меня вот тоже волновал всегда этот вопрос :)

"LINQ to SQL grew out of the C# team. At the same time, the Data team was working on the EDM and Entity Framework. When both technologies were presented, it was a bit of a shock for each team to see that they had to products that, on the surface, were similar."

И еще пара слов о том, почему все-таки выжил Entity Framework:

"Eventually, LINQ to SQL became part of ADO.NET (it is a Data Access feature after all) and was handed over to the ADO.NET team. It wasn't the happiest of occasions for it's creators since the writing seemed already to be on the wall."

Видите, как все просто :)

Thursday, October 30, 2008

Архитектура StackOverflow.com

Сегодня по дороге на работу/с работы прослушал подкаст Hanseminutes #134 о том, как Jeff Atwood, Joel Spolsky и его команда создали сайт StackOverflow.com. Пара слов о сайте: это с одной стороны типичный Q&A сайт-сообщество, а с другой – немного другая песня. Джефф и Джоэль провели достаточно серьезный анализ похожих сайтов и вроде как постарались сделать максимально удобный и продвинутый ресурс, ориентированный на IT-специалистов. Не могу судить о том, успешен он или нет, но вроде бы за месяц у них уже много пользователей.

В этом подкасте не было бы ничего особенного, если бы не техническое обсуждение особенностей реализации сайта. Во-первых, сайт написан с использованием ASP.NET MVC, что уже примечательно для меня, т.к. я последнюю неделю занимался его изучением. И это несмотря на то, что ASP.NET MVC еще только недавно вышел в бете. То есть это один из первых достаточно популярных сайтов, которые используют эту технологию. Во-вторых, несмотря на то, что это сайт, предполагающий высокую нагрузку, они использовали Linq to SQL в качестве ORM. Также они сами признаются, что им пришлось напрячься, чтобы реализовать output-кеширование разных разделов страницы (некоторые куски страницы обновляются раз в минуту, некоторые – чаще). Еще у них были большие проблемы с локами и дедлоками в MS SQL сервере и они были вынуждены перейти на read committed snapshot”-транзакции (как это сделать в Linq to SQL и Entity Framework, можно почитать здесь). И это еще не все. Они использовали GZIP-сжатие страниц, причем не только для уменьшения размера страницы, но и для уменьшения размера страницы в кеше. И им действительно удалось добиться быстрой работы сайта на серьезных нагрузках. Можете проверить сами.

Однако это была лишь присказка :) Сказка начинается теперь. Угадайте, какое у них железо, сколько у них серверов? 4? 8? Больше? ОДИН! Один сервер, на котором крутятся и web-приложение, и база данных. 8-ядерный процессор, 4 Гб ОЗУ, 64-битная архитектура (процессор, ОС, .NET Framework, MS SQL сервер). Ничего особенного. В ответ на это Скотт говорит что-то вроде «это же не best practice, как вообще можно так делать?». И ему отвечают: «ну, мы перейдем со временем на ДВА сервера... когда будет нужно :)». И в конце добавляют контрольный выстрел в голову: их build-сервер, построенный на CC.NET, работает на ТОМ же production-сервере. Это какой-то ужас с точки зрения развертывания приложений, но это работает! :)

Со всем этим они умудряются делать ежедневные релизы новой версии, прогоняя ее на unit-тестах при каждом билде (обычный CI). Это значит, что у них получается нормальная стабильная версия, готовая к выходу в люди, каждый день. Думаю, нам всем есть чему поучиться в плане написания качественного софта, не говоря уже про performance на таком железе :) Да, нагрузка у них пока еще не слишком большая, но все же уже не детская. Я знаю продукты, на которых сайт лежал уже на десятке пользователей :)

А вы говорите, различные facebook’и работают на десятках, а то и сотнях серверов? Ну-ну :)

PS. Да, там уже доступна вторая серия этого общения. Еще не успел послушать, но если будет что-то интересное, сделаю update.

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

Upd2. Если кому-то интересно подробнее почитать про то, как рождался сайт StackOverflow.com, кому пришла в голову идея, сколько людей и как долго его писали, какие есть идеи его монетизации и сколько стоит обслуживание сайта в месяц - Джоэль рассказывает об этом в своей статье "How Hard Could It Be?: The Unproven Path"

Saturday, October 25, 2008

IT-отрасль и финансовый кризис

Сейчас о финансовом кризисе не говорит и не пишет разве что ленивый. Мы тут с другом тоже на досуге поразмыслили над тем, что несет финансовый кризис индустрии разработки ПО в целом и аутсорсингу ПО в частности.

Disclaimer. Эти размышления не претендуют на истину в последней инстанции, более того, они касаются в основном рынка разработки ПО города Харькова, хотя, думаю, их можно аппроксимировать как минимум на всю Украину, а, возможно, даже и на все постсоветское пространство.

Итак, вернемся к теме. С большой долей вероятности можно считать, что «золотой век русского программиста» если не закончился, то остановился. Я не скажу за всю Одессу, но в Харькове уже сейчас заметны некоторые отрицательные тенденции. Часть клиентов останавливает рост своих команд, некоторые компании, которые раньше брали людей на работу толпами, уже в открытую говорят о возможных сокращениях штата.

Причины и следствия

Попробуем проанализировать ситуацию. Финансовый кризис, прежде всего, вызван кризисом инвестиций. На западе очень частой является ситуация, когда у одних людей (разработчиков) есть идея стартапа, а у других – деньги (венчурные капиталисты и другие инвесторы). Раньше инвесторы позволяли себе роскошь выделять средства на рискованные проекты (даже после краха доткомов), теперь же с этим будет туго. Если вы заняты разработкой подобного стартапа, будьте готовы к тому, что на проекте может быть урезан бюджет или он может быть вообще закрыт (это, в частности, про меня). В тех же ситуациях, когда компания заказывает проект для себя, для автоматизации своего производства или дальнейшей продажи, можно ожидать всякого. При отсутствии финансов компании в первую очередь урезают всякие дополнительные расходы, к которым зачастую относят и разработку ПО, если, конечно, компания не зарабатывает продажей этого ПО. Немаловажно здесь и состояние компании-клиента и область ее бизнеса. Некоторые отрасли будут более подвержены кризису, чем другие. То есть вашему проекту может повезти, а может и не очень. В то же время, финансовый кризис может подстегнуть рост аутсорсинга, потому что западные и дальневосточные компании будут вынуждены уменьшать расходы, как и 8 лет назад, когда крах доткомов и 11 сентября дали толчок быстрому развитию аутсорсинга. Подробнее об этом можно почитать здесь и здесь.

Чем это грозит простым труженикам клавиатуры и мышки?

Теперь посмотрим на то, чем это грозит всем нам. На мой взгляд, потенциально возможны как минимум 2 этапа перед тем, как ситуация снова вернется в старую колею: 1) остановка развития и 2) деградация. Заденут ли нас оба этапа или ситуация успеет нормализироваться до наступления второго – это очень интересный вопрос.

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

Второй этап будет намного хуже, чем первый. И начнется он, если, не дай Бог, клиенты начнут банкротиться или сворачивать проекты. Судя по тому, что аналитики прогнозируют пик кризиса на начало 2009 года, это может начаться именно тогда. А может и не начаться – я не специалист, чтобы ответить на этот вопрос. Однако, чисто гипотетически, ЕСЛИ такое произойдет, то всем нам это грозит двумя вещами: 1) начнутся массовые увольнения вследствие закрытия целых проектов, которые увеличат предложение на рынке, при этом спрос будет почти равен нулю, 2) будут понижения зарплат, если уж не для текущих сотрудников, то, по крайней мере, для тех, кого принимают на работу. Средние зарплаты начнут падать.

Так что, о постоянных существенных повышениях зарплат (за 4 года, которые я слежу за рынком, средние зарплаты в аутсорсовых компаниях повысились в 4-8 раза) и легких переходах с места на место можно забыть. Кроме того, возможно обострение противоречий между сотрудниками компаний и их владельцами. Понять можно и тех, и других: обеим сторонам будут нужны деньги, чтобы выжить. Вот только для первых это означает увеличение зарплаты, а для вторых - уменьшение расходов, в том числе и на зарплату. Если вам повезло, и вы нашли хорошее место работы с достойной зарплатой – good for you. Возможно, нас всех пронесет на этот раз, а возможно, и нет, и мы получим еще один 2000-2002 год, когда зарплаты держались на уровне 200-400 долларов, а новых сотрудников предпочитали не нанимать.

Как пережить смутные времена?

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

Быть эффективным и полезным – это значит не просто делать свою работу, но делать ее быстро, качественно, ответственно и, что самое главное, добиваться положительного результата. Про эффективность хорошо написал Сергей Розовик. Если вы привыкли часть рабочего времени посвящать социальным сетям или башоргу – пора отучиться от этой вредной привычки. Если вы привыкли работать расслабленно, спустя рукава – почитайте про «7 навыков высокоэффективных людей», статьи о лайфхакинге и других способах повышения личной производительности и эффективности. Если у вас есть проблемы на проекте – надо задуматься об их причинах и устранить их.

Быть перспективным – это значит не просто обладать потенциалом, но использовать и повышать его. Если вы уже забыли, когда последний раз читали книги или статьи по программированию (менеджменту, тестированию, администрированию, нужное подчеркнуть) – впору заняться самообразованием. IT – это не та отрасль, где можно выйти из университета с полученным багажом знаний и годами его использовать в работе, здесь нужно развиваться. Почитайте про новые технологии, языки программирования, подходы к разработке и методологии ведения проектов. Даже если вам это не понадобиться сегодня, вам это может срочно понадобиться завтра при приеме на новую работу.

Ну, и последний совет. Финансовый кризис ударит по нам не только со стороны работы, но и стороны нашей повседневной жизни. Проанализируйте ситуацию и советы аналитиков о том, что нужно делать с лежащими на депозитах деньгами, счетами, кредитами. Запасайтесь картошкой :) Отучите себя наконец-то брать потребительские кредиты! А если нужен кредит на машину/квартиру, то лучше повременить. Еще о стратегиях выживания во время кризиса можно почитать у Макса Крайнова, а можно найти и другие – благо их сейчас полно.

Какие будут результаты

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

На локальном же уровне в нашей отрасли наверняка произойдут следующие вещи:

1) Произойдет естественный отбор, количество компаний на рынке сократиться, исчезнут самые слабые и неприспособленные

2) Выжившие компании сбросят лишний вес, который им мешал развиваться дальше, станут более гибкими и устойчивыми (почитайте, например, книгу Луиса Герстнера «Кто сказал что слоны не умеют танцевать?» о том, как IBM вышла из крутого пике в начале 90-х годов)

3) Рынок IT-специалистов стабилизируется, зарплаты перестанут расти неимоверными скачками, а сами специалисты станут умнее и профессиональнее.

4) Вполне возможно, что государство наконец-то обратит свое внимание на IT-сферу и у нас наконец-то появиться нормальные законы, регулирующие рынок, упрощающие развитие и улучшающие конкурентоспособность наших компаний (как в Индии, например)

Еще немного о кризисе понятными словами можно прочитать здесь:

Понятно о кризисе
Экономический кризис на пальцах
Прививка от идиотизма

Еще немного о пользе unit-тестирования

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

1. Unit-тесты позволяют найти баги в существующем коде

Пример. Следуя совету в первую очередь писать тесты на критический функционал (во-первых, его меньше, во-вторых, если он ломается, то ломается с большим грохотом), мы решили покрыть тестами систему прав и некоторый кусок бизнес-логики, реализующий сложные вычисления по иерархиям объектов и их связям с другими иерархиями. Казалось бы, проекту уже стукнул годик, этому коду – более полугода, была уже куча итераций с тестированием, код должен работать правильно! Ага, щаз. При помощи unit-тестов было найдено полдюжины багов в логике вычисления прав (штуки три – вообще критических) и чуть меньше в вычислениях по иерархиям.

2. Зачастую unit-тесты позволяют быстрее и проще тестировать код, чем это можно сделать через UI

Столкнувшись с ситуацией, описанной в предыдущем примере, я начал задавать себе вопрос: почему баги не были найдены раньше, и получил достаточно простой ответ: сделать это через UI очень сложно. Наши QA, как и программисты, которые писали этот код, не виноваты – просто есть определенная степень сложности в системе, после которой и первые, и вторые просто физически не могут проверить все варианты в установленные сжатые сроки. Например, система прав учитывает с одной стороны иерархию пользователей и ролей, а с другой – иерархию объектов. Обе иерархии имеют свои правила наследования прав, которые влияют на получение окончательного результата. Вычисления очень похожи: они учитывают как иерархические связи внутри иерархии, так и связи с другими иерархиями. При этом через UI это все просто невозможно протестировать полностью в резонное время! Вот здесь-то на помощь и приходят unit-тесты. Быстро воссоздав иерархии объектов и связи между ними в тестах, мы легко нашли и обезвредили толпу багов.

3. Unit-тесты позволяют почувствовать душок, исходящий от вашего кода

Этот пункт присутствовал и в предыдущей статье, но я еще раз столкнулся с его истинностью. Возвращаясь к предыдущему примеру: при написании unit-тестов я столкнулся с кодом, в котором логика была размазана не просто по нескольким классам приложения, а даже выходила за рамки слоя. Сообразив, что такое тестировать будет несколько неудобно, я зарефакторил код, вернув его туда, где он должен быть. После этого написание тестов свелось к простым вещам. Другой пример: иногда при написании теста на большой метод, начинаешь понимать, что количество тестов растет, и неплохо бы разбить код на несколько методов, что облегчит тестирование и сделает код более правильным. Вывод: очень часто сложность или невозможность написания тестов являются сигналом к тому, что код нужно менять.

Я не буду уже писать о том, как написанные unit-тесты помогали нам после изменения существующего кода (допустим, добавления кеширования) – здесь их польза и так ясна как день :)

Sunday, October 19, 2008

Happy Birthday!

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

Что хочется сказать по этому поводу? Прежде всего то, что я доволен прошедшим годом. В общей сложности было написано 32 сообщения, что сравнительно немного, но в то же время надеюсь, что содержимое этих сообщений было небессмысленно и полезно вам. То, что это было полезно мне самому – без сомнения. Во-первых, это возможность делится своим опытом с единомышленниками, обсуждать что-то, помогать другим. Во-вторых, это способ сингулировать свои знания и опыт в одном месте и не забыть что-то важное. В-третьих, блог – это еще и средство самовыражения, причем не самое плохое, не говоря уже о том, что это в какой-то степени визитная карточка специалиста. Ну, и в-четвертых, иногда так хочется чем-то поделиться, что нет возможности не написать – это единственный выход дать волю своим чувствам и эмоциям, поделиться с окружающими :)

За этот год сформировалась определенная тематика блога: это не чистый технический блог, но в то же время и не дневник. Я стараюсь писать около 80% постов об IT и программировании, так как это основное назначение блога, но в то же время разбавляю эти сообщения рассказами о путешествиях и походах, в которых я побывал, а также фотографиями разной направленности. Думаю, такой подход имеет право на жизнь. Кроме того, я стараюсь писать о том, с чем сталкивался сам, делиться своим опытом. Принципиально стараюсь не писать сообщений типа «Сегодня вышла технология такая-то, в ней есть такой вот переведенный мной с MSDN список фич».

Также где-то около недели назад я настроил Google Analytics для блога (сделать это можно для любого сайта, а как это сделать для blogspot’а, можно прочитать здесь). Жаль, конечно, что я этого не сделал раньше (урок №1), но все же уже даже по данным за эту неделю можно сделать определенные выводы о том, кто посещает блог, откуда, зачем и в каких количествах.

Абсолютное большинство посетителей – пользователи из России и Украины, что понятно. За недельку несколько человек заглянули из Испании и Норвегии, но, скорее всего они испугались, закрыли окошко и пошли дальше :) Хотя один человек оставил комментарий на английском в теме об Entity Framework rollback, но я его увидел слишком поздно. Урок №2 – не забывайте настраивать оповещение о комментариях на почту, если не хотите пропустить что-то важное.

По данным Google Reader, на блог сейчас подписано около 20 человек, что не очень много, за неделю же пришло 60 уникальных пользователей. Еще один вывод: люди приходят на блог, но при этом редко добавляют его в ридеры. Так что если вы иногда читаете блог, но еще не добавили его в ридер – сделайте это, мне будет приятно :) Конечно, если он того стоит.

Дальше о браузерах. Думаю, поклонникам Firefox будет приятно узнать, что по данным моего блога, его используют больше 50% пользователей (ура, товарищи!), IE вместе с Оперой делят почетное второе место с 17%, а Хромом пользуются уже почти 9% (ага, попались, гуглолюбы :)). Есть даже 1% SeaMonkey (это еще что за зверь такой?), а вот Сафари пока не было (хотя что им делать на блоге, посвященном технологиям Microsoft).

Распределение по операционным системам очень простое: 96% за Windows, 4% пользуются Linux и Mac (причем, Firefox).

61% посетителей приходит на сайт из поисковиков (70% - Google, 30% - Yandex), 23% - с других сайтов (спасибо Паше Подлипенскому, Наде Плаховой и каким-то неизвестным блогам в Blogger), и 16% - напрямую вбивая адрес в поисковике.

Ну, и самое интересное – зачем же люди сюда идут? Судя по поисковым запросам, довольно много посетителей приходит, чтобы почитать про Entity Framework, второй по популярности темой оказались unit-тесты, что не может не радовать, далее с большим отставанием остальные темы. Понравилось, что люди ищут навыки программиста, mind maps, подкасты для программистов и даже слова для песни «Щедрик» :)

Еще несколько выводов за этот год: почти никто не комментирует сообщения о путешествиях, но раз друзья присылают сообщения в аську, что им нравятся отчеты, то буду продолжать в том же духе. Ну, и урок №3 – длинные технические посты обычно не комментируются вообще, а вот маленькие с размышлениями на разные темы – за милую душу :)

И напоследок небольшой хит-парад постов.

Самые обсуждаемые:
Entity Framework: Vote Of Confidence (8)
Оценка уровня программиста (8)
Entity Framework + NAnt - трудности с включением файлов модели в сборку (6)
Наш ответ Scott'у Hanselman'у (5)
Аутсорсинг ПО = утечка мозгов (5)
Планирование, учитывающее предыдущие результаты (5)

Полезные технические (с моей точки зрения):
Continuous Tech Development (полезно всем, кто хочет профессионально развиваться и в то же время тратить на это минимум свободного времени)
Оценка уровня программиста (о том, какие критерии оценки я вынес для себя)
Mind maps и анализ требований (mind maps – интересная техника, советую всем)
Entity Framework rollback: Часть 4. Пример приложения (советую все 4 части тем, кто хочет разобраться в том, как работает EF внутри)
OutOfMemoryException и как его побороть (многие друзья написали, что круто, но сложно :) – невзирая на это, советую всем, кто хочет разобраться с тем, как отлаживать сложные ситуации)
Введение в mock-объекты. Классификация (must-read для всех, кто хочет понять, что такое mock-объекты и как их правильно использовать)
Unit-тестирование для Entity Framework (название говорит само за себя)

Интересные нетехнические посты:
Хамар-Дабан, пик Черского (отчет о походе на пик Черского, что возле Байкала)
Байкал (описание Байкала)
Иркутск и панорамы из путешествия на Байкал (описание Иркутска и панорамы)
Путешествие «Щедрика» (о том, как малоизвестная украинская мелодия стала одной из самых популярных в мире, а мы об этом и не знаем)
Фильм "Бесценный доллар" (интересный, хотя и спорный фильм об экономике Штатов и мировой экономике)

Итак, итоги года подведены. Спасибо всем, кто читает этот блог! Если у вас есть советы, критика или комментарии по его улучшению – поделитесь со мной :) Я же, со своей стороны, постараюсь писать больше и интереснее.

Sunday, October 12, 2008

Unit-тестирование для Entity Framework

Как вы уже наверно догадались по тематике моих нескольких предыдущих постов, я сейчас активно занимаюсь изучением и внедрением unit-тестов на своем проекте. Сегодня я хотел бы поделиться решением одной из самых трудных задач, с которой мне пришлось столкнуться, а именно реализацией поддержки unit-тестирования в Entity Framework.

Если вы сейчас погуглите по этой теме, то найдете мало ответов на простой вопрос: как заставить unit-тесты работать с Entity Framework. Думаю, есть несколько вариантов решения, но я бы хотел поделиться нашим. Его преимущество в том, что он не использует базу. Можно долго спорить на тему того, как нужно тестировать Data Access Layer, чтобы код лез в базу или не лез туда. У варианта с использованием базы есть как свои достоинства, так и недостатки. Лично я считаю, что unit-тесты должны покрывать полностью лишь код DAL'а, если же вы хотите заодно проверять и базу – напишите небольшое количество integration-тестов, которые будут проверять соединение, а также какой-то минимальный набор CRUD-запросов, чтобы при случае найти ошибки и здесь. Мы же дальше будем рассматривать тестирование без базы данных.

Итак, в чем же, собственно, состоит проблема? Дело в том, что Entity Framework, к сожалению, проектировался не совсем для того, чтобы код, содержащий вызовы к нему, можно было легко протестировать – слишком уж завязаны сущности на контекст и слишком уж много функций контекст берет на себя (куда ж без этого):

  • сгенерированные ObjectQuery<T>-свойства для доступа к данным обращаются напрямую к базе данных
  • не поддерживает POCO (Plain Old CLR Objects), все объекты содержат navigation properties и Load-методы, которые также обращаются к базе
  • не поддерживает принцип Persistence Ignorance, то есть все объекты сами знают, как себя сохранять

Однако, несмотря на все это, Entity Framework обладает дополнительными полезными свойствами, которые нам помогут:

  • EF кеширует сущности в памяти, более того, позволяет работать с контекстом, как датасет: у сущностей есть состояния (unchanged, inserted, updated, deleted и др.), а также метод AcceptAllChanges, который переводит добавленные или измененные объекты в состояние unchanged
  • EF не поддерживает lazy loading, который бы мог привести к проблемам
  • контекст EF, основываясь на схеме модели, имеет достаточное количество информации о типах связей между сущностями, constraint'ах, not null-полях и т.д., чтобы автоматически проверять, все ли вы верно проинициализировали перед тем, как засылать сущность в базу данных, причем делает это даже когда вы не работаете с базой, а вызываете AcceptAllChanges()
  • внутренний API контекста достаточно открыт, чтобы самому производить доступ к закешированным сущностям, обходя стандартные ObjectQuery-свойства

Итак, как же нам нужно модифицировать наш код, чтобы он стал тестабельным?

  1. Прежде всего, для нашего же удобства нам стоит выделить код, работающий с IQueryable, ObjectQuery и IRelatedEnd.Load в отдельный слой – Data Access Layer. Не важно, как вы это реализуете, лишь бы у вас был контроль над теми местами, где идут запросы к EF. Если вы это уже сделали, good for you.
  2. Далее нужно перевести контекст в так называемый metadata-only режим, который исключает доступ к базе данных вообще. Перевод этот производится урезанием строки соединения до путей к метаданным и названия провайдера. То есть строку соединения с базой мы не пишем.
  3. Для тех случаев, когда мы работаем с unit-тестами, нужно заменить вызовы SaveChanges на AcceptAllChanges
  4. Для тех случаев, когда мы работаем с unit-тестами, нужно реализовать получение данных исключительно из ObjectStateManager, т.е. из памяти, а не из автосгенерированных ObjectQuery-свойств.
  5. Для тех случаев, когда мы работаем с unit-тестами, исключаем из кода любые вызовы IRelatedEnd.Load, то есть все обращения к Load-методу из navigation-свойств сущностей.
  6. Если был реализован lazy loading (через все те же IRelatedEnd.Load), необходимо его отключить.

Возникает вопрос: как ЭТО ВСЕ реализовать физически в коде? Не писать же отдельные методы доступа к данным для unit-тестов. Ответ прост: это можно реализовать при помощи Dependency Injection. То есть основная идея такова: нужно реализовать специальный интерфейс, содержащий методы GetEntities<T>, GetEntity<T> и ApplyPendingChanges, а также как минимум двух его наследников: одного для реального доступа к базе, другого – для работы с контекстом в памяти. Все, наши методы DAL, естественно, будут использовать именно эти методы, а вот какой объект будет инициализирован в DAL – это уже зависит от того, находимся мы в реальном приложении, или в режиме тестирования.

Я решил приготовить пример кода вместо того, чтобы делать еще один длиннющий пост с подробностями. Реализация достаточно проста, поэтому, думаю, кому нужно, те разберутся и без моей помощи :) В примере используется контекст на базе данных Northwind, в качестве unit-test framework'а я использовал NUnit. Если будут вопросы по коду – пишите в комментариях. Будет много вопросов – напишу отдельный пост с разъяснениями.

Собственно, пример реализации можно найти здесь:

Пример реализации поддержки unit-тестирования в Entity Framework

Удачи всем!

Upd. В ответ на справедливое замечание Миши Чалого о том, что в коде нифига не DI, изменил код, чтобы DI там было :) Кроме того, вынес знание об инициализации контекста и mock-обертку в проект unit-теста, где они и должны быть. Код стал более правильным, так что прошу скачивать и использовать вторую версию по той же ссылке.