вторник, 15 сентября 2009 г.

Книга “Adrenaline Junkies and Template Zombies: Understanding Patterns of Project Behavior” by Tom Demarco, Piter Hruschka, Tim Lister et al.

Adrenaline_Junkies_and_Template_Zombies Труды Кристофера Александера вот уже в течение сорока лет оказывают серьезное влияние на светлые умы деятелей программной индустрии. И хотя первое упоминание книг Александера в компьютерной литературе, датируется 1969 годом в книге Эда Йордона и Алистера Коберна, идеи Александера оставались в тени, и не стали достоянием широкой компьютерной общественности, вплоть до середины девяностых годов, до выхода знаменитой книги «Банды четырех». Именно выход этой книги ознаменовал начало новой эпохи в нашей индустрии, эпохи всевозможных шаблонов. Идея шаблонов настолько понравилась общественности, что она стала завоевывать все новые и новые области. Так появились шаблоны реализации (implementation patterns), шаблоны асинхронного программирования, шаблоны корпоративных приложений, шаблоны реализации распределенных приложений и многие другие. Несмотря на свое широкое распространение, шаблоны оставались прерогативой архитектора и разработчика, а не менеджера. Но за последние несколько лет эта картина начала изменяться. Вслед за книгой  Джеймса Коплиена  и Нила Харрисона вышла книга Тома ДеМарко, Тима Листера и др. с интригующим названием “Adrenaline Junkies and Template Zombies: Understanding Patterns of Project Behavior”, посвященная шаблонам поведения программных проектов.

Книга состоит из 86 шаблонов, каждый из которых занимает 2-3 страницы и описывает определенную ситуацию, так или иначе связанную с состоянием или поведением программного проекта. Первым шаблоном в книге является шаблон “Адреналиновые наркоманы” (Adrenaline Junkies), который описывает такое состояние проекта, при котором считается, что «безумная активность является знаком здоровой продуктивности». Сотрудникам проекта приходится постоянно переключаться с одной задачи на другую, так и не доводя ни одну из них до логического завершения. Последним шаблоном в книге является шаблон “Зомби шаблонов” (Template Zombies), когда «проектная команда позволяет, чтобы работа управлялась шаблонами, вместо того, чтобы продумать процесс, необходимый для завершения проекта». Этот шаблон характеризует противоположную ситуацию, в которой форма становится гораздо важнее содержания. Между этими двумя шаблонами располагаются оставшиеся 84 шаблона, так или иначе описывающих поведение или состояние проекта. И хотя эти два шаблона не являются чем-то уникальным, они являются частью названия книги и именно они изображены на обложке книги в виде комичных человечков.

«Большая часть участников проекта довольно хорошо разбираются в распознавании образов и обладают внутренним чутьем (“я чувствую, что с этим проектом случится беда”), но не так хорошо разбираются в обобщении этих шаблонов в более удобную форму. Эта книга именно об этом. Мы, шестеро авторов, собрали весь свой опыт для изложения шаблонов, которые мы изучили в течение общих ста пятидесяти лет опыта».

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

P.S. MUST HAVE

UPD: Спасибо Василию Подобеду за коррективы в русскоязычном написании имени Алистера Коберна (Alistair Cockburn).

Книга Гради Буча и др. "Объектно-ориентированный анализ и проектирование с примерами приложений"

Booch_OOA_And_OOD_2007 «Звезда в преддверии коллапса; ребенок, который учится читать; клетки крови, атакующие вирус, - это только некоторые из потрясающе сложных объектов физического мира. Компьютерные программы тоже бывают сложными, однако их сложность совершенно другого рода. … Эйнштейн утверждал, что должны существовать простые объяснения природных процессов, так как Бог не действует из каприза или по произволу. У программиста нет такого утешения: сложность, с которой он должен справиться, лежит в самой природе системы».

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

Авторы пишут: «Эксперименты психологов, например, Миллера, показывают, что максимальное количество порций информации, которыми человек может оперировать одновременно, приблизительно равно семи (плюс-минус две). Вероятно, это ограничение пропускной способности информационного канала связано с объемом краткосрочной памяти человека». И именно это ограничение является своего рода лакмусовой бумажкой при объектно-ориентированной декомпозиции сложной системы.

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

