Практика: Рефакторинг потока с созданием Subflow
Введение: Зачем и когда нужен рефакторинг?
В процессе разработки и эксплуатации систем автоматизации на Node-RED потоки (flows) со временем усложняются, разрастаются и начинают жить своей жизнью. То, что начиналось как простой и понятный сценарий, может превратиться в сложную, запутанную конструкцию. Рефакторинг в контексте Node-RED — это процесс изменения внутренней структуры потока без изменения его внешнего функционального поведения. Иными словами, вы улучшаете "внутренности" вашей логики, чтобы она стала чище, понятнее и надежнее, при этом для внешней системы (других потоков, устройств, пользователей) все продолжает работать как прежде.
Основная цель рефакторинга — борьба с так называемыми "спагетти-потоками". Это визуально хаотичные потоки с множеством пересекающихся связей, отсутствием четкой структуры и, что самое главное, с большим количеством дублирующейся логики.
> 📋 Ключевые понятия:
> * Рефакторинг: Дисциплинированный процесс улучшения существующего кода (или потока Node-RED) без изменения его наблюдаемого поведения.
> * Дублирование кода: Повторение идентичных или очень похожих участков логики в разных частях проекта. Это главный "враг" поддерживаемости.
> * Читаемость потока: Легкость, с которой другой инженер (или вы сами через месяц) может понять логику работы потока, просто взглянув на него.
Ключевыми признаками, указывающими на острую необходимость рефакторинга, являются:
В рамках этого урока мы проведем практический рефакторинг типичного "проблемного" потока. Исходная задача: система собирает данные о температуре с трех MQTT-датчиков (в гостиной, спальне и на улице), проверяет их на корректность, преобразует и отправляет тревожное уведомление, если температура превышает заданный порог. Изначально эта логика была реализована простым копированием, что привело к созданию громоздкого и неудобного в поддержке потока. Наша задача — исправить это с помощью Subflow.
---
Анализ исходного потока и выявление повторяющихся блоков
Перед тем как приступить к изменениям, необходимо тщательно проанализировать существующий поток и точно определить участки, требующие улучшения. Давайте рассмотрим наш исходный сценарий.
Исходный поток: Обработка трех датчиков температурыПредставьте себе поток, который выглядит примерно так:
// =================== Датчик: Гостиная =====================
[mqtt in: "sensors/living_room/temp"] -> [json] -> [function: "Validate Living Room"] -> [switch: "> 25°C"] -> [change: "Формирование уведомления"] -> [link out: "Уведомления"]
// =================== Датчик: Спальня =======================
[mqtt in: "sensors/bedroom/temp"] -> [json] -> [function: "Validate Bedroom"] -> [switch: "> 22°C"] -> [change: "Формирование уведомления"] -> [link out: "Уведомления"]
// =================== Датчик: Улица =========================
[mqtt in: "sensors/outside/temp"] -> [json] -> [function: "Validate Outside"] -> [switch: "> 30°C"] -> [change: "Формирование уведомления"] -> [link out: "Уведомления"]
// =================== Центральный обработчик ошибок ===================
[catch] -> [function: "Форматирование ошибки"] -> [link out: "Логирование ошибок"]
Входящие сообщения по MQTT имеют следующий формат:
{
"value": 26.5,
"unit": "C",
"ts": 1678886400000
}
Даже в этом упрощенном текстовом представлении проблема очевидна. Мы видим три практически идентичные цепочки нод. В каждой из них происходит:
> 💡 Подсказка: Хорошее практическое правило: если вы копируете-вставляете цепочку из более чем 3-4 нод, скорее всего, этот участок — кандидат на преобразование в Subflow (подпоток).
Недостатки текущего подхода:- Трудоемкая поддержка: Представьте, что вы решили изменить логику валидации — например, добавить проверку на слишком старую метку времени `ts`. Вам придется внести это изменение в три разные ноды `function`, увеличивая вероятность ошибки.
- Плохая масштабируемость: Что произойдет, когда вам понадобится добавить еще 10 датчиков? Вам придется 10 раз скопировать эту цепочку, превратив ваш поток в настоящего монстра.
- Низкая читаемость: Поток становится визуально загроможденным. Вместо того чтобы видеть логику верхнего уровня ("обработать 3 датчика"), мы вынуждены разбираться в деталях реализации для каждого из них.
Наша цель — избавиться от дублирования. Мы должны создать единый, переиспользуемый логический блок, который будет выполнять всю последовательность действий: валидацию, проверку порога и формирование уведомления. Этот блок должен быть настраиваемым, чтобы мы могли указать для него уникальные параметры, такие как имя датчика и пороговое значение температуры. Этот блок и есть Subflow.
---
Практикум: Создание Subflow из группы нод
Теперь перейдем от теории к практике. Процесс создания подпотока из существующей логики в Node-RED максимально автоматизирован и не требует сложных действий.
Возьмем одну из наших повторяющихся цепочек нод — например, ту, что обрабатывает данные из гостиной. Она состоит из нод `json`, `function`, `switch` и `change`.
Шаг 1: Выделение группы нод* Нода `json`
* Нода `function: "Validate Living Room"`
* Нода `switch: "> 25°C"`
* Нода `change: "Формирование уведомления"`
// Иллюстративное изображение, показывающее меню
Node-RED автоматически выполнит преобразование: выбранные вами ноды исчезнут с основного потока, а на их месте появится один-единственный прямоугольный блок с серым фоном. Это и есть ваш новый экземпляр Subflow. Все связи с другими нодами (в нашем случае, с `mqtt in` и `link out`) будут сохранены.
Шаг 3: Первичная настройка SubflowСразу после создания Subflow представляет собой "серый ящик" без названия. Чтобы сделать его понятным, необходимо выполнить первичную настройку.
Крайне важно правильно настроить входы и выходы Subflow. Node-RED делает это автоматически на основе исходных связей, но стоит проверить и сделать их более понятными.
- В редакторе шаблона Subflow вы увидите ноды `input` и `output`.
- Если ваша логика предполагает несколько путей (например, "успешная обработка" и "ошибка валидации"), вы можете добавить несколько выходов. Для этого в меню свойств Subflow (вкладка `Appearance`) можно увеличить количество выходов.
- Дважды кликните на ноду `output` и дайте ей осмысленное имя в поле `Name`, например:
* `Output 2`: `Ошибка обработки`
После этих действий ваш Subflow готов к дальнейшей настройке. Он уже инкапсулирует логику, но пока еще не является универсальным, так как пороговые значения и имена датчиков "зашиты" прямо в дочерние ноды. Следующий шаг — исправить это.
---
Конфигурация свойств Subflow для универсальности
На данный момент наш Subflow `Sensor Data Processor` все еще содержит "жестко закодированные" параметры из исходной цепочки (например, порог `25°C` и текст уведомления для "Гостиной"). Чтобы сделать его по-настоящему переиспользуемым, мы должны вынести эти параметры наружу, используя свойства Subflow, также известные как переменные окружения (Environment Variables).
Шаг 1: Переход в режим редактирования шаблона Subflow* `SENSOR_NAME`: Имя датчика, которое будет использоваться в текстах уведомлений и логов.
* `Name`: `SENSOR_NAME`
* `Label`: `Имя датчика`
* `Type`: `text`
* `Default Value` (опционально): `Неизвестный датчик`
* `TEMP_THRESHOLD_HIGH`: Верхний порог температуры для срабатывания тревоги.
* `Name`: `TEMP_THRESHOLD_HIGH`
* `Label`: `Верхний порог температуры, °C`
* `Type`: `number`
* `Default Value`: `99`
Теперь эти переменные будут доступны для настройки в каждом экземпляре Subflow.
Шаг 3: Использование переменных в дочерних нодахТеперь самое главное — заставить внутренние ноды Subflow использовать эти переменные. Это делается с помощью специального синтаксиса.
Модификация ноды `switch`
Ранее нода `switch` имела жестко заданное правило `> 25`. Теперь мы заменим его на использование переменной `TEMP_THRESHOLD_HIGH`.
Теперь порог срабатывания будет браться из настроек экземпляра Subflow.
Модификация ноды `change` или `function` для формирования уведомления
Ранее нода `change` формировала статичный текст. Заменим ее на ноду `function`, чтобы создать более информативное сообщение с использованием `SENSOR_NAME`.
// Получаем доступ к переменным окружения Subflow
const sensorName = env.get("SENSOR_NAME");
const highThreshold = env.get("TEMP_THRESHOLD_HIGH");
// Получаем текущее значение температуры из входящего сообщения
const currentTemp = msg.payload.value;
// Формируем информативное сообщение
const alertMessage = `Внимание! Температура с датчика "${sensorName}" превысила порог. Текущее значение: ${currentTemp}°C (Порог: ${highThreshold}°C).`;
// Формируем payload для отправки в систему уведомлений
msg.payload = {
"level": "CRITICAL",
"source": sensorName,
"message": alertMessage
};
// Как мы рассматривали в уроке по аудиту, формируем объект msg.audit
msg.audit = {
"level": "WARN",
"event": "ThresholdExceeded",
"entity_id": sensorName,
"details": `Temperature ${currentTemp}°C exceeded threshold of ${highThreshold}°C`
};
return msg;
> ℹ️ Информация: Доступ к переменным окружения Subflow внутри ноды `function` осуществляется через глобальный объект `env` и его метод `env.get("VAR_NAME")`. В других нодах, таких как `change` или `switch`, для этого используется тип `env` и синтаксис `${VAR_NAME}`.
Теперь наш Subflow `Sensor Data Processor` стал полноценным, универсальным и настраиваемым компонентом.
---
Замена дублирующихся блоков экземплярами Subflow
Настало время завершить рефакторинг и привести наш поток в порядок. Мы заменим все громоздкие дублирующиеся цепочки на аккуратные экземпляры нашего нового, универсального Subflow.
> ⚠️ Внимание: Перед удалением старой логики убедитесь, что вы сделали резервную копию потока (через меню `☰ > Export`). Также рекомендуется сначала отключить старые цепочки (выделить их и нажать `Ctrl+E` или через меню `Disable`), протестировать новые с Subflow, и только потом окончательно удалять. Это сбережет вам время и нервы в случае ошибки.
Шаг 1: Очистка основного потокаТеперь настроим каждый экземпляр Subflow индивидуально:
- Для датчика "Гостиная":
2. Перейдите на вкладку `Properties`.
3. Заполните поля, которые мы создали:
* `Имя датчика`: `Гостиная`
* `Верхний порог температуры, °C`: `25`
4. Нажмите `Done`.
- Для датчика "Спальня":
2. Заполните свойства:
* `Имя датчика`: `Спальня`
* `Верхний порог температуры, °C`: `22`
3. Нажмите `Done`.
- Для датчика "Улица":
2. Заполните свойства:
* `Имя датчика`: `Улица`
* `Верхний порог температуры, °C`: `30`
3. Нажмите `Done`.
Шаг 3: Визуальное сравнение "до" и "после"Теперь ваш поток преобразился. Сравним его состояние.
| Критерий | До рефакторинга | После рефакторинга |
| ----------------- | ----------------------------------------------------- | --------------------------------------------------------- |
| Количество нод | ~15 нод (3 5) | 6 нод (3 `mqtt in` + 3 * `Subflow`) |
| Читаемость | Низкая. Нужно анализировать каждую цепочку. | Высокая. Сразу видна общая архитектура. |
| Поддержка | Сложная. Изменение логики требует правок в 3+ местах. | Простая. Изменение логики вносится 1 раз в шаблоне Subflow. |
| Масштабируемость| Низкая. Добавление датчика — это копипаст 5 нод. | Высокая. Добавление датчика — это одна нода `Subflow`. |
Итоговый поток выглядит примерно так:// =================== Обработка датчиков (рефакторинг) =====================
[mqtt in: "sensors/living_room/temp"] -> [Subflow: Sensor Data Processor (Гостиная)] --+
|
[mqtt in: "sensors/bedroom/temp"] -> [Subflow: Sensor Data Processor (Спальня)] --+--> [link out: "Уведомления"]
|
[mqtt in: "sensors/outside/temp"] -> [Subflow: Sensor Data Processor (Улица)] --+
После выполнения этих шагов нажмите кнопку `Deploy`. Ваша система продолжит работать так же, как и раньше, но ее внутренняя организация стала несравнимо лучше. Вы успешно провели рефакторинг!
---
Резюме и рекомендации
В этом уроке мы на практике выполнили одну из важнейших процедур в жизненном цикле любого проекта автоматизации — рефакторинг. Мы преобразовали громоздкий, монолитный и повторяющийся поток в элегантную, структурированную и модульную систему.
Ключевые итоги проделанной работы:Использование Subflow — это не просто "красивость", а фундаментальный принцип построения сложных и надежных систем. Он позволяет мыслить на более высоком уровне модульности, оперируя готовыми логическими блоками, а не отдельными нодами.
Рекомендации по дальнейшему улучшению:- Создайте корпоративную библиотеку Subflows: По мере работы над проектами у вас будут появляться стандартные задачи: обработка данных с Modbus-счетчиков, управление диммерами DALI, контроль протечек, взаимодействие с панелями KNX. Оформляйте решения этих задач в виде универсальных Subflows и сохраняйте их. Эта библиотека станет вашим главным активом, ускоряющим разработку новых проектов в разы.
- Используйте вложенные Subflows: Для очень сложных задач один Subflow может содержать внутри себя другие, более простые Subflows. Это позволяет достигать еще больших уровней абстракции.
- Документируйте Subflows: Всегда подробно описывайте назначение Subflow, его входы, выходы и настраиваемые свойства прямо в его шаблоне (вкладка `Appearance > Info`). Это поможет вашим коллегам (и вам в будущем) быстро понять, как использовать компонент.
Что дальше?
Мы освоили мощный инструмент организации кода. В следующем уроке мы погрузимся в более сложные сценарии использования Subflows, в частности, рассмотрим, как управлять состоянием внутри подпотока, используя контекст, и как реализовать логику, которая требует сохранения данных между вызовами.