Kebanyakan developer menggunakan LLM secara terbalik. Kita mendeskripsikan keinginan dalam lima kata, mendapatkan 200 baris kode, dan menghabiskan satu jam berikutnya mendebug asumsi yang dibuat model.

Saya menghabiskan tiga bulan menjalankan workflow sebaliknya: menulis penjelasan desain lengkap sebelum meminta satu baris kode pun. Kode yang dihasilkan memiliki lebih sedikit bug, tetapi peningkatan nyata ada pada pemahaman saya sendiri.

Apa itu Literate Programming dengan LLM?

Donald Knuth mencetuskan istilah “literate programming” pada 1984. Idinya sederhana: tulis prosa yang menjelaskan program Anda, lalu anyam kode ke dalam prosa tersebut. Kebanyakan developer mengabaikannya karena memelihara dua artefak itu melelahkan. LLM mengubah perhitungan itu. Prosa tersebut bukan lagi dokumentasi. Itu adalah prompt.

Ketika Anda menjelaskan pendekatan Anda ke LLM sebelum meminta kode, Anda melakukan sesuatu yang berbeda dari prompt engineering standar. Anda tidak mengoptimalkan token untuk model. Anda memaksa diri sendiri untuk mengartikulasikan constraint, edge case, dan dependency sebelum model menghaluksinasi arsitektur untuk Anda. LLM menjadi rubber duck yang membalas, tetapi hanya setelah Anda melakukan pemikiran yang sebenarnya.

Eksperimen: 30 Hari Menjelaskan Dulu

Saya memilih 12 tugas nyata dari backlog saya, mulai dari parser CSV hingga strategi reconnect WebSocket. Untuk masing-masing, saya menggunakan dua pendekatan:

  1. Jalan pintas: Tempel deskripsi tiket ke LLM dan minta kode.
  2. Penjelasan: Tulis 300 hingga 500 kata yang mendeskripsikan masalah, constraint, strategi penanganan error, dan test case. Lalu minta LLM mengimplementasikan desain tersebut.

Saya mengevaluasi output berdasarkan tiga kriteria: bug yang ditemukan dalam code review, waktu hingga merge, dan jumlah follow-up prompt yang diperlukan untuk memperbaiki masalah.

Angkanya sangat berbeda. Kode explanation-first membutuhkan rata-rata 1,2 follow-up prompt, dibandingkan dengan 4,7 untuk pendekatan jalan pintas. Code review menemukan 0,3 bug per submission explanation-first versus 2,1 bug untuk kode jalan pintas.

Masalahnya adalah waktu. Menulis penjelasan menambah 15 hingga 20 menit di awal setiap tugas. Apakah itu menguntungkan tergantung pada apa yang Anda bangun.

Cara Menyusun Penjelasan yang Benar-benar Berfungsi

Penjelasan yang baik bukan sekadar prompt yang lebih panjang. Itu adalah design document dengan struktur spesifik. Berikut template yang saya perhalus setelah minggu pertama:

## Problem
[What are we solving and why?]

