Dokumen, test, dan kode Anda adalah tiga file yang menceritakan kisah yang sama dengan buruk.

Anda memperbarui function signature di source. Anda lupa contoh README. Seminggu kemudian, karyawan baru menyalin snippet usang ke produksi. File test Anda masih mengkodekan perilaku lama sebagai hasil yang diharapkan. Sekarang Anda memiliki dua bug dan tiket dokumentasi.

Ini adalah masalah sinkronisasi. Setiap codebase memilikinya. Sebagian besar tim menyelesaikannya dengan disiplin, yang gagal saat seseorang tergesa-gesa.

Literate programming memperbaikinya dengan menjadikan file prosa sebagai sumber kebenaran. Anda menulis dokumen Markdown yang dibaca manusia. Alat kecil mengekstrak code block ke file Python yang dapat dijalankan. Test, implementasi, dan penjelasan semua tinggal di satu tempat. Ubah prosa, dan Anda mengubah kode.

Apa itu literate programming?

Donald Knuth mencetuskan istilah ini pada tahun 1984. Idinya sederhana: tulis program seperti Anda menulis esai. Jelaskan logikanya dalam bahasa alami, selipkan kodenya, dan biarkan alat bernama tangle mengekstrak source yang dapat dieksekusi sementara weave menghasilkan dokumentasi yang diformat.

Alat klasik, seperti sistem WEB Knuth, terikat erat dengan Pascal dan TeX. Alat tersebut tidak pernah populer dalam pengembangan software mainstream. Workflow terasa asing, toolingnya berat, dan sebagian besar programmer lebih suka membaca kode di IDE, bukan di PDF.

Namun wawasan intinya masih valid. Prosa dan kode harus berbagi single source of truth. Bahasa modern dan Markdown membuat ini lebih mudah dari yang bisa Knuth bayangkan. Anda tidak memerlukan compiler khusus. Anda membutuhkan sekitar tiga puluh baris Python dan parser Markdown.

Bagaimana satu file Markdown menjadi test suite

Mekanismenya mudah. Anda menulis file .md dengan fenced code blocks. Komentar header di dalam setiap block memberi tahu extractor file mana yang menjadi tujuannya. Extractor menggabungkan block dengan header yang sama menjadi satu module Python. Kemudian Anda menjalankan pytest pada hasilnya.

Berikut contoh lengkapnya. Simpan sebagai calculator.md:

# Calculator: addition and its properties

The `add` function is trivial. That is the point. Even trivial code deserves context about why it exists and what invariants it preserves.

```python
# calc.py
def add(a: int, b: int) -> int:
    """Return the sum of two integers."""
    return a + b

Addition commutes. We verify this explicitly because it is a property future maintainers might break by accident if they switch to a different implementation.

# test_calc.py
from calc import add

def test_addition_commutes():
    assert add(2, 3) == add(3, 2)

def test_identity():
    assert add(7, 0) == 7

Komentar `# calc.py` dan `# test_calc.py` bukanlah sintaksis ajaib. Mereka adalah konvensi yang dipahami skrip kita.

## Skrip ekstraksi

Ini adalah seluruh alatnya. Simpan sebagai `tangle.py`:

```python
import re
import tempfile
import subprocess
from pathlib import Path


def tangle(md_path: Path, out_dir: Path) -> Path:
    """Extract code blocks from Markdown into runnable Python files."""
    content = md_path.read_text()
    files: dict[str, list[str]] = {}

    # Extract all python code blocks
    for block in re.findall(r"```python\n(.*?)```", content, re.DOTALL):
        lines = block.strip().split("\n")

        # First line like '# filename.py' sets the target file
        if lines and lines[0].startswith("# "):
            filename = lines[0][2:].strip()
            code = lines[1:]
        else:
            filename = "module.py"
            code = lines

        files.setdefault(filename, []).extend(code)

    # Write extracted files
    out_dir.mkdir(parents=True, exist_ok=True)
    for filename, code_lines in files.items():
        (out_dir / filename).write_text("\n".join(code_lines) + "\n")

    return out_dir


if __name__ == "__main__":
    out = tangle(Path("calculator.md"), Path("build"))
    result = subprocess.run(
        ["python", "-m", "pytest", str(out), "-v"],
        capture_output=False,
    )
    raise SystemExit(result.returncode)

Jalankan:

$ python tangle.py
============================= test session starts ==============================
build/test_calc.py::test_addition_commutes PASSED
build/test_calc.py::test_identity PASSED
============================== 2 passed in 0.01s

Direktori build/ sekarang berisi calc.py dan test_calc.py. Anda dapat mengimpornya, melakukan type-check, atau mengirimkannya sebagai package. File Markdown adalah sumber kanonik. Segala sesuatu yang lain dibuat secara otomatis.

Kapan satu file membantu, dan kapan merugikan

Pendekatan ini unggul untuk library, algoritma, dan apa pun di mana mengapa sama pentingnya dengan apa. Dokumentasi API, kode penelitian, dan pipeline konfigurasi semuanya mendapat manfaat dari prosa yang tetap terkait dengan implementasi.

Ini tidak unggul untuk kode aplikasi yang banyak boilerplate-nya. Sebuah Django view dengan lima belas decorator import tidak memerlukan esai. Jika module Anda sebagian besar adalah framework plumbing, overhead dari struktur literate menambah gesekan tanpa kejelasan.

Keterbatasan lainnya adalah dukungan alat. IDE mengharapkan menemukan kode Anda di file .py. Jump-to-definition, inline linting, dan autocomplete semuanya memerlukan file yang dibuat sebelumnya agar berfungsi. Anda dapat mengatasinya dengan menjalankan tangle.py sebagai pre-commit hook atau sebagai bagian dari build step. Namun ini adalah langkah tambahan. Jika tim Anda sudah kesulitan dengan kompleksitas build, menambahkan custom extraction pipeline mungkin tidak sepadan.

Alat nyata yang melakukan ini

Skrip tiga puluh baris di atas cukup untuk memulai. Jika Anda menginginkan sesuatu yang production-ready, ada opsi yang matang.

Entangled adalah alat literate programming modern yang bekerja dengan bahasa apa pun. Ini menggunakan sintaksis yang sedikit berbeda, tetapi idenya identik. Ini melacak dependency antar code block dan mendukung banyak file output.

Jupyter notebooks memecahkan masalah serupa untuk data science. Mereka mencampur prosa, kode, dan output dalam satu file. Kekurangannya adalah notebook sangat buruk untuk version control. Diff tidak terbaca, dan konflik merge sering terjadi.

Org-mode dengan Babel adalah implementasi paling kuat. Jika Anda sudah tinggal di Emacs, ini tak tertandingi. Jika tidak, kurva pembelajarannya curam.

Untuk sebagian besar tim, extractor Markdown sederhana adalah jalan tengah yang pragmatis. Ini menggunakan alat yang sudah dipahami semua orang.

FAQ

Apakah ini berfungsi dengan type checker?

Ya. Buat file .py terlebih dahulu, lalu jalankan mypy atau pyright terhadap direktori build.

Bagaimana dengan bahasa selain Python?

Skrip tangle.py bersifat language-agnostic. Ubah regex dari python menjadi rust atau go dan itu berfungsi sama. Anda bahkan dapat mencampur bahasa dalam satu dokumen.

Bagaimana cara menangani import di seluruh block?

Extractor menggabungkan semua block dengan nama file yang sama dalam urutan kemunculannya di Markdown. Simpan import Anda di block pertama untuk file tersebut, atau ulangi. Python menangani import duplikat dengan anggun.

Mulai dengan satu module

Anda tidak perlu menulis ulang seluruh codebase. Pilih satu module kecil di mana dokumentasi selalu tertinggal dari kode. Konversikan ke file Markdown, tambahkan skrip ekstraksi ke CI pipeline Anda, dan jalankan test terhadap output yang dihasilkan.

Jika dokumen mulai tetap terkini tanpa pengingat, Anda akan tahu bahwa pendekatan ini berfungsi. Jika terasa seperti overhead, tinggalkan saja. Literate programming adalah alat, bukan agama. Tujuannya bukan untuk mengesankan Knuth. Tujuannya adalah berhenti berbohong pada diri sendiri dalam tiga file yang berbeda.