Ide pemrograman terstruktur Edsger Dijkstra menjelaskan mengapa kode yang disiplin dan sederhana tetap benar dan mudah dipelihara seiring tim, fitur, dan sistem tumbuh.

Perangkat lunak jarang gagal karena tidak bisa ditulis. Ia gagal karena, setahun kemudian, tidak ada yang bisa mengubahnya dengan aman.
Saat basis kode tumbuh, setiap tweak “kecil” mulai merambat: perbaikan bug memutus fitur jauh, kebutuhan baru memaksa penulisan ulang, dan refaktor sederhana berubah menjadi minggu koordinasi. Bagian tersulit bukan menambahkan kode—melainkan menjaga perilaku tetap dapat diprediksi saat segala sesuatu di sekitarnya berubah.
Edsger Dijkstra berargumen bahwa ketepatan dan kesederhanaan harus menjadi tujuan pokok, bukan sekadar hal yang bagus dimiliki. Imbalannya bukan semata akademis. Ketika sistem lebih mudah untuk dinalar, tim menghabiskan lebih sedikit waktu memadamkan kebakaran dan lebih banyak waktu membangun.
Ketika orang mengatakan perangkat lunak perlu “skala,” sering mereka maksudkan performa. Poin Dijkstra berbeda: kompleksitas juga bertambah seiring skala.
Skala muncul sebagai:
Pemrograman terstruktur bukan soal ketat demi ketat. Ini soal memilih alur kontrol dan dekomposisi yang memudahkan menjawab dua pertanyaan:
Saat perilaku dapat diprediksi, perubahan menjadi rutinitas alih-alih berisiko. Itulah mengapa Dijkstra masih relevan: disiplinnya menargetkan hambatan nyata perangkat lunak yang tumbuh—memahaminya cukup baik untuk memperbaikinya.
Edsger W. Dijkstra (1930–2002) adalah ilmuwan komputer Belanda yang membantu membentuk cara programmer berpikir tentang membangun perangkat lunak yang andal. Ia mengerjakan sistem operasi awal, berkontribusi pada algoritme (termasuk algoritme jalur terpendek yang memakai namanya), dan—yang paling penting bagi pengembang sehari-hari—mendorong gagasan bahwa pemrograman harus sesuatu yang bisa kita nalar, bukan sekadar mencoba sampai tampak bekerja.
Dijkstra lebih peduli apakah program bisa dijelaskan mengapa benar untuk kasus yang penting, daripada apakah program menghasilkan keluaran yang benar untuk beberapa contoh saja.
Jika Anda bisa menyatakan apa yang seharusnya dilakukan sebuah potong kode, Anda harus bisa berargumen (langkah demi langkah) bahwa kode itu benar-benar melakukannya. Pola pikir ini secara alami menghasilkan kode yang lebih mudah diikuti, lebih mudah direview, dan kurang bergantung pada debug heroik.
Beberapa tulisan Dijkstra terasa tanpa kompromi. Ia mengkritik trik “pintar”, alur kontrol yang ceroboh, dan kebiasaan coding yang menyulitkan penalaran. Keketatannya bukan soal mengatur gaya; ini soal mengurangi ambiguitas. Ketika makna kode jelas, Anda menghabiskan lebih sedikit waktu berdebat tentang niat dan lebih banyak memvalidasi perilaku.
Pemrograman terstruktur adalah praktik membangun program dari seperangkat kecil struktur kontrol yang jelas—urutan, seleksi (if/else), dan pengulangan (loop)—daripada lompatan alur yang kusut. Tujuannya sederhana: membuat jalur melalui program dapat dimengerti sehingga Anda bisa menjelaskannya, memeliharanya, dan mengubahnya dengan percaya diri.
Orang sering menggambarkan kualitas perangkat lunak sebagai “cepat”, “cantik”, atau “banyak fitur”. Pengguna mengalami ketepatan berbeda: sebagai keyakinan tenang bahwa aplikasi tidak akan mengejutkan mereka. Saat ketepatan ada, tidak ada yang memperhatikannya. Saat hilang, semua hal lain berhenti penting.
“Berfungsi sekarang” biasanya berarti Anda mencoba beberapa jalur dan mendapat hasil yang diharapkan. “Terus berfungsi” berarti sistem berperilaku seperti dimaksudkan seiring waktu, kasus tepi, dan perubahan—setelah refaktor, integrasi baru, lalu lintas meningkat, dan anggota tim baru mengubah kode.
Sebuah fitur bisa “berfungsi sekarang” namun rapuh:
Ketepatan soal menghapus asumsi tersembunyi ini—atau membuatnya eksplisit.
Bug kecil jarang tetap kecil saat perangkat lunak tumbuh. Satu keadaan yang salah, satu off-by-one, atau satu aturan penanganan error yang tidak jelas disalin ke modul baru, dibungkus layanan lain, di-cache, di-retry, atau “diakali.” Seiring waktu, tim berhenti bertanya “apa yang benar?” dan mulai menanyakan “apa yang biasanya terjadi?” Saat itulah respons insiden berubah menjadi arkeologi.
Pengganda masalahnya adalah ketergantungan: perilaku kecil yang salah menjadi banyak perilaku turunannya, masing-masing dengan perbaikan parsialnya sendiri.
Kode yang jelas meningkatkan ketepatan karena meningkatkan komunikasi:
Ketepatan berarti: untuk input dan situasi yang kami klaim dukung, sistem konsisten menghasilkan hasil yang kami janjikan—sementara gagal dengan cara yang dapat diprediksi dan dijelaskan bila tidak mungkin.
Kesederhanaan bukan soal membuat kode “imut”, minimal, atau pintar. Ini soal membuat perilaku mudah diprediksi, dijelaskan, dan diubah tanpa ketakutan. Dijkstra menghargai kesederhanaan karena itu meningkatkan kemampuan kita untuk bernalar tentang program—terutama ketika basis kode dan tim tumbuh.
Kode sederhana mempertahankan sedikit gagasan yang bergerak sekaligus: aliran data jelas, alur kontrol jelas, dan tanggung jawab jelas. Ia tidak memaksa pembaca mensimulasikan banyak jalur alternatif di kepala mereka.
Kesederhanaan bukan:
Banyak sistem menjadi sulit diubah bukan karena domainnya memang kompleks, melainkan karena kita menambahkan kompleksitas aksidental: flag yang saling berinteraksi dengan cara tak terduga, patch kasus khusus yang tak pernah dihapus, dan lapisan yang ada kebanyakan untuk mengakali keputusan sebelumnya.
Setiap pengecualian tambahan adalah pajak pada pemahaman. Biayanya muncul kemudian, saat seseorang mencoba memperbaiki bug dan menemukan bahwa perubahan di satu area secara halus memutus beberapa area lain.
Saat desain sederhana, kemajuan datang dari kerja bertahap: perubahan yang bisa direview, diff kecil, dan lebih sedikit perbaikan darurat. Tim tidak membutuhkan pengembang “pahlawan” yang mengingat setiap kasus tepi historis atau bisa debug di tekanan jam 2 pagi. Sebaliknya, sistem mendukung rentang perhatian manusia normal.
Uji praktis: jika Anda terus menambah pengecualian (“kecuali…”, “selain ketika…”, “hanya untuk pelanggan ini…”), Anda mungkin menumpuk kompleksitas aksidental. Pilih solusi yang mengurangi percabangan perilaku—satu aturan konsisten mengalahkan lima kasus khusus, bahkan jika aturan konsisten itu sedikit lebih umum dari yang Anda bayangkan awalnya.
Pemrograman terstruktur adalah gagasan sederhana dengan konsekuensi besar: tulis kode sehingga jalur eksekusinya mudah diikuti. Secara sederhana, sebagian besar program bisa dibangun dari tiga blok bangunan—urutan, seleksi, dan pengulangan—tanpa mengandalkan lompatan yang kusut.
if/else, switch).for, while).Saat alur kontrol disusun dari struktur ini, Anda biasanya bisa menjelaskan apa yang program lakukan dengan membaca dari atas ke bawah, tanpa perlu “teleportasi” di file.
Sebelum pemrograman terstruktur menjadi norma, banyak basis kode mengandalkan lompatan sewenang-wenang (gaya goto klasik). Masalahnya bukan bahwa lompatan selalu jahat; masalahnya lompatan tak terbatas menciptakan jalur eksekusi yang sulit diprediksi. Anda akhirnya bertanya “Bagaimana kita sampai di sini?” dan “Dalam keadaan apa variabel ini?”—dan kode tak menjawab dengan jelas.
Alur kontrol yang jelas membantu manusia membangun model mental yang benar. Model itu yang Anda andalkan saat debugging, mereview pull request, atau mengubah perilaku di bawah tekanan waktu.
Saat struktur konsisten, modifikasi menjadi lebih aman: Anda bisa mengubah satu cabang tanpa memengaruhi cabang lain, atau merapikan loop tanpa melewatkan jalur keluar tersembunyi. Keterbacaan bukan sekadar estetika—itu fondasi untuk mengubah perilaku dengan percaya diri tanpa merusak yang sudah bekerja.
Dijkstra mendorong gagasan sederhana: jika Anda bisa menjelaskan mengapa kode itu benar, Anda bisa mengubahnya dengan lebih sedikit ketakutan. Tiga alat penalaran kecil membuat itu praktis—tanpa mengubah tim Anda menjadi matematikawan.
Sebuah invariant adalah fakta yang tetap benar saat sepotong kode berjalan, terutama di dalam loop.
Contoh: Anda menjumlahkan harga di keranjang. Invariant berguna: “total sama dengan jumlah semua item yang sudah diproses.” Jika itu tetap benar tiap langkah, saat loop selesai hasilnya dapat dipercaya.
Invariant kuat karena memusatkan perhatian pada apa yang tidak boleh rusak, bukan hanya apa yang harus terjadi selanjutnya.
Precondition adalah apa yang harus benar sebelum fungsi dijalankan. Postcondition adalah apa yang dijamin fungsi setelah selesai.
Contoh sehari-hari:
Dalam kode, precondition mungkin “daftar input terurut,” dan postcondition mungkin “output terurut dan mengandung elemen yang sama plus yang disisipkan.”
Saat Anda menulis ini (bahkan secara informal), desain menjadi lebih tajam: Anda memutuskan apa yang fungsi harapkan dan jamin, dan secara alami membuatnya lebih kecil dan fokus.
Dalam review, ini menggeser perdebatan dari gaya (“Saya akan menulisnya berbeda”) menuju ketepatan (“Apakah kode ini mempertahankan invariant?” “Apakah kita menegakkan precondition atau mendokumentasikannya?”).
Anda tidak perlu bukti formal untuk mendapatkan manfaatnya. Pilih loop paling bug-prone atau update state tersulit dan tambahkan komentar invariant satu baris di atasnya. Saat seseorang mengedit nanti, komentar itu berfungsi sebagai pembatas: jika perubahan merusak fakta ini, kode tidak lagi aman.
Pengujian dan penalaran bertujuan pada hasil yang sama—perangkat lunak berperilaku sesuai maksud—tetapi bekerja sangat berbeda. Tes menemukan masalah dengan mencoba contoh. Penalaran mencegah kategori masalah dengan membuat logika eksplisit dan dapat diperiksa.
Tes adalah jaring pengaman praktis. Mereka menangkap regresi, memverifikasi skenario dunia nyata, dan mendokumentasikan perilaku yang bisa dijalankan oleh seluruh tim.
Tapi tes hanya bisa menunjukkan adanya bug, bukan ketiadaannya. Tidak ada suite tes yang menutup setiap input, setiap variasi waktu, atau setiap interaksi antar fitur. Banyak kegagalan “jalan di mesin saya” berasal dari kombinasi tak teruji: input langka, urutan operasi spesifik, atau state subtil yang muncul setelah beberapa langkah.
Penalaran soal membuktikan properti kode: “loop ini selalu berakhir,” “variabel ini tidak pernah negatif,” “fungsi ini tidak pernah mengembalikan objek tidak valid.” Bila dilakukan dengan baik, ia menyingkirkan seluruh kelas cacat—khususnya di batas dan kasus tepi.
Batasannya adalah usaha dan cakupan. Bukti formal penuh untuk seluruh produk jarang ekonomis. Penalaran paling efektif bila diterapkan selektif: algoritme inti, alur sensitif keamanan, logika uang/billing, dan konkurensi.
Gunakan tes secara luas, dan terapkan penalaran lebih dalam di tempat kegagalan mahal.
Jembatan praktis antara keduanya adalah menjadikan niat dapat dieksekusi:
Teknik-teknik ini tidak menggantikan tes—mereka mengencangkan jaring. Mereka mengubah ekspektasi samar menjadi aturan yang dapat diperiksa, membuat bug lebih sulit ditulis dan lebih mudah didiagnosis.
Kode “pintar” sering terasa sebagai kemenangan sesaat: lebih sedikit baris, trik rapi, one-liner yang membuat Anda merasa pintar. Masalahnya, kepintaran tidak skala melintasi waktu atau orang. Enam bulan kemudian, penulis lupa triknya. Rekan baru membacanya secara literal, melewatkan asumsi tersembunyi, dan mengubahnya sehingga merusak perilaku. Itu “utang kepintaran”: kecepatan jangka pendek dibayar dengan kebingungan jangka panjang.
Poin Dijkstra bukan “tulis kode membosankan” sebagai preferensi gaya—melainkan bahwa batasan yang disiplin membuat program lebih mudah dinalar. Di tim, batasan juga mengurangi kelelahan pengambilan keputusan. Jika setiap orang sudah tahu default (cara menamai, cara menyusun fungsi, apa yang dianggap “selesai”), Anda berhenti memperdebatkan dasar di setiap pull request. Waktu itu kembali ke pekerjaan produk.
Disiplin muncul dalam praktik rutin:
Beberapa kebiasaan konkret mencegah akumulasi utang kepintaran:
calculate_total() daripada do_it()).Disiplin bukan soal kesempurnaan—melainkan membuat perubahan berikutnya dapat diprediksi.
Modularitas bukan sekadar “membagi kode menjadi file.” Ini mengisolasi keputusan di balik batas yang jelas, sehingga sistem lainnya tidak perlu tahu (atau peduli) tentang detail internal. Modul menyembunyikan bagian berantakan—struktur data, kasus tepi, trik performa—sambil mengekspor permukaan kecil yang stabil.
Saat permintaan perubahan datang, hasil idealnya: satu modul berubah, dan sisanya tetap utuh. Itulah makna praktis “menjaga perubahan lokal.” Batas mencegah keterkaitan tidak sengaja—di mana memperbarui satu fitur diam-diam memutus tiga fitur lain karena mereka berbagi asumsi.
Batas yang baik juga memudahkan penalaran. Jika Anda bisa menyatakan apa yang dijamin sebuah modul, Anda bisa bernalar tentang program lebih besar tanpa membaca ulang seluruh implementasinya setiap kali.
Antarmuka adalah janji: “Dengan input ini, saya akan menghasilkan output ini dan mempertahankan aturan ini.” Saat janji itu jelas, tim bisa bekerja paralel:
Ini bukan soal birokrasi—melainkan menciptakan titik koordinasi aman di basis kode yang tumbuh.
Anda tidak perlu review arsitektur besar untuk meningkatkan modularitas. Coba cek ringan ini:
Batas yang digambar dengan baik mengubah “perubahan” dari kejadian berskala sistem menjadi edit lokal.
Saat perangkat lunak kecil, Anda bisa “mengingat semuanya.” Pada skala, itu berhenti berlaku—dan mode kegagalan menjadi familiar.
Gejala umum tampak seperti ini:
Taruhan inti Dijkstra adalah bahwa manusia adalah hambatan. Alur kontrol yang jelas, unit kecil berdefinisi baik, dan kode yang bisa dinalar bukan pilihan estetika—mereka pengganda kapasitas.
Dalam basis kode besar, struktur berfungsi seperti kompresi pemahaman. Jika fungsi punya input/output eksplisit, modul punya batas yang bisa Anda beri nama, dan jalur “happy path” tidak tercampur dengan setiap kasus tepi, pengembang menghabiskan lebih sedikit waktu menyusun kembali niat dan lebih banyak waktu membuat perubahan yang disengaja.
Saat tim tumbuh, biaya komunikasi naik lebih cepat daripada jumlah baris. Kode yang disiplin dan terbaca mengurangi jumlah pengetahuan tribal yang dibutuhkan untuk berkontribusi dengan aman.
Itu terlihat langsung pada onboarding: insinyur baru bisa mengikuti pola yang dapat diprediksi, mempelajari sekumpulan konvensi kecil, dan membuat perubahan tanpa memerlukan tur panjang "ada-ada." Kode itu sendiri mengajarkan sistem.
Saat insiden, waktu langka dan kepercayaan rapuh. Kode yang ditulis dengan asumsi eksplisit (precondition), pemeriksaan bermakna, dan alur kontrol langsung lebih mudah ditelusuri dalam tekanan.
Sebaliknya, perubahan disiplin lebih mudah di-rollback. Edit kecil, lokal, dengan batas jelas mengurangi kemungkinan rollback memicu kegagalan baru. Hasilnya bukan kesempurnaan—melainkan lebih sedikit kejutan, pemulihan lebih cepat, dan sistem yang tetap dapat dipelihara seiring tahun dan kontributor bertambah.
Poin Dijkstra bukan “tulis kode dengan cara lama.” Poinnya: “tulis kode yang bisa Anda jelaskan.” Anda dapat mengadopsi pola pikir itu tanpa mengubah setiap fitur menjadi bukti formal.
Mulai dengan pilihan yang membuat penalaran murah:
Heuristik yang baik: jika Anda tidak bisa meringkas apa yang dijamin fungsi dalam satu kalimat, kemungkinan fungsi itu melakukan terlalu banyak.
Anda tidak perlu sprint refaktor besar. Tambah struktur di sambungan-sambungan:
isEligibleForRefund).Upgrade ini bersifat inkremental: mereka mengurangi beban kognitif untuk perubahan berikutnya.
Saat mereview (atau menulis) perubahan, tanyakan:
Jika reviewer tidak bisa menjawab cepat, kode memberi sinyal adanya dependensi tersembunyi.
Komentar yang mengulang kode cepat menjadi basi. Sebaliknya, tulis mengapa kode itu benar: asumsi kunci, kasus tepi yang dijaga, dan apa yang akan rusak jika asumsi berubah. Catatan singkat seperti “Invariant: total selalu sama dengan jumlah item yang diproses” bisa lebih berharga daripada paragraf narasi.
Jika Anda ingin tempat ringan untuk menangkap kebiasaan ini, kumpulkan dalam checklist bersama (lihat /blog/practical-checklist-for-disciplined-code).
Tim modern kian memanfaatkan AI untuk mempercepat pengiriman. Risikonya sudah dikenal: kecepatan hari ini bisa berubah menjadi kebingungan besok jika kode yang dihasilkan sulit dijelaskan.
Cara ramah-Dijkstra memakai AI adalah memperlakukannya sebagai akselerator untuk pemikiran terstruktur, bukan pengganti. Contoh, saat membangun di Koder.ai—platform vibe-coding di mana Anda membuat aplikasi web, backend, dan mobile lewat chat—Anda bisa menjaga kebiasaan "reasoning first" dengan membuat prompt dan langkah review eksplisit:
Bahkan jika Anda akhirnya mengekspor kode sumber dan menjalankannya di tempat lain, prinsip yang sama berlaku: kode ter-generate haruslah kode yang bisa Anda jelaskan.
Ini checklist ringan "ramah-Dijkstra" yang bisa Anda gunakan saat review, refaktor, atau sebelum merge. Bukan soal menulis bukti sepanjang hari—melainkan membuat ketepatan dan kejelasan menjadi default.
total selalu sama dengan jumlah item yang diproses” mencegah bug subtil.Pilih satu modul berantakan dan strukturkan alur kontrol terlebih dahulu:
Lalu tambahkan beberapa tes terfokus di sekitar batas baru. Jika Anda ingin pola lebih banyak seperti ini, telusuri posting terkait di /blog.
Karena saat basis kode tumbuh, hambatan utamanya menjadi memahami—bukan mengetik. Penekanan Dijkstra pada alur kontrol yang dapat diprediksi, kontrak yang jelas, dan ketepatan mengurangi risiko bahwa “perubahan kecil” menyebabkan perilaku mengejutkan di tempat lain, yang justru memperlambat tim seiring waktu.
Di konteks ini, “skala” kurang berarti performa dan lebih berarti kompleksitas yang berlipat:
Kekuatan-kekuatan ini membuat kemampuan bernalar dan keterdugaan lebih bernilai daripada kepintaran sesaat.
Pemrograman terstruktur mengutamakan sekumpulan struktur kontrol yang jelas:
if/else, switch)for, while)Tujuannya bukan kaku, melainkan membuat jalur eksekusi mudah diikuti sehingga Anda bisa menjelaskan perilaku, meninjau perubahan, dan men-debug tanpa “melompat-lompat” di file.
Masalahnya adalah lompatan tanpa batas yang menciptakan jalur eksekusi sulit diperkirakan dan keadaan (state) yang tidak jelas. Saat alur kontrol kusut, pengembang buang waktu menjawab pertanyaan dasar seperti “Bagaimana kita sampai ke sini?” dan “Nilai variabel ini dalam keadaan apa?”
Padanan modernnya termasuk branching yang sangat dalam, banyak exit tersebar, dan perubahan state terselubung yang membuat perilaku sulit ditelusuri.
Ketepatan adalah “fitur senyap” yang diandalkan pengguna: sistem konsisten melakukan apa yang dijanjikan dan gagal dengan cara yang dapat diprediksi serta dijelaskan ketika tidak bisa. Ini membedakan antara “berfungsi pada beberapa contoh” dan “tetap berfungsi setelah refaktor, integrasi, dan kasus tepi muncul.”
Karena ketergantungan memperkuat kesalahan. Keadaan yang salah kecil atau bug batas sering disalin, di-cache, di-retry, dibungkus, dan “diakali” di banyak modul dan layanan. Seiring waktu, tim berhenti bertanya “apa yang benar?” dan mulai bergantung pada “apa yang biasanya terjadi,” sehingga insiden menjadi lebih sulit dan perubahan terasa berisiko.
Kesederhanaan di sini berarti sedikit gagasan yang bergerak sekaligus: tanggung jawab jelas, aliran data jelas, dan sedikit kasus khusus. Bukan soal baris kode lebih sedikit atau satu-liner pintar.
Uji yang baik: apakah perilaku tetap mudah diprediksi saat kebutuhan berubah? Jika setiap kasus baru menambah aturan “kecuali…”, Anda menumpuk kompleksitas aksidental.
Invariant adalah fakta yang harus tetap benar selama loop atau transisi state. Cara ringan menggunakannya:
total sama dengan jumlah item yang sudah diproses”)Ini membuat perubahan berikutnya lebih aman karena orang selanjutnya tahu apa yang tidak boleh rusak.
Pengujian menemukan bug dengan mencoba contoh; penalaran mencegah kategori bug tertentu dengan membuat logika eksplisit. Tes tidak bisa membuktikan tidak ada cacat karena tidak mungkin menutup semua input atau waktu. Penalaran sangat berharga untuk area berbiaya tinggi (uang, keamanan, konkurensi).
Perpaduan praktis: tes luas + assertion terarah + precondition/postcondition jelas di sekitar logika kritis.
Mulai dari langkah kecil dan dapat diulang yang mengurangi beban kognitif:
Itu upgrade struktur inkremental yang membuat perubahan berikutnya lebih murah tanpa perlu rewrite besar.