Знаменитый гуру программирования Гради Буч в 2001 году сделал важное признание в статье Through the Looking Glass: «Я был неправ: все-таки есть нечто более глубокое, нечто поистине более богатое возможностями, чем объекты». Этими словами Буч представил аспектно-ориентированное программирование (АОП) — на тот момент новейшую парадигму программирования.

История индустрии ПО началась с огромных машин и машинного кода — ни о чем не говорящих человеку рядов чисел, поэтому решение вычислительных задач с точки зрения проектирования и программирования было чрезвычайно трудоемким. Требовались простые, удобные механизмы абстракции для упрощения процессов моделирования и программирования. Машинные языки сменил ассемблер, который в 60-х — начале 70-х уступил место структурным и процедурным языкам, позднее вытесненным объектно-ориентированным с его принципами инкапсуляции, наследования и полиморфизма.

Эволюция парадигм программирования происходила в связи с потребностью в разделении ответственности (separation of concerns): артефактов, концепций или особенностей, интересующих участников проекта разработки ПО. Зона ответственности может быть функциональной (например, добавление элемента к списку) или нефункциональной (например, быстродействие, безопасность и т. д.) либо относиться к этапам жизненного цикла разработки ПО, например к проектированию.

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

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

Типичная программа представляет собой набор взаимосвязанных друг с другом компонентов (например, отвечающих за управление транзакциями), обычно имеющих взаимозависимости по тем или иным функциям. Такое взаимосоединение называется сквозной функциональностью (cross-cutting concern), и аспектно-ориентированное программирование занимается ее систематизацией, преобразованием в отдельные модули и их управлением.

Основным инструментом АОП являются аспекты — модули, реализующие сквозную функциональность. На сегодня существует несколько аспектно-ориентированных языков и инструментов — например, AspectJ и PostSharp.

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

Фундаментальные принципы эволюции парадигм

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

Принцип эквивалентности

 

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

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

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

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

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

Принцип превосходства

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

  • управляющие структуры процедурного программирования превосходят регистры, команды адресации, флаги и всевозможные переходы, присущие программированию на ассемблере;
  • процедуры и модули в процедурном программировании превосходят блоки кода в ассемблере;
  • классы в объектно-ориентированном программировании превзошли структуры данных и процедуры, характерные для процедурного программирования; наследование и полиморфизм в ООП превзошли наборы процедур и механизмы вызова, свойственные процедурному программированию;
  • аспекты в АОП превзошли разбросанный и запутанный код ООП; точки соединения (join point) и срезы (pointcut) в АОП абстрагируют статическое и динамическое связывание, а также другие особенности ООП.

Место новой парадигмы программирования в иерархии зависит от уровня ее превосходства по сравнению с существующими.

Принцип связанных абстракций

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

Принцип ограниченной видимости

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

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

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

Принцип порога полезности

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

Этот принцип отвечает на два важных вопроса: как узнать, когда нужна новая, более мощная парадигма программирования, и каковы задачи, трудности и зоны ответственности, для которых конкретная парадигма не подходит? Следующие примеры иллюстрируют этот принцип:

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

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

Принцип продления срока жизни

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

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

Принцип неизбежности сложностей

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

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

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

***

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

Девон Симмондс (simmonds@uncw.edu) — доцент Университета Северной Каролины.
Devon M. Simmonds, The Programming Paradigm Evolution, IEEE Computer, June 2012, IEEE Computer Society. All rights reserved. Reprinted with permission.