понедельник, февраля 11, 2008

«Пилите пули, Шура, они серебряные…»

или "В цепких лапах библиотек"


Поиски серебряной пули в разработке ПО продолжаются.
Сегодня читаю в «Блоге об IT бизнесе» Виктора Ронина статью «Серебряная пуля в разрезе». Если в прошлых постах автор искал источники повышения производительности разработки софта в мотивации программистов, то теперь он ищет серебряную пулю в open source software. Вот несколько цитат:

«Однако, я напоследок оставил то, что действительно является «серебряной пулей».
Это – библиотеки. Точнее не только библиотеки, но и разнообразные платформы. За последние двадцать лет появилось гигантское количество открытого доступного кода или хотя бы платформ с открытыми интерфейсами.
...
2008 год: RFID ворота на складе, подключенные к машинам на которых установлен Ubuntu с программой, которая выгребает данных с RFID считыватели и по сети закатывает в БД. На сервере крутится программа, которая выгребает данные из БД и через SOAP загружает их в Sales Force Automation...

Дай бог 0.01% [второй] системы написано программистом, а остальные (сайт sales force, библиотеки по работе с RFID, база данных, tcp/ip, xml, soap, графический интерфейс, броузер и т.п.) досталось ему фактически забесплатно. Если бы он пытался написать все сам (даже «срезая углы») у него это заняло бы всю жизнь.

...мы и не заметили серебряной пули, хотя она находится у нас под носом."


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

С одной стороны, прогресс вроде бы есть. Ранее интерфейс типичного бизнес приложения мерцал зелеными буквами и цифрами на мониторе PDP терминала, и представлял собой последовательность меню в стиле «Нажмите 1 для просмотра баланса; Нажмите 2 для пополнения счета; Нажмите Esc…». Сегодня, мы можем мышкой перетаскивать активы из Нью-Йоркского филиала в Нижегородский, на интерфейсе, представляющем собой географическую карту, и смотреть отчеты в своем мобильном телефоне. Т.е. тысячи новых возможностей, интерактивность, мобильность, красочность и т.д.
С другой стороны, прогресса никакого и нет. Как требовалось раньше 10 человеко-лет на разработку среднего бизнес приложения, так и сегодня требуется 10 человеко-лет. Как строился квартальный отчет 45 минут, так он и строится 45 минут.

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

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

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

  • наличие ошибок в библиотеках и компонентах. И обязательно эта ошибка проявляется именно у вас.

  • несоответствие системных требований библиотеки и основной системы. Мы подключаем компонент и обнаруживаем, что он сожрал все процессорное время, или вообще свалил программу из-за несоответствия потоковой модели апартаментов.

  • неэффективное использование библиотек из-за недостаточного знания ее внутреннего устройства. Например, мы можем, по незнанию использовать один экземпляр на все приложение интерфейса «Соединение» из внешней библиотеки, и получим кучу проблем с блокировками, просто потому, что мы не знали о том, что наилучшей стратегией использования этой библиотеки является создание отдельного экземпляра «Соединения» для каждого вызова.


Я думаю, все сталкивались с подобными проблемами. Многие склонны списывать эти проблемы на «кривые ручки» тех, кто создает библиотеки и компоненты, либо на собственные «кривые ручки». Некоторые уверены, что по настоящему «классные» компоненты лишены всех этих недостатков. Есть также мнение, что библиотеки и компоненты с открытым кодом способны решить все эти проблемы. Это очень распространенное мнение, и я с ним не согласен. Сейчас попытаюсь объяснить почему.

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

  • компоненты с открытым кодом имеют меньше ошибок. Даже если это и не так, у нас есть исходный код, и мы можем быстро исправить ошибку.

  • компоненты с открытым кодом открыты (извините за тавтологию), мы можем добавить нужный нам функционал, выкинуть не нужный.


