Ada kalanya tim memilih jalan pintas: melewatkan validasi demi rilis besok pagi, menyalin blok kode daripada menyusun abstraksi, atau menunda perombakan modul yang mulai membusuk. Pilihan itu sering benar — tetapi selalu ada tagihannya. Technical debt adalah metafora untuk tagihan tersebut: kecepatan yang kita pinjam hari ini dan bunga yang kita bayar di setiap perubahan berikutnya. Pada chapter ini kita pelajari cara meminjam secara sadar, mencatat utangnya, dan melunasinya sebelum bunganya menenggelamkan tim.
Metafora Utang: Meminjam Kecepatan, Membayar Bunga #
Dalam keuangan, utang memungkinkan kita membeli sekarang dan membayar nanti, dengan bunga sebagai harga waktu. Dalam kode, utang bekerja serupa: kita mengirim fitur lebih cepat sekarang dengan struktur yang lebih rapuh, lalu membayar “bunga” berupa melambatnya setiap perubahan di area tersebut. Setiap programmer yang perlu menyentuh modul penuh jalan pintas harus membaca ulang logika yang berbelit, menambal kasus yang bocor, dan menahan diri untuk tidak merapikan — itulah cicilan bunga yang jatuh tempo setiap saat.
Persis seperti utang finansial, utang teknis bukan sesuatu yang baik atau buruk secara mutlak; utang adalah alat. Pinjaman yang produktif membiayai pertumbuhan, sedangkan pinjaman yang dipakai menutup pinjaman lain berakhir membengkak. Bedanya, utang teknis sering terbentuk tanpa keputusan sadar — dan di situlah letak bahayanya.
Utang yang Disengaja dan yang Tidak #
Utang teknis terbagi dua menurut cara terbentuknya. Utang disengaja (deliberate) lahir dari keputusan eksplisit: tim tahu ada cara yang lebih bersih, menimbangnya, lalu memilih jalan cepat dengan kesadaran penuh — misalnya menunda normalisasi basis data demi mengunci tanggal demo. Utang tidak disengaja (inadvertent) terbentuk tanpa pernah diputuskan siapa pun: kurangnya pengalaman membuat kita tidak tahu ada pola yang lebih tepat, atau tekanan deadline membuat review terlewat memperhatikan arsitektur.
Keduanya punya nasib yang sangat berbeda. Utang disengaja bisa didaftarkan, diberi rencana pelunasan, dan dipantau. Utang tidak disengaja justru baru terlihat setelah bunganya menumpuk: satu hari tim sadar bahwa modul tertentu selalu jadi sumber bug, padahal tidak pernah ada keputusan untuk membiarkannya rusak. Karena itu langkah pertama mengelola utang bukanlah melunasinya, melainkan membuatnya terlihat.
Kuadran Utang #
Martin Fowler mempertajam pembedaan di atas dengan kuadran dua dimensi: apakah utang disengaja atau tidak, dan apakah keputusannya bijak atau cerobong. Kuadran ini berguna karena menunjukkan bahwa masalah terbesar bukanlah utang itu sendiri, melainkan posisinya di kuadran mana:
| Bijak (prudent) | Cerobong (reckless) | |
|---|---|---|
| Disengaja | “Kita tahu ini utang; kirim dulu untuk memenangkan pasar, lunasi kuartal depan.” | “Tidak ada waktu untuk desain — yang penting jalan dulu.” |
| Tidak disengaja | “Baru sekarang kita paham bagaimana kode ini seharusnya ditulis.” | “Apa itu arsitektur berlapis?” |
Kiri atas adalah posisi ideal: keputusan sadar dengan rencana pelunasan. Kanan atas terdengar wajar di tengah deadline, tetapi tanpa rencana pelunasan ia cepat melorot ke bawah. Kiri bawah adalah utang dari ketidaktahuan yang jujur — tak terhindarkan dan menjadi bahan belajar. Kanan bawah adalah zona paling mahal: tim tidak tahu apa yang tidak diketahuinya, dan bunganya dibayar tanpa pernah dipahami sebagai bunga.
Kapan Utang Itu Pintar #
Utang termasuk keputusan pintar pada tiga situasi yang khas. Pertama, eksperimen: ketika kita belum tahu apakah sebuah fitur akan dipakai, menulis cepat dan kotor untuk belajar dari pengguna lebih murah daripada membangun arsitektur elegan untuk sesuatu yang mungkin dibuang. Kedua, jendela pasar atau deadline nyata: memenangkan kontrak karena hadir dua minggu lebih awal bisa jauh melebihi biaya pelunasan utangnya. Ketiga, prototipe internal yang memang ditakdirkan dibuang.
Ketiganya berbagi dua syarat yang sama: utang dibuat dengan kesadaran penuh, dan ada rencana pelunasan yang ditulis — kapan, oleh siapa, dengan patokan apa selesai. Utang pintar tanpa rencana pelunasan hanyalah utang cerobong yang menunggu tanggal jatuh tempo.
Gejala Bunga yang Menumpuk #
Bunga utang teknis tidak muncul di laporan keuangan; gejalanya terasa di ritme kerja tim. Kenali bentuk-bentuk berikut:
- Rasa takut menyentuh. Anggota tim menghindari modul tertentu, atau setiap perubahan kecil di sana selalu disertai doa dan tiga hari pengujian manual.
- Klaster bug. Laporan bug terus bermuncul dari file atau fitur yang sama, satu diperbaiki dua lagi muncul — tanda klasik struktur yang tidak lagi menahan beban.
- Estimasi menggelembung. Fitur yang secara logika sederhana selalu diestimasi besar karena harus melewati rawa kode lama.
- Onboarding melambat. Programmer baru butuh berminggu-minggu untuk produktif karena logika tersebar dan tidak ada yang berani dijelaskan singkat.
Jika dua gejala atau lebih muncul di area yang sama, area itu sedang membayar bunga tinggi — dan pelunasannya layak naik prioritas.
Melacak Utang dengan TODO dan Tautan Issue #
Utang yang tidak tercatat adalah utang gelap: tidak ada yang tahu jumlahnya, dan tidak akan pernah dianggarkan. Praktik paling sederhana adalah menandainya langsung di kode dengan komentar TODO yang selalu membawa tautan ke issue tracker, misalnya TODO(#123). Kode berikut adalah pemindai kecil yang mengekstrak semua penanda semacam itu dari sebuah string kode:
<?php
$kode = <<<'KODE'
<?php
// TODO(#123): ganti pencarian linier dengan index saat tabel melewati 100 ribu baris
class PelacakStok
{
public function cari(array $stok, string $sku): ?array
{
foreach ($stok as $baris) {
if ($baris["sku"] === $sku) {
return $baris;
}
}
return null;
}
}
// TODO(#456): pecah impor() — sudah 180 baris dan menyentuh tiga tabel
KODE;
function cariTodo(string $kode): array
{
$temuan = [];
foreach (explode("\n", $kode) as $nomor => $baris) {
if (preg_match('/TODO\(#(\d+)\):\s*(.+)/', $baris, $cocok) === 1) {
$temuan[] = [
"baris" => $nomor + 1,
"issue" => "#" . $cocok[1],
"catatan" => rtrim($cocok[2]),
];
}
}
return $temuan;
}
foreach (cariTodo($kode) as $temuan) {
echo "Baris ", $temuan["baris"], " → issue ", $temuan["issue"], ": ", $temuan["catatan"], "\n";
}Output yang Dihasilkan:
Baris 2 → issue #123: ganti pencarian linier dengan index saat tabel melewati 100 ribu baris
Baris 16 → issue #456: pecah impor() — sudah 180 baris dan menyentuh tiga tabelMari kita bedah cara kerja pemindai ini:
<<<'KODE' ... KODE: nowdoc menyimpan cuplikan kode yang akan dipindai sebagai string biasa — tanda kutip dan tanda dolar di dalamnya tidak diinterpolasi.explode("\n", $kode): kode dipecah per baris, danforeachmemberi nomor indeks mulai dari nol, sehingga nomor baris sebenarnya adalah$nomor + 1.preg_match('/TODO\(#(\d+)\):\s*(.+)/', ...): pola menangkap nomor issue dalam grup(\d+)dan sisa catatan dalam(.+). PenandaTODO(#456):tanpa nomor tidak akan cocok — formatnya memang dibuat ketat.$temuan[]: setiap kecocokan dikumpulkan sebagai asosiasi baris, issue, dan catatan, siap dipetakan ke daftar kerja atau laporan.
Pemindai semacam ini bisa dijalankan di CI untuk menghitung total utang tercatat, atau sekadar dipakai saat perencanaan sprint untuk memastikan tidak ada TODO yang bertahan melewati umur yang disepakati tim.
Register Utang dan Keputusan Pelunasan #
Untuk keputusan yang lebih besar, komentar TODO tidak cukup — kita perlu register utang: daftar terpusat (di issue tracker dengan label khusus, atau satu dokumen hidup) yang mencatat tiap utang berikut estimasi pelunasannya dan estimasi bunganya. Kode berikut menghitung keputusan pelunasan dari register sederhana:
<?php
$registerUtang = [
["utang" => "pencarian stok tanpa index", "issue" => "#123", "cicilan" => 16, "bunga" => 4, "sisaRilis" => 6],
["utang" => "modul impor monolitik", "issue" => "#456", "cicilan" => 40, "bunga" => 4, "sisaRilis" => 8],
["utang" => "duplikasi validasi form", "issue" => "#789", "cicilan" => 12, "bunga" => 1, "sisaRilis" => 4],
];
function putuskanPelunasan(array $utang): string
{
$bungaTertunda = $utang["bunga"] * $utang["sisaRilis"];
return $bungaTertunda > $utang["cicilan"] ? "lunasi sekarang" : "jadwalkan bertahap";
}
foreach ($registerUtang as $utang) {
printf(
"%s (%s): cicilan %d jam, bunga %d jam per rilis, sisa %d rilis → %s\n",
$utang["utang"],
$utang["issue"],
$utang["cicilan"],
$utang["bunga"],
$utang["sisaRilis"],
putuskanPelunasan($utang)
);
}Output yang Dihasilkan:
pencarian stok tanpa index (#123): cicilan 16 jam, bunga 4 jam per rilis, sisa 6 rilis → lunasi sekarang
modul impor monolitik (#456): cicilan 40 jam, bunga 4 jam per rilis, sisa 8 rilis → jadwalkan bertahap
duplikasi validasi form (#789): cicilan 12 jam, bunga 1 jam per rilis, sisa 4 rilis → jadwalkan bertahapLogikanya sederhana namun mencerminkan cara berpikir keuangan. Utang pencarian stok menelan 4 jam bunga di setiap rilis dan masih ada 6 rilis mendatang — total 24 jam bunga tertunda, jauh melebihi cicilan 16 jam untuk membangun index, jadi ia harus dilunasi sekarang. Sebaliknya, memecah modul impor yang monolitik butuh 40 jam sementara bunganya “hanya” 32 jam sampai akhir horizon perencanaan — lebih bijak dijadwalkan bertahap. Angka-angka ini adalah estimasi kasar, tetapi justru dengan membuatnya eksplisit, diskusi “kita utang dulu atau bayar dulu” berubah dari perasaan menjadi perhitungan.
Anggaran Refactoring per Sprint #
Cara membayar utang yang paling berkelanjutan adalah menjadikannya pos anggaran tetap, bukan menunggu waktu senggang — yang tidak pernah datang. Tim-tim yang sehat umumnya menyisihkan porsi tetap dari kapasitas setiap sprint, misalnya 10 hingga 20 persen, khusus untuk melunasi utang prioritas tertinggi dari register. Porsi ini juga menjaga kesehatan secara psikologis: pelunasan tidak terasa seperti gangguan terhadap “pekerjaan sungguhan”, karena ia memang punya jatah resmi.
Ringkasan #
Technical debt adalah alat untuk meminjam kecepatan, bukan aib yang harus disembunyikan. Utang yang sehat bersifat disengaja, tercatat, dan punya rencana pelunasan; utang yang gelap menumpuk bunga berupa rasa takut menyentuh kode, klaster bug, dan estimasi yang terus menggelembung. TODO dengan tautan issue membuat utang kecil terlihat, register dengan hitungan bunga membuat utang besar bisa diprioritaskan, dan anggaran refactor per sprint memastikan pelunasannya benar-benar terjadi. Salah satu bentuk utang yang paling sering diabaikan justru hidup di luar kode kita sendiri: paket-paket dari pihak ketiga yang kita ajak menumpang di aplikasi — topik chapter berikutnya.
Referensi #
- Technical Debt — Martin Fowler — asal metafora utang teknis dan penjelasannya.
- Technical Debt Quadrant — Martin Fowler — rincian kuadran disengaja/tidak disengaja versus bijak/cerobong.
- Komentar PHP — php.net — sintaks komentar satu baris yang memuat penanda TODO.