Ketika Andrej Karpathy melempar istilah vibe coding ke linimasa, komunitas developer terbelah menjadi dua kubu. Kubu pertama menyambutnya dengan euforia magis: cukup duduk santai, ketik beberapa kalimat santai dalam bahasa manusia, biarkan Large Language Model (LLM) menyusun ribuan baris kode, lalu nikmati hasilnya. Kubu kedua—para engineer yang sudah kenyang begadang memadamkan api di server produksi—hanya bisa mengernyitkan dahi.

Bagi siapa pun yang pernah membangun prototipe akhir pekan atau aplikasi todo-list sederhana, vibe coding terasa seperti sihir murni. Namun, lempar pendekatan serba santai itu ke dalam arsitektur monorepo skala menengah dengan lima puluh paket internal, dependensi tipe yang rapuh, dan logika transaksi finansial. Seketika, getaran menyenangkan itu berubah menjadi mimpi buruk teknis: impor modul siluman (ghost imports), kebocoran state yang sulit dilacak, dan halusinasi sintaksis yang merusak stabilitas sistem.

Model bahasa generatif pada dasarnya adalah mesin probabilistik. Mereka tidak benar-benar "memahami" arsitektur sistem; mereka hanya menebak kelanjutan token yang paling masuk akal secara statistik. Ketika kita membiarkan AI mendefinisikan implementasi sekaligus memvalidasi kebenarannya sendiri, kita sedang menyerahkan kemudi kapal tanker kepada ilusi.

Jalan keluar dari kebuntuan ini bukanlah menolak AI dan kembali memahat setiap baris kode secara manual seperti biksu abad pertengahan. Jawabannya adalah mendisiplinkan energi liar tersebut. Kita butuh sebuah metodologi yang memberi kebebasan berkreasi bagi AI, namun dalam sangkar matematis yang kedap udara.

Metodologi itu sudah ada di depan mata kita selama lebih dari dua dekade: Test-Driven Development (TDD).


Ilusi Kecepatan dan Jebakan Konteks Monorepo

Mengapa coding assistant modern—entah itu Claude 3.7 Sonnet, GPT-4o, maupun model penalaran seperti DeepSeek-R1—sering kali menghasilkan kode yang tampak meyakinkan namun hancur saat diintegrasikan ke arsitektur monorepo?

Jawabannya terletak pada keterbatasan jendela konteks (context window) dan degradasi atensi.

Dalam sebuah monorepo berbasis Turborepo atau Nx, sebuah modul billing-service tidak berdiri sendiri. Ia mengimpor shared types dari @company/core-types, memanggil helper database dari @company/database, dan mematuhi format event bus dari @company/event-stream. Ketika Anda meminta LLM menambahkan sebuah fitur dengan prompt deskriptif yang panjang:

> "Tolong buatkan fungsi untuk memproses pemotongan saldo pengguna dan kirim notifikasi jika saldo di bawah batas minimum..."

LLM akan mulai "menebak". Jika konteks paket internal tidak dimuat seutuhnya ke dalam prompt, model akan mengarang fungsi pembantu yang tidak pernah ada, menggunakan versi library yang usang, atau memutasi struktur objek tanpa mengindahkan kontrak TypeScript yang sudah ada.

python
[Prompt Deskriptif Luwes] 
       │
       ▼
[Halusinasi Probabilistik AI] ──► Menghasilkan Asumsi Sintaksis Palsu
       │
       ▼
[Kompilasi Monorepo Gagal]   ──► Developer Menghabiskan 40 Menit Men-debug "Vibe"

Ketika developer mencoba membetulkan kesalahan tersebut dengan prompt tambahan, konteks percakapan justru membengkak. Model mulai melupakan instruksi awal, token membengkak secara eksponensial, dan efisiensi waktu yang dijanjikan di awal lenyap tak berbekas.


TDD sebagai Sangkar Baja Mesin Probabilistik

Test-Driven Development awalnya dirancang oleh Kent Beck untuk melindungi manusia dari kecerobohan berpikir mereka sendiri. Pola klasiknya sangat sederhana: Red (tulis tes yang gagal) $\rightarrow$ Green (tulis kode minimal agar tes lolos) $\rightarrow$ Refactor (perbaiki struktur kode tanpa mengubah perilakunya).

Ketika diterapkan pada interaksi manusia-ke-AI, TDD bermutasi menjadi instrumen kendali yang luar biasa efektif. Tes unit bukan lagi sekadar instrumen pengujian kualitas perangkat lunak pasca-produksi; ia bertransformasi menjadi kontrak batas tak kasat mata (guardrail).