Все это так. Но все это несущественно для решения тех проблем, которые порождаются использованием компонентов. Дело в том, что библиотеки и компоненты не в силах побороть трудности создания программных систем, обусловленные основными свойствами этих систем: сложностью, согласованностью, изменяемостью и незримостью [Брукс, Мифический человеко-месяц, глава 16]. Как бы мы не делили код на библиотеки, он все равно будет скомпилирован, слинкован и выполнен в едином адресном пространстве. Мы ничего не можем сделать с изменчивостью, - меняется внутреннее устройство библиотек, меняется интерфейс – это объективно. Мы не можем отменить согласованности, совместно работать могут только согласованные интерфейсы. Если мы используем не ту версию компонента, мы получаем runtime error. И все мы знаем, что такое “dll hell”. В этом плане, не имеет никакого значения, доступны исходные коды компонент в процессе создания программы, или не доступны.

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

Многим из вас, вероятно, приходилось сталкиваться с системами, как бы застывшими в своем развитии. Вот стоит система, созданная на основе «Турбо-Москаль» версии 5.1. «А чего вы не переведете ее на новую версию, ведь давно вышел «Гипер-Москаль» версии 9.0?». «Да ребятам там в пятом «Турбо-Москале» много чего пришлось переделать руками. Когда попробовали перейти на восьмую версию - даже не скомпилировалось. А ребята то уже уволились. Стали смотреть, что они там наменяли, но ты же знаешь, в пятом «Москале» двадцать мегабайт исходников. Разве там разберешься!». Знакомая картина?

Поэтому при использовании библиотек с открытым кодом используют специальные практики. Свои изменения необходимо, абсолютно необходимо ввести в общий код библиотеки (компонента). А для этого вы должны пойти на сайт сообщества, которое разрабатывает нужный вам компонент, и зарегистрировать в багтрекере (bugtracker) или в бэклоге (backlog) проекта баг (bug) или фичу (feature) или ишью (issue) - по обстоятельствам. Затем вам надо подключиться к репозиторию исходников проекта и взять рабочую копию исходного кода. Потом внести свои изменения в код, протестировать, интегрировать, затем создать патч (patch) и отправить его координатору проекта. Если координатор сочтет, что ваш патч удачен (а если не сочтет?), он включит его в код проекта и он войдет в следующий unstable релиз. Только так вы избавите себя от проблем с неподдерживаемыми изменениями в исходном коде сторонних компонент. Но вы почувствовали, как запахло жареным? Нет? Вы чувствуете, что вы начали заниматься разработкой компонента с открытым кодом, вместо того, чтобы заниматься разработкой своей системы. Оно вам надо!

В общем, что компоненты без исходных кодов, что с исходными кодами – проблем с ними предостаточно. Конечно аргумент «если писать все самому, это займет всю жизнь» принимается и с ним никто не спорит. Но если рассматривать проблему шире, ведь бизнес приложения живут десятилетиями, живут и изменяются, то ясно, что библиотеки это просто один из инструментов, позволяющий нам создавать то, что называется «современное программное обеспечение». Не больше и не меньше. Как было 10 человеко-лет на систему, так и осталось.

Ну а что же может повысить эффективность создания программного обеспечения? Я думаю то, что помогает преодолевать основные факторы сдерживающие эффективность: сложность, изменчивость, связанность и неосязаемость. Заменить подключаемые библиотеки на связываемые автономные сервисы (слышали про SOA), для борьбы со сложностью. Вместо монолитных компилируемых приложений использовать распределенные, конфигурируемые в runtime для борьбы со связанностью и изменчивостью. Либо более радикальные решения в этом же направлении, связанные с применением динамических языков, позволяющих изменять интерфейсы взаимодействия непосредственно во время исполнения. С неосязаемостью программного обеспечения и связанными с этим проблемами помогают бороться проектирование на основе моделей (Model Driven Design) и разрабатываемые методики статического и динамического анализа кода. Конечно, решение одних проблем породит новые. Но мы научимся за стандартные 10 человеко-лет, создавать такие программы, о которых сейчас даже не мечтаем.
А серебряной пути нет.

суббота, февраля 09, 2008

Реальные оценки

Возвращаясь, в очередной раз, к теме оценки сроков проектов (project estimation), я хочу поговорить не о точности, техниках и методиках оценки, а совершенно о других вещах. Я хочу поговорить о честности и смелости.

