Трудящийся программист

Мультипарадигматическая .NET.Часть 1

Тэд Ньюард

Ted NewardЗа прошедшие годы многие из сообщества .NET слышали о «личностях» Microsoft, взятых этой компанией для среды Visual Studio: Эйнштейн (гений), Элвис (рок-звезда) и Морт («средний» разработчик). Как бы ни были эти личности полезны Microsoft в попытке четко понять, для кого они создают Visual Studio и платформу Microsoft .NET, я придерживаюсь другого мнения. Фактически я пришел к выводу, что подавляющее большинство разработчиков в огромной экосистеме .NET относятся к одному из двух основных (и в высшей степени типичных) лагерей.

**Разработчик на C++.**Это разработчик, который изучил все «хорошие» принципы объектно-ориентированного программирования и при каждом случае старается создавать полнофункциональные модели предметной области, даже если пишет командный файл. Если он уже в возрасте, чтобы по-настоящему профессионально писать на C++, все шансы за то, что он сосредоточится на создании инфраструктур и повторно используемых абстракций — причем до такой степени, что скорее всего его ПО никогда не дойдет до стадии распространения. Таких разработчиков можно узнать по напыщенному виду, и они нередко цитируют людям «Шаблоны», тщетно пытаясь «обучить хоть чему-нибудь этих бедолаг, сбившихся в толпу, в которой никто не понимает, что такое Качество без Имени».

Разработчик на VB. Это разработчик, который наслышан об этой кутерьме с объектами, шаблонами, процедурами и прочим, с чем носились как с писаной торбой все эти годы (или десятилетия?), и твердо решивший «я буду делать все, пока этот код поставляется». По сути, этот разработчик так озабочен на поставке кода, что, когда его попросят добавить новую кнопку на существующую форму, он перепишет ее заново. Таким разработчикам можно присваивать товарный знак «не говорите мне, как это работает, просто скажите, что делать», и нередко они грешат выдиранием кода из гугла, его вставкой в свои программы (иногда случайно), если он работает.

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

Существование этих двух групп я отмечаю в основном потому, что следующая серия статей в этой рубрике будет адресована по большей части второй группе — разработчикам на VB, которые не тратят много времени на размышления о структуре ПО. Возможно вас это удивит, но то, о чем пойдет речь, должно заинтересовать и первую группу, так как с выпуском Visual Studio 2010 и .NET Framework 4 многие вещи значительно усложнились — по крайней мере, в языковом пространстве. Если программистам-работягам в предстоящее десятилетие выпадет шанс проектировать ПО (или расширять уже разработанное), не превращая его в гигантскую липкую мешанину, то только потому, что они получат знание основ проектирования с применением множества парадигм, т. е. освоят то, что я называю мультипарадигматическим программированием (multiparadigmatic programming). (Да, название претенциозное. Я же выходец из разработчиков на C++ — подайте на меня в суд.)

Проектирование с применением множества парадигм

Термин «проектирование с применением множества парадигм» (multi-paradigm design) (и концепция, если можно сказать, что у нее один автор) взят из книги Джеймса О. Коплена (James O. Coplien) «Multi-Paradigm Design for C++» (Addison-Wesley Professional, 1998). Обратив внимание на последнюю часть названия, вы без труда догадаетесь, какому из двух лагерей изначально адресовалась эта книга. Однако взгляды, изложенные Копленом, вызвали реакцию в обоих лагерях, и даже спустя десятилетие споры продолжаются.

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

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

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

Лично я прозрел по этому предмету, слушая выступление Андерса Хейлсберга (Anders Hejlsberg), который рассказывал о C# 3.0 на совещании по динамическим языкам в Редмонде несколько лет назад. Он обратил внимание на то, что в C# включены некоторые идеи из других языков, и сказал примерно следующее:«Языки теряют свои классификации. Больше нельзя сказать, что такой-то язык чисто объектно-ориентированный, а такой-то исключительно динамический, потому что слишком многое в них заимствуется из других языков с иными принципами».

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

C# заходит еще дальше, включив концепции функционального программирования в версии 3.0 и динамического в версии 4.0. В Visual Basic «динамика» поддерживалась издавна (хотя от нее было принято воротить нос), а благодаря стремлению Microsoft к «языковому паритету» между ним и C# он поддерживает теперь те же средства. Без этих дополнительных языковых парадигм, незаметных при поверхностном взгляде, решения вроде LINQ были бы куда сложнее и заставили бы разработчиков полагаться на другие механизмы (например, на генерацию кода, которая сама по себе является интересным аспектом метапрограммирования, но об этом мы поговорим позже), позволяющие использовать общность рабочих систем теми способами, которые невозможны при «наследовании от базового класса».

И это главное, что мы будем исследовать: ревнители объектно-ориентированного мира годами настаивали на том, что наследование — лучший подход к повторному использованию кода. (Это же и причина того, что по умолчанию классы не помечаются как sealed в C# или NotInheritable в Visual Basic.) И тем не менее, на самом деле мы не переопределяем методы базового класса, когда наследуем от класса Form в Windows Forms или ASP.NET; вместо этого мы предоставляем делегаты для вызова. А почему бы просто не переопределять? Зачем вводить издержки (как бы они ни были малы) вызова делегата? Зачем отделенный код (code-behind) отходит от модели на основе наследования к модели на основе частичных классов? И к чему вообще методы расширения?

Ответы на каждый из этих вопросов можно объяснить (и они были объяснены!) в объектно-ориентированной терминологии, но нижележащая причина остается прежней: не все можно легко представить классическими конструкциями объектного проектирования. Попытка отыскать общие блоки кода и свести их в единственную конструкцию (в ООП это класс, в структурном программировании — структура данных, в функциональном программировании — функция и т. д.) является целью проектирования ПО, и чем больше у нас способов, позволяющих варьировать части кода, нуждающиеся в варьировании, тем больше систем мы можем написать, которые выдержат суровые зимы у клиентов, названивающих нам со словами «Я тут забыл сказать вам об одной небольшой детали...».

В качестве упражнения подумайте над следующим: в .NET Framework 2.0 появились обобщения (параметризованные типы). Почему, спросите вы? Какой цели они служат с точки зрения проектирования? (И между прочим, с ответами наподобие «они позволяют создавать типизированные наборы» вы попадете пальцем в небо — Windows Communication Foundation интенсивно использует обобщения, причем так, что дело явно не только в типизированных наборах.)

Об этом мы поговорим в следующей части.

Еще не мертв (или не закончил)!

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

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

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

Удачи в кодировании!

Тэд Ньюард  (Ted Neward) — глава компании Neward and Associates, специализирующейся на гибких и надежных корпоративных системах с применением .NET и Java. Он автор более 100 статей, обладатель статуса MVP по C#, является спикером INETA, а также автором и соавтором десятка книг, включая еще не выпущенную «Professional F# 2.0» (Wrox). Он регулярно занимается консультированием преподаванием. Связаться с ним можно по адресу ted@tedneward.com или через блог blogs.tedneward.com.