Во второй части книги, авторы описывают метод построения сложных систем, основанный на объектной модели. Сначала вводится система графических обозначений (теперь в книге используется язык UML, вместо «нотации Буча»), а затем рассматриваются основы обобщенного процесса разработки. Вот, что они говорят: «Дилетанты постоянно ищут некий волшебный метод или инструмент, который мог бы сделать процесс разработки программ тривиальным. В отличие от них, профессионалы знают, что такой панацеи не существует. Дилетанты хотят иметь готовые рецепты; профессионалы знают, что такой подход ведет к негодным проектным решениям и нагромождению лжи, за которой разработчики скрываются от ответственности за ранее принятые неверные решения. Дилетанты либо игнорируют документацию вообще, либо делают из нее фетиш, заботясь больше о том, как их бумажный продукт выглядит в глазах заказчика, чем о его сути. Профессионал признает важность документации, но всегда отдает предпочтение разумным архитектурным новшествам. Процесс объектно-ориентированного анализа и проектирования невозможно описать с помощью рецептов, однако он определен достаточно хорошо, чтобы стать основой прогнозируемого и воспроизводимого процесса разработки программного обеспечения».

Авторы подчеркивают важность архитектурной целостности, итеративного и поступательного жизненного цикла разработки. Интересной особенностью изложения является то, что авторы не считают рациональный унифицированный процесс разработки единственно верным во всех случаях, а ускоренные методы (agile process) неверными в корне. «Выбирая между ускоренным и планомерным проектированием, следует оценивать риск. С какими рисками сталкивается проект? Выберите стиль и соответствующие методы, минимизирующие эти риски…. Выбор процесса проектирования не означает, что работа сделана. Этот процесс следует уточнять на протяжении всего жизненного цикла проекта. Инструменты, работающие хорошо, следует оставить, а инструменты, работающие плохо, - исключить. Целью должен быть непрерывный процесс усовершенствования, основанный на практическом опыте».

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

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

Книга Эндрю Ханта и Дэвида Томас “Программист-прагматик. Путь от подмастерья к мастеру”

The_Pragmatic_ProgrammerЕсть книги, которые учат нас определенным аспектам нашей профессии. Одни говорят о какой-то конкретной технологии или языке программирования, который может стать не актуальным в тот момент, когда вы ее принесли из книжного магазина. Другие учат нас, своим методологиям, говорят, что они самые лучшие, приводя примеры, которые обязательно подтверждают правоту их слов. Третьи знакомят нас с операционными системами, их преимуществами и недостатками. Четвертые рассказывают о модных системах управлениях базами данных и правильными способами их применения.

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

Но очень часто нам не хватает «клея», который склеил бы все наши знания и позволил бы перейти на новый уровень. Ведь развитие человека, как и многое в этом мире идет по спирали, является итеративным и поступательным процессом. И эта одна из немногих книг, которая позволит подняться на несколько уровней вверх и двигаться по этой спирали в более быстром темпе.

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

«Мне кажется, что языки, используемые нами для выражения своих идей, становятся частью нас самих, поэтому, если вы знаете только один язык, может показаться, будто сторонники других языков представляют опасность лично для вас. Мне представляется, что выход из этой ситуации – освоение других языков. Сомневаюсь, что можно быть профессионалом в области ПО и знать лишь один язык программирования. Может быть и экономическая причина: фундаментальные знания выходят за границы языка программирования в отличие от многих практических навыков. Поэтому, если я знаю только язык X и его наборы инструментов, а вы являетесь сторонником языка Y и его наборов инструментов, вы представляете угрозу для источника моего заработка. И вновь решение, как мне кажется, - в знании нескольких языков и наборов инструментов (а также твердое понимание фундаментальных концепций)».

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

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

Единственный выход из сложившейся ситуации – инвестирование в собственный "портфель знаний" на регулярной основе.

Многие авторы поднимают проблему модульности и связанности. Авторы этой книги не исключение. Но они используют несколько непривычный термин «ортогональность».

«Термин "ортогональность" заимствован из геометрии. Две линии являются ортогональными, если они пересекаются под прямым углом, например оси координат на графике… Этот термин был введен в информатике для обозначения некой разновидности независимости или несвязанности. Два или более объекта ортогональны, если изменения, вносимые в один из них, не влияют на любой другой».

Не было ли у вас случая, когда вы за месяц до завершения проекта понимаете, что DCOM для построения клиент-серверной архитектуры не подходит? И нужно искать альтернативные решения… Я был в такой ситуации, и только ортогональность системы позволили перейти на .Net Remoting за неделю и не сорвать сроки.

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

Крис Дейт в предисловии к своей замечательной книге "Введение в системы баз данных" приводит следующую выдержку Бертрана Рассела:

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

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

А разве не были вы в такой ситуации, когда менеджер проекта, представитель заказчика или кто-то из руководства подходит к вам и спрашивает: "Сколько тебе нужно времени, на завершение того-то и того-то?" И как часто вы отвечали: "Неделя" или "Три дня", когда совершенно точно понимали, что вы совершенно не понимали (простите за каламбур), что от вас требуется?

