Як полегшити підтримку коду, керованого подіями?


16

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

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

Це може призвести до тонких і важких проблем налагодження, коли хтось додає нові обробники подій.

Редагувати з коментарів: Навіть якщо на практиці існують деякі добрі практики, як, наприклад, шина подій із широким додатком та обробники, які передають бізнес в іншу частину програми, є момент, коли код починає важко читати, тому що існує багато зареєстровані обробники з багатьох місць (особливо це стосується автобуса).

Потім діаграма послідовностей починає переглядати складні, витрачається час на з'ясування того, що відбувається, і сеанс налагодження стає безладним (точка розриву у менеджера обробників під час ітерації на обробниках, особливо радісно з асинхронним обробником та деяка фільтрація над ним).

//////////////
Приклад

У мене є сервіс, який витягує деякі дані на сервер. У клієнта у нас є базовий компонент, який викликає цю послугу за допомогою зворотного дзвінка. Щоб надати точку розширення користувачам компонента та уникнути зв’язку між різними компонентами, ми запускаємо деякі події: один перед надсиланням запиту, один, коли відповідь повертається, а інший у разі відмови. У нас є основний набір обробників, які попередньо зареєстровані, які забезпечують поведінку компонента за замовчуванням.

Тепер користувачі компонента (і ми також є користувачем компонента) можуть додати деякі обробники, щоб здійснити певну зміну поведінки (змінити запит, журнали, аналіз даних, фільтрацію даних, масаж даних, фантазійну анімацію інтерфейсу, ланцюг кількох послідовних запитів , що б там не було). Тому деякі обробники повинні бути виконані до / після деяких інших, і вони зареєстровані з безлічі різних точок входу в програму.

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

Цей дизайн з'явився тому, що використання спадщини починало бути повним безладом. Система заходів використовується у вигляді композиції, де ви ще не знаєте, якими будуть ваші композити.

Кінець прикладу
//////////////

Тож мені цікаво, як інші люди справляються з таким кодом. Як при його написанні, так і при читанні.

Чи є у вас якісь методи чи інструменти, які дозволять вам писати та підтримувати такий код без особливого болю?


Ти маєш на увазі, окрім того, як відтворювати логіку від обробників подій?
Теластин

Документуйте, що далі.

@Telastyn, я не впевнений, що я повністю розумію, що ви маєте на увазі під "окрім рефакторингу логіки з обробників подій".
Гійом

@Thorbjoern: дивіться моє оновлення.
Гійом

3
Здається, ви не використовуєте правильний інструмент для роботи? Я маю на увазі, якщо порядок має значення, тоді ви не повинні використовувати звичайні події для цих частин програми в першу чергу. В основному, якщо в типовій шинній системі є обмеження щодо порядку подій, це вже не типова система шин, але ви все одно використовуєте її як таку. Що означає, що вам потрібно вирішити це питання, додавши додаткову логіку для обробки замовлення. Принаймні, якщо я правильно зрозумів вашу проблему ..
stijn

Відповіді:


7

Я виявив, що обробка подій за допомогою стека внутрішніх подій (точніше, черги LIFO з довільним видаленням) значно спрощує програмування, кероване подіями. Це дозволяє розділити обробку "зовнішньої події" на кілька менших "внутрішніх подій", з чітко визначеним станом між ними. Для отримання додаткової інформації дивіться мою відповідь на це запитання .

Тут я представляю простий приклад, який вирішується за цією схемою.

Припустимо, ви використовуєте об'єкт A для виконання певної послуги, і ви надаєте йому зворотний дзвінок, щоб повідомити вас про його завершення. Однак A такий, що після виклику зворотного дзвінка може знадобитися зробити ще якусь роботу. Небезпека виникає, коли в межах цього зворотного дзвінка ви вирішите, що вам більше не потрібен A, і ви так чи інакше його знищуєте. Але вам дзвонять з A - якщо A, після повернення дзвінка, не зможе безпечно зрозуміти, що він знищений, може призвести до краху при спробі виконати решту робіт.

ПРИМІТКА: Це правда, що ви могли зробити "знищення" іншим способом, як, наприклад, зменшення кількості знижок, але це просто призводить до проміжних станів, додаткових кодів та помилок при їх обробці; краще для A просто перестати працювати повністю після того, як вам це більше не потрібно, ніж продовжувати в деякому проміжному стані.

У моєму шаблоні A просто запланував би подальшу роботу, яку він повинен виконати, натиснувши внутрішню подію (завдання) у чергу LIFO циклу події, потім перейде до виклику зворотного дзвінка та негайно повернеться до циклу подій . Цей фрагмент коду він більше не становить небезпеки, оскільки A просто повертається. Тепер, якщо зворотний виклик не знищує A, надіслане завдання в кінцевому підсумку буде виконано циклом подій, щоб виконати свою додаткову роботу (після того, як буде виконано зворотний виклик, і всі його штовхані завдання, рекурсивно). З іншого боку, якщо зворотний виклик знищує A, деструктор A або функція deinit можуть видалити висунуте завдання зі стека подій, що неявно перешкоджає виконанню висунутого завдання.


