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

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

Около 25 лет назад интерактивный текстовый редактор можно было разработать, вложившись всего в 8 000 байт памяти. (Современные редакторы требуют в 100 раз больше!) Операционная система должна была обходиться 8 000 байтами, а компилятор умещаться в 32 Кбайта, тогда как их современные потомки требуют мегабайты. Стало ли всё это раздувшееся ПО быстрее? Наоборот. Если бы не аппаратное обеспечение, ставшее быстрее в тысячу раз, современный софт был бы совершенно непригоден к использованию.

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

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

Например, с 1978 по 1993 год семейство процессоров Intel 8086 увеличило мощность в 335 раз, плотность транзисторов - в 107 раз, а цену - примерно в 3 раза. Перспективы продолжения роста производительности всё ещё устойчивы, и нет признаков того, что ненасытный аппетит софта будет удовлетворён в ближайшее время^1. Эта тенденция породила множество правил, законов и следствий, которые - как обычно в таких случаях - выражены в общих терминах; следовательно, они ни доказуемы, ни опровержимы. С долей юмора два следующих закона довольно точно отражают состояние дел:

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

  • Программное обеспечение замедляется быстрее, чем ускоряется аппаратное обеспечение. (Райзер)

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

Причины «раздутого софта»

Очевидно, двум факторам способствуют принятие всё более разрастающегося софта: (1) быстро растущая мощность аппаратного обеспечения и (2) неспособность пользователей отличить обязательные функции от необязательных. Но, возможно, ещё важнее, чем искать причины терпимости, - задаться вопросом: что толкает софт к усложнению?

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

Все функции, сразу

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

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

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

Для некоторых сложность = мощь

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

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

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

  • Зависимость клиента прибыльнее, чем обучение клиента.

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

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

Хорошая инженерия характеризуется постепенным, пошаговым совершенствованием продуктов.

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

Никогда не хватает времени

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

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

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

Языки и методология проектирования

Хотя исследования в области программного обеспечения, которые теоретически содержат ключ к многим будущим технологиям, получили серьёзную поддержку, их результаты кажутся едва ли полезными для индустрии. Методичное проектирование, например, оказывается нежелательным, поскольку такие продукты требуют слишком много «времени до выхода на рынок». Аналитическая проверка и техники доказательства корректности обстоят ещё хуже; кроме того, они требуют более высокого интеллектуального уровня, чем обычная практика «попробуй - и исправь». Предложение сократить сложность ПО, сосредоточившись только на необходимом, быстро отвергается как нелепое на фоне любви клиентов к побрякушкам. Когда действует принцип «всё дозволено», методологии и дисциплина становятся первыми жертвами.

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

Методологии языков программирования особенно спорны. В 1970-х было широко распространено мнение, что разработка программ должна основываться на хорошо структурированных методах и уровнях абстракции с чётко определёнными спецификациями. Абстрактный тип данных лучше всего выражал эту идею и нашёл отражение в таких новых тогда языках, как Modula-2 и Ada. Сегодня программисты покидают хорошо структурированные языки и переходят в основном на C. Язык C даже не позволяет компиляторам выполнять надёжную проверку типов, хотя эта задача компилятора - одна из самых полезных в разработке, поскольку она позволяет находить концептуальные ошибки на ранней стадии. Без проверки типов идея абстракции в языках остаётся пустой и академической. Абстракция возможна только в языках, предполагающих строгую статическую типизацию каждой переменной и функции. В этом отношении C терпит неудачу - он напоминает ассемблер, где «всё возможно».

Изобретение колеса заново?

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

  • Прогресс приемлем, только если он совместим с текущим состоянием.

  • Следовать стандарту всегда безопаснее.

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

Какой мрачный образ; какой пессимист! - подумает читатель. Ни намёка на светлое будущее вычислений, доселе считавшееся само собой разумеющимся.

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

Проект OBERON

В период между 1986 и 1989 годами Юрг Гуткнехт и я разработали и реализовали новую программную систему - называемую Oberon - для современных рабочих станций, основываясь исключительно на аппаратном обеспечении. Наша первоочередная цель состояла в том, чтобы показать: программное обеспечение можно разрабатывать, используя лишь малую часть памяти и вычислительной мощности, обычно считающейся необходимой, - и при этом не жертвуя гибкостью, функциональностью или удобством использования.

Система Oberon используется с 1989 года, применяясь для подготовки документов, разработки программного обеспечения, САПР электронных схем и многого другого. В систему входят:

  • управление памятью,

  • файловая система,

  • менеджер оконного отображения,

  • сеть с серверами,

  • компилятор,

  • редакторы текста, графики и документов.

Разработанная и реализованная - с нуля - двумя людьми за три года, Oberon была перенесена на несколько коммерческих рабочих станций и нашла множество энтузиастов, особенно потому, что доступна бесплатно.^2

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

Как возможно построить программную систему усилиями примерно пяти человеко-лет и представить её в одной книге?^3

Три основных принципа

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

Во-вторых, мы хотели использовать по-настоящему объектно-ориентированный язык программирования, один из безопасных по типам. Это, вместе с нашей верой в то, что первый принцип должен применяться к инструментам ещё более строго, чем к самой разрабатываемой системе, вынудило нас разработать собственный язык и создать для него компилятор. Так появился Oberon^4 - язык, происходящий от Modula-2 посредством исключения менее важных (например, поддиапазонов и перечислений) и небезопасных возможностей (например, преобразований типов и вариантных записей).

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

