Anti-SOLID: The Hard Truths of Overengineering

Jotted down by Jay when best practices become anti-patterns.

In my previous note, I talked about how SOLID saves you from spaghetti code. But let's have some real talk. Sometimes, blindly following best practices is the fastest way to ruin a perfectly good codebase.

When you read programming blogs, everything is black and white. But in the trenches of real engineering, there's a lot of grey. Here is the pragmatist's counter-argument to the sacred SOLID principles.

1. SRP: The "Micro-Class Spaghetti"

If you stubbornly divide a simple process into 10 different tiny files just to satisfy the Single Responsibility Principle, you destroy code discoverability.

Sometimes, having a single 300-line service class where you can read the entire business flow top-to-bottom is infinitely better than jumping across 10 single-method classes just to figure out how a user logs in. Readability matters more than dogmatism.

Jumping through 10 files is just routing hell!

2. OCP: The Trap of YAGNI

YAGNI stands for "You Aren't Gonna Need It". Anticipating changes that never happen is a massive waste of time and mental energy.

Building a complex, generic 'plugin' architecture for an export feature that only exports to PDF and CSV—and honestly, will only ever export to those two—is textbook overengineering. Sometimes, a simple switch statement is exactly what the doctor ordered. Keep it simple.

3. LSP: The Interface Explosion

Strictly enforcing the Liskov Substitution Principle often leads to creating hundreds of hyper-specific interfaces just to avoid throwing an exception from a base class. You end up polluting the codebase with things like IFlyable, IWalkable, and IBreatheable.

Too many interfaces!

Sometimes, throwing a NotSupportedException for a weird edge case (like a Penguin trying to fly) is totally fine. It solves the immediate business problem without requiring a multi-week architectural rewrite.

4. ISP: Constructor Hell

If you segregate your interfaces too ruthlessly, your dependency injection gets completely out of control.

Instead of passing one cohesive IStoreContext object down your stack, you end up having to inject 8 tiny, heavily-segregated interfaces into a single constructor. The dependency wiring becomes harder to test, trace, and maintain than the actual logic itself.

Constructors shouldn't need 10 arguments!

5. DIP: The Database Myth

Here's the classic Dependency Inversion argument: "You must abstract your database so you can easily swap MySQL for MongoDB later!"

Here's the hard truth: How often do you actually change your primary database in production? Almost never. It's a massive, multi-year migration project even if your code is perfectly abstracted.

Adding unnecessary layers of abstractions (like slapping a custom Repository Pattern on top of an ORM that is already an abstraction) adds deep mental overhead, makes SQL queries harder to debug, and introduces performance degradation due to indirection. For straightforward web services, instantiating your specific database context is perfectly fine.

Stop abstracting the abstraction!

The Real Best Practice

Be pragmatic. SOLID principles are fantastic guidelines, but they are not the law. Understand the rules so you know exactly when, why, and how to break them gracefully!

Anti-SOLID: Kenyataan Pahit soal Overengineering

Ditulis Jay pas capek ngeliat best practice jadi anti-pattern.

Di catatan sebelumnya, gue ngebahas gimana SOLID bisa nyelametin lo dari kode yang berantakan. Tapi ayo kita ngomong jujur-jujuran. Kadang, ngikutin 'best practice' secara membabi buta itu jalan paling cepet buat ngerusak kode yang sebenernya udah bagus.

Pas lo baca blog programming tutorial, rasanya kok serba hitam-putih. Tapi di dunia nyata engineering, banyak banget area abu-abu. Ini dia opini jujur dari sudut pandang engineer pragmatis buat ngebantah prinsip SOLID yang katanya suci itu.

1. SRP: Spaghetti Micro-Class

Kalo lo maksain mecah satu proses bisnis ke 10 file kecil-kecil cuma demi menuhin syarat SRP, lo malah ngancurin 'discoverability' (kemudahan tracking) dari kode lo sendiri.

Kadang, punya satu class service sepanjang 300 baris di mana lo bisa baca seluruh alur bisnis dari atas sampe bawah, itu jauh lebih gampang dimengerti daripada harus muter-muter buka 10 file yang isinya cuma satu method doang. Kemudahan dibaca itu lebih penting daripada fanatisme teori.

Buka 10 file cuma bikin pusing nyari routingnya!

2. OCP: Jebakan YAGNI (You Aren't Gonna Need It)

Ngantisipasi keperluan fitur ajaib di masa depan yang ujung-ujungnya nggak pernah kejadian itu buang-buang waktu dan tenaga banget.

Ngebangun arsitektur 'plugin' super kompleks buat fitur export yang isinya cuma cetak PDF sama CSV—dan emang aplikasinya cuma butuh itu sampe kiamat—adalah contoh nyata overengineering. Kadang, pake switch statement biasa itu udah obat yang paling pas, bro. Bikin simpel aja napa!

3. LSP: Ledakan Interface

Kalo lo terlalu saklek sama LSP, ujung-ujungnya codebase lo bakal penuh sama ratusan interface aneh cuma demi ngehindarin error dari base class. Kode lo bakal keracunan bentukan kayak IFlyable, IWalkable, dan IBreatheable.

Kebanyakan interface kyk mau buka pameran!

Kadang, sengaja nge-throw NotSupportedException buat edge case aneh (kayak Penguin yang disuruh terbang) itu sah-sah aja kok. Problem bisnis kelar instan tanpa harus ngerombak arsitektur habis-habisan selama berminggu-minggu.

4. ISP: Neraka Constructor

Kalo lo mecah interface terlalu ekstrem bin sadis, setup dependency injection lo bakal berantakan total.

Daripada cuma passing satu IStoreContext yang utuh ke komponen lo, lo malah harus nge-inject 8 interface kecil-kecil ke dalem satu constructor. Setup wiring-nya malah jadi lebih ribet di-maintain, susah di-test, dan ngabisin waktu daripada logika inti kodenya sendiri.

Gila constructor kok parameternya 10!

5. DIP: Mitos Pindah Database

Ini dia argumen paling usang soal DIP: "Lo harus nge-abstraksi database lo, biar kapan-kapan gampang kalo mau pindah dari MySQL ke MongoDB!"

Tanyain ke diri lo sendiri: Seberapa sering sih lo beneran mindahin database utama pas aplikasi udah running di production? Hampir nggak pernah. Kalo pun kejadian, itu proyek migrasi raksasa bertahun-tahun, nggak peduli seketat apa abstraksi kode lo.

Nambah-nambahin layer abstraksi (kayak sok-sokan bikin Repository Pattern di atas ORM yang padahal udah berfungsi sebagai abstraksi) cuma nambah-nambahin beban pikiran. Bikin query SQL makin susah di-debug, plus bikin performa turun dikit gara-gara kebanyakan indirection. Buat web service standar mah, spesifik langsung manggil koneksi database aslinya itu gapapa banget kalik.

Berhenti nge-abstraksi hal yg udah abstrak!

Best Practice yang Sebenarnya

Jadilah pragmatis. Prinsip SOLID itu pedoman yang mantep banget, tapi inget, mereka bukan hukum mutlak. Pahami aturannya luar dalem, biar lo tau kapan, kenapa, dan gimana caranya ngelanggar aturan itu dengan elegan!