python
┌─────────────────────────────────────────────────────────────┐
│                   ALUR VIBE CODING TERSTRUKTUR              │
└─────────────────────────────────────────────────────────────┘
  1. MANUSIA(Arsitek)
     ├─ Menulis Definisi Tipe Data(Interface/Contract)
     └─ Menulis Unit Test Deskriptif(Failing Test / RED)
                    │
                    ▼
  2. CODING ASSISTANT(Eksekutor)
     ├─ Diberikan payload: File Test + Interface
     └─ Menghasilkan Kode Implementasi Produksi murni
                    │
                    ▼
  3. TEST RUNNER OTOMATIS
     ├─ Evaluasi: Apakah Output Hijau(GREEN)?
     │    ├─ [TIDAK] ──► Umpankan error stack trace ke LLM
     │    └─ [YA]    ──► Selesai / Lanjut ke Refactoring

Mengapa pendekatan ini bekerja secara spektakuler pada coding assistant?

  1. Stack Trace adalah Bahasa Ibu LLM: Model bahasa sangat buruk dalam menerjemahkan kalimat metaforis seperti "buat kodenya lebih clean dan aman", tetapi mereka sangat jenius dalam membaca pesan galat deterministik seperti:
AssertionError: expected 'FAILED_INSUFFICIENT_FUNDS' to deeply equal 'ERR_BALANCE_LOW'.
  1. Memangkas Overload Konteks: Anda tidak perlu menyuapi LLM dengan seluruh dokumentasi arsitektur monorepo. Cukup suapi file uji dan dependensi antarmukanya (interface). Model memiliki fokus 100% pada pemenuhan ekspektasi tes.
  2. Mengeliminasi Self-Delusion: AI tidak bisa berhalusinasi bahwa kodenya "berjalan lancar" jika test runner di terminal Anda masih memuntahkan warna merah.

Blueprint Praktis: Mengunci Batasan AI dalam 4 Langkah

Mari kita bedah alur kerja nyata di lingkungan TypeScript/Vitest dalam sebuah layanan finansial monorepo. Skenarionya: kita ingin membangun Idempotency Handler untuk mencegah double-charge transaksi.

Langkah 1: Manusia Mendefinisikan Kontrak Data (Types)

Jangan biarkan AI mengarang struktur payload sesuka hatinya. Definisikan kontrak antarmukanya terlebih dahulu pada shared types:

typescript
// packages/contracts/src/idempotency.ts
export interface IdempotencyRecord<T = unknown> {
  key: string;
  lockedAt: number;
  status: 'PENDING' | 'RESOLVED' | 'FAILED';
  responsePayload?: T;
}

export interface IdempotencyStorage {
  acquireLock(key: string, ttlMs: number): Promise<boolean>;
  getRecord<T>(key: string): Promise<IdempotencyRecord<T> | null>;
  saveResult<T>(key: string, payload: T): Promise<void>;
}

Langkah 2: Manusia Menulis Unit Test (Red Phase)

Tulis skenario uji yang mencakup kondisi normal, race condition, dan kegagalan sistem. Buat mock objek sesederhana mungkin.

typescript
// services/billing/src/__tests__/idempotency-manager.spec.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { IdempotencyManager } from '../idempotency-manager';
import type { IdempotencyStorage } from '@company/contracts';

describe('IdempotencyManager', () => {
  let mockStorage: IdempotencyStorage;
  let manager: IdempotencyManager;

  beforeEach(() => {
    mockStorage = {
      acquireLock: vi.fn(),
      getRecord: vi.fn(),
      saveResult: vi.fn(),
    };
    manager = new IdempotencyManager(mockStorage, 5000);
  });

  it('harus mengeksekusi operasi jika lock berhasil didapatkan', async () => {
    vi.mocked(mockStorage.acquireLock).mockResolvedValue(true);
    const mockAction = vi.fn().mockResolvedValue({ success: true, txId: 'tx_123' });

    const result = await manager.execute('req_unique_1', mockAction);

    expect(result).toEqual({ success: true, txId: 'tx_123' });
    expect(mockStorage.saveResult).toHaveBeenCalledWith('req_unique_1', {
      success: true,
      txId: 'tx_123',
    });
  });

  it('harus melempar ConcurrentExecutionError jika request sedang diproses worker lain', async () => {
    vi.mocked(mockStorage.acquireLock).mockResolvedValue(false);
    vi.mocked(mockStorage.getRecord).mockResolvedValue({
      key: 'req_unique_2',
      lockedAt: Date.now() - 1000,
      status: 'PENDING',
    });

    const mockAction = vi.fn();

    await expect(manager.execute('req_unique_2', mockAction)).rejects.toThrow(
      'CONCURRENT_TRANSACTION_IN_PROGRESS'
    );
    expect(mockAction).not.toHaveBeenCalled();
  });
});