Расширение типов

Если, например, тип Viewer определён в модуле Viewers, тогда тип TextViewer может быть определён как расширение Viewer (обычно в другом модуле, добавляемом в систему). Все операции, применимые к Viewer, одинаково применимы к TextViewer, и все свойства Viewer также присущи TextViewer.

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

Расширение типа - типичная объектно-ориентированная возможность. Чтобы избежать вводящих в заблуждение антропоморфизмов, мы предпочитаем говорить «TextViewer совместим с Viewer», а не «TextViewer наследует Viewer». Мы также избегаем вводить новую терминологию для широко известных концепций; например, мы сохраняем слово тип вместо класс, а также слова переменная и процедура, избегая экземпляр и метод. Очевидно, наш первый принцип - фокус на essentials - применим и к терминологии.

История одного типа данных

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

В ядре системы тип Text определён как последовательность символов с атрибутами шрифта, смещения и цвета. Базовые операции редактирования предоставлены модулем TextFrames.

Модуль электронной почты не включён в ядро, но может быть добавлен при необходимости. Когда он добавляется, модуль электронной почты опирается на ядро и импортирует типы Text и TextFrame для отображения текстов. Это означает, что стандартные операции редактирования могут применяться к полученным письмам. Письма можно изменять, копировать и вставлять в другие тексты на экране с помощью операций ядра. Единственные операции, которые предоставляет модуль почты - получение, отправка и удаление сообщений, плюс команда показа каталога почтового ящика.

Активация операций

Другой пример, иллюстрирующий нашу стратегию - активация операций. Программы в Oberon не запускаются; вместо этого отдельные процедуры экспортируются из модулей. Если модуль M экспортирует процедуру P, тогда P может быть вызвана (активирована), просто указав на строку M.P в любом тексте на экране и щёлкнув кнопкой мыши. Такое прямолинейное выполнение команд открывает следующие возможности:

  1. Часто используемые команды включаются в короткие фрагменты текста - tool-texts, которые напоминают настраиваемые меню, хотя для них не требуется специальное меню-программное обеспечение. Они обычно отображаются в небольших окнах (viewers).

  2. Расширив систему простым графическим редактором, предоставляющим подписи на основе текстов Oberon, команды могут быть выделены и украшены рамками и тенями. Это создаёт всплывающие и/или выпадающие меню, кнопки и иконки - «бесплатно», поскольку используется тот же механизм активации команд.

  3. Сообщение, полученное по электронной почте, может содержать текст и команды. Команды выполняются, когда получатель щёлкает по ним внутри письма (без копирования в специальное окно). Мы используем это, например, когда объявляем о новых или обновлённых модулях: письмо содержит команды получения и список имён модулей для загрузки из сети. Весь процесс требует лишь нескольких щелчков.

Сохраняя простоту

Стратегия сохранения ядра системы простым, но расширяемым, вознаграждает скромного пользователя. Ядро Oberon занимает менее 200 Кбайт, включая редактор и компилятор. Система на базе Oberon нуждается в расширении только если требуются большие или сложные приложения (например, CAD). Если используются несколько таких приложений, система не требует их одновременной загрузки. Эта экономия достигается благодаря следующим свойствам:

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

  2. Каждый модуль находится в памяти в виде одной копии. Это правило запрещает создание связанных загрузочных файлов (core images). Обычно связанные файлы вводятся в ОС потому, что процесс линковки медленный и сложный. В Oberon линковка неотделима от загрузки. Это допустимо, поскольку процессы быстры и выполняются автоматически при первом обращении к модулю.

Цена простоты

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

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

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

В заключение - девять уроков, извлечённых из проекта Oberon, которые стоит иметь в виду каждому, кто начинает разрабатывать новую систему ПО:

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

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

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

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

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

  6. Проблемы коммуникации растут с ростом команды. Если они доминируют, и команда, и проект в беде.

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

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

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

Проект Oberon показал, что гибкие и мощные системы можно создавать с существенно меньшими ресурсами и в более короткие сроки. Болезнь раздувания ПО - не «закон природы». Она предотвратима, и задача инженера - ограничить её.

Ссылки

  1. E. Perratore et al., “Fighting Fatware,” Byte, Vol. 18, No. 4, Apr. 1993, pp. 98–108.

  2. M. Reiser, The Oberon System, Addison-Wesley, Reading, Mass., 1991.

  3. N. Wirth, J. Gutknecht, Project Oberon - The Design of an Operating System and Compiler, Addison-Wesley, Reading, Mass., 1992.

  4. M. Reiser, N. Wirth, Programming in Oberon - Steps Beyond Pascal and Modula, Addison-Wesley, Reading, Mass., 1992.

Никлаус Вирт (1934 - 2024) - профессор компьютерных наук Швейцарского федерального технологического института (ETH), Цюрих. Он разработал языки Pascal (1970), Modula (1980) и Oberon (1988), рабочие станции Lilith (1980) и Ceres (1986), а также их операционные системы. Лауреат премий IEEE Emmanuel Piore и Turing Award (1984).