- Published on
Anti-Pattern - Cara OOP Menghancurkan Embedded System
- Authors
📘 Artikel 8: Anti-Pattern: Cara OOP Menghancurkan Embedded System
Posisi: Artikel 8 dari 8 Domain Keputusan: Guardrail & Production Audit Status Lock: 🔒 Final Audit — Tidak Mengubah Arsitektur Entry Context: IndustrialNode/IndustrialNode.ino
- 📘 Artikel 8: Anti-Pattern: Cara OOP Menghancurkan Embedded System
- 1. Problem Reality
- 2. Root Cause Analysis
- 3. Design Principle (Guardrail Final)
- 4. Implementation Audit Pattern (ESP32 Context)
- 5. Constraint & Embedded Impact
- 6. Failure Scenario
- 7. Anti-Pattern (Red Flag List Final)
- 8. Freeze Confirmation
- 9. Engineering Checklist (Production Audit Final)
- 10. Summary (5 Bullet Maksimal)
1. Problem Reality
Setelah Artikel 2–7:
- Dependency terkendali.
- Memory disiplin.
- Layering terkunci.
- Concurrency dibatasi.
- Communication terisolasi.
- Reliability formal.
Firmware terlihat modern.
Namun bahaya terbesar sekarang bukan procedural spaghetti.
Bahaya terbesar adalah:
OOP digunakan tanpa disiplin.
Gejala nyata di produksi:
- Banyak interface.
- Banyak class kecil.
- Banyak template.
- Banyak virtual.
- Banyak abstraction layer.
Namun:
- Heap tidak stabil.
- Stack tidak terprediksi.
- Latency spike.
- OTA kadang gagal.
- Race sulit dilacak.
Firmware menjadi kompleks bukan karena C, tetapi karena abstraction tidak dikontrol.
2. Root Cause Analysis
OOP menghancurkan embedded system jika digunakan tanpa constraint.
2.1 Abstraction Tanpa Justifikasi
Membuat interface untuk “future extensibility” padahal:
- Hanya ada satu implementasi.
- Tidak pernah diganti.
Overhead tanpa nilai.
2.2 Dynamic Polymorphism Tidak Perlu
class IRelay {
public:
virtual void set(bool on) = 0;
};
Jika hanya ada satu RelayDriver:
- Virtual table tidak memberi nilai.
- Menambah complexity.
- Menambah indirection.
2.3 Template Overengineering
Generic container, policy-based design, template metaprogramming berat.
Firmware bukan library generic.
2.4 Hidden Allocation
std::string payload = buildJson();
Atau:
std::function<void()> callback;
Allocation tersembunyi.
Fragmentasi perlahan.
2.5 Singleton Terselubung
Logger::instance().log("error");
Hidden global mutable.
Melanggar Artikel 2.
🔎 Visualisasi Abstraction Tanpa Constraint


