Refactoring C++ in OpenBMC: How an AI Agent Fought a 280-Line Function

The createSensors function from OpenBMC (the open-source firmware for server BMCs) crams 280 lines of code into 12 different tasks: it asynchronously fetches configuration from entity-manager on a single thread, searches for I2C drivers via sysfs, checks device presence on the bus, matches devices against configuration, creates and registers HwmonTempSensor objects on D-Bus, manages trigger thresholds, polling frequency, and power state, handles logic for multiple sensors on one device, and supports both a full scan at startup and targeted configuration updates. The AI agent proposed splitting the function into 7 concrete steps: extract a SensorScanner class with methods buildConfigMap, findSensorPaths, ensureDevice, processPath, and matchConfig; eliminate copy-paste in sensor creation (60 lines of duplication); move names into matching structs; separate scan logic from update logic; isolate side effects; and replace deep nesting with early continues. • **Refactoring result**: the nesting (6–7 levels of if/for) disappeared, functions shrank to 20–30 lines, and a clear structure emerged. But the git diff revealed something unexpected — more green code (additions) appeared than red (deletions). The signatures of 10 new methods, plus new structs and classes, outweighed the 60 lines of copy-paste that were saved. • **The architectural problem**: OpenBMC's microservices don't read configs directly — a central service called entity-manager does, publishing data to D-Bus in chunks. The sensor service has no way of knowing when the configuration is fully ready (there are no transactions), so it relies on a 1-second timeout (filterTimer.expires_after(std::chrono::seconds(1))). The firstScan, sensorsChanged, and activateOnly flags in createSensors exist for exactly this reason — to track partial updates.

AI-processed from Habr AI; edited by Hamidun News
Refactoring C++ in OpenBMC: How an AI Agent Fought a 280-Line Function
Source: Habr AI. Collage: Hamidun News.
Listen to article

What an AI agent can actually do during a refactor

A developer analyzed the HwmonTempSensor microservice from OpenBMC — the open-source firmware project for Linux-based server management modules. The entire service revolves around a single function, createSensors, which collects readings from hardware sensors (temperature, pressure, humidity, current, voltage) and publishes them over the D-Bus bus for other services, such as the web interface, to consume.

The 280-line function performs 12 isolated tasks: it fetches configuration from entity-manager asynchronously, but on a single thread; searches for I2C drivers via sysfs; checks device presence on the bus; matches configuration against the devices it found; creates, activates, or recreates HwmonTempSensor objects; registers them on D-Bus; manages trigger thresholds; manages polling frequency; manages power state; implements logic for multiple sensors on a single I2C device; supports a full initial scan at startup; and handles targeted updates when configuration changes on the fly.

Loop and conditional nesting reaches 6–7 levels deep (for → for/if → if → if → if). When the developer asked the AI to analyze the problems, the agent quickly delivered its diagnosis: an overloaded function, spaghetti logic mixing unrelated operations (searching, matching, creating, filtering), and complete untestability.

7 concrete refactoring steps

The agent proposed extracting a SensorScanner class, where each step becomes a private method of 20–30 lines:

Step 1: Create a struct SensorScanner with a constructor taking the parameters io, objectServer, sensors, dbusConnection, sensorsChanged, and activateOnly; expose a single public scan method as the sole entry point for processing configuration.

Step 2: Split the logic into private methods: buildConfigMap (parsing configuration), findSensorPaths (finding sysfs paths), ensureDevice (instantiating I2C devices), processPath (processing a single sysfs path in a loop), and matchConfig (matching a path against configuration).

Step 3: Eliminate duplication in sensor creation. In the original code, the logic for creating a HwmonTempSensor is duplicated twice — once for the main sensor and once for additional ones. Move it into a createOrActivateSensor method with parameters like hwmonFile, i2cDev, and others. This saved 60 lines of copy-paste.

Steps 4–7: Move the Name1..N identifiers into separate matching structs; separate full-scan logic from configuration-update logic; isolate all side effects into dedicated methods; replace deep if-nesting with early continues.

The paradox: the refactor made the code prettier, not shorter

After implementing the agent's suggestions, createSensors shrank to a single line:

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

The nesting disappeared, the methods became small, and the structures became explicit. This is exactly the kind of code interviewers ask for: C++20, templates, RAII, structured bindings.

