Никогда не догоняйте
устремившихся вперёд.
Через пять минут, ругаясь,
Побегут они обратно,
И тогда, толпу возглавив,
Вы помчитесь впереди.
Термин «гуглизация» (googlization) не случайно созвучен с другим, с глобализацией. И если глобализация систематично уничтожает закрытые экономики, то «гуглизация» нивелирует энциклопедические знания. Пространство практического применения эрудита сузилось до рамок игры «Что? Где? Когда?». Доступность информации снизила ее значимость, ценность стали представлять не сами сведения из статей энциклопедии, а владение технологиями.
Часть пролетариата умственного труда, способная хранить и воспроизводить технологии, превратилась в «когнитариат». Появился термин «индустриальная археология», касающийся реинжиниринга[16] систем в промышленной эксплуатации, принципы и технологии работы которых неизвестны никому из обслуживающего их персонала.
Технологии в аппаратном обеспечении, «железе», подчинены законам физики, что делает их развитие предсказуемым с достаточной долей достоверности. Зная, какие работы ведутся в лабораториях, можно предугадывать потолок их развития и предполагать сроки готовности к практическому использованию. Например, сейчас в активной фазе находятся прикладные исследования по созданию масштабируемой технологии проектирования и производства устройств, способных заменить нынешние полупроводниковые схемы. Вывод: через десятилетие мир вычислительных устройств изменится.
В противоположность этому, мир программных технологий основан на математических и лингвистических моделях и подчинён законам ведения бизнеса. Крупные капиталовложения, сделанные в существующие средства разработки, инфраструктуру и обучение пользователей, должны окупаться независимо от значения синуса в военное время, релятивистских поправок и элементной базы ЭВМ. Вывод: радикальных изменений в софтостроительной сфере ожидать не следует, ситуация находится под чутким контролем крупных корпораций и развивается эволюционно.
Тем не менее в софтостроении, даже кустарном и далёком от индустриализации, технологии составляют основу. О них мы и поговорим.
Для развития аппаратной части определяющими являются физические законы, а основой индустриализации в производстве «железа» стали проектирование и сборка устройств из стандартизованных компонентов.
Конечно, в софтостроении тоже имеются относительно стандартные подсистемы: операционные среды, базы данных, веб-серверы, программируемые терминалы и тому подобное. Однако их масштаб соответствует не компоненту в устройстве, а достаточно сложной аппаратной подсистеме вроде маршрутизатора или сервера.
Возможность собирать изделия из «кубиков» стала предметом зависти софтостроителей, вылившейся в итоге в компонентный подход к разработке. Панацеи, разумеется, не получилось, несмотря на серьёзный вклад технологии в повторное использование «кубиков», оказавшихся скорее серыми ящиками с малопонятной начинкой. Но появился целый рынок, где писатели компонентов предлагают свои изделия «компонентокидателям» – это жаргонное слово возникло в среде наиболее массового применения компонентов, где их выбирают на палитре мышкой и, протаскивая, кидают[17] на разрабатываемую экранную форму.
Для аппаратуры используется модель конечного автомата. Во-первых, она обеспечивает полноту тестирования. Во-вторых, компонент работает с заданной тактовой частотой, то есть обеспечивает на выходе сигнал за определённый интервал времени. В-третьих, внешних характеристик (состояний) у микросхемы примерно два в степени количества «ножек», что на порядки меньше, чем у программных «кубиков». В-четвёртых, высокая степень стандартизации даёт возможность заменить компоненты одного производителя на другие, избежав сколько-нибудь значительных модификаций проекта.
В софтостроении использовать конечно-автоматную модель для программного компонента можно при двух основных условиях:
• Программисту не забыли объяснить эту теорию ещё в вузе (см. выше про «Круговорот»).
• Количество состояний обозримо: они, как и переходы, достаточно легко определяются и формализуются.
Второй пункт более важен. На практике количество состояний даже несложного модуля запредельно велико, поэтому программист использует их объединения в группы и применяет различные эвристики для обеспечения желаемого результата на выходе при заданном входе.
Возьмём относительно простой пример: компонент, конвертирующий сумму из одной валюты в другую.
Из элементов стандартизации точно присутствуют коды валют по ISO 4217[18] и, частично, список служб, к которым компонент может обращаться (см., например, каталог служб Financial API). Интерфейс самого компонента не стандартизован, для возможной его замены в будущем без последующей структурной перекройки вашего приложения потребуется обернуть компонент в адаптер (привет, шаблоны!). Это поможет избежать реструктуризации при замене, но не гарантирует работоспособность на том же входном наборе.
Теперь оценим количество состояний, которые необходимо охватить для полноты модульного тестирования, раз уж мы следуем логике разработки «железа». ISO 4217 даёт список из 164 валют. Предположим, что наши входные данные:
• имеют только два знака после запятой;
• значения положительные;
• максимальная величина – 1 миллион;
• дата конвертации всегда текущая;
• мы используем только 10 валют из 164.
Несложный комбинаторный подсчёт показывает, что даже такой сильно урезанный входной набор характеризуется количеством размещений из 10 по 2, помноженным на 100 миллионов входных значений (1 миллион с шагом 0,01):
102 × 100 000 000 = 10 000 000 000.
То есть для обеспечения полноты тестирования нашего входного набора потребуется 10 миллиардов проверок! Сравните, например, с микросхемой дешифратора, преобразующего входное 4-разрядное двоичное значение в сигнал на одном из 16 выходов. Входных наборов будет всего 16, а таблица истинности состоит из 162 = 256 значений.
На практике программист применит допустимую эвристику и будет тестировать, например, только несколько значений (один миллион, ноль, случайная величина из диапазона) для нескольких типовых конвертаций из 100 возможных, дополнительно проверяя допустимую точность значений на входе. При этом формальный показатель покрытия модульными тестами по-прежнему будет 100 %…
Но это ещё не всё. Микросхема работает с заданной тактовой частотой. Если, например, частота равна 1 МГц, то подав на вход набор значений, вы гарантированно через одну микросекунду получите результат на выходе.
Если же вы подадите набор значений на вход нашего компонента, то время отклика будет неопределённым. Может быть, программа отработает за секунду.
Может быть, зависнет навечно, если не предусмотрен тайм-аут. А если несколько параллельных запросов?
Поэтому вдобавок к модульному тесту необходимо программировать тест производительности (нагрузочный), который тем не менее не гарантирует время отклика, а только позволяет определить его ожидаемое значение при некоторых условиях.
Таким образом, собрав из кучи микросхем устройство, мы уверены, что оно будет работать:
• согласно таблицам истинности;
• с заданной тактовой частотой.
Собрав же из компонентов программу, мы можем только:
• приблизительно и с некоторой вероятностью оценивать время отклика на выходе;
• в большинстве случаев ограничиться выборочным тестированием, забыв о полноте.
Если вам говорят: «Пришло время собранных из кубиков программ», будьте в курсе ограничений технологии. Очень уж далеки программные компоненты от электронных кубиков.
Любая программа, даже созданная визуально, имеет в своей основе исходный код на каком-либо языке программирования.
Безысходное программирование – это программирование без «исходников». То есть мы пишем свой код, не имея исходных текстов используемой подпрограммы, класса, компонента и т. п.
Когда необходимо обеспечить гарантированную работу приложения, включающего в себя сторонние библиотеки или компоненты, то, не имея доступа к их исходному коду, вы остаётесь один на один с «чёрным ящиком». Даже покрыв их тестами, близкими к параноидальным, вы не сможете понять всю внутреннюю логику работы и предусмотреть адекватную реакцию системы на нестандартные ситуации. Поэтому программирование без исходников в таком сценарии превращается в настоящую безысходность и безнадёгу.
Пока цена ошибки в приложении – потеря нескольких строк введённой пользователем информации, дело может ограничиться долгоживущей записью в базе данных ошибок, закрываемой не её исправлением, а описанием обхода «граблей»[19]. Но ситуация кардинально поменяется, если цена будет исчисляться многими нулями потерь от упущенной сделки в торговой системе, сотнями исков клиентов, получивших неверные счета, или того хуже – аварией на производстве. Ответственность с разработчиков никто не снимал.
В рамках аудита нередко приходилось наблюдать, как правят программный код триггеров и хранимых процедур прямо в базе данных. Ассоциация с этим непотребством у меня тесно связана с утилитой debug, которая в MS DOS позволяла писать машинные команды прямо в память. Или с командой type > program.com для набора машинного кода с консоли в исполняемый файл. Понятное дело, что занимаются такими вещами при разработке программного обеспечения только от безысходности.
Частным, но частым случаем безысходного программирования является софтостроение без использования системы управления исходным кодом (revision control system), позволяющей архивировать и отслеживать все его изменения.
В 1980-х годах у японцев существовала программа по созданию ЭВМ 5-го поколения. К сожалению, цель достигнута не была, хотя проявилось множество побочных эффектов вроде всплеска интереса к искусственному интеллекту, популяризации языка Пролог, да и отрицательный опыт – тоже опыт, возможно, не менее ценный.
В итоге, спустя 20 с лишним лет, все мы – и разработчики, и пользователи – продолжаем сидеть на «числогрызах» 4-го поколения. Производительность «железа» возросла на порядки, почти упёршись в физические ограничения миниатюризации полупроводников и скорость света. Стоимость тоже на порядки, но снизилась. Увеличилась надёжность, развилась инфраструктура, особенно сетевая. Параллелизация вычислений пошла в массы на плечах многоядерных процессоров.
Прежними остались лишь принципы, заложенные ещё в 1930-х годах и названные, согласно месту, Гарвардской и Принстонской архитектурами ЭВМ. Вчерашний студент теперь пишет не на ассемблере и C, а на Java, будучи уверенным в принципиальной новизне ситуации, не всегда осознавая, что изменилось только количество герц тактовой частоты и байтов запоминающих устройств.
Возросла ли при этом скорость разработки? Вопрос достаточно сложный, даже если сузить периметр до программирования согласно постановке задачи. Тем не менее я рискнул бы утверждать, что не только не возросла, но, наоборот, снизилась.
Массовые технологии, доступные шести миллионам программистов, являются универсальными, то есть могут быть использованы для разработки большинства типов программ, пакетов и систем. Поэтому важным элементом бизнеса становится не столько сокращение срока разработки, сколько максими-зация использования стандартных сред, компонентов и фреймворков. И хотя по срокам, бюджету и количеству разработчиков владеющие специализированными технологиями выигрывают у бригады «универсалов», но возникающие при этом риски могут свести к минимуму весь выигрыш.
Конечно, специализированные средства разработки всегда обеспечат преимущества по сравнению с универсальными. Тем не менее основная разработка по-прежнему будет идти на весьма ограниченном наборе универсальных сред и фреймворков, выталкивая специализированную в ниши, где сроки и производительность являются наиболее важными.
Бывшие разработчики PowerBuilder или FoxPro неоднократно выражали мне своё недоумение по поводу того, что для простейших операций, вроде настраиваемого показа табличных наборов данных на клиенте, теперь приходится тратить уйму времени и писать десятки строк кода, а каждая корректировка структур данных должна быть отражена во всех слоях системы. Опуская технические ограничения классических клиент-серверных приложений, нетрудно убедиться, что найти на рынке труда специалиста по PowerBuilder на порядок сложнее, чем VB.NET-программиста. К тому же поставщик среды PowerBuilder после многих перекупок за последние годы в итоге выглядит не слишком жизнеспособным.
С другой стороны, количество работающих в софтостроении женщин росло до начала 1990-х годов, после чего резко пошло на убыль. Рисунок 2 представляет ситуацию в США, но и в СССР и позднее в РФ она вряд ли отличалась.
Рис. 2. Процент женщин, занятых в компьютерной отрасли в 1985–2009 годах, согласно данным американского бюро статистики труда
Такая тенденция иллюстрирует факт ухода технологий софтостроения от специализированных сред, не требующих работы на далёком от решаемой прикладной задачи уровне математических абстракций, в которых прекрасный пол почему-то считается менее способным разбираться.
В начале своей трудовой деятельности я наблюдал изображённый на графике пик в среде женщин-программистов. В отделах конструкторско-технологического центра профессия прикладного программиста прекрасно сочеталась с равноправием: мужчин и женщин среди них было примерно поровну. В разгаре был переход с больших ЭВМ на «персоналки» и локальные сети NetWare. Автоматизированные информационные системы переносили на новую платформу, используя специализированные среды разработки приложений баз данных типа FoxPro, Clipper, dBase. И женская половина коллектива успешно справлялась с поставленными перед ними задачами.
Одна умная девушка, получавшая в школе хорошие отметки по математике и без особого труда оперирующая программами и данными в среде FoxPro, после знакомства со средой C++ Builder высказалась максимально ясно: «Язык понравился, но я не поняла, зачем мне нужны эти классы…».
Впоследствии мне не раз приходилось видеть исходники программисток на вполне себе объектно-ориентированном Delphi/C++Builder. В прикладном коде никакого объектного подхода, конечно, не было, всё ограничивалось компоновкой экранных форм стандартными элементами среды и написанием обработчиков событий в процедурном стиле.
Разумеется, это говорит не о «плохости» ООП, а о высоком уровне компетенции, необходимом, чтобы эта технология давала осязаемые преимущества. Тогда как цель прикладника – побыстрее собрать работающее решение для заказчика. Неплохим компромиссом был Visual Basic, похороненный Microsoft в начале 2000-х годов. Хотя ему и далеко до специализированных сред по удобству и скорости, VB не навязывал следование ООП, давая органично встраивать процедурную обработку между склеенными компонентами.
Про объектно-ориентированный подход мы ещё поговорим, но у меня сложилось мнение, что будучи реализованной повсеместно без малейших представлений о её применимости, эта технология сыграла не последнюю роль в вытеснении женского труда из отрасли.
В одном из проектов у меня произошёл с заказчиком весьма характерный разговор, ярко иллюстрирующий приоритеты при освоении бюджета даже в относительно небольшой частной компании:
Заказчик: Нам необходимо рассчитать ряд показателей на основе данных одной базы, но использовать их будут из таблиц в другой базе данных.
Я: Сделаем расчёт на SQL, заполним таблицы напрямую. Базы данных у вас на одном сервере?
Заказчик: На одном, но теоретически могут быть разнесены…
Я: Значит, просто поменяется источник расчётных данных: локальный на удалённый.
Заказчик: Э-э-э… А по сравнению с пакетом сервиса интеграции[20] скорость не замедлится?
Я: Наоборот, все будет работать быстрее. Две стадии – запрос с расчётами и заливка результата – вместо трёх.
Заказчик: Ух ты, здорово! (мнётся)
Я (с пониманием в голосе): Если вы хотите привлечь к работе ещё одного человека, то мы заполним данные в расчётной базе, а потом ваш сотрудник сделает пакет, который просто перекачает данные из одной базы в другую.
Заказчик (радостно): Да, я бы предпочёл сделать так!
Речь шла о регулярном заполнении пары таблиц объёмом примерно в сотню миллионов строк. Вместо прямого пути с настраиваемым источником данных ради приобщения к действу ещё одного «выпускника курсов» заказчик выбрал локальный расчёт с последующий перекачкой данных. В итоге используемое дисковое пространство удваивается, время увеличивается.
Служба «бизнес-интеллекта»[21] предприятия – неисчерпаемый кладезь такого рода задач для новоиспечённых специалистов курсов переквалификации и их начальников.
В послужном списке одной конторы имелась небольшая и простая система ведения заказов на рекламу для книжно-журнального издательства. Полтора десятка сущностей (и таблиц), с десяток экранных форм.
Лет 10–15 назад можно было бы взять на выбор Delphi/C++ Builder, PowerBuilder, Visual Basic, FoxPro, лёгкую клиент-серверную СУБД и сделать приложение за 3–5 дней с написанием каких-то сотен строк прикладного кода. Внесение изменений типа «добавления атрибута к сущности» вместе с воссозданием инсталлятора и скрипта обновления базы данных занимало час-два.
В 2009 году приложение было сделано на платформе. NET в трёхзвенной архитектуре: сервер приложений на базе WCF, Entity Framework, СУБД SQL Server 2005 и клиент в виде подключаемого модуля (add-in) к Office 2007 на WinForms. Спасибо, что не на WPF. Приложение занимает примерно 20 тысяч строк на C#, из них более половины являются техническими: слой объектов доступа к данным, прокси классов для WCF и прочая начинка. Конфигурационный файл для WCF-сервера – 300 строк XML. Это больше, чем нужно написать, например, Delphi-кода для логики отображения форм во всем приложении.
Первоначальная разработка заняла у фирмы порядка трёх недель работы одного программиста при том, что большая часть кода генерируется из модели. Отладка проблемы в канале WCF при нештатном исключении занимает часы. При добавлении атрибута изменение поднимается по всем звеньям, что также может потребовать длительного времени.
Наверное, и в 2009 году можно было бы обойтись разработкой на VB.NET приложения, напрямую работающего с СУБД через DataSet. И даже с учётом необходимости устанавливать. NET на рабочем месте, это было бы не намного хуже и медленнее, чем 10–15 лет назад.
Но механизм принятия решения базировался на других критериях:
• менеджеру, в соответствии с корпоративным стандартом, необходимо было использовать только платформы и средства Microsoft;
• программист не имел опыта разработки вне шаблонов многозвенной архитектуры и проекций объектов на реляционную СУБД, поэтому не стал рисковать.
Такие решения принимаются в мире ежечасно. Поэтому новичкам не раз предстоит столкнуться с заданием типа «быстро добавить поле в форму» и познакомиться с внутренним устройством подобных программ – карманных монстров, готовых откусить палец неосторожно сунутой руки.
Всякий раз, когда приходилось что-то делать при помощи технологии ASP. NET или просто править чей-то код, даже правильно написанный, меня не покидало ощущение копания по локоть в большой столовской кастрюле с макаронами.
Давайте вспомним историю. Успех в 1994–1995 годах первой версии бесплатной и открытой платформы PHP, называвшейся тогда Personal Home Page, показал, что веб быстрым темпом трансформируется из источника статической информации в среду динамических интерактивных приложений, доступных через «стандартный» проводник-браузер. Ниже я объясню, почему взял слово «стандартный» в кавычки. Microsoft не могла остаться в стороне и выдала собственное решение под названием ASP (Active Server Pages), работающее, разумеется, только под Windows.
Лежащий в основе названных платформ принцип был просто замечательным, хотя и совсем не новым. Логика приложений реализовывалась на стороне сервера скриптами на интерпретируемом языке, тонкий клиент-браузер в качестве терминала только отображал информацию и ограниченный набор элементов управления вроде кнопок. Вскоре выяснилось, что привыкшему к интерактивности полноценных приложений пользователю одних лишь кнопок не хватает. Тогда и в браузеры (то есть на стороне клиента) тоже включили поддержку скриптовых языков.
В итоге исходная веб-страница, ранее содержавшая только разметку гипертекста, стала включать в себя скрипты для выполнения вначале на сервере, а затем и на клиенте. Можете представить, какова была эта «лапша» на сколько-нибудь сложной странице ASP. Многие сотни строк каши из HTML, VBScript и клиентского JavaScript.
Последующая эволюция технологии была посвящена борьбе с этой лапшой, чтобы программный код мог развиваться и поддерживаться в большем объёме и не только его непосредственными авторами. На другом фронте бои шли за отделение данных от их представления на страницах, чтобы красивую обёртку рисовали профессиональные дизайнеры-графики, не являющиеся программистами.
Однако, несмотря на значительный прогресс за последние 15 лет, производительность разработки пользовательского интерфейса для веб-приложений в разы отстаёт от автономных приложений, тех самых, что «компонентокидатели» на Visual Basic, Delphi или C++ Builder делали 15 лет назад.
Если взять простой пример отображения модального диалога, то в Delphi, Visual Basic или WinForms-приложении потребуется написать одну строку кода для вызова формы и вторую – для проверки статуса возврата. Для веб-приложения, во-первых, реализация этого сценария одними серверными скриптами невозможна, необходимо задействовать клиентские. Во-вторых, необходимо хорошо представлять себе механизмы взаимодействия браузера и веб-сервера, чтобы синхронизировать вызовы и организовать передачу статуса. Наконец, веб-приложение не имеет состояния, поэтому понятие пользовательской сессии очень условное. Например, после 15-минутной паузы в деятельности клиента сервер решает, что сеанс закончен.
Теперь представьте, что под модальным окном с индикатором выполнения и единственной кнопкой «Прервать» вам надо запустить асинхронную обработку с обновлением информации в главном окне. В автономном приложении снова пишем несколько строк кода, добавляя обработчик события с делегатом из основной формы. А вот в веб… Даже краткое описание займёт несколько абзацев и будет касаться зоопарка технических ухищрений.
В качестве иллюстрации, существующая подсистема пользовательского интерфейса у одного из наших клиентов насчитывала всего около четырёх десятков экранных форм. Но для реализации только логики отображения потребовалась примерно сотня тысяч (!) строк code-behind[22] и Java-скриптов, несмотря на то, что создатели чётко отделили слой представлений от прикладной обработки, следовали логике «модель – представление – котроллер»[23], а общие элементы управления разного уровня – от собственных (custom) до композитных (user) – свели в библиотеки.
Легко проследить даже на простом примере, что для программиста помимо решения собственно прикладной задачи находится уйма забот. Основной целью такой дополнительной головной боли является платформенная независимость клиентской части приложения и максимально облегчённое развёртывание так называемого «тонкого» клиента, которым является веб-браузер.
Действительно, переносить автономное приложение между разными операционными системами и аппаратными платформами трудно. Большинство из них пишутся под Windows. Приложения на Java или WinForms.NET переносить легче, но для развёртывания требуется предустановленная среда времени исполнения (runtime) соответствующего фреймворка не ниже определённой версии. Гораздо меньше проблем с развёртыванием у FreePascal/Lazarus (открытый многоплатформенный аналог Delphi), новой версии Delphi XE или C++/Qt-приложений. Но, во-первых, перечисленное – далеко не самые массовые технологии, представляющие по этой причине дополнительные риски для менеджеров. Во-вторых, для обеспечения переноса и сам код, и требования к его написанию усложнятся, тогда как тестировать придётся на всех целевых платформах.
Поэтому на первый взгляд идея универсального программируемого терминала, которым является веб-браузер, поддерживающий стандарты взаимодействия с веб-сервером, выглядит привлекательно. Никакого развёртывания, никакого администрирования на рабочем месте. Именно этот аргумент и стал решающим в конце 1990-х годов для внедрения веб в корпоративную среду. Гладко было на бумаге, но забыли про овраги…
Достаточно быстро выяснилось, что разработка приложения, корректно работающего хотя бы под двумя типами браузеров (Internet Explorer, Netscape и впоследствии Mozilla) – задача не менее сложная, чем написание кода в автономном приложении на базе переносимой оконной подсистемы (Lazarus, C++ и другие). А тестировать нужно не только под разными браузерами, но и под разными операционными системами. С учётом версий браузеров.
Поскольку отступать было поздно (см. информацию про капиталовложения в начале раздела), эту проблему решили в лоб. Корпоративная среда в отличие от общедоступного Интернета имеет свои стандарты. Поэтому при разработке веб-приложений достаточно было согласовать внутренние требования предприятия с возможностями разработчиков. К началу 2000-х годов установился фактический стандарт корпоративного веб-приложения: Internet Explorer 6 с последним пакетом обновления под Windows 2000 или Windows XP.
Под эти требования за 10 лет было написано великое множество приложений. А когда пришла пора обновлять браузеры, внезапно выяснилось, что их новые версии далеко не всегда совместимы с находящимися в эксплуатации системами. И по этой причине простое обновление Internet Explorer 6 на 7 вызовет паралич информационных систем предприятия.
Достаточно свежий пример. В одной крупной конторе (более 10 тысяч сотрудников) система учёта рабочего времени из Internet Explorer 7, 8 или 9 на основной странице ввода зацикливалась, эмулируя скриптами щелчки мыши и подвешивая браузер. В Firefox 3 зацикливания не происходило, но не работали всплывающие окна. В более поздних версиях Firefox система не работала совсем, выдавая «browser is not supported». В Chrome корректно работала предварительная версия, но сданная в эксплуатацию почему-то лишилась этого качества с выдачей сообщения о несовместимости: «The iView is not compatible with your browser, operating system, or device».
Итого в 2011 году приложение по-прежнему стабильно работало только в Internet Explorer 6, выпущенном в 2001 году, то есть 10 лет назад.
За эти же 10 лет прошло огромное количество презентаций о том, что инвестиции сделаны не зря, о том, как замечательно работают веб-приложения в интранете и корпоративной среде в целом. На деле же оказалось, что, собрав свои приложения на базе веб-технологии, корпорация оказалась заложником версии и марки конкретного браузера. Даже переход с Internet Explorer 6 на 7, не говоря уже о Firefox или Chrome, оказался катастрофой масштаба предприятия с долгими месяцами миграции и последующей стабилизации. Разумеется, если есть кому стабилизировать, ведь за 5–10 лет сменяются разработчики, уходят с рынка прежние поставщики. Для таких случаев приложение остаётся жить на виртуальной машине под старой версией операционной системы и проводника.
Предприятие оказывается один на один с веб-технологией, которая, как утверждали вначале, ничего не стоит при развёртывании и не требует администрирования на рабочем месте. Про затраты на обновление браузера, конечно, тогда никто не заикался, хотя соблюдения в необходимом объёме стандартов веб-терминалами как не было, так и нет.
Менеджеры другой фирмы стали искать решение проблемы у Google, отдав ему на откуп корпоративный документооборот, групповую работу и почту. Новое обоснование выглядело так: «Уж Google-то обеспечит совместимость приложений со своим браузером!» Не знаю, слышали ли поверившие в такой довод хоть что-нибудь об открытых системах и о печально завершившейся истории с IBM, продававшей свои большие ЭВМ вместе со своим же, привязанным к ним программным обеспечением. Человеческая история имеет свойство повторяться.
Вернемся к классическим автономным приложениям: даже в самом примитивном варианте развёртывания при использовании обычных исполняемых файлов с запуском с разделяемого сетевого диска обновление одной программы не вызывает крах остальных. Не зря тот же SAP для работы с R/3, ключевой системой предприятия, использует самое что ни на есть полноценное оконное приложение, оставляя веб для частных случаев.
Факт наличия у большинства корпоративных клиентов только Internet Explorer версии 6 в качестве стандарта не раз оборачивался казусами. Так, обновление нашего внутреннего сервера Microsoft Exchange привело к тому, что веб-почта в Internet Explorer 6 стала работать со множеством ограничений, например, отсутствует разметка текста, нет проверки орфографии, не показывается дерево папок. Чтобы отправить почту, пришлось соединяться с сервером (!), где изначально стоял Internet Explorer 7, и работать оттуда через терминал.
Напоследок хочется пожелать коллегам, ответственным за выбор технологий, всячески обосновывать необходимость использования веб-интерфейса в вашей системе, принимая в рассмотрение другие пути.
Появившись в 1995 году, технология Java сразу пошла на штурм рабочих мест и персональных компьютеров пользователей в локальных и глобальных сетях. Наступление проводилось в двух направлениях: полноценные «настольные» (desktop) приложения и так называемые апплеты[24], то есть приложения, имеющие ограничения среды исполнения типа «песочница» (sandbox). Например, апплет не мог обращаться к дискам компьютера.
Несмотря на значительные маркетинговые усилия корпорации Sun, результаты к концу 1990-х годов оказались неутешительны: на основной платформе пользователей – персональных компьютерах – среда исполнения Java была редким гостем, сами приложения можно было сосчитать по пальцам одной руки (навскидку вспоминается только Star Office), веб-сайтов, поддерживавших апплеты, было исчезающе мало, а настойчивые просьбы с их страниц скачать и установить 20 мегабайтов исполняемого кода для просмотра информации выглядели издевательством при существовавших тогда скоростях и ограничениях трафика. Несомненно, судебная тяжба Sun в 1997 году с Microsoft, тут же прекратившей распространение Java вместе с Windows, также сыграла свою роль. Но основными объективными причинами такого исхода были:
• универсальность и кроссплатформенность среды, обернувшаяся низким быстродействием и невыразительными средствами отображения под вполне конкретной и основной для пользователя операционной системой Windows;
• необходимость установки и обновления среды времени исполнения (Java runtime).
В 2007 году Sun утверждала, что среда исполнения Java установлена на 700 миллионах персональных компьютеров[25], правда не уточнялась её версия. В декабре 2011 года уже новый владелец – корпорация Oracle – привёл данные о том, что Java установлена на 850 миллионах персональных компьютеров и миллиардах устройств в мире[26]. Но поезд ушёл, развитие приложений на десктопах сместилось далеко в сторону по пути начинённых скриптами веб-браузеров, а рост количества мобильных устройств положил конец монополии «персоналок» в роли основного пользовательского терминала.
Тем не менее необходимость в кросс-платформенных богатых интерактивными возможностями интернет-приложениях[27] никуда не исчезла, поскольку браузеры, нашпигованные скриптовой начинкой, обладали ещё большими техническими ограничениями и низким быстродействием даже по сравнению с апплетами. Эта ниша к началу 2000-х годов оказалась плотно занятой Flash-приложениями, специализирующимися на отображении мультимедийного содержания. Учтя ошибки Java, разработчики из Macromedia сделали инсталляцию среды исполнения максимально лёгкой в загрузке и простой в установке.
Упомянутая специализация технологии на интерактивном мультимедийном содержании веб-сайтов, включая потоковое аудио и видео, с другой стороны, оказалась непригодной для использования в разработке корпоративных приложений, продолжавшей по этой причине использовать браузеры со скриптами.
К решению проблемы подключилась Microsoft. Первым «блином» в 2005 году стала технология ClickOnce развёртывания полноценных WinForms-приложений. По-прежнему клиентское рабочее место требовало предварительно установки среды исполнения. NET версии 2. Но развёртывание и автоматическое обновление приложения и его компонентов было полностью автоматизировано. Первоначально пользователь, не имеющий прав локального администратора, устанавливал необходимую программу, просто щёлкнув по ссылке в браузере, далее запуская её с рабочего стола или из меню. Sun отреагировала молниеносно, добавив аналогичную возможность под названием Java Web Start.
Но «блин» всё-таки вышел комом. По данным AssetMetrix[28], основной парк корпоративных компьютеров в 2005 году составляли «персоналки» под управлением Windows 2000 (48 %) и Windows XP (38 %). Имея полную возможность предустановить среду. NET 2 на все эти рабочие места вместе с очередным пакетом обновлений, Microsoft не решилась на такой шаг, тем самым фактически похоронив массовое использование новой технологии разработчиками, имевшими неосторожность надеяться на помощь корпорации в развёртывании тяжёлых клиентских приложений.
Возможно, одной из причин стала потеря интереса Microsoft к WinForms, чьё развитие было заморожено, и переход в. NET 3 к более общей технологии построения пользовательских интерфейсов WPF[29], отличающейся универсальностью и большей трудоёмкостью в прикладной разработке, но позволяющей полностью разделить труд программистов и дизайнеров, что имело смысл в достаточно больших и специализированных проектах. Вот вам очередная иллюстрация к теме прогресса в производительности разработки.
Побочным продуктом WPF стал Silverlight. По сути, это реинкарнация Java-апплетов, но в 2007 году, спустя более 10 лет, и в среде. NET. Кроме того Silverlight должен был по замыслу авторов составить конкуренцию Flash в области мультимедийных интернет-приложений.
В отличие от WPF, Silverlight вызвал больший энтузиазм разработчиков корпоративных приложений. Во-первых, для развёртывания не требовалась вся среда. NET целиком, достаточно было установить её часть, размер дистрибутива которой составлял всего порядка 5 мегабайтов. Поэтому на очередные обещания Microsoft предустановить. NET 3 можно было не полагаться, тем более при уже анонсированном. NET 3.5. Во-вторых, приложение можно было запускать не только в окне браузера, но и автономно.
Наша контора среагировала достаточно быстро, и к 2009 году в софто-строительной фабрике уже имелся номинальный генератор кода по модели для Silverlight-приложений. Ожидая взросления и стабилизации технологии, периодически подступаясь к теме, я собирал мнения коллег о встретившихся им подводных камнях.
Прежде всего насторожили меня новости про отсутствие в Silverlight отличных от юникода[30] кодировок. Их нет в константах, а Encoding.GetEncoding (1251) выдаёт ошибку. Как корректно импортировать в приложение ASCII[31]-файл? Никак. Из этого вытекала невозможность полноценной работы приложения с обыкновенным текстовым файлом данных, вроде CSV (comma separated values).
Прямой доступ к базам данных также отсутствовал. Можно было пойти окольными путями через COM interops и ADO, но для этого требовались очень серьёзные поводы.
И тут в корпорации, аккурат к октябрьской конференции разработчиков 2010 года, издали новый декрет: «Наша стратегия по Silverlight изменилась»[32]. Сессий по новой версии Silverlight 5 на мероприятии не было вовсе. Снова часы пробили полночь, и карета превратилась в тыкву. Приоритетом стал HTML 5.
Silverlight вырос до вполне взрослой версии 4, уже давно вышла Visual Studio 2010, где встроена поддержка разработки приложений под него. Но зададимся вопросом: «Может ли пользователь установить себе Silverlight-приложение, не будучи администратором на своем компьютере?» Ответ, мягко говоря, разочаровывающий: «Нет, не может».
Это значит, что развёртывать Silverlight-песочницы на машинах пользователей должны сами компании через своих специалистов, ответственных за инфраструктуру. Хотя в соответствующем официальном документе описано много способов облегчения администраторской деятельности, факт остаётся фактом: технология в своей 4-й (!) версии не может быть использована в корпоративной среде без серьёзных накладных расходов.
Итак, итог к 2012 году. Во-первых, «старые» технологии вроде автономного оконного кроссплатформенного приложения на Lazarus/FreePascal, Delphi XE или Qt/C++ по-прежнему позволяют сделать то, что нельзя сделать «новыми и прогрессивными». Во-вторых, ценность Silverlight по сравнению с полноценным. NET на уровне развёртывания практически нулевая. Видимо, по этой причине Microsoft недавно закрыла веб-сайт silverlight.net, в очередной раз оставив разработчиков в интересном положении.
Из продвигаемых Microsoft за последние 10 лет технологий для разработки полноценных пользовательских интерфейсов, не заброшенных на пыльный чердак, остался только WPF, имеющий весьма сомнительную ценность для небольших коллективов и отдельных разработчиков. WPF – это ниша крупных автономных Windows-приложений. Кроме того, сама по себе она невелика, в ней уже есть WinForms – более простой и быстрый в разработке фреймворк, к тому же переносимый под Linux/Mono. Поэтому при соответствующих ограничениях развёртывания выбор по-прежнему лежит между веб-браузером или условным Delphi, хочешь ты этого или нет…
Говоря об объектно-ориентированном подходе и программировании, принято добрым словом вспоминать начало 1970-х годов и язык Smalltalk, скромно умалчивая, что понадобилось ещё почти 15 лет до начала массового применения технологии в отрасли, прежде всего, за счёт появления C++ и позднее – Объектного Паскаля. Потому что фактическим отраслевым стандартом был язык C, а Паскаль широко использовался для обучения и в основном для прикладного программирования, если не рассматривать исключения вроде первой версии Microsoft Windows. Религиозные войны 1970–80-х годов в новостных группах проходили под лозунгом «Си против Паскаля». По этой причине революционный переход сообществ на Smalltalk выглядел маловероятным, тогда как объектно-ориентированные расширения вышеупомянутых языков были восприняты положительно. Немудрено, что многие концепции Smalltalk были в них реализованы.
В начале широкой популяризации ООП, происходившей в основном за счёт языка C++, одним из главных доводов был следующий: «ООП позволяет увеличить количество кода, которое может написать и сопровождать один среднестатистический программист». Приводились даже цифры, что-то около 15 тысяч строк в процедурно-модульном стиле[33] и порядка 25 тысяч строк на C++.
Довод в целом правильный, хотя из него совсем не следовало, что десяти программистам на C++ будет легче сопровождать общую систему, чем десяти программистам на C. Про это как-то забыли, потому что существовало много автономных проектов, управляемых процессом типа бруксовской операционной бригады[34] с главным программистом, отвечающим за всё решение. Собственно, и Бьёрн Страуструп, создатель C++, прежде всего преследовал цели увеличения производительности своего программистского труда.
Как только «главным программистом» стал «коллективный разум» муравейника, неважно мечущийся ли на планёрках «гибкой» (agile) разработки, прозаседавшийся ли на совещаниях по тяжёлой поступи RUP[35], проблема мгновенно всплыла, порождая Ад Паттернов[36], Чистилище нескончаемого рефакторинга[37] и модульных тестов, недосягаемый Рай генерации по моделям кода безлюдного Ада.
Термин «Ад Паттернов» может показаться вам незнакомым, поэтому я расшифрую подробнее это широко распространившееся явление:
• слепое и зачастую вынужденное следование шаблонным решениям;
• глубокие иерархии наследования реализации, интерфейсов и вложения при отсутствии даже не очень глубокого анализа предметной области;
• вынужденное использование все более сложных и многоуровневых конструкций в стиле «новый адаптер вызывает старый» по мере так называемого эволюционного развития системы;
• лоскутная[38] интеграция существующих систем и создание поверх них новых слоёв API[39].
В результате эволюционного создания Ада Паттернов основной ценностью программиста становится знание, как в данной конкретной системе реализовать даже простую новую функцию, не прибегая к многодневным археологическим раскопкам и минимизируя риски дестабилизации. Код начинает изобиловать плохо читаемыми и небезопасными конструкциями:
Services.Oragnization.ContainerProvider.ProviderInventory.InventorySectorPrivate.
Stacks[0].Code.Equals("S01")
Последствия от создания Ада Паттернов ужасны не столько невозможностью быстро разобраться в чужом коде, сколько наличием многих неявных зависимостей. Например, в рамках относительно автономного проекта мне пришлось интегрироваться с общим для нескольких групп фреймворком ради вызова единственной функции авторизации пользователя: передаёшь ей имя и пароль, в ответ «да/нет». Этот вызов повлёк за собой необходимость явного включения в. NET-приложение пяти сборок. После компиляции эти пять сборок притащили за собой ещё более 30, б€ольшая часть из которых обладала совершенно не относящимися к безопасности названиями, вроде XsltTransform. В результате объём дистрибутива для развёртывания вырос ещё на сотню мегабайтов и почти на 40 файлов. Вот тебе и вызвал функцию…
Разумеется, превратить код программы в тарелку спагетти можно без особого труда и в процедурно-модульной технологии. Разница в том, что распутывать процедурное спагетти гораздо легче, чем лапшу объектно-ориентированную. Потому что кроме вложенности вызовов процедур в ООП имеет место различного типа вложенность объектов – от наследования реализации до многоуровневых ассоциаций, и совмещение в классах собственно структур данных и процедурного кода.
Несомненно, C++ является мощным инструментом программиста, хотя и с достаточно высоким порогом входа, предоставляющим практически неограниченные возможности профессионалам с потребностью технического творчества. Я видел немалое количество примеров изящных фреймворков и прочих «башен из слоновой кости», выполненных одиночками или небольшим коллективом. Но крупные проекты подвержены влиянию уже упоминавшейся гауссианы (см. рис. 1). Нормальное распределение вовлекает в процесс большое количество крепких профессионалов-середняков, которым надо сделать «чтобы работало» с наименьшими телодвижениями во время нормированного рабочего дня. Если Microsoft или Lockheed Martin – подрядчик Министерства Обороны США, имеют возможность растянуть кривую на графике вправо и вложить немалые
средства во внутреннюю стандартизацию кодирования, то в обычной ситуации оказывается, что C++, действительно увеличивавший личную продуктивность Страуструпа и его коллег, начинает тормозить производительность большого софтостроительного цеха где-нибудь в жарком субтропическом опенспейсе[40] площадью в гектар. Помимо общих проблем интеграции, на C++ достаточно просто «выстрелить себе в ногу», и человеческий фактор быстро становится ключевым риском проекта.
Если вернуться к вопросу стандартов кодирования на C++, хорошим примером будет разработка программной начинки нового истребителя F-35 [15]. Объем разработанного кода порядка 10 миллионов строк, это даже больше, чем Windows. Следовательно, стандарт вполне пригоден к практическому использованию. Но имеются ли у вас в проекте ресурсы для того, чтобы не только обучить всех программистов 150-страничному своду правил, но и постоянно контролировать его исполнение?
Поэтому появились новые C-подобные языки: сначала Java, а чуть позже и C#. Они резко снизили порог входа за счёт увеличения безопасности программирования, ранее связанной прежде всего с ручным управлением памятью. Среды времени исполнения Java и. NET решили проблему двоичной совместимости и повторного использования компонентов системы, написанных на разных языках для различных аппаратных платформ.
Когда многие технические проблемы были решены, оказалось, что ООП очень требовательно к проектированию, так и оставшемуся сложным и недостаточно формализуемым процессом. Похожая ситуация была в середине XX века в медицине: после изобретения антибиотиков первое место по смертности перешло от инфекционных болезней к сердечно-сосудистым.
Примерно в то же время в сообществе начались дискуссии, появились первые публикации вроде уже ставшей классической «Почему объектно-ориентированное программирование провалилось?»[41]. Эксперты по ООП в своих книгах стали нехотя писать о том, что технология тем эффективнее, чем более идеален моделируемый ею мир.
Рис. 3. Эмпирическое сравнение производительности процедурно-реляционного и объектно-ориентированного подходов в зависимости от достигнутой степени формализации моделируемого мира
Действительно, вспомним ещё раз Smalltalk. Его концепции выросли из задач построения графического интерфейса пользователя. Взглянув на любой оконный фреймворк, вы увидите искусственный мир, идеальный с точки зрения его авторов. Многоуровневые иерархии классов не воссозданы многолетним трудом классификации объектов окружающего мира, а выращены в виртуальных пробирках лабораторий разработчиков.
Учебники по ООП полны примеров, как легко и красиво решается задачка отображения геометрических фигур на холсте с одним абстрактным предком и виртуальной функцией показа. Но стоит применить такой подход к объектам реального мира, как возникнет необходимость во множественном наследовании от сотни разношёрстных абстрактных заготовок. Объект «книга» в приложении для библиотеки должен обладать свойствами «абстрактного печатного издания», в магазине – «абстрактного товара», в музее – «абстрактного экспоната», в редакции, типографии, в службе доставки… Можете продолжить сами.
Попытки выпутаться из этой ситуации за счёт агрегации приводят к новым дивным мирам, существующим только в воображении разработчиков. Теперь объект «книга» это контейнер для чего-то продающегося, выдаваемого, хранящегося и пылящегося. Необходимо быстро менять контекст: в магазине вкладывать в книгу товар, в библиотеке – печатное издание, в отделе «книга-почтой» – ещё какую-нибудь хреновину. Плодятся новые многоуровневые иерархии, но теперь уже не наследования (is a), а вложения (is a part of).
Изящнее выглядят интерфейсы. Но если в реальном мире книга, она и в музее – книга, то во вселенной интерфейсов «книга в музее» – неопознанный объект, пока не реализован соответствующий интерфейс «экспонат». Дальше интерфейсы пересекаются, обобщаются, и мы получаем ту же самую иерархию наследования, от которой сбежали. Но теперь это уже иерархия, во-первых, множественная, а во-вторых, состоящая из абстрактных классов без какой-либо реализации вообще (интерфейс, по сути, есть pure abstract class). Если же мы отказываемся от обобщения интерфейсов, то фактически оказываемся в рамках современных реализаций модульного программирования типа Оберон[42].
Тем не менее все три подхода применимы и могут дать хороший результат при высокой квалификации проектировщиков и наличии проработанных моделей предметной области.
Одна из причин подобных злоключений в том, что концепции, выдвигаемые ООП, на самом деле не являются его особенностями за исключением наследования реализации от обобщённых предков с виртуализацией их функций. И по несчастливому стечению обстоятельств именно наследование реализации является одним из основных механизмов порождения ада наследуемых ошибок, неявных зависимостей и хрупкого дизайна. Все остальные концепты от инкапсуляции и абстракции до полиморфизма имеются в вашем распоряжении без ООП. Полиморфизм с проекциями вместо таблиц наличествует даже в SQL.
Мой субъективный опыт подтверждает, что за исключением фреймворков весьма абстрактного уровня, сделанных «с чистого листа» небольшими группами профессионалов высокого класса, Объектно-Ориентированный Подход на практике в большинстве случаев превращает проект или продукт, переваливший за сотню-другую тысяч строк, в упомянутый Ад Паттернов, который, несмотря на формальную архитектурную правильность и её же функциональную бессмысленность, никто без помощи авторов развивать не может.
С другой стороны, любая неясность в постановке задачи вынуждает разработчиков сосредотачиваться не на её решении, а на архитектуре, позволяющей «без особых затруднений» менять логику приложения и переходить с расчёта зарплаты колхоза на прогноз удоев фермы.
Результат неизменно стабильный…
Особо хочу остановиться на тезисе уменьшения сложности при использовании ООП для создания фреймворков. Современное состояние дел – это платформа. NET с примерно 40 тысячами классов и типов ещё в версии 3.5. Вдумайтесь, вам предлагают для выражения потребностей прикладного программирования язык с 40 тысячами слов, без учёта глаголов и прилагательных, называя такую технологию упрощением.
Александр Сергеевич Пушкин использовал в своем творчестве порядка 24 тысяч слов. Толковый словарь Ожегова содержит около 70 тысяч слов. Среднестатистический русский человек использует в повседневной жизни от 5 до 10 тысяч слов[43], из них только 3 тысячи являются общеупотребительными. Получается, что даже наследник гения Пушкина способен охватить менее половины предлагаемой технологии, при том что её словарь сравним с естественным языком!