## Constraints
[What can't we do? What formats, protocols, or dependencies must we respect?]

## Edge Cases
[What happens when input is null, empty, malformed, or hostile?]

## Error Handling Strategy
[Fail fast or fail graceful? What do we log? What do we return?]

## Test Cases
[The specific inputs and expected outputs that prove this works.]

Berikut contoh nyata. Saya membutuhkan fungsi yang memvalidasi tanda tangan webhook menggunakan HMAC-SHA256, tetapi dengan syarat: header tanda tangan bisa menggunakan dua format berbeda tergantung pada layanan yang mengirimkan request.

Problem: Memvalidasi tanda tangan webhook yang masuk. Beberapa pengirim menggunakan X-Signature: sha256=<hex> dan yang lain menggunakan X-Signature: <hex> tanpa prefiks. Kita perlu mendukung keduanya tanpa merusak integrasi yang ada.

Constraints: Harus menggunakan module crypto Node.js. Harus menggunakan perbandingan constant-time untuk mencegah timing attack. Harus mengembalikan boolean yang jelas, tidak pernah melempar pada input yang malformed.

Edge Cases: Header hilang. Header adalah string kosong. Header memiliki prefiks tidak dikenal seperti sha512=. String hex memiliki panjang ganjil atau mengandung karakter non-hex.

Error Handling Strategy: Log alasan kegagalan spesifik pada level debug, tetapi kembalikan false untuk semua kasus invalid. Jangan pernah membocorkan detail tanda tangan dalam pesan error.

Test Cases:

  1. Valid sha256=abc123... matches computed signature.
  2. Valid bare abc123... matches computed signature.
  3. Missing header returns false.
  4. Wrong signature returns false.
  5. Non-hex characters in signature return false.

Kemudian saya menambahkan: “Implement this function in TypeScript.”

Model mengembalikan ini:

import { createHmac, timingSafeEqual } from 'crypto';

export function validateWebhookSignature(
  payload: string,
  header: string | undefined,
  secret: string
): boolean {
  if (!header || header.length === 0) {
    return false;
  }

  const hexPattern = /^[0-9a-fA-F]+$/;
  let signatureHex: string;

  const prefixMatch = header.match(/^sha256=(.+)$/);
  if (prefixMatch) {
    signatureHex = prefixMatch[1];
  } else {
    signatureHex = header;
  }

  if (!hexPattern.test(signatureHex) || signatureHex.length % 2 !== 0) {
    return false;
  }

  const expected = createHmac('sha256', secret).update(payload).digest();
  const actual = Buffer.from(signatureHex, 'hex');

  if (actual.length !== expected.length) {
    return false;
  }

  return timingSafeEqual(actual, expected);
}

Ini berhasil dikompilasi pada percobaan pertama. Lebih penting lagi, ini menangani setiap edge case yang saya tentukan karena saya sudah melakukan pemikiran tersebut. Model tidak perlu menebak apa yang saya maksud dengan “validate”.

Di Mana Ini Membantu dan Di Mana Ini Membuang Waktu

Pendekatan ini bersinar untuk apa pun yang memiliki branching logic, dependency eksternal, atau implikasi keamanan. Validator webhook di atas adalah contoh sempurna. Model tidak dapat mengetahui threat model Anda kecuali Anda memberitahukannya.

Tetapi untuk boilerplate, ini berlebihan. Saya tidak menulis 500 kata untuk menghasilkan komponen form React atau migration database standar. Untuk hal tersebut, pendekatan jalan pintas berfungsi dengan baik karena pattern-nya sudah cukup terwakili dalam training data dan stakes-nya rendah.

Jebakan nyata ada di tengah: tugas yang terasa sederhana tetapi sebenarnya tidak. Sebuah parser CSV tampak mudah sampai Anda menemukan field yang dikutip mengandung koma, atau marker BOM, atau mixed line endings. Inilah tugas-tugas di mana melewatkan penjelasan akan membuat Anda kehilangan waktu paling banyak nantinya.

Workflow yang Bisa Anda Tiru Hari Ini

Anda tidak perlu mengubah seluruh proses Anda. Cobalah ini untuk satu tugas minggu ini:

  1. Buka chat LLM Anda dan tulis penjelasan terlebih dahulu. Jangan sebutkan kode dulu.
  2. Baca kembali penjelasan Anda. Jika Anda tidak dapat mengartikulasikan edge case-nya, berhenti. Anda belum siap menulis kode, dan Anda pasti belum siap meminta LLM menulisnya untuk Anda.
  3. Tambahkan permintaan implementasi Anda ke pesan yang sama.
  4. Tinjau output terhadap test case Anda sendiri, bukan terhadap intuisi Anda.

Langkah yang paling banyak orang lewati adalah nomor dua. Jika penjelasan Anda kabur, LLM akan mencerminkan kekaburan itu kembali kepada Anda dalam bentuk bug yang halus. Penjelasan tersebut adalah smoke test untuk pemahaman Anda sendiri.

Manfaat Nyata Bukanlah Kodenya

Setelah 30 hari, saya menyadari sesuatu yang tidak terduga. Kodenya memang lebih baik, tetapi skill desain saya sendiri meningkat lebih cepat. Menulis penjelasan memaksa Anda untuk menamai asumsi Anda secara eksplisit. Ketika Anda melakukan itu, Anda sering menyadari bahwa pendekatan Anda salah sebelum menulis satu baris kode pun.

LLM masih melakukan pengetikan. Tetapi Anda yang melakukan pemikiran, yang selalu menjadi bottleneck.

Jika Anda ingin mencoba ini sendiri, mulailah dengan bug fix berikutnya. Sebelum Anda meminta bantuan LLM, tulis dua paragraf yang menjelaskan apa yang menurut Anda sedang terjadi dan mengapa. Anda mungkin menyelesaikannya sebelum selesai menulis.