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



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

Image

Image

Image

Image

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::string
  • std::function
  • std::vector growth
  • 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/delete di runtime.
  • Tidak ada String di 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.