Langkah 3: Menginstruksikan AI dengan Format Target Terisolasi

Buka prompt AI assistant (baik melalui interface terminal, ekstensi IDE, atau integrasi API). Berikan instruksi dengan formula ringkas:

markdown
Anda adalah backend developer spesialis Node.js & TypeScript.
Tugas Anda: Implementasikan kelas `IdempotencyManager` agar SEMUA unit test berikut lulus tanpa kesalahan kompilasi.

ATURAN KETAT:
1. Hanya gunakan import tipe dari `@company/contracts`.
2. Jangan menambahkan dependencies runtime baru di luar yang tertera.
3. Tangani kasus edge saat storage melempar koneksi timeout.

Berikut file interface dan unit test yang harus dipenuhi:
[Tempel kode Type & Test dari Langkah 1 & 2]

Langkah 4: Evaluasi Deterministik dan Loop Koreksi Mandiri

AI akan menghasilkan implementasi kode produksi:

typescript
// services/billing/src/idempotency-manager.ts
import type { IdempotencyStorage, IdempotencyRecord } from '@company/contracts';

export class ConcurrentExecutionError extends Error {
  constructor(message = 'CONCURRENT_TRANSACTION_IN_PROGRESS') {
    super(message);
    this.name = 'ConcurrentExecutionError';
  }
}

export class IdempotencyManager {
  constructor(
    private readonly storage: IdempotencyStorage,
    private readonly lockTtlMs: number = 5000
  ) {}

  async execute<T>(key: string, action: () => Promise<T>): Promise<T> {
    const lockAcquired = await this.storage.acquireLock(key, this.lockTtlMs);

    if (!lockAcquired) {
      const existing = await this.storage.getRecord<T>(key);
      if (existing?.status === 'PENDING') {
        throw new ConcurrentExecutionError();
      }
      if (existing?.status === 'RESOLVED' && existing.responsePayload) {
        return existing.responsePayload;
      }
      throw new ConcurrentExecutionError('Unresolved lock state');
    }

    try {
      const result = await action();
      await this.storage.saveResult(key, result);
      return result;
    } catch(error) {
      // Logic pembersihan jika aksi gagal
      throw error;
    }
  }
}

Jalankan test runner Anda di terminal:

bash
pnpm --filter @company/billing test

Jika ada kegagalan, jangan menulis paragraf komplain panjang ke LLM. Salin pesan galat dari konsol, tempelkan ke prompt:

> "Test kedua gagal dengan output: AssertionError: expected [Function] to throw ConcurrentExecutionError. Perbaiki implementasi di atas."

Dalam satu iterasi terarah, AI akan menyempurnakan implementasi hingga seluruh rangkaian uji berubah menjadi hijau. Tanpa basa-basi, tanpa kode siluman yang merusak paket monorepo lainnya.


Perbandingan Pendekatan: Menghitung Biaya Teknis

Mari kita bedah secara objektif disparitas performa antara gaya Vibe Coding Liar dengan Vibe Coding Terstruktur Berbasis TDD:

Parameter EvaluasiVibe Coding Liar (Prompt Santai)Vibe Coding Terstruktur (TDD-First)
Beban Kognitif DeveloperRendah di awal, sangat melelahkan saat debugging runtimeSedikit lebih tinggi di awal (desain kontrak), nol saat integrasi
Konsumsi Token & Biaya APITinggi (berputar-putar dalam klarifikasi prompt panjang)Efisien & terisolasi (prompt ringkas berbasis kode & tipe)
Ketahanan RefactoringRapuh. Modifikasi sedikit sering merusak dependensi tersembunyiSangat kokoh. Test harness langsung mendeteksi regresi
Kompatibilitas MonorepoBuruk; rawan import path fiktif dan benturan dependensiTerisolasi secara ketat dalam cakupan mocking package
Tingkat Halusinasi Logika30% - 60% pada skenario kompleks & percabangan errorMendekati 0% (karena setiap cabang logis diverifikasi compiler & test)
Kecepatan Delivery RiilCepat di 10 menit pertama, melambat drastis setelahnyaKonstan dan linear dari awal hingga integrasi ke CI/CD pipeline

