Ide Terbaik yang Tidak Pernah Berhasil

Pada tahun 1984, Donald Knuth menerbitkan sebuah makalah yang mengusulkan pembalikan radikal. Program tidak boleh ditulis untuk compiler dan dianotasi untuk manusia. Program harus ditulis sebagai sastra untuk manusia, dari mana compiler mengekstrak bagian yang dapat dieksekusi. Ia menyebutnya literate programming, dan ia membangun TeX dengan cara ini.

Idenya indah. Namun idenya juga hampir sepenuhnya tidak ada dalam pengembangan perangkat lunak modern.

TeX tetap menjadi salah satu program besar yang paling andal dan paling dipahami yang pernah ditulis. Keberhasilan itu sebagian karena Knuth menulisnya sebagai literate program. Jadi pertanyaannya bukan apakah literate programming dapat berfungsi. Jelas bisa. Pertanyaannya adalah mengapa itu berhasil untuk Knuth dan hampir tidak ada orang lain.

Apa yang Sebenarnya Dimaksud dengan Literate Programming

Sistem WEB Knuth, kemudian CWEB untuk C, bekerja dengan membagi satu file sumber menjadi dua output berbeda. Anda menulis dokumen narasi yang menjelaskan apa yang dilakukan program, mengapa melakukannya, dan bagaimana bagian-bagiannya menyatu. Tertanam di dalam narasi tersebut adalah fragmen kode.

Tool tangle mengekstrak fragmen kode, mengurutkannya untuk compiler, dan menghasilkan file yang dapat dijalankan oleh mesin. Tool weave mengekstrak prosa, memformat fragmen kode sebagai blok yang dicetak dengan rapi, dan menghasilkan dokumen yang dapat dibaca manusia.

Berikut ini tampilan program CWEB kecil dalam praktiknya:

@* Introduction. This program computes the sum of a list of integers.
The algorithm is straightforward: iterate through the array and accumulate.

@c
#include <stdio.h>

@<Function prototypes@>;
@<Main program@>;

@*2 The core logic.
We define |sum_array| to take a pointer and a length, returning the total.

@<Function definitions@>=
int sum_array(const int *arr, size_t n) {
    int total = 0;
    for (size_t i = 0; i < n; i++) {
        total += arr[i];
    }
    return total;
}

@*2 Entry point.
A small driver to exercise the function.

@<Main program@>=
int main(void) {
    int data[] = {1, 2, 3, 4, 5};
    size_t len = sizeof(data) / sizeof(data[0]);
    printf("%d\n", sum_array(data, len));
    return 0;
}

Perhatikan apa yang terjadi. Kode tidak diatur berdasarkan urutan kompilasi. Kode diatur berdasarkan logika narasi. Penjelasan didahulukan. Implementasi mengikuti ketika pembaca siap menerimanya. Sintaks @<Main program@> adalah chunk bernama yang dapat didefinisikan di mana saja dalam dokumen dan dirakit oleh tangle ke dalam urutan yang benar untuk compiler.

Ini adalah gagasan intinya: manusia membaca penjelasan top-down. Compiler mendapatkan grafik dependency bottom-up. Keduanya mendapatkan format yang mereka sukai.

Mengapa Berhasil untuk Knuth

Knuth bukan programmer biasa. Ia adalah matematikawan dan penulis yang kebetulan menulis program. Ia mendesain algoritma di atas kertas selama berbulan-bulan sebelum menyentuh keyboard. Ia menulis buku yang masih dibaca beberapa dekade kemudian. Ketika Knuth mengatakan kode harus menjadi sastra, ia bermaksud sastra yang sebenarnya, dan ia memiliki keahlian untuk mewujudkannya.

TeX juga merupakan kandidat yang sempurna. Itu adalah program batch dengan spesifikasi yang stabil. Inti algoritmik, terutama rutin pemenggalan baris dan pemenggalan halaman, mendapat manfaat besar dari penjelasan matematika yang diperpanjang. Kodenya berubah perlahan. Dokumentasinya lebih lama dari implementasinya.

Sebagian besar perangkat lunak tidak seperti ini. Sebagian besar perangkat lunak berubah setiap hari. Spesifikasinya ditemukan, bukan didesain. Audiensnya bukan pembaca yang mencoba memahami algoritma. Itu adalah developer yang mencoba memperbaiki bug sebelum standup.

Masalah Pengeditan

Literate programming mengasumsikan proses penulisan. Sebagian besar pemrograman adalah proses pengeditan.

Ketika Anda menulis esai, Anda menyusun, merevisi, dan memoles. Strukturnya direncanakan. Ketika Anda menulis perangkat lunak, Anda menjelajahi, menguji, merefactor, dan mengirimkan. Strukturnya muncul. Dokumentasi dalam literate program bukanlah lapisan yang Anda terapkan setelahnya. Itu adalah struktur utama. Setiap kali Anda mengganti nama variabel, mengekstrak fungsi, atau menyusun ulang logika, Anda menulis ulang sastranya.

Ini menciptakan siklus yang merusak. Jika prosa erat dengan kode, refactoring menjadi mahal. Jika prosa longgar, prosa menyimpang dari kode dan menjadi salah. Bagaimanapun, dokumentasi membusuk.

Version control modern memperburuk keadaan. Sebuah literate program adalah dokumen narasi tunggal. Sebuah pull request adalah diff terhadap pohon sumber. Tool review kode memahami baris di main.c. Mereka tidak memahami chunk naratif di main.w. Ekosistem tooling, dari syntax highlighting hingga analisis statis hingga pipeline CI, mengharapkan file sumber yang dapat dikompilasi. Literate programming mengharuskan Anda memilih keluar dari semuanya.

