Refactoring Embedded System Software with AI
- Yanbing Li
- Mar 15
- 5 min read
After completing the Coursera course Team Software Engineering with AI, which emphasized collaborative Python development with AI-assisted workflows, I began mapping those practices to area I devoted my life’s prime time in: embedded software for optical transport systems. While Python dominated the course examples, the core ideas of AI-assisted pair programming, automated testing, clear documentation, and dependency streamlining translate surprisingly well to embedded C/C++ environments, especially in the context of legacy system modernization.
This blog explores how AI can help transform embedded "bristle code" into scalable, robust software systems that empower precise, localized testing and change management, ultimately elevating quality, reduce time-to-market for carrier-grade optical products.
The Bristle Code Problem in Optical Platforms
Embedded systems in optical transport are often built on top of legacy C/C++ bases that have evolved over decades:
Hardware drivers tightly coupled with business logic
Hardcoded state transitions
Sparse documentation
Minimal unit test infrastructure
Manual integration-heavy QA processes
This makes change expensive and quality assurance holistic by necessity. Every bug fix or new feature potentially destabilizes the whole system, requiring broad system validation which is both expensive and time-consuming – one of the most frequently happenings contributing project schedule delays or unpredictable “running away train” GA dates.
With AI-assisted refactoring, we can reshape that landscape.
AI-Assisted Pair Coding: A Design Force Multiplier
The course's focus on AI as a development partner revealed how these tools can:
Deconstruct legacy logic into clean abstractions
Suggest modular design patterns
Scaffold error handling, boundary checks, and unit test shells
Generate interface documentation from context
When applied to embedded C/C++ in optical systems, this means turning fragile runtime behavior into intentionally designed and testable code.
From Bristle Monoliths to Modular Systems: AI as a Refactoring Catalyst
In embedded optical platforms, legacy code often takes the form of monolithic C/C++ systems, where hardware drivers, control logic, state machines, and protocol handlers are all tangled together. This "bristle code" is hard to reason about, harder to test, and risky to change.
AI-assisted development opens the door for transformational refactoring, turning fragile, centralized logic into robust, secure, and scalable modular architecture.
Refactoring with Directional Interfaces
We can now think about embedded modularity not just vertically (north/south) but multi-directionally:
Direction | Purpose and AI Opportunity |
Northbound | Interfaces to controllers (e.g., NETCONF/YANG, CLI, SNMP) can be made self-documenting with AI-generated stubs, test cases, and coverage validation. |
Southbound | HAL/SAI/TAI adapters benefit from AI-generated interface scaffolds, abstraction layer validation, and register-mapping logic. |
Eastbound | Internal module-to-module interfaces (e.g., fault management → telemetry) can be teased apart into clean service contracts with AI-assisted dependency analysis and decoupling refactors. |
Leftbound | Change interfaces (e.g., configuration → reconciliation → action) can be refactored into observable, testable state transitions, with AI helping to infer boundaries and isolate side effects. |
This model reinforces architectural resilience: you separate intent from implementation, abstract complexity, and build towards testable contracts between units.
Example 1: Refactoring a Fault Dispatcher for Extensibility
❌ Before: Monolithic Dispatcher
void handle_fault(int fault_code) {
if (fault_code == 101) {
log_event("XFP laser fault");
shutdown_laser_module();
raise_alarm("XFP");
} else if (fault_code == 102 || fault_code == 103) {
log_event("PSU overvoltage");
notify_controller("PSU_OV");
raise_alarm("POWER");
}
// Many more conditions...
}
➕ Adding a New Feature (Over-temp Fault)
void handle_fault(int fault_code) {
if (fault_code == 101) {
log_event("XFP laser fault");
shutdown_laser_module();
raise_alarm("XFP");
} else if (fault_code == 102 || fault_code == 103) {
log_event("PSU overvoltage");
notify_controller("PSU_OV");
raise_alarm("POWER");
} else if (fault_code == 104) {
log_event("Transceiver over-temp");
set_throttle_mode();
raise_alarm("TEMP");
}
}
📋 Required Testing (Pre-refactor)
Full regression of entire handle_fault() logic
Revalidation of logging, shutdown, alarm systems
Manual or scripted integration test for over-temp case
Risk of cascading effects if conditions overlap or sequence breaks
⏱ Estimated effort: 2–3 developer-days including integration coordination
🤖 After: Modular Handler with Enum
typedef enum {
FAULT_XFP_LASER,
FAULT_PSU_OVERVOLTAGE,
FAULT_TRANSCEIVER_OVER_TEMP,
// ...
} fault_type_t;
void handle_fault(fault_type_t type) {
switch (type) {
case FAULT_XFP_LASER: process_xfp_fault(); break;
case FAULT_PSU_OVERVOLTAGE: process_power_fault(); break;
case FAULT_TRANSCEIVER_OVER_TEMP: process_temp_fault(); break;
default: log_event("Unknown fault type");
}
}
void process_temp_fault() {
log_event("Transceiver over-temp");
set_throttle_mode();
raise_alarm("TEMP");
}
📋 Required Testing (Post-refactor)
Unit test for process_temp_fault() only
Mock test for switch-case routing (e.g., handle_fault(FAULT_TRANSCEIVER_OVER_TEMP))
No need to reverify unrelated cases or shared state logic
⏱ Estimated effort: 0.5 developer-day
Example 2: Refactoring a YANG-Based Config Handler
❌ Before: Path-Specific Inline Logic
int config_handler_xfp_power(const char* xpath, sr_val_t* value) {
if (strcmp(xpath, "/optical:xfp/power") == 0) {
if (value->type == SR_INT8_T) {
int power = value->data.int8_val;
if (power < 0 || power > 10) return SR_ERR_INVAL_ARG;
set_xfp_power(power);
return SR_ERR_OK;
}
}
return SR_ERR_INVAL_ARG;
}
➕ Adding New Feature (Threshold Configuration)
int config_handler_xfp_power(const char* xpath, sr_val_t* value) {
if (strcmp(xpath, "/optical:xfp/power") == 0) {
// existing logic...
} else if (strcmp(xpath, "/optical:xfp/threshold") == 0) {
if (value->type == SR_INT8_T) {
int threshold = value->data.int8_val;
if (threshold < 1 || threshold > 15) return SR_ERR_INVAL_ARG;
set_xfp_threshold(threshold);
return SR_ERR_OK;
}
}
return SR_ERR_INVAL_ARG;
}
📋 Required Testing (Pre-refactor)
Validation of full xpath string parsing logic
Regression of both existing and new path handling
Shared function makes mocking harder; requires system-level emulation
⏱ Estimated effort: 2–2.5 developer-days
🤖 After: Decoupled, Interface-Mapped Functions
sr_error_t handle_config_set(const char* xpath, const sr_val_t* val) {
if (match_xpath(xpath, "/optical:xfp/power")) return set_power_safe(val);
if (match_xpath(xpath, "/optical:xfp/threshold")) return set_power_threshold_safe(val);
return SR_ERR_NOT_FOUND;
}
sr_error_t set_power_threshold_safe(const sr_val_t* val) {
CHECK_TYPE(val, SR_INT8_T);
int threshold = val->data.int8_val;
if (threshold < 1 || threshold > 15) return SR_ERR_INVAL_ARG;
return hal_xfp_set_threshold(threshold);
}
📋 Required Testing (Post-refactor)
Unit test for set_power_threshold_safe()
XPath routing coverage via mock framework or lookup injection
No overlap with other handlers
⏱ Estimated effort: 0.5–1 developer-day
The Architectural Dividend: Local Assurance
Refactoring with AI doesn’t just clarify code—it reshapes the change and validation model. In the past:
A single change could require full integration revalidation.
Legacy coupling meant global uncertainty with every patch.
Post-refactoring:
Each interface boundary becomes a seam for mocking and isolation.
Developers test only where the change occurs, not everywhere.
The CI strategy shifts from system-wide smoke tests to layered validation.
“With clean structure, testing becomes a scalpel, not a sledgehammer.”
This unlocks scalable innovation while maintaining carrier-grade confidence.
Final Thought
AI-assisted development doesn’t replace embedded engineers—it amplifies them. In the world of optical software, where uptime is sacred and legacy code is the norm, AI unlocks a future where we can:
Refactor with confidence
Test with surgical precision
Document with clarity
Change safely, one interface at a time
The payoff? Carrier-grade innovation that moves at developer speed.
Appendix:
Embedded vs. Python-Based Engineering Practices
Area | Python AI Systems | Embedded Optical Systems |
Language | Python | C/C++, Python for tools |
Testing | pytest, mock, coverage | CppUTest, Unity, Python CLI mocks |
Documentation | Docstrings, Sphinx | Doxygen, Markdown, header comments |
Dependencies | pip, venv, requirements.txt | CMake, Yocto, vendor SDKs |
AI pair coding | Logic generation, refactoring | Modularization, HAL split, test stubs |
Profiling | cProfile, line_profiler | gprof, perf, vendor tracers |




Comments