Orkestrasi Model dan Efisiensi Infrastruktur

Dalam alur kerja TDD terstruktur, kebutuhan model AI tidaklah homogen.

Saat Anda menyusun desain domain kompleks atau memikirkan skenario uji batas (edge-case unit tests), Anda membutuhkan model kelas berat dengan daya penalaran tinggi seperti Claude 3.7 Sonnet atau o3-mini. Namun, saat Anda hanya meminta AI menulis boilerplate implementasi untuk memenuhi unit test yang sudah jelas, model yang lebih cepat dan murah seperti DeepSeek-V3 atau GPT-4o-mini sudah lebih dari cukup.

Di sinilah peran infrastruktur perutean model menjadi krusial. Memasang banyak SDK individual dan mengelola billing API yang tersebar untuk masing-masing penyedia model di tim engineering monorepo adalah sumber pemborosan waktu.

python
┌──────────────────────────┐
                          │  IDE / CLI Test Runner   │
                          └─────────────┬────────────┘
                                        │
                                        ▼
                          ┌──────────────────────────┐
                          │  AiStudio.id API Gateway │
                          │  (Unified OpenAI Format) │
                          └──────┬────────────┬──────┘
                                 │            │
             ┌───────────────────┘            └───────────────────┐
             ▼                                                    ▼
┌─────────────────────────┐                          ┌─────────────────────────┐
│ Reasoning Layer         │                          │ Fast Implementation     │
│ (Claude 3.7 / R1)       │                          │ (DeepSeek / 4o-mini)    │
│ Tulis Skenario Uji Edge │                          │ Loloskan Unit Test      │
└─────────────────────────┘                          └─────────────────────────┘

Melalui pemanfaatan ekosistem AiStudio.id API Gateway, para software engineer dan kreator teknologi di Indonesia dapat menyatukan seluruh orkestrasi ini dalam satu pintu. Dengan antarmuka tunggal yang kompatibel penuh dengan standar OpenAI API:

Satu Saldo untuk Semua Model Terkemuka: Anda dapat menembak endpoint Claude 3.7 Sonnet untuk mendesain arsitektur pengujian, lalu beralih ke model eksekutor kode secepat kilat tanpa perlu berganti platform konfigurasi.
Latensi Rendah dan Stabilitas Integrasi: Infrastruktur gateway yang dioptimalkan memastikan komunikasi antara IDE/CLI lokal dengan server model berjalan tanpa kendala koneksi internasional yang sering timeout.
Manajemen Token yang Terkendali: Tim engineering monorepo dapat menetapkan alokasi token yang jelas pada setiap pipeline otomatis, mencegah tagihan membengkak akibat infinite loop perbaikan kode.

Integrasi ini membuat siklus Red-Green-Refactor berbasis AI berjalan mulus, hemat biaya, dan siap diskalakan ke seluruh tim pengembangan.


Mengubah Kebiasaan: Menjadi Sutradara Kode, Bukan Sekadar Penonton

Pergeseran terbesar yang dituntut dari software engineer hari ini bukanlah menghafal nama fungsi API atau sintaks regex. Mesin probabilistik sudah mengambil alih tugas repetitif itu dengan sangat baik. Peran sejati kita kini bertransformasi menjadi pemberi batasan sistem (constraint architect).

Vibe coding tanpa struktur ibarat mengendarai mobil sport bertenaga buas di jalan pegunungan bersalju tanpa rem: menyenangkan selama dua detik pertama sebelum akhirnya Anda terlempar ke jurang.

Sebaliknya, menyusun unit test contract* sebelum memanggil AI adalah tindakan memasang pagar pembatas baja di setiap tikungan tajam. Anda tetap bisa melaju secepat kilat, tetapi risiko tergelincir lenyap dari persamaan.

Mulailah kebiasaan ini pada sprint berikutnya:

  1. Kunci struktur data dengan interface yang kaku.

  2. Tulis tes yang mendefinisikan apa arti "sukses" secara matematis.

  3. Lepaskan coding assistant untuk mengisi kekosongan logikanya.

  4. Nikmati kecepatan pembangunan sistem tanpa sedikit pun mengorbankan ketenangan tidur Anda di malam hari.


Catatan Penulis

Sandra
Sandra

> Sandra menulis seputar rekayasa prompt, efisiensi arsitektur AI, dan produk digital di AiStudio.id.