Refactoring Legacy PLC Code: From Spaghetti to Structure
How to refactor messy legacy PLC code into maintainable structure. Covers identifying spaghetti code, step-by-step refactoring, when to refactor vs. rewrite, and practical examples.
Refactoring Legacy PLC Code: From Spaghetti to Structure
"Spaghetti code" in PLCs looks like: one giant OB1 with 500 networks, no subroutines, hundreds of markers with no names, jump instructions that go in every direction, and modifications layered on top of each other over 20 years. This guide explains how to turn it into maintainable code — without breaking anything.
Signs of Spaghetti Code
- Everything in OB1: No PBs, FBs, or FCs called. All logic in one massive block.
- Excessive markers: Hundreds of M bits used as intermediate variables, with no documentation.
- Jump spaghetti: SPB and SPA instructions that jump forward and backward across the block, making linear reading impossible.
- Copied code: The same logic pattern duplicated 10 times with slightly different addresses instead of using a parameterized FB.
- Magic numbers: Timer values, limits, and setpoints hardcoded directly in the logic (KF +1234) instead of stored in a DB.
- Dead code: Networks or blocks that are never executed but still present.
- Workarounds on top of workarounds: Logic that has been patched multiple times, with commented-out code, NOP placeholders, and bypass logic.
Rule #1: Never Refactor a Running System Without Testing
Refactoring means changing the code structure without changing the behavior. But any change risks introducing bugs. Before touching anything:
- Back up the current program (verified backup — compare online vs. offline)
- Document the current behavior (observe a complete machine cycle, record outputs at each step)
- Create a test plan (how will you verify the refactored code works identically?)
- Plan a rollback (if something goes wrong, how fast can you restore the original program?)
Step 1: Extract Functional Blocks
Take sections of OB1 that handle a specific function and move them into their own blocks:
| Function | Before (in OB1) | After (separate block) |
|---|---|---|
| Conveyor control | Networks 10–25 | FC_Conveyor |
| Filling station | Networks 26–50 | FB_Filling (with instance DB) |
| Safety interlocks | Networks scattered throughout | FC_Safety (called first in OB1) |
| HMI interface | Networks scattered throughout | FC_HMI_Interface |
How: In TIA Portal, select the networks, cut, create new FC/FB, paste. Update the OB1 to call the new block. Test immediately.
Step 2: Replace Markers with Structured Data
Instead of 50 individual markers (M 10.0, M 10.1, M 20.0, MW 30...), create a global data block with meaningful names:
Before:
M 10.0 — (unknown purpose)
M 10.1 — (unknown purpose)
MW 30 — (some setpoint)
After:
DB_Machine.Conveyor.Running : BOOL
DB_Machine.Conveyor.Fault : BOOL
DB_Machine.Filling.Setpoint : INT := 1500
Step 3: Eliminate Jump Spaghetti
Replace SPB/SPBN/SPA patterns with IF/THEN/ELSE in SCL:
Before (AWL — hard to read):
L MW 10
L KF +3
!=F
SPBN =M020
U E 0.0
SPB =M021
SPA =M099
M021: ...
M020: ...
M099: NOP 0
After (SCL — readable):
IF #Step = 3 AND #Start_Button THEN
// Step 3 with start pressed
ELSIF #Step = 3 THEN
// Step 3 without start
END_IF;
Step 4: Parameterize Repeated Logic
If the same pattern appears for multiple machines/stations, create a parameterized FB:
Before: 10 copies of motor control logic with different addresses.
After: One FB_Motor with parameters (Start, Stop, Fault_Input, Motor_Output), called 10 times with different actual parameters.
Step 5: Move Constants to Data Blocks
Replace hardcoded values (magic numbers) with named constants in a parameter DB:
Before:
L KF +1500 // What is 1500? Speed? Temperature? Delay?
T MW 30
After:
DB_Parameters.Conveyor_Speed_Setpoint := 1500; // rpm
When to Refactor vs. When to Rewrite
| Situation | Recommendation |
|---|---|
| Code works but is hard to maintain | Refactor gradually |
| Small changes needed, code is messy | Refactor the affected section only |
| Code has fundamental design problems (wrong architecture) | Rewrite |
| Migration to new platform (S5→S7, S7-300→S7-1500) | Combine migration with refactoring |
| No one understands the code at all | Document first (PLCcheck Pro), then decide |
Golden rule: If you are migrating anyway, refactoring is "free" — you are already testing the entire program. This is the best time to improve structure.
Frequently Asked Questions
How do I refactor without stopping production?
Refactor in the offline project. Test thoroughly in simulation (PLCSIM). Deploy during a planned maintenance window. Have the original program ready for immediate rollback.
Should I convert everything to SCL during refactoring?
Not necessarily. Keep simple bit logic in KOP/FUP (easier to monitor online). Convert complex jump-heavy AWL to SCL. Convert calculations and data processing to SCL. Use the best language for each task.
Maintained by PLCcheck.ai. Last update: March 2026. Not affiliated with Siemens AG.
Related Articles
Common PLC Programming Mistakes and How to Fix Them
The 10 most common PLC programming mistakes found in industrial plants. Each mistake with explanation, real-world consequence, and fix. Covers S5 and S7.
12 min read
plc-programmingOptimizing PLC Scan Time: Practical Techniques
Practical techniques for reducing PLC cycle time. Covers code structure, conditional execution, data type selection, optimized data blocks, and interrupt-based architecture.
10 min read
plc-programmingPLC Code Review Best Practices for Industrial Plants
How to conduct a systematic PLC code review in an industrial plant. Covers what to check, how to prioritize, common issues found, and a review checklist.
10 min read
Analyze your PLC code with AI
PLCcheck Pro explains, documents, optimizes, and migrates PLC code — automatically.
Try PLCcheck Pro →Not affiliated with Siemens AG. S5, S7, STEP 5, STEP 7, and TIA Portal are trademarks of Siemens AG.