ГлавнаяАкадемияNode-RED: установка, flows, msg/JSON, отладка → Практика: Рефакторинг потока с созданием Subflow

Практика: Рефакторинг потока с созданием Subflow

Урок 4 · Node-RED: установка, flows, msg/JSON, отладка · 30 мин · theory

Введение: Зачем и когда нужен рефакторинг?

В процессе разработки и эксплуатации систем автоматизации на Node-RED потоки (flows) со временем усложняются, разрастаются и начинают жить своей жизнью. То, что начиналось как простой и понятный сценарий, может превратиться в сложную, запутанную конструкцию. Рефакторинг в контексте Node-RED — это процесс изменения внутренней структуры потока без изменения его внешнего функционального поведения. Иными словами, вы улучшаете "внутренности" вашей логики, чтобы она стала чище, понятнее и надежнее, при этом для внешней системы (других потоков, устройств, пользователей) все продолжает работать как прежде.

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

> 📋 Ключевые понятия:

> * Рефакторинг: Дисциплинированный процесс улучшения существующего кода (или потока Node-RED) без изменения его наблюдаемого поведения.

> * Дублирование кода: Повторение идентичных или очень похожих участков логики в разных частях проекта. Это главный "враг" поддерживаемости.

> * Читаемость потока: Легкость, с которой другой инженер (или вы сами через месяц) может понять логику работы потока, просто взглянув на него.

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

  • Дублирование логики: Вы видите абсолютно одинаковые цепочки из 3–5 и более нод в разных местах вашего потока или проекта. Например, одинаковый способ обработки данных с однотипных датчиков.
  • Избыточная сложность: Один-единственный поток разросся до такой степени, что не помещается на одном экране. Чтобы понять, как он работает, приходится долго прокручивать и отслеживать связи.
  • Сложность внесения изменений: Вам нужно внести простое изменение (например, поменять порог срабатывания для всех датчиков), и для этого приходится редактировать 5, 10 или 20 разных нод. Это трудозатратно и повышает риск человеческой ошибки.
  • Трудности в отладке: Когда в "спагетти-потоке" возникает ошибка, найти ее источник становится настоящим квестом.
  • В рамках этого урока мы проведем практический рефакторинг типичного "проблемного" потока. Исходная задача: система собирает данные о температуре с трех 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

    }

    Даже в этом упрощенном текстовом представлении проблема очевидна. Мы видим три практически идентичные цепочки нод. В каждой из них происходит:

  • Прием MQTT сообщения (нода `mqtt in`).
  • Парсинг JSON (нода `json`).
  • Валидация данных (нода `function`). Код внутри этих функций может быть идентичным, проверяя, что `msg.payload.value` является числом в допустимом диапазоне (например, от -50 до +50).
  • Проверка порога (нода `switch`). Проверяется, превышает ли `msg.payload.value` заданное пороговое значение.
  • Формирование уведомления (нода `change`). Задается текст уведомления.
  • Отправка на обработку (нода `link out`).
  • > 💡 Подсказка: Хорошее практическое правило: если вы копируете-вставляете цепочку из более чем 3-4 нод, скорее всего, этот участок — кандидат на преобразование в Subflow (подпоток).

    Недостатки текущего подхода: Формулировка задачи рефакторинга:

    Наша цель — избавиться от дублирования. Мы должны создать единый, переиспользуемый логический блок, который будет выполнять всю последовательность действий: валидацию, проверку порога и формирование уведомления. Этот блок должен быть настраиваемым, чтобы мы могли указать для него уникальные параметры, такие как имя датчика и пороговое значение температуры. Этот блок и есть Subflow.

    ---

    Практикум: Создание Subflow из группы нод

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

    Возьмем одну из наших повторяющихся цепочек нод — например, ту, что обрабатывает данные из гостиной. Она состоит из нод `json`, `function`, `switch` и `change`.

    Шаг 1: Выделение группы нод
  • Зажмите клавишу `Shift` на клавиатуре.
  • Не отпуская `Shift`, последовательно кликните левой кнопкой мыши на все ноды, которые вы хотите включить в подпоток:
  • * Нода `json`

    * Нода `function: "Validate Living Room"`

    * Нода `switch: "> 25°C"`

    * Нода `change: "Формирование уведомления"`

  • После выделения все выбранные ноды будут обведены оранжевой пунктирной линией.
  • Шаг 2: Использование функции "Selection to Subflow"
  • Найдите в правом верхнем углу интерфейса Node-RED кнопку меню "гамбургер" (☰).
  • Нажмите на нее, чтобы открыть главное меню.
  • Выберите пункт `Subflows`, а затем в выпадающем меню — `Selection to Subflow`.
  • // Иллюстративное изображение, показывающее меню

    Node-RED автоматически выполнит преобразование: выбранные вами ноды исчезнут с основного потока, а на их месте появится один-единственный прямоугольный блок с серым фоном. Это и есть ваш новый экземпляр Subflow. Все связи с другими нодами (в нашем случае, с `mqtt in` и `link out`) будут сохранены.

    Шаг 3: Первичная настройка Subflow

    Сразу после создания Subflow представляет собой "серый ящик" без названия. Чтобы сделать его понятным, необходимо выполнить первичную настройку.

  • Дважды кликните на новый блок Subflow. Откроется диалоговое окно его свойств.
  • Вверху вы увидите кнопку `Edit Subflow Template`. Нажмите на нее.
  • Вы перейдете в редактор шаблона Subflow. Здесь вы видите те самые ноды, которые мы выделили ранее.
  • Вверху редактора шаблона найдите поле `Name`. Задайте осмысленное имя для вашего подпотока, например, `Sensor Data Processor`.
  • Вы также можете изменить иконку и цвет для лучшей визуальной идентификации.
  • Ша- 4: Определение входов и выходов

    Крайне важно правильно настроить входы и выходы Subflow. Node-RED делает это автоматически на основе исходных связей, но стоит проверить и сделать их более понятными.

    * `Output 1`: `Тревожное уведомление`

    * `Output 2`: `Ошибка обработки`

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

    ---

    Конфигурация свойств Subflow для универсальности

    На данный момент наш Subflow `Sensor Data Processor` все еще содержит "жестко закодированные" параметры из исходной цепочки (например, порог `25°C` и текст уведомления для "Гостиной"). Чтобы сделать его по-настоящему переиспользуемым, мы должны вынести эти параметры наружу, используя свойства Subflow, также известные как переменные окружения (Environment Variables).

    Шаг 1: Переход в режим редактирования шаблона Subflow
  • В основном потоке дважды кликните на экземпляр вашего Subflow `Sensor Data Processor`.
  • В открывшемся окне нажмите кнопку `Edit Subflow Template`.
  • Шаг 2: Создание переменных окружения
  • В редакторе шаблона Subflow найдите вверху кнопку `Properties`.
  • Нажмите `+add property`, чтобы добавить новую переменную.
  • Создадим две переменные, которые сделают наш компонент гибким:
  • * `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`.

  • Откройте ноду `switch` внутри Subflow.
  • В правиле проверки измените тип второго операнда с `number` на `env` (Environment Variable).
  • В поле для значения укажите имя нашей переменной: `TEMP_THRESHOLD_HIGH`.
  • Теперь порог срабатывания будет браться из настроек экземпляра Subflow.

    Модификация ноды `change` или `function` для формирования уведомления

    Ранее нода `change` формировала статичный текст. Заменим ее на ноду `function`, чтобы создать более информативное сообщение с использованием `SENSOR_NAME`.

  • Удалите старую ноду `change`.
  • Добавьте на ее место ноду `function`.
  • Напишите в ней следующий код:
  • // Получаем доступ к переменным окружения 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: Очистка основного потока
  • Удалите все старые, дублирующиеся цепочки нод, которые мы ранее анализировали (`json`, `function`, `switch`, `change`).
  • Оставьте только ноды `mqtt in` для каждого датчика и общие обработчики (`link out` для уведомлений, `catch` для ошибок).
  • Шаг 2: Размещение и настройка экземпляров Subflow
  • На панели палитры слева, в самом верху, вы найдете ваш новый Subflow `Sensor Data Processor`.
  • Перетащите его на холст три раза — по одному для каждого датчика.
  • Соедините выход каждой ноды `mqtt in` со входом соответствующего экземпляра Subflow. Выход `Тревожное уведомление` каждого Subflow соедините с нодой `link out: "Уведомления"`.
  • Теперь настроим каждый экземпляр Subflow индивидуально:

    1. Дважды кликните на первый экземпляр Subflow.

    2. Перейдите на вкладку `Properties`.

    3. Заполните поля, которые мы создали:

    * `Имя датчика`: `Гостиная`

    * `Верхний порог температуры, °C`: `25`

    4. Нажмите `Done`.

    1. Дважды кликните на второй экземпляр Subflow.

    2. Заполните свойства:

    * `Имя датчика`: `Спальня`

    * `Верхний порог температуры, °C`: `22`

    3. Нажмите `Done`.

    1. Дважды кликните на третий экземпляр Subflow.

    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`. Ваша система продолжит работать так же, как и раньше, но ее внутренняя организация стала несравнимо лучше. Вы успешно провели рефакторинг!

    ---

    Резюме и рекомендации

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

    Ключевые итоги проделанной работы:
  • Мы идентифицировали "код-дубликат" и осознали его негативное влияние на поддержку и масштабируемость проекта.
  • Мы использовали встроенный инструмент Node-RED для преобразования повторяющейся логики в Subflow (подпоток).
  • Мы применили механизм свойств (переменных окружения), чтобы превратить специфический блок логики в универсальный и настраиваемый компонент. Этот процесс называется абстракцией.
  • Мы заменили старую логику на экземпляры нового Subflow, кардинально улучшив читаемость и поддерживаемость нашего потока.
  • Использование Subflow — это не просто "красивость", а фундаментальный принцип построения сложных и надежных систем. Он позволяет мыслить на более высоком уровне модульности, оперируя готовыми логическими блоками, а не отдельными нодами.

    Рекомендации по дальнейшему улучшению:

    Что дальше?

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