Рефакторинг C++ в OpenBMC: как ИИ боролся с 280-строчной функцией

Функция createSensors из проекта OpenBMC (открытой прошивке для BMC серверов) собирает 280 строк код и выполняет 12 разных задач: получает конфигурацию от entity-manager асинхронно в одном потоке, ищет I2C-драйверы через sysfs, проверяет присутствие устройств на шине, сопоставляет с конфигурацией, создает и регистрирует объекты HwmonTempSensor на D-Bus, управляет порогами срабатывания, частотой опроса, состоянием питания, логикой нескольких датчиков на одном устройстве, поддерживает полный скан при старте и точечное обновление конфигурации. ИИ-агент предложил разбить функцию на 7 конкретных шагов: выделить класс SensorScanner с методами buildConfigMap, findSensorPaths, ensureDevice, processPath и matchConfig; устранить копипаст при создании датчиков (60 строк дублирования); вынести имена в структуры матчинга; разделить логику скана от обновления; изолировать side-эффекты; заменить глубокую вложенность на ранние continue. • **Результат рефакторинга**: вложенность (6–7 уровней if/for) ушла, функции уменьшились до 20–30 строк, появилась явная структура. Но git-diff показал неожиданное — зелёного кода (новое) стало больше, чем красного (удалено). Дополнены сигнатуры 10 методов, новые структуры и классы, которые скомпенсировали сэкономленные 60 строк копипаста. • **Архитектурная беда**: микросервисы OpenBMC не читают конфиги напрямую — это делает центральный сервис entity-manager, выкладывающий данные на D-Bus порционно. Наш сервис не знает, когда конфигурация готова (нет транзакций), поэтому используется тайм-аут на 1 секунду (filterTimer.expires_after(std::chrono::seconds(1))). Флаги firstScan, sensorsChanged, activateOnly в createSensors существуют именно поэтому — отследить порционные обновления.

AI-обработка оригинала Habr AI; редакция Hamidun News
Рефакторинг C++ в OpenBMC: как ИИ боролся с 280-строчной функцией
Источник: Habr AI. Коллаж: Hamidun News.
Слушать статью

На что способен ИИ-агент при рефакторинге

Разработчик проанализировал микросервис HwmonTempSensor из OpenBMC — проекта открытой прошивки для модулей управления серверами на базе Linux. Весь сервис сосредоточен вокруг одной функции createSensors, которая собирает показания с аппаратных датчиков (температуры, давления, влажности, тока, напряжения) и публикует их через D-Bus шину для других сервисов, например веб-интерфейса.

Функция на 280 строк выполняет 12 изолированных задач: получает конфигурацию от entity-manager асинхронно, но в одном потоке; ищет драйверы I2C через sysfs; проверяет присутствие устройств на шине; сопоставляет конфигурацию с найденными устройствами; создает, активирует или пересоздает объекты HwmonTempSensor; регистрирует их на D-Bus; управляет порогами срабатывания; управляет частотой опроса; управляет состоянием питания; реализует логику работы нескольких датчиков на одном I2C-устройстве; поддерживает полный первичный скан при старте; обрабатывает точечное обновление при изменении конфигурации на лету.

Вложенность циклов и условных операторов достигает 6–7 уровней (for → for/if → if → if → if). Когда разработчик попросил ИИ проанализировать проблемы, агент быстро выдал диагноз: перегруженность функции, спагетти-логика из разнородных операций (поиск, матчинг, создание, фильтрация), полная нетестируемость.

7 конкретных шагов рефакторинга

Агент предложил выделить класс SensorScanner, где каждый шаг станет приватным методом на 20–30 строк:

Шаг 1: создать struct SensorScanner с конструктором, принимающим параметры io, objectServer, sensors, dbusConnection, sensorsChanged, activateOnly; один публичный метод scan как единственная входная точка обработки конфигурации.

Шаг 2: разбить логику на приватные методы: buildConfigMap (парсинг конфигурации), findSensorPaths (поиск sysfs-путей), ensureDevice (инстанцирование I2C-устройств), processPath (обработка одного sysfs-пути в цикле), matchConfig (сопоставление пути с конфигурацией).

Шаг 3: устранить дублирование при создании датчиков. В оригинальном коде логика создания HwmonTempSensor продублирована дважды (для основного датчика и дополнительных). Вынести в метод createOrActivateSensor с параметрами hwmonFile, i2cDev и другие. Это сэкономило 60 строк копипаста.

Шаги 4–7: вынести имена Name1..N в отдельные структуры матчинга; разделить логику полного скана от логики обновления конфигурации; изолировать все side-эффекты в отдельные методы; заменить глубокую вложенность if на ранние continue.

Парадокс результата: рефакторинг сделал код красивей, но не короче

После реализации предложений функция createSensors стала одной строкой:

```cpp void createSensors(...) { auto scanner = std::make_shared<SensorScanner>(...); scanner->run(); } ```