Авторы советуют говорить: "Я вернусь к вам с этим позже".

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

А что вы думаете по поводу требований? Может быть, ваши требования нужно было "добывать в карьере"? А может, быть вы столкнулись с проблемой чрезмерной спецификации? Может быть, вы сталкивались с документами, которые занимали десятки страниц, в которых было определено, сколько "битиков" должно быть отведено на тот или иной параметр, в которых выдумывалась собственная терминология, полностью противоречащая предметной области? Когда за всем за этим просто невозможно разглядеть семантику… что же должна делать система в конечном итоге? К сожалению, не все стремятся постоянно пополнять свой багаж знаний…

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

Многое из того, что описывают авторы интуитивно понятные каждому из нас, многое способно изменить нашу точку зрения на какой-то аспект разработки программного обеспечения. Авторы не предлагают "серебряных пуль" и никто не гарантирует, что путь от подмастерья к мастеру может быть простым. Нет, это не так…

Главное, что хотят авторы, это то, "чтобы вы гордились правом собственности. "Я это написал, и я стою за своей работой". Ваша подпись должна стать признанным знаком качества. Люди должны увидеть ваше имя в заголовке программы и рассчитывать на то, что она будет солидной, хорошо составленной, проверенной и документированной. Это должна быть поистине профессиональная работа. Написанная настоящим профессионалом своего дела.

Программистом-прагматиком".

среда, 9 сентября 2009 г.

Death March, 3rd edition by Edward Yourdon

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

Изначально материалы книги были закрыты для общего доступа, автор мотивировал это следующим: “All of this is on a separate website from this blog — partly because I’m using Apple’s iWeb development tool to generate new content quickly and easily (with hyperlinks, graphic images, YouTube clips, Google Maps, RSS feed, and various other widgets), and partly to make it password-protected in order to keep spammers and hackers out.”, но затем общий доступ был открыт всем желающим (видимо автору надоело отвечать на множество писем), поэтому сейчас материалы общедоступны на отдельном блоге: http://web.me.com/yourdon/DM3e/DM3e/DM3e.html.

Сейчас доступны следующие разделы:

Thursday, July 2, 2009

понедельник, 7 сентября 2009 г.

Перевод статьи “The Google Way: Give Engineers Room”

Недавно на блоге Эдварда Йордона натолкнулся на один весьма интересный документ: “Top Ten Software Engineering Conceptsи там в разделе “Peopleware” заинтересовался ссылкой: Google’s HR Strategy, которая и привела меня к статье “The Google Way: Give Engineers Room” by BHARAT MEDIRATTA; as told to JULIE BICK, New York Times, October 21, 2007. Оригинал статьи можно найти по этой ссылке, а здесь я хочу представить ее перевод.

Разработчикам Google разрешается использовать 20 процентов своего рабочего времени на задачи, связанные с работой компании, но интересные сотрудникам лично. Это значит, что если вас есть интересная идея, у вас всегда найдется время позаниматься ею.

Барат Медиратта (Bharat Mediratta) принимает участие в групплетах (grouplet meeting) в Google, которые отражают и подчеркивают возможность разработчиков тратить свое время на независимые проекты.

Это выглядит очевидным, что люди работают лучше, если они занимаются чем-то любимым, и множество отличных технологий нашли свое начало в этих 20 процентах рабочего времени, включая Gmail, Google News и даже Google shuttle, который возит людей на работу в штаб-квартиру компании в Маунтин-Вью, Калифорния.

Если идея, которая возникла при использовании 20 процентов свободного времени, это новый продукт, то обычно достаточно всего лишь найти несколько единомышленников и начать кодировать. Но когда то, над чем вы действительно хотите работать – это сделать серьезные изменения в целой организации, то вам нужно нечто совершенно новое – вам нужны «групплеты».

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

Давайте рассмотрим группу разработчиков, желающих продвинуть «гибкое программирование» (“agile programming”) внутри компании.  Гибкое программирование – это подход к разработке программного обеспечения, который включает в себя раннюю и частую обратную связь, и уже применялся в нескольких частях компании.

Групплет по гибкой разработке был основан в попытке распространения этой идеи по всей организации. Осуществлялось это путем создания максимального числа групп для обучения новому процессу. Они создали «Часы гибкой разработки», куда вы могли заглянуть и задать вопросы об этом процессе. Они раздавали книги и проводили откровенные беседы по этой теме. Они посещали собрания персонала и создали концепцию «Сафари гибкой разработки», в которой вы могли добровольно участвовать некоторое время в составе группы, использующей гибкую разработку, чтобы посмотреть на нее на практике.