7

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


Так, це корисна порада. Кількість виготовлених журналів може бути страхітливою (я пам'ятаю, що було важко знайти причину циклу при обробці події дуже складної форми.)
Гійом

Можливо, одне рішення - занести цікаву інформацію в окремий файл журналу. Крім того, ви можете призначити UUID кожній події, щоб ви могли відстежувати кожну подію легше. Ви також можете написати певний інструмент звітності, який дозволяє витягувати конкретну інформацію з файлів журналу. (Або в якості альтернативи, використовуйте перемикачі в коді для реєстрації різної інформації в різні файли журналу).
Джорджіо

3

Тож мені цікаво, як інші люди справляються з таким кодом. Як при його написанні, так і при читанні.

Модель програмування, керованого подіями, певною мірою спрощує кодування. Він, ймовірно, розвинувся як заміна великих операторів Select (або випадку), що використовуються в старих мовах, і набув популярності в ранніх середовищах візуального розвитку, таких як VB 3 (Не цитуйте мене в історії, я не перевіряв)!

Модель стає болем, якщо послідовність подій має значення і коли 1 діла ділиться на багато подій. Цей стиль процесу порушує переваги такого підходу. За будь-яку ціну спробуйте зробити код дії інкапсульованим у відповідній події і не піднімайте події зсередини подій. Це потім стає набагато гіршим, ніж спагетті, що випливає з GoTo.

Іноді розробники прагнуть надати функціональний інтерфейс GUI, який вимагає такої залежності від подій, але насправді немає реальної альтернативи, яка значно простіша.

Підсумок тут полягає в тому, що методика непогана, якщо її використовувати розумно.


Чи є їхній альтернативний дизайн, щоб уникнути "гіршого, ніж спагетті"?
Гійом

Я не впевнений, що існує простий спосіб без спрощення очікуваної поведінки графічного інтерфейсу, але якщо ви перерахуєте всі ваші взаємодії користувачів з вікном / сторінкою і для кожного визначите, що саме буде робити ваша програма, ви можете почати групувати код в одне місце і направляти кілька подій до групи допоміжних методів, які відповідають за реальну обробку запиту. Також може допомогти відокремлення коду, який обслуговує лише графічний інтерфейс, від коду, який виконує обробку заднього кінця.
NoChance

Необхідність опублікувати це повідомлення виникла з рефакторингу, коли я перейшов із пекла спадщини до чогось, що ґрунтується на події. З деякого моменту це дійсно краще, але в інших - це зовсім погано ... Оскільки у мене вже виникли проблеми з «подіями дикими» в деяких GUI, мені цікаво, що можна зробити для покращення обслуговування таких порізаний код події.
Гійом

Програмування на основі подій набагато старше, ніж VB; він був присутній у графічному інтерфейсі SunTools, і до цього я, здається, пам’ятаю, що він був вбудований у мову Simula.
кевін клайн

@Guillaume, я думаю, ви переробили сервіс. Мій вище опис був насправді заснований на подіях графічного інтерфейсу. Вам (справді) потрібен такий тип обробки?
NoChance

3

Мені хотілося оновити цю відповідь, оскільки я отримав декілька моментів "еврики", після того як після "сплющення" та "сплющення" протікає контроль і сформулював кілька нових думок з цього приводу.

Складні побічні ефекти проти складних контрольних потоків

Я виявив, що мій мозок може переносити складні побічні ефекти або складні графічні потоки управління, як зазвичай ви виявляєте при обробці подій, але не комбо обох.

Я можу легко міркувати про код, який викликає 4 різних побічних ефекту, якщо вони застосовуються з дуже простим контрольним потоком, як у послідовному forциклі. Мій мозок може терпіти послідовний цикл, який змінює розмір та перетворює елементи, оживляє їх, перемальовує їх та оновлює якийсь допоміжний статус. Це досить просто зрозуміти.

Я також можу зрозуміти складний потік управління, як ви можете отримати з каскадними подіями або переходом складної структури даних, схожих на графік, якщо в процесі відбувається просто дуже простий побічний ефект, коли порядок не має значення ні найменшого біта, як елементи маркування оброблятись відкладеним способом у простому послідовному циклі.

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

Складні контрольні потоки, як правило, ускладнюють міркування про те, коли / де відбуватимуться речі. Це стає справді головним болем, якщо ці складні контрольні потоки викликають складну комбінацію побічних ефектів, коли важливо зрозуміти, коли / де відбуваються речі, як побічні ефекти, які мають певну залежність порядку, де одна річ повинна статися до наступної.

Спростіть контрольний потік або побічні ефекти

Отже, що ви робите, коли стикаєтеся з вищезазначеним сценарієм, який так важко осмислити? Стратегія полягає у спрощенні контрольного потоку або побічних ефектів.

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

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

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


2

Те, що для мене спрацювало, - це зробити кожну подію самостійно, без посилання на інші події. Якщо вони приходять в asynchroniously, ви не маєте послідовність, таким чином намагаючись з'ясувати , що відбувається , в якому порядку НЕ має сенсу, крім того , що неможливо.

У кінцевому підсумку ви маєте купу структур даних, які читаються та змінюються, створюються та видаляються десятками потоків у певному порядку. Вам потрібно зробити дуже правильне багатопотокове програмування, що непросто. Ви також повинні подумати багатопотоково, як у "З цією подією я збираюся переглянути дані, які я маю в певний момент, без огляду на те, що це було мікросекундою раніше, без огляду на те, що щойно змінило його" , і не зважаючи на те, що зроблять 100 ниток, які чекають на мене, щоб випустити замок. Тоді я внесу свої зміни, виходячи з цього навіть того, що я бачу. Тоді я закінчую ".

Одне, що мені здається, - це сканування певної колекції та переконання, що і посилання, і сама колекція (якщо не безпечна нитка) заблоковані правильно та синхронізовані правильно з іншими даними. У міру збільшення подій ця робота зростає. Але якби я відстежував зв’язки між подіями, ця руйна робота зростала б набагато швидше. Плюс іноді багато блокування можна виділити власним методом, фактично спростивши код.

Трактувати кожну нитку як абсолютно незалежну сутність є важко (через багатоядерну багатопотоковість), але можливо. "Масштабований" може бути словом, яке я шукаю. Удвічі більше подій займає лише вдвічі більше роботи, а може бути і лише в 1,5 рази більше. Спроба координувати більше асинхронних подій швидко поховає вас.


Я оновив своє запитання. Ви абсолютно праві щодо послідовності та асинхронізації матеріалів. Як би ви поводилися з випадком, коли подія X повинна виконуватися після того, як всі інші події будуть оброблені? Схоже, мені потрібно створити більш складний менеджер обробників, але я також хочу уникати надмірної складності для користувачів.
Гійом

У наборі даних є перемикач і деякі поля, які встановлюються під час зчитування події X. Коли всі інші обробляються, ви перевіряєте вимикач і знаєте, що ви повинні обробити X і маєте дані. Фактично, комутатор і дані повинні стояти самостійно. Коли ви встановите, ви повинні подумати: "Я повинен виконати цю роботу", а не "Я повинен здати X." Наступна проблема: як ви знаєте, що проводяться події? а що робити, якщо ви отримаєте 2 або більше подій X? Найгірше, ви можете запустити циклічне обслуговування, що перевіряє ситуацію і може діяти за власною ініціативою. (Немає входу протягом 3 секунд? Встановлено перемикач X? Потім запустіть код відключення.
RalphChapin

2

Здається, ви шукаєте Державні машини та заходи, керовані подіями .

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

Ось короткий огляд впровадження державної машини. Робочий процес роботи машини складається з станів. Кожен стан складається з одного або декількох обробників подій. Кожен обробник подій повинен містити затримку або IEventActivity як першу діяльність. Кожен обробник подій також може містити активність SetStateActivity, яка використовується для переходу з одного стану в інший.

Кожен робочий процес машини машини має два властивості: InitialStateName та CompletedStateName. Коли створюється екземпляр робочого процесу стан машини, він ставиться у властивість InitialStateName. Коли стан машина досягає властивості CompletedStateName, він закінчує виконання.


2
Хоча це теоретично може відповісти на питання, бажано було б сюди включити істотні частини відповіді та надати посилання для довідки.
Томас Оуенс

Але якщо у вас до кожної держави приєднано десятки обробників, вам буде не так добре, намагаючись зрозуміти, що відбувається ... І кожен компонент, що базується на подіях, не може бути описаний як стан машини.
Гійом

1

Код, керований подіями, не є справжньою проблемою. Я фактично не маю жодних проблем з дотриманням логіки у навіть керованому коді, де зворотний виклик чітко визначений або використовуються поточні зворотні дзвінки. Наприклад зворотній зв'язок стилю генератора в Торнадо дуже легко простежити.

Насправді важко налагодити - це динамічно генеровані функціональні виклики. Шаблон (анти?), Який я б назвав "Фабрика зворотного виклику" з пекла. Однак заводи цього виду функцій однаково важко відладкувати в традиційному потоці.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.