Ketidakcocokan Keterampilan

Knuth mengasumsikan bahwa programmer juga penulis. Sebagian besar tidak.

Penulisan teknis yang baik jarang karena sulit. Itu membutuhkan empati untuk pembaca yang belum memahami apa yang Anda pahami. Itu membutuhkan disiplin untuk menjelaskan mengapa, bukan hanya apa. Itu membutuhkan pengeditan, yang merupakan keterampilan terpisah dari pengkodean.

Ketika Anda meminta tim untuk menulis literate programs, Anda meminta mereka menjadi Knuth. Anda meminta prosa yang diperpanjang yang menjelaskan maksud, mengeksplorasi alternatif, dan membimbing pembaca melalui penalaran. Komentar kode rata-rata adalah // TODO: fix this. Kesenjangan antara itu dan sastra bukan masalah tooling. Itu adalah masalah manusia.

Inilah sebabnya mengapa upaya untuk menghidupkan kembali literate programming dengan tool yang lebih baik secara konsisten gagal. Bottlenecknya tidak pernah adalah toolchain weave dan tangle. Bottlenecknya adalah bahwa sebagian besar programmer tidak ingin menulis esai, dan sebagian besar codebase tidak memberi imbalan pada dokumentasi berkualitas esai.

Apa yang Sebenarnya Menggantikannya

Industri tidak meninggalkan tujuan Knuth. Industri mencapai versi yang lebih lemah darinya melalui cara yang berbeda.

Sistem tipe sekarang mengenkode maksud yang dulu memerlukan paragraf penjelasan. Ketika fungsi menerima NonEmptyList<T> alih-alih List<T>, type checker menegakkan jaminan yang hanya bisa dideskripsikan oleh dokumentasi. Ketika borrow checker Rust menolak escape referensi, itu mengkomunikasikan batasan yang akan membutuhkan halaman untuk dijelaskan dalam prosa.

Docstrings dan tool dokumentasi API seperti Javadoc, Rustdoc, dan TypeDoc menciptakan jalan tengah. Mereka menjaga dokumentasi berdampingan dengan kode tanpa mengharuskan dokumentasi untuk menggerakkan struktur. Anda dapat membaca sumber atau dokumen yang dihasilkan, dan keduanya tetap sinkron karena berada dalam file yang sama.

Test suite menjadi dokumentasi yang dapat dieksekusi. Test yang ditulis dengan baik mengatakan “diberikan input ini, harapkan output ini” lebih tepat daripada prosa yang pernah bisa. Test property-based mengenkode invarian. Test snapshot menangkap maksud. Test berjalan di setiap commit, sehingga tidak bisa menyimpang seperti prosa.

Mungkin yang paling penting, bahasa pemrograman itu sendiri menjadi lebih literer. Python dibaca seperti pseudocode. Sintaks if let Rust mengekspresikan maksud pattern matching secara langsung. Ketika kode adalah penjelasan, Anda tidak membutuhkan penjelasan terpisah dari kode.

Versi Modern yang Memang Berhasil

Ada satu tempat di mana visi Knuth bertahan hampir utuh: computational notebooks.

Jupyter notebooks, R Markdown, dan Observable memungkinkan prosa dan kode untuk hidup berdampingan dalam satu dokumen. Prosa menjelaskan. Kode dieksekusi. Output muncul inline. Ini adalah literate programming dalam segala hal kecuali nama.

Notebook berhasil di mana WEB gagal karena konteksnya berbeda. Seorang data scientist yang menjelajahi dataset terlibat dalam proses naratif. Kode berubah perlahan. Audiensnya adalah pembaca manusia yang mencoba memahami keputusan. Siklus pengeditan adalah eksplorasi, bukan refactoring. Batasan yang membunuh literate programming dalam rekayasa perangkat lunak adalah fitur dalam data science.

Pelajarannya bukan bahwa literate programming salah. Pelajarannya adalah ide tersebut memiliki niche ekologis yang sempit. Ia berkembang ketika program adalah artefak yang selesai yang dimaksudkan untuk dipelajari. Ia tercekik ketika program adalah sistem hidup yang dimaksudkan untuk diubah.

Apa yang Perlu Diambil Darinya

Anda mungkin tidak akan mengadopsi CWEB untuk layanan Anda berikutnya. Tidak apa-apa. Tetapi wawasan mendasar Knuth masih layak diterapkan.

Beri nama sesuatu agar mereka menjelaskan diri mereka sendiri. Fungsi bernama process_data adalah kegagalan literate programming bahkan jika Anda tidak pernah menyentuh WEB. Fungsi bernama remove_expired_sessions_older_than membawa dokumentasinya sendiri.

Tulis penjelasan yang Anda harap Anda miliki. Jika bagian kode memerlukan paragraf prosa untuk dipahami, tulis paragrafnya. Taruh di komentar, docstring, atau dokumen desain. Mediumnya kurang penting daripada tindakan menjelaskan.

Pisahkan struktur naratif dari struktur compiler ketika itu membantu. README-driven development, architecture decision records, dan RFC adalah cara-cara untuk menulis cerita yang menghadap manusia tanpa melawan toolchain Anda.

Knuth ingin kita menulis program seperti buku. Kita akhirnya menulis buku tentang program. Itu adalah kemenangan yang lebih lemah dari yang ia harapkan, tetapi itu masih merupakan kemenangan.