Если вы двигаетесь так же быстро, как и Google, то у вас не всегда бывает возможность завершать мелкие вещи, которые со временем начинают мешать и беспокоить. Поэтому в добавок к усилиям нашей команды обеспечения качества,  у нас есть групплет Исправь Это (Fixit), который координирует особые дни, когда наши разработчики сосредотачиваются на решениях определенных проблем. Иногда у нас проходят дни исправления документации (Documentation Fixits), когда все мы стараемся навести порядок во внутренней документации, о которой периодически забываем.

Или мой любимый: исправления пожеланий пользователей (Customer Happiness Fixit), когда мы исправляем те маленькие проблемы, которые достают и расстраивают наших пользователей, например, исправление неудачных горячих клавиш в мобильных телефонах. Многие из этих событий проходят со специальными футболками и подарками для разработчиков, которые посвящают слишком мало времени работе над этими проблемами.

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

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

Мы начали создавать лучшие инструменты и проводить непринужденные беседы с различными техническими группами. Мы начали создавать учебный план для Нуглеров (Nooglers), новичков Google, чтобы они начинали свою работу правильно. Нашими объединенными 20 процентами времени, мы медленно повернули организации на 90 градусов и сделали тестирование разработчиком обычной практикой.

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

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

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

В конечном итоге, идея стала частью корпоративной культуры, даже корпоративной шуткой: «Простите, мне нужно пойти почитать про тестирование». Вот тогда мы поняли, что мы получили то, что хотели: способ опубликовать наше сообщение.

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

Но когда вы даете разработчикам шанс применить свою страсть на благо компании, они могут делать удивительные вещи.

Барат Медиратта, разработчик программного обеспечения в Google.

суббота, 5 сентября 2009 г.

Книга Брайан Нойес "Привязка данных в Windows Forms"

Аннотация

Почти всем серьезным приложениям Windows приходится, с одной стороны, использовать данные, хранящиеся в базах данных, с другой - обеспечивать взаимодействие пользователя с этими данными. Именно здесь вступают в действие механизмы и средства привязки данных, которым посвящена книга. Она охватывает все аспекты привязки данных в Windows Forms. Описываются средства, обеспечивающие связь с базой данных, такие, как типизированные наборы данных и адаптеры таблиц, собственно привязываемые к данным элементы управления, события и интерфейсы привязки данных, средства и методики обработки ошибок. Даются подробные инструкции и рекомендации по работе с инструментальными средствами Microsoft Visual Studio для привязки данных. Большое внимание уделяется нововведениям, таким, как адаптеры таблиц и элемент управления DataGridView: Обсуждается создание специальных пользовательских элементов управления и объектов, пригодных для привязки данных. В приложениях рассматриваются параллельные вопросы, связанные с привязкой данных, такие, как привязка в ASP.NET и доступ данным в ADO.NET.
Книга адресована прежде всего программистам, занимающимся созданием клиентских приложений для баз данных.

Комментарий

Книга Брайана Нойеса является лучшей по вопросам привязки данных в Windows Forms, которая когда-либо выходила в свет, как на русском, так и на английском языках. Вы только представьте себе, теме привязки данных, которую сложно назвать обширной, посвящены 632 страницы. Это в полтора больше, чем в последней книге Чарлза Петцольда, посвященной Windows Forms! Конечно, в книге затронуты и вспомогательные темы, такие как контейнеры ADO.NET, привязка данных в ASP.NET и WPF, но все же, большая часть материала посвящена именно привязке данных в Windows Forms. Книга сочетает в себе глубину изложения и широту охвата материала, содержит множество практических примеров применения той или иной возможности. Вместе с книгой Брайана я бы порекомендовал посмотреть книгу Криса Селлза “Windows Forms 2.0. Programming”, в которой вы сможете найти ответы, как на вопросы привязки данных, так и на многие другие, которые могут возникнуть при разработке приложений Windows Forms.

четверг, 3 сентября 2009 г.

Применение NetDataContractSerializer в WCF

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

Частично решить эту проблему можно путем задания известных типов (Known Types), причем сделать это можно самым различным способом (я описывал все эти способы в цикле статей «Известные типы в WCF», часть 1, часть 2, часть 3 и часть 4). Но этот вариант не всегда является удобным решением. Приложение может перейти на WCF с .Net Remoting, в котором используется механизм форматеров для сериализации/десериализации и существует возможность передавать между клиентом и сервером любые сериализируемые сущности. Кроме того, даже в новом приложении, использующем WCF как на стороне сервиса, так и на стороне клиента, могут применяться сложные объекты в качестве аргументов и возвращаемых значений операций контракта. И хотя это не отвечает принципам сервис-ориентированного программирования, разработчики могут сознательно пойти на этот шаг, зная, что их приложение не предусматривает разработку клиентских частей с использованием других технологий.