Первоначальная оценка проекта практически всегда происходит на фоне конфликта интересов. Менеджер (и в его лице вся проектная команда) заинтересован в том, чтобы все было долго и дорого. Это понятно, ведь «вес» менеджера прямо пропорционален размеру бюджета, который он осваивает. Заказчик хочет, чтобы все было быстро и дешево. Ситуации, когда разработка проекта ведется в условиях практически неограниченного бюджета, чрезвычайно редки.
Как делается оценка проекта. Сначала менеджер собирает технические оценки, которые делают программисты. Затем, он учитывает некоторые показатели, вроде размера команды, времени на обучение и отпуска и т.д. и выдает окончательную оценку.
Когда заказчик внешний, то оценка проекта, это фактически предмет торговли, со всеми вытекающими. Проект надо продать подороже, но не перегнуть палку, чтобы заказчик не ушел. Либо заказчик очень важен для компании, и необходимо начать проект любой ценой.
Когда заказчик внутренний, то обычно все еще хуже, чем с внешним. Внутренний заказчик, это чаще всего начальник менеджера или начальник начальника. Какие уж тут торги.

Вот тут самое время поговорить о честности и смелости. Ведь что часто происходит? Начинается новый проект, менеджер приносит оценки - «это можно сделать командой в восемь человек за девять месяцев». На что получает немедленный и категоричный ответ начальника: «Любой аутсорсер сделает это в два раза быстрее и в два раза меньшим количеством людей». Чем не повод поговорить о смелости и честности?
Бывает, конечно, и не так плохо. Вместо категоричного ответа можно услышать что-то вроде: «Вы знаете, что у нас ограничения по бюджету. Вот если бы вы смогли сделать это за шесть месяцев командой из шести человек, было бы здорово». В любом случае менеджер проекта (или просто программист, если проект маленький) испытывает на этом этапе очень сильное давление со стороны начальства, заказчика, отдела продаж, и т.д.

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

Существует и другой путь, под девизом «Главное начать. А потом они никуда не денутся». Есть мнение, что следует соглашаться на сокращение предварительных оценок, чтобы склонить руководство или заказчика к тому, чтобы начать проект. После того как проект будет начат и в него будут вложены определенные средства, то им придется довести его до конца, несмотря на перенос сроков. А объективные причины, объясняющие причины сдвига сроков мы всегда найдем. Надо сказать, что это весьма и весьма распространенный подход. Это путь смелый, но не честный, и надо сказать весьма рискованный. Соглашаясь сделать проект на $250 000, отчетливо сознавая, что потребуется не менее $500 000, вы подставляете человека, принимающего решение о старте проекта (будь то ваш начальник или представитель заказчика) на серьезную сумму, и если по результатам проекта вам придется всего лишь расстаться с работой - это можно считать довольно легким исходом.

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

Ну, и наконец, можно поступить честно и смело. Не поддаваться давлению, сказать все, что вы думаете своему начальнику и уйти, хлопнув дверью. И честно, и смело, и…. непрофессионально.

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

Практически всегда, предметом торговли может быть объем функционала. Можно пожертвовать второстепенными функциями, можно пересмотреть системные требования. Например, можно отказаться от поддержки многоплатформенности, или кроссбраузерности, обычно это позволяет значительно сократить затраты.

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

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

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

пятница, февраля 08, 2008

Entity Framework - Identity, Computed и ключевые столбцы

Таблицы БД могут содержать столбцы, которые изменяются в момент сохранения изменений на стороне БД. В SQL Server это столбцы IDENTITY и вычисляемые (computed) столбцы. Как с такими таблицами работает Entity Framework?

Рассмотрим фрагмент Storage Schema (это Xml описание схемы БД в составе EDM)



<EntityType Name="Invoice">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
<Property Name="Customer" Type="nvarchar" Nullable="false" MaxLength="50" />
<Property Name="Cost" Type="money" Nullable="false" />
<Property Name="Discount" Type="money" Nullable="false" />
<Property Name="Total" Type="money" StoreGeneratedPattern="Computed" />
</EntityType>



