Введение в Поведенческое программирование

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

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

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

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

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

Ниже приведен пример обновления программного обеспечения:

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

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

Разработка только с добавлением

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

Поскольку узким местом кажется артефакт, что, если бы у нас не было доступа к самому артефакту? Что, если единственный способ внести изменения в систему - это добавить новый код? Тогда нам не нужно было бы читать и понимать, где втиснуть наши изменения. Мы просто добавляли что-то в соответствии с новыми требованиями, и все как-то волшебным образом работало.

Давайте углубимся в идею разработки только с добавлением.

Здесь я представляю концепцию b-потоков, которые возникают из парадигмы под названием Поведенческое программирование (BP). Ниже показано, как они работают.

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

Давайте подробнее рассмотрим, как выполняются b-потоки с помощью системы поведенческого программирования. Ниже мы выполняем простой b-поток под названием «добавить горячее 3 раза при низком уровне воды»:

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

Давайте теперь добавим новую нитку B, которая «3 раза добавит холода при низком уровне воды»:

Б-образная нить остается нетронутой. Мы добавили новый справа. Пока ничего удивительного. Поведение этой программы (скомпрометированной этими двумя b-потоками) заключается в том, что всякий раз, когда происходит «waterLevelLow», мы добавляем 3 раза горячее, а затем 3 раза холодное.

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

В этом примере мы добавляем еще один b-поток под названием «стабильность» (мы снова просто добавляем его справа от существующих b-потоков):

Каждый раз, когда желтая линия меняет свое положение (когда b-поток переходит к следующему оператору yield), мы проверяем все операторы yield вдоль желтой линии по всем b-потокам. Поэтому желтая линия «пересекает» все нити b.

Если есть b-поток, который блокирует событие, другие b-потоки не могут его запустить и, следовательно, не могут продолжить свое выполнение.

В этом примере наш недавно добавленный b-поток блокирует addCold до тех пор, пока не сработает addHot. После чего происходит обратное.

На каждом шаге зеленые квадраты показывают «выбранное событие». Красные поля показывают, какое событие заблокировано.

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

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

Трассировка событий

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

Чтобы изменить систему в соответствии с новыми требованиями, нам разрешено добавлять только код, в частности b-потоки; аналогично тому, как мы это делали в предыдущем примере горячего / холодного.

Ниже приведен пример такой системы, как банкомат:

По мере того, как что-то происходит с банкоматом, мы видим его след. Например, когда пользователь вставляет карту, мы видим, что происходит «cardInserted». Когда учетная запись загружается, мы видим «loadingAccount» и т. Д.

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

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

В приведенном выше примере мы видим, где в трассировке событий должны отображаться события «showAd» и «adShown»; а именно перед «loadingAccount» (поскольку это то, что нам говорит новое требование).

Но как мы можем добавить эти два события в этот конкретный раздел трассировки событий без доступа к исходному коду? Что ж, используя b-нити и систему координации воображаемой желтой линии, мы можем сделать это вполне естественно:

По сути, мы проталкиваем эти два b-потока в систему ATM (опять же, не зная и не заботясь о том, как текущий код написан или реализован).

B-потоки координируют это новое требование, ожидая «cardIsValid», что в настоящее время происходит до «loadingAccount».

Как только происходит «cardIsValid», левый b-поток блокирует «loadingAccount» до «adShown». В то же время b-поток справа запрашивает «showAd», затем фактически показывает рекламу, вызывая функцию showAdvertiments(), и, наконец, запрашивает «adShown».

Этот последний запрос «adShown» позволяет первому b-потоку освободиться и, наконец, вызвать событие «loadingAccount».

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

Соответствие требованиям

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

Новое требование предлагает «не показывать рекламу, если пользователь корпоративный»:

И снова вот как мы это реализуем:

В b-потоке справа «loadingAccount» освобождается от блокировки при возникновении «isEnterprise». А слева мы просто блокируем showAd, когда происходит isEnterprise.

Направление этих двух b-потоков (или их установка наверху) в банкомат сгенерирует соответствующую трассировку событий.

Точка синхронизации

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

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

Мы привыкли вносить изменения, «смешивая» их в коде, что требует более глубокого понимания системы:

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

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

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

Заключение

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

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

  • Https://github.com/lmatteis/behavioral - реализация системы b-нитей на JavaScript, описанная выше.
  • Https://github.com/lmatteis/react-behavioral - реализация, которая может использоваться напрямую с React (очень экспериментальная)
  • Https://github.com/lmatteis/redux-behavioral - Redux - это система событий, в которой изменения описываются как следы. BP очень хорошо работает с этой системой и, следовательно, с этой библиотекой.

Наконец, есть и другие, менее технические статьи, которые я написал, и выступления, которые я дал на эту тему, которые могут вызвать ваш интерес:

Выводы

  • «Перемещение» этой воображаемой линии путем запроса, ожидания и блокировки событий позволяет осуществлять инкрементную разработку.
  • Соответствие требованиям и тому, как люди думают о поведении.
  • B-потоки являются «сложенными сверху» и не требуют привязки к конкретным компонентам, возможности подключения или заказа.

Минусы

  • Другой взгляд на программирование.
  • По иронии судьбы, более естественные способы программирования воспринимаются как неестественные из-за нашего прошлого обучения.
  • Масштабируется ли он с тысячами или миллионами одновременных b-потоков?
  • В настоящее время не используется многими людьми.
  • Отсутствие лучших практик, инструментов, сообщества и т. Д.