Невозможность передачи полиморфных объектов связано с тем, что по умолчанию инфраструктура WCF использует DataContractSerializer для сериализации/десериализации объектов. Основной особенностью этого сериализатора является то, что информация о типе не сохраняется при сериализации, а берется из контракта операции. Это вполне естественное поведение, ведь WCF – это реализация промышленного стандарта по созданию сервис-ориентированных приложений, а такие приложения являются нейтральными к технологии, на которых создается сервис или его клиент, а тип параметра относится к специфике CLR, а значит является деталью реализации.

Кроме DataContractSerializer в WCF предусмотрена возможность применения других типов сериализаторов, таких как старый добрый XmlSerializer, NetDataContractSerializer и DataContractJsonSerializer. XmlSerializer для своей работы требует, чтобы сериализируемый класс был публичным, имел конструктор по умолчанию и сериализирует вначале все открытые поля в порядке объявления, а затем свойства (read/write properties) также в порядке объявления. Для применения XmlSerializer вместо DataContractSerializer необходимо пометить отдельный метод сервиса, либо класс сервиса целиком атрибутом XmlSerializerFormatAttribute.

NetDataContractSerializer работает аналогично DataContractSerializer, и имеет прямую и обратную совместимость с DataContractSerializer, что позволяет сериализовать объект одним сериализатором, а десериализовать другим.

NetDataContractSerializer добавляет полную информацию о сериализируемом типе в поток данных, что позволяет загрузить необходимую сборку при десериализации и требует совместного использования типом между сервисом и клиентом. Совместное использование типов (sharing types) нарушает принципы сервис-ориентированного программирования, которые основаны на разделении контрактов (sharing contracts), поэтому разработчики WCF не предусмотрели простого способа использования NetDataContractSerializer в приложениях.

Для возможности использования NetDataContractSerializer c определенной операцией, контрактом или службой целиком, необходимо реализовать один из следующих интерфейсов IOperationBehavior, IContractBehavior и IServiceBehavior соответственно. Каждый из этих интерфейсов имеет одинаковую функциональность и сводится к замене поведения с типом DataContractSerializerOperationBehavior на объект собственного класса, возвращающий необходимый сериализатор. Наиболее простым способ применения является создание класса наследника от System.Attribute и применение созданного атрибута к операции, контракту или сервису целиком.

 
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class |
    AttributeTargets.Interface)]
public class NetDataContractFormatAttribute :
    Attribute, IOperationBehavior,
    IServiceBehavior, IContractBehavior
{
    void IOperationBehavior.AddBindingParameters(
        OperationDescription description, BindingParameterCollection parameters)
    { }
 
    void IOperationBehavior.ApplyClientBehavior(
        OperationDescription description,
        ClientOperation proxy)
    {
        ReplaceDataContractSerializerOperationBehavior(description);
    }
    void IOperationBehavior.ApplyDispatchBehavior(
        OperationDescription description,
        DispatchOperation dispatch)
    {
        ReplaceDataContractSerializerOperationBehavior(description);
    }
    void IOperationBehavior.Validate(
        OperationDescription description)
    { }
 
   void IServiceBehavior.AddBindingParameters(
        ServiceDescription serviceDescription,
        ServiceHostBase serviceHostBase,
        Collection<ServiceEndpoint> endpoints,
        BindingParameterCollection bindingParameters)
    {
        ReplaceDataContractSerializerOperationBehavior(serviceDescription);
    }
 
    void IServiceBehavior.ApplyDispatchBehavior(
        ServiceDescription serviceDescription,
        ServiceHostBase serviceHostBase)
    {
        ReplaceDataContractSerializerOperationBehavior(serviceDescription);
    }
 
    void IServiceBehavior.Validate(
        ServiceDescription serviceDescription,
        ServiceHostBase serviceHostBase)
    { }
 
    void IContractBehavior.AddBindingParameters(
        ContractDescription contractDescription,
        ServiceEndpoint endpoint,
        BindingParameterCollection bindingParameters)
    { }
 
    void IContractBehavior.ApplyClientBehavior(
        ContractDescription contractDescription,
        ServiceEndpoint endpoint,
        ClientRuntime clientRuntime)
    {
        ReplaceDataContractSerializerOperationBehavior(contractDescription);
    }
 
    void IContractBehavior.ApplyDispatchBehavior(
        ContractDescription contractDescription,
        ServiceEndpoint endpoint,
        DispatchRuntime dispatchRuntime)
    {
        ReplaceDataContractSerializerOperationBehavior(contractDescription);
    }
 
    void IContractBehavior.Validate(
        ContractDescription contractDescription,
        ServiceEndpoint endpoint)
    { }
 
 
    private static void ReplaceDataContractSerializerOperationBehavior(
        ServiceDescription description)
    {
        foreach (var endpoint in description.Endpoints)
        {
            ReplaceDataContractSerializerOperationBehavior(endpoint);
        }
    }
 
    private static void ReplaceDataContractSerializerOperationBehavior(
        ContractDescription description)
    {
        foreach (var operation in description.Operations)
        {
            ReplaceDataContractSerializerOperationBehavior(operation);
        }
    }
 
    private static void ReplaceDataContractSerializerOperationBehavior(
        ServiceEndpoint endpoint)
    {
        // ignore mex
        if (endpoint.Contract.ContractType == typeof(IMetadataExchange))
        {
            return;
        }
        ReplaceDataContractSerializerOperationBehavior(endpoint.Contract);
    }
 
    private static void ReplaceDataContractSerializerOperationBehavior(
        OperationDescription description)
    {
        var behavior = description.Behaviors.Find<DataContractSerializerOperationBehavior>();
        if (behavior != null)
        {
            description.Behaviors.Remove(behavior);
            description.Behaviors.Add(new NetDataContractSerializerOperationBehavior(description));
        }
 
    }
 
    public class NetDataContractSerializerOperationBehavior
        : DataContractSerializerOperationBehavior
    {
        public NetDataContractSerializerOperationBehavior(
            OperationDescription description)
            : base(description) { }
 
        public override XmlObjectSerializer CreateSerializer(
            Type type, string name,
            string ns, IList<Type> knownTypes)
        {
            return new NetDataContractSerializer();
        }
 
        public override XmlObjectSerializer CreateSerializer(
            Type type, XmlDictionaryString name,
            XmlDictionaryString ns, IList<Type> knownTypes)
        {
            return new NetDataContractSerializer();
        }
    }
}
 