Vложенность ушла, методы стали маленькими, структуры явными. На собеседованиях требуют именно такой код: C++20, шаблоны, RAII, Structured binding.

Но git-diff выдал неожиданное: зелёного кода (новые строки) стало заметно больше, чем красного (удалено). Общий объем кода вырос, хотя копипаст был найден и выпилен. Причина? 10 новых методов требуют 10 сигнатур; новые структуры и классы (чтобы не передавать под десяток параметров по ссылке); определения для изоляции логики. Страшный код просто стал по-модному структурированным.

Главное откровение: нельзя отрефакторить одну функцию

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

OpenBMC гордится Key Features: Runtime re-configurable from d-bus; Isolated: each sensor type is isolated into its own daemon; Async single-threaded: uses sdbusplus/asio bindings. Звучит современно и масштабируемо.

Реальность иная. Вместо простого алгоритма (читай JSON-конфиг → найди устройства → работай) авторы разделили неделимый процесс на микросервисы. Отдельные сервисы датчиков не читают конфигурационные JSON-файлы — это делает центральный сервис entity-manager. Он парсит ВСЕ конфиги и выкладывает данные на D-Bus... постепенно! Все микросервисы вынуждены ждать, пока их нужная часть конфигурации будет опубликована.

Главная проблема: нет гарантии атомарности. Наш сервис не знает, когда entity-manager закончит вещать данные для всех устройств в системе. Разработчики D-Bus и микросервисных концепций в embedded никогда не слышали про транзакции.

Решение: тайм-аут на глаз. Пока entity-manager шлет куски конфига, фильтр постоянно сбрасывает счетчик. Выставляется задержка в 1 секунду (filterTimer.expires_after(std::chrono::seconds(1))). Если целую секунду новых данных не прилетало — считаем, что конфигурация готова, вызываем createSensors.

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

Вывод: это Сизифов труд. Нельзя сделать рефакторинг одной, сколь угодно страшной С++ функции и ожидать, что это спасет проект. Код здесь — не первопричина, это просто реализация абстрактных концепций, которые кто-то притащил в embedded, не подумав о деталях.

Сюжет

Вопросы и ответы по материалу

Что известно по теме: на что способен ИИ-агент при рефакторинге?

Разработчик проанализировал микросервис HwmonTempSensor из OpenBMC — проекта открытой прошивки для модулей управления серверами на базе Linux. Весь сервис сосредоточен вокруг одной функции createSensors, которая собирает показания с аппаратных датчиков (температуры, давления, влажности, тока, напряжения) и публикует их через D-Bus шину для других сервисов, например веб-интерфейса. Функция на 280 строк выполняет 12 изолированных задач: получает конфигурацию от entity-manager асинхронно, но в одном потоке; ищет драйверы I2C через sysfs; проверяет присутствие устройств на шине

Что известно по теме: 7 конкретных шагов рефакторинга?

Агент предложил выделить класс SensorScanner, где каждый шаг станет приватным методом на 20–30 строк: Шаг 1: создать struct SensorScanner с конструктором, принимающим параметры io, objectServer, sensors, dbusConnection, sensorsChanged, activateOnly; один публичный метод scan как единственная входная точка обработки конфигурации. Шаг 2: разбить логику на приватные методы: buildConfigMap (парсинг конфигурации), findSensorPaths (поиск sysfs-путей), ensureDevice (инстанцирование I2C-устройств), processPath (обработка одного sysfs-пути в цикле), matchConfig (сопоставление пути с

Что известно по теме: парадокс результата: рефакторинг сделал код красивей, но не короче?

После реализации предложений функция createSensors стала одной строкой: ```cpp void createSensors(...) { auto scanner = std::make_shared<SensorScanner>(...); scanner->run(); } ``` Vложенность ушла, методы стали маленькими, структуры явными. На собеседованиях требуют именно такой код: C++20, шаблоны, RAII, Structured binding. Но git-diff выдал неожиданное: зелёного кода (новые строки) стало заметно больше, чем красного (удалено). Общий объем кода вырос, хотя копипаст был найден и выпилен. Причина? 10 новых методов требуют 10 сигнатур; новые структуры и классы (чтобы не перед

ЖХ
Жемал Хамидун — CPO Alpina Digital, продакт-менеджер AlpinaGPT. Делает AI-продукты с 2019 года. Hamidun News — pet project, который вырос в production-агрегатор с 6000+ читателей.
✦ Практика · Hamidun Academy

Соберите рабочего ИИ-агента или сервис своими руками без ручного кода

В Академии Жемала Хамидуна (academy.hamidun.com) открыт бесплатный 72-часовой тест-драйв: интерактивные скетч-уроки по Claude Code и Cursor, живой Tech Radar и готовые шаблоны агентов.

Открыть 72ч бесплатный доступ →Интенсив по Вайб-кодингу
Что вы думаете?
Загружаем комментарии…