Terlalu banyak layer tanpa kebutuhan nyata meningkatkan kompleksitas tanpa meningkatkan reliability.
3. Design Principle (Guardrail Final)
Artikel 8 tidak menambah rule baru.
Artikel ini memperkuat guardrail.
🔒 Rule 1 — Abstraction Must Justify Cost
Setiap abstraction harus menjawab:
- Apa problem nyata yang diselesaikan?
- Apa impact RAM?
- Apa impact stack?
- Apa impact determinism?
Jika tidak jelas → jangan pakai.
🔒 Rule 2 — No Virtual Abuse
Dynamic polymorphism hanya jika:
- Benar-benar ada multiple implementation nyata.
- Tidak berada di hot path control.
Default: concrete class.
🔒 Rule 3 — No Template Overengineering
Tidak boleh:
- Template metaprogramming berat.
- Generic container kompleks.
- Policy-based design yang membingungkan tim.
Firmware ≠ framework.
🔒 Rule 4 — Hidden Heap is Forbidden
Audit:
std::stringstd::functionstd::vectorgrowth- Library pihak ketiga
Semua harus diketahui allocation behavior-nya.
🔒 Rule 5 — Audit Before Feature
Sebelum menambah fitur baru:
- Cek dependency discipline (Artikel 2).
- Cek memory discipline (Artikel 3).
- Cek layering (Artikel 4).
- Cek concurrency (Artikel 5).
- Cek communication boundary (Artikel 6).
- Cek reliability model (Artikel 7).
Jika melanggar → redesign dulu.
4. Implementation Audit Pattern (ESP32 Context)
4.1 Virtual Abuse Example
class IActuator {
public:
virtual void set(bool on) = 0;
};
Jika hanya ada satu implementasi:
- Gunakan concrete class.
- Hindari virtual table.
4.2 Hidden Allocation Example
String payload = "{ \"temp\": " + String(temp) + " }";
Melanggar Artikel 3.
Gunakan static buffer + snprintf.
4.3 Singleton Terselubung
WiFiManager::instance().connect();
Melanggar:
- Artikel 2 (no global mutable)
- Artikel 4 (composition root tunggal)
5. Constraint & Embedded Impact
Overengineering bukan sekadar masalah gaya. Ia berdampak langsung pada resource terbatas.
5.1 RAM Impact
- Virtual table menambah overhead.
- Template instantiation menggandakan code dan data.
- Hidden allocation menyebabkan fragmentasi.
Pada ESP32:
- TLS membutuhkan blok heap kontigu besar.
- Fragmentasi kecil bisa menggagalkan handshake.
Abstraction tanpa kontrol = heap tidak stabil.
5.2 Flash Impact
Template berat dan generic abstraction:
- Menggandakan instantiation code.
- Binary size naik signifikan.
- OTA gagal karena firmware melebihi partisi.
Firmware production harus sadar partisi OTA.
5.3 Stack Impact
Virtual call chain panjang:
- Menambah stack depth.
- Sulit diprediksi.
Exception (jika digunakan):
- Stack unwind tidak deterministik.
- Melanggar Artikel 3 & 7.
5.4 Determinism Impact
Dynamic dispatch di hot path:
- Indirection tambahan.
- Latency kecil tapi akumulatif.
Control loop embedded:
- Lebih penting konsistensi daripada fleksibilitas.
6. Failure Scenario
Overengineering sering tidak langsung terlihat.
Ia muncul dalam jangka panjang.
Scenario 1 — Template Logging Berat
Generic logging dengan template variadic:
- Flash melonjak.
- OTA gagal update.
- Device tidak bisa diperbaiki jarak jauh.
Root cause: abstraction tanpa constraint.
Scenario 2 — Virtual di Control Loop
ControlApp menggunakan interface virtual di hot path.
Saat traffic tinggi:
- Latency spike.
- Jitter control meningkat.
- Watchdog reset sporadis.
Scenario 3 — std::string di Payload MQTT
Penggunaan std::string di publish periodik:
- Heap churn.
- Fragmentasi perlahan.
- TLS gagal setelah minggu ke-3.
Masalah terlihat sebagai “network instability”, padahal memory issue.
Scenario 4 — Singleton Logger Multi-Task
Logger global diakses banyak task tanpa desain ownership jelas.
- Race condition.
- Deadlock.
- Crash sporadis sulit direproduksi.
Melanggar Artikel 2 & 5.
7. Anti-Pattern (Red Flag List Final)
Jika ≥3 muncul di firmware Anda, risiko produksi tinggi.
❌ Virtual di hot path ❌ Template kompleks tanpa kebutuhan nyata ❌ std::string heavy ❌ std::function di callback kritikal ❌ Hidden allocation ❌ Singleton ❌ Circular dependency ❌ Exception ❌ Destructor logic kompleks ❌ Logging blocking ❌ Queue tak terbatas ❌ Publish langsung tanpa CommManager
Ini adalah audit lintas Artikel 2–7.
8. Freeze Confirmation
Artikel 8 tidak menambah rule baru.
Yang dianggap final:
- Dependency discipline (Artikel 2)
- Memory discipline (Artikel 3)
- Layering freeze (Artikel 4)
- Concurrency boundary (Artikel 5)
- Communication boundary (Artikel 6)
- Reliability model (Artikel 7)
Artikel 8 hanya mengaudit dan menjaga.
Tidak ada perubahan arsitektur setelah ini.
9. Engineering Checklist (Production Audit Final)
Gunakan checklist ini sebelum release firmware produksi.
Dependency
- Tidak ada global mutable.
- Tidak ada singleton.
- Semua dependency via constructor.
Memory
- Tidak ada
new/deletedi runtime. - Tidak ada
Stringdi hot path. - Heap watermark stabil.
- Tidak ada container growable tak terbatas.
Architecture
- Dependency DAG.
- Tidak ada circular include.
- Composition root tunggal di
IndustrialNode.ino.
Concurrency
- ISR hanya notify.
- ControlTask tidak blocking.
- Queue fixed-size.
- Tidak ada shared mutable tanpa owner.
Communication
- Semua lewat
svc_CommManager. - State machine eksplisit.
- Credential terisolasi.
- OTA guarded.
Reliability
- Error enum eksplisit.
- Tidak ada exception.
- Watchdog encapsulated.
- Fail-safe state ada.
- Health telemetry aktif.
Jika semua lulus → firmware siap produksi jangka panjang.
10. Summary (5 Bullet Maksimal)
- OOP bisa menyelamatkan firmware — atau menghancurkannya.
- Abstraction tanpa constraint adalah bahaya.
- Hidden allocation adalah silent killer.
- Audit lebih penting daripada fitur.
- Disiplin arsitektur menentukan umur 3–5 tahun firmware.
Catatan Penyusunan Artikel ini disusun sebagai materi edukasi dan referensi umum berdasarkan berbagai sumber pustaka, praktik lapangan, serta bantuan alat penulisan. Pembaca disarankan untuk melakukan verifikasi lanjutan dan penyesuaian sesuai dengan kondisi serta kebutuhan masing-masing sistem.