Это описание таблицы Invoice. Столбец “Id” объявлен identity, а столбец “Total” - это вычисляемый столбец ([Cost]-[Discount]). Мы видим, что визард при генерации EDM добавил в описания этих столбцов атрибут StoreGeneratedPattern. Именно этот атрибут сигнализирует EF о том, что значение данного столбца вычисляется в БД.
StoreGeneratedPattern имеет три значения: “None”, “Identity”, “Computed”. Значение “None” принято по умолчанию. “Computed” обозначает, что столбец содержит вычисляемое значение, и при вставке и обновлении EF вместо того, чтобы передавать значения для этого столбца в БД, получает его значение после обновления. “Identity” соответственно обозначает столбец Identity. По смыслу он практически идентичен значению “Computed”, но заставляет EF генерировать специальный SQL код для получения значения этого столбца после вставки:


select [Id] from [dbo].[Invoice]
where @@ROWCOUNT > 0 and [Id] = scope_identity()


Свойства классов, замапленные на вычисляемые столбцы хот и имеют аксессор set, никогда не сохраняют свои значения в БД. Что бы мы ни присваивали свойству Invoice.Total после сохранения изменений, там будет записано значение, прочитанное из БД.

В этом отношении свойства классов, которые входят в состав Entity Key - ключа сущности, отличаются еще более странным поведением. Логично было бы предположить что Invoice.Id будет вести себя подобно Invoice.Total. Поскольку это свойство замаплено на identity столбец, то какие бы значения мы ему не присваивали. После сохранения мы получим значение, установленное БД. Это так лишь отчасти:


01 using (TestEntities context = new TestEntities())
02 {
03 Invoice inv = new Invoice();
04 inv.Id = 1;
05 inv.Total = 10;
06 context.AddToInvoice(inv);
07 context.SaveChanges(true);
08 Console.WriteLine(inv.Id);
09 inv.Id = 1000;
10 context.SaveChanges(true);
11 Console.WriteLine(inv.Id);
12 }


В приведенном коде мы сперва создаем экземпляр Invoice, а затем присваиваем значения его свойствам Id и Total. Затем мы сохраняем изменения в БД. После сохранения, в строке 8 мы видим в свойстве Id значение назначенное БД. Однако, когда в строке 9 мы пытаемся изменить значение Id то получаем исключение:

"The property 'Id' is read-only because it is part of the entity's key."

Вот так. В строке 4 присваивание Id проходит нормально, а здесь сваливается с исключением. Изменять значения ключевых свойств (свойств, которые входят в состав ключа) можно только, если состояние объекта “Detached” или “Added”. Такое поведение появилось только в beta 3. В предыдущем CTP изменения значений ключевых свойств тупо сохранялись в БД или игнорировались для identity столбцов.

четверг, февраля 07, 2008

Опрос - что важнее всего в разработке ПО?

Разрабатывать софт интересно, но сложно. У каждого из нас есть свой положительный и отрицательный опыт в этом деле. Что, по вашему мнению, наиболее важно для успеха в разработке ПО? Форма для голосования слева на сайдбаре.
Голосуем в течении недели, а потом посмотрим результаты, и я расскажу, что самое важное по моему мнению. Заодно и сравним :)

P.S. Просьба указывать три, ну максимум четыре варианта, иначе опрос теряет смысл.

вторник, февраля 05, 2008

Entity Framework - производительность

Для всех, кого интересует производительность Entity Framework, рекомендую почитать пост Brian Dawson в ADO.NET team blog - "Exploring the Performance of the ADO.NET Entity Framework - Part 1".

Там нет сравнительных характеристик, но зато подробно расписано из чего складывается время выполнения запроса, что кэшируется и как, и каким образом это время можно сократить. Очень интересные вещи, понимание которых, помогает писать правильный код. Например, Object context construction занимает 1.38% времени при самом худшем раскладе. А загруженные в MetadataWorkplace метаданные EDM сохраняются в глобальном кэшэ (на уровне приложения). Это, например, означает, что нет никакой надобности кэшировать контекст в ASP.Net сессии, а лучше создавать свой экземпляр для каждого ASP.NET запроса.