Пример использования следующий:
 
//Применение к отдельной операции
[OperationContract]
[NetDataContractFormat]
Shape CreateShape(ShapeType shapeType, int id);
 
//Применение к контракту
[ServiceContract(CallbackContract = typeof(IServiceCallback), 
    SessionMode = SessionMode.Required)]
[NetDataContractFormat]
public interface IService { ... }
 
//Применение к сервису
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, 
    ConcurrencyMode = ConcurrencyMode.Multiple, 
    UseSynchronizationContext = false)]
[NetDataContractFormat]
public class Service : IService { ... }
 

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

среда, 2 сентября 2009 г.

Известные типы в WCF. Часть 4

Задание обобщенных известных типов

Обобщенные типы (generic types) играют важную роль при программировании на платформе .Net, но их применение весьма ограничено, когда речь заходит о разработке контрактов сервисов. Это связано с тем, что обобщенные типы относятся к специфике платформы .Net, а одной из основных парадигм сервис-ориентированного программирования является публикация контракта и сокрытие деталей реализации.

Хотя применение обобщенных методов в качестве контракта сервиса невозможно, применение «закрытых» (closed) обобщенных типов вполне допустимо, если аргумент типа является допустимым в качестве контракта данных.

ПРИМЕЧАНИЕ
Закрытым обобщенным типом (closed generic type) называется обобщенный тип, которому всем аргументам-типам переданы действительные типы данных. Таким образом, List является открытым типом, а List - закрытым.

[DataContract]
public class Shape<T> { ... }
 
[DataContract]
public class Circle<T> : Shape<T> { ... }
 
[DataContract]
public class Square<T> : Shape<T> { ... }
 
[ServiceContract]
public interface IService
{
    [OperationContract]
    void ProcessShapeOfInt(Shape<int> shape); //Вариант 1
 
    [OperationContract]
    void ProcessShape<T>(Shape<T> shape); //Вариант 2
 
    ...
}

Применение операции ProcessShapOfInt вполне допустимо, в то время, как применение операции ProcessShape приведет к следующей ошибке:

ERROR: System.Runtime.Serialization.InvalidDataContractException: Невозможно экспортировать тип "Server.Shape`1[T]" как тип схемы, так как он является открытым базовым типом. Базовый тип можно экспортировать, только если все типы его базовых параметров являются реальными типами.

ПРИМЕЧАНИЕ
Возможность применения закрытых типов обусловлено тем, что при импортировании метаданных контракта все закрытые обобщенные параметры переименовываются согласно следующей схемы: <исходное имя>of<имена параметров типов><хеш>. Таким образом метаданные службы не содержат информации об обобщенных параметрах и не нарушают принципы сервис-ориентированного программирования.