But the git diff revealed something unexpected: there was noticeably more green code (new lines) than red (deleted lines). The overall codebase grew, even though the copy-paste had been found and removed. Why? 10 new methods require 10 signatures; new structs and classes were needed (to avoid passing a dozen parameters by reference); and additional definitions were needed to isolate the logic. The scary code simply became fashionably structured.

The real revelation: you can't refactor a single function in isolation

This is where the pain starts. The horror of this particular function isn't the mistake of a tired programmer, nor a random slip-up. It's a direct, inevitable consequence of poor architectural decisions made across the entire project.

OpenBMC is proud of its key features: runtime reconfigurable from D-Bus; isolated — each sensor type runs in its own daemon; async single-threaded — uses sdbusplus/asio bindings. It sounds modern and scalable.

The reality is different. Instead of a simple algorithm (read JSON config → find devices → do the work), the authors split an inherently indivisible process across microservices. Individual sensor services don't read the configuration JSON files themselves — a central service, entity-manager, does that. It parses ALL the configs and publishes the data to D-Bus... gradually! Every microservice is forced to wait until the part of the configuration it needs has been published.

The core problem: there's no guarantee of atomicity. The sensor service has no way of knowing when entity-manager has finished broadcasting data for every device in the system. Whoever designed D-Bus and the microservices concept for this embedded system apparently never heard of transactions.

The workaround: an eyeballed timeout. As entity-manager sends chunks of configuration, a filter keeps resetting a counter. A 1-second delay is set (filterTimer.expires_after(std::chrono::seconds(1))). If a full second passes with no new data arriving, the system assumes the configuration is complete and calls createSensors.

It's precisely this architectural shortcoming that turned createSensors into hell. It has to juggle the firstScan, sensorsChanged, and activateOnly flags on the fly, because data arrives in pieces and the system has to correctly remember its state and react to updates.

The takeaway: this is a Sisyphean task. You can't refactor a single C++ function, no matter how terrifying, and expect it to save the project. The code here isn't the root cause — it's just an implementation of abstract concepts that someone dragged into an embedded system without thinking through the details.

Story

Key Questions Answered

What an AI agent can actually do during a refactor?

A developer analyzed the HwmonTempSensor microservice from OpenBMC — the open-source firmware project for Linux-based server management modules. The entire service revolves around a single function, createSensors, which collects readings from hardware sensors (temperature, pressure, humidity, current, voltage) and publishes them over the D-Bus bus for other services, such as the web interface, to consume. The 280-line function performs 12 isolated tasks: it fetches configuration from entity-manager asynchronously, but on a single thread; searches for I2C drivers via sysfs;

What are the key details on 7 concrete refactoring steps?

The agent proposed extracting a SensorScanner class, where each step becomes a private method of 20–30 lines: Step 1: Create a struct SensorScanner with a constructor taking the parameters io, objectServer, sensors, dbusConnection, sensorsChanged, and activateOnly; expose a single public scan method as the sole entry point for processing configuration. Step 2: Split the logic into private methods: buildConfigMap (parsing configuration), findSensorPaths (finding sysfs paths), ensureDevice (instantiating I2C devices), processPath (processing a single sysfs path in a loop), an

What are the key details on The paradox: the refactor made the code prettier, not shorter?

After implementing the agent's suggestions, createSensors shrank to a single line: ```cpp void createSensors(...) { auto scanner = std::make_shared<SensorScanner>(...); scanner->run(); } ``` The nesting disappeared, the methods became small, and the structures became explicit. This is exactly the kind of code interviewers ask for: C++20, templates, RAII, structured bindings. But the git diff revealed something unexpected: there was noticeably more green code (new lines) than red (deleted lines). The overall codebase grew, even though the copy-paste had been found and remove

ZK
Zhemal Khamidun — CPO at Alpina Digital, product manager of AlpinaGPT. Building AI products since 2019. Hamidun News is a pet project that grew into a production aggregator with 6,000+ readers.
✦ Hands-On AI Engineering · Hamidun Academy

Build & Ship Production AI Agents Without Boilerplate Code

Start your free 72-hour trial at Hamidun Academy (academy.hamidun.com) — interactive lessons on Claude Code & Cursor, live Tech Radar, and production agent templates.

Start 72h Free Trial →Vibe-Coding Intensive
What do you think?
Loading comments…