Как и применение обобщенных типов в операциях контракта, добавление обобщенных типов в список известных типов имеет ряд особенностей.

Рассмотрим следующий код:

[DataContract]
[KnownType(typeof(Circle<>))]
[KnownType(typeof(Square<>))]
public class Shape<T> { ... }

Хотя приведенный код компилируется и запускается, при обновление метаданных с помощью Metadata Exchange Endpoint будет невозможно. При попытке обновления метаданных с помощью Microsoft Visual Studio вы получите ошибку: «Метаданные содержат неразрешимую ссылку: “net.tcp://localhost:6101/WCFTestService/mex”». Чтобы понять точнее, что же не так с метаданными, необходимо воспользоваться утилитой SvcUtil.exe. В этом случае ошибка будет более понятной:

Ошибка при получении известных типов для типа "Server.Square`1[T]". Этот тип не должен быть открытым или частичным базовым классом.

Для задания обобщенных известных типов с помощью атрибута KnownType необходимо воспользоваться версией конструктора, принимающей имя метода:

[DataContract]
[KnownType("GetKnownTypes")]
public class Shape<T>
{
    ...
    static IEnumerable<Type> GetKnownTypes()
    {
        return new [] { typeof(Circle<T>), typeof(Square<T>) };
    }
}


Еще одним способом задания обобщенных известных типов является применение конфигурационного файла приложения следующим образом:

<system.runtime.serialization>
  <dataContractSerializer>
    <declaredTypes>
      <add type = "Server.Shape2`1, Server">
        <knownType type = "Server.Circle2`1, Server">
          <parameter index="0"/>
        </knownType>
        <knownType type = "Server.Square2`1, Server">
          <parameter index="0"/>
        </knownType>
      </add>
    </declaredTypes>
  </dataContractSerializer>
</system.runtime.serialization>

Выводы

Разработчики WCF предусмотрели множество способов задания известных типов, каждый из которых имеет свои преимущества и недостатки. Для наиболее простых приложений самым простым способом является применение атрибута KnownTypeAttribute к базовому классу иерархии; для более сложных способов лучше воспользоваться атрибутом ServiceKnownTypeAttribute либо применять декларативный способ задания известных типов с помощью конфигурационного файла. Передача известных типов в конструкторе DataContractSerializer является наиболее трудоемкой, но позволяет не только задавать известные типы, но и перейти к NetDataContractSerializer в случае невозможности заранее определить динамические типы объектов, передаваемые в операциях контракта.

Известные типы в WCF. Часть 3

Использование конструктора класса DataContractSerializer

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

public static void Constructor2()
{
    // Create a generic List of types and add the known types
    // to the collection.
    List<Type> knownTypeList = new List<Type>();
    knownTypeList.Add(typeof(PurchaseOrder));
    knownTypeList.Add(typeof(PurchaseOrderV3));
 
    // Create a DatatContractSerializer with the collection.
    DataContractSerializer ser2 = new DataContractSerializer(
        typeof(Orders), knownTypeList);
 
    // Other code not shown.
}
 
Но остается вопрос в том, как этот способ можно применить на практике. Разработчики WCF предусмотрели множество способов настройки и расширения функциональных возможностей операций, контрактов и служб, для этого предусмотрены интерфейсы IOperationBehavior, IContractBehavior и IServiceBehavior соответственно. Каждый из этих интерфейсов имеют одинаковую функциональность и могут применяться как для сервиса, так для клиента. Существует два способа применения этих интерфейсов на практике: путем программного добавления объектов, реализующих соответствующий интерфейс перед открытием хоста (на стороне сервиса) или созданием прокси (на стороне клиента), либо путем использования пользовательских атрибутов (для этого класс, реализующий один из вышеперечисленных интерфейсов должен быть наследником класса System.Attribute). Наиболее простым способом является создания класса, реализующего все вышеперечисленные интерфейсы, а также являющимся наследником от System.Attribute, что позволит применять его любым способом. Идея реализации сводится к замене поведения с типом DataContractSerializerOperationBehavior на объект собственного класса, возвращающий необходимый сериализатор.
 
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | 
                AttributeTargets.Interface)]
public class ShapeKnownTypeAttribute : Attribute, 
       IOperationBehavior, IServiceBehavior, IContractBehavior
{
    void IOperationBehavior.AddBindingParameters(OperationDescription description, 
       BindingParameterCollection parameters)
    {}
 
    void IOperationBehavior.ApplyClientBehavior(OperationDescription description, 
       System.ServiceModel.Dispatcher.ClientOperation proxy)
    {
        ReplaceDataContractSerializerOperationBehavior(description);
    }
    void IOperationBehavior.ApplyDispatchBehavior(OperationDescription description, 
       System.ServiceModel.Dispatcher.DispatchOperation dispatch)
    {
        ReplaceDataContractSerializerOperationBehavior(description);
    }
    void IOperationBehavior.Validate(OperationDescription description)
    {}
 
 
    void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription,
       ServiceHostBase serviceHostBase,
       System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints,
       BindingParameterCollection bindingParameters)
    {
        ReplaceDataContractSerializerOperationBehavior(serviceDescription);
    }
 
    void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription,
       ServiceHostBase serviceHostBase)
    {
        ReplaceDataContractSerializerOperationBehavior(serviceDescription);
    }
 
    void IServiceBehavior.Validate(ServiceDescription serviceDescription,
       ServiceHostBase serviceHostBase)
    {}
 
    void IContractBehavior.AddBindingParameters(ContractDescription contractDescription,
       ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {}
 
    void IContractBehavior.ApplyClientBehavior(ContractDescription contractDescription,
       ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        ReplaceDataContractSerializerOperationBehavior(contractDescription);
    }
 
    void IContractBehavior.ApplyDispatchBehavior(ContractDescription contractDescription,
       ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
    {
        ReplaceDataContractSerializerOperationBehavior(contractDescription);
    }
 
    void IContractBehavior.Validate(ContractDescription contractDescription,
       ServiceEndpoint endpoint)
    {}
 
 
    private static void ReplaceDataContractSerializerOperationBehavior(ServiceDescription description)
    {
        foreach (var endpoint in description.Endpoints)
        {
            ReplaceDataContractSerializerOperationBehavior(endpoint);
        }
    }
 
    private static void ReplaceDataContractSerializerOperationBehavior(ContractDescription description)
    {
        foreach (var operation in description.Operations)
        {
            ReplaceDataContractSerializerOperationBehavior(operation);
        }
    }
 
    private static void ReplaceDataContractSerializerOperationBehavior(ServiceEndpoint endpoint)
    {
        // ignore mex
        if (endpoint.Contract.ContractType == typeof(IMetadataExchange))
        {
            return;
        }
        ReplaceDataContractSerializerOperationBehavior(endpoint.Contract);
    }
 
    private static void ReplaceDataContractSerializerOperationBehavior(OperationDescription description)
    {
        var behavior = description.Behaviors.Find<DataContractSerializerOperationBehavior>();
        if (behavior != null)
        {
            description.Behaviors.Remove(behavior);
            description.Behaviors.Add(new ShapeDataContractSerializerOperationBehavior(description));
        }
 
    }
 
    public class ShapeDataContractSerializerOperationBehavior : DataContractSerializerOperationBehavior
    {
        public ShapeDataContractSerializerOperationBehavior(OperationDescription description)
            : base(description) { }
 
        public override XmlObjectSerializer CreateSerializer(Type type, string name, string ns, IList<Type> knownTypes)
        {
            var shapeKnownTypes = new List<Type> { typeof(Circle), typeof(Square) };
            return new DataContractSerializer(type, name, ns, shapeKnownTypes);
        }
 
        public override XmlObjectSerializer CreateSerializer(Type type, 
XmlDictionaryString name, XmlDictionaryString ns, IList<Type> knownTypes)
        {
            var shapeKnownTypes = new List<Type> { typeof(Circle), typeof(Square) };
            return new DataContractSerializer(type, name, ns, shapeKnownTypes);
        }
    }
 
}
Применение созданного класса следующее:
//На стороне сервиса
[ServiceContract()]
[ShapeKnownType]
public interface IService {...}
 
//На стороне клиента
[GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[ServiceContractAttribute(ConfigurationName="ServiceReference.IService"]
[Server.ShapeKnownType] //ДАННЫЙ АТРИБУТ ДОБАВЛЕН В УЖЕ СГЕНЕРИРОВАННЫЙ КОД!
public interface IService { ... }
 
Применение класса ShapeKnownType к операциям является аналогичным.
 
ПРИМЕЧАНИЕ
 
Использование объектов, реализующих один из вышеперечисленных интерфейсов на стороне сервиса не добавляет информацию об известных типах в метаданные сервиса, поэтому необходимо использование общих типов между клиентом и сервисом, и требует симметричного применения соответствующих атрибутов на стороне клиента.
 
Несмотря на то, что данный способ является самым трудоемким и может потребовать модификацию сгенерированного кода на стороне клиента, он показывает, каким образом можно изменять поведение инфраструктуры WCF и настраивать его по своему усмотрению. Именно таким способом осуществляется применение суррогатных типов (Surrogate Types), применение NetDataContractSerializer вместо DataContractSerializer, применение собственных сериализаторов и решение многих других задач расширения функциональности WCF.