Topik ini mendokumentasikan API lapisan skrip Pencatat sehingga pembuat skrip, baik pengguna aplikasi maupun pengembang host, dapat menggunakan mesin tanpa membaca implementasinya. Dokumentasi disimpan bersama kode dalam bentuk komentar kepala per fungsi, dirangkum menjadi tabel API, dan ditutup dengan dokumentasi skema konfigurasi yang menjadi sumber kebenaran bagi pembuat berkas data.
Komentar kepala per fungsi#
Konvensi dokumentasi proyek ini menaruh komentar lengkap di atas setiap fungsi publik, dengan tiga bagian tetap, yaitu tujuan, parameter, dan nilai balik. Bentuk yang sama juga diberikan pada fungsi internal yang sulit dibaca, karena pembaca implementasi berikutnya berhak memperoleh penjelasan yang sama. Komentar ditulis dengan tanda hubung ganda Lua sehingga berkas tetap dapat dijalankan tanpa alat tambahan, dan tampilan teksnya tidak terpisah dari kodenya.
Berkas berikut adalah versi mesin dengan komentar dokumentasi lengkap. Perilakunya sama dengan versi pada komponen permukaan error, sehingga yang berubah hanyalah keterbacaannya.
-- mesin.lua
-- Mesin konfigurasi aplikasi Pencatat.
--
-- Modul ini menjadi lapisan skrip yang dibaca host Pencatat. Versi ini
-- menyertakan komentar dokumentasi lengkap pada seluruh fungsi publiknya.
-- Perilakunya sama dengan versi yang menyertakan muat_aman.
--
-- Ringkasan API:
-- mesin.muat_konfigurasi(kandidat) -> final, pelanggaran
-- mesin.muat_aman(kandidat) -> sukses, hasil
-- mesin.tampilkan(final) -> nil
local mesin = {}
-- Skema konfigurasi, yaitu tiga grup kunci sah beserta tipe, default, dan batas.
-- Table ini menjadi sumber kebenaran perilaku bagi dokumentasi skema.
local SKEMA = {
tema = {
nama = { tipe = "string", default = "gelap" },
warna = { tipe = "string", default = "#1e1e2e" },
ukuran_teks = { tipe = "integer", default = 14, minimum = 8, maksimum = 32 },
},
aturan = {
batas_panjang = { tipe = "integer", default = 1000, minimum = 1, maksimum = 100000 },
pola_berkas = { tipe = "string", default = "catatan%d+" },
},
konten = {
label = { tipe = "string", default = "Catatan" },
},
}
--- Memetakan nama tipe skema menjadi sebutan yang tampil pada pesan.
--
-- Tujuan : menjaga pesan pelanggaran tetap berbahasa yang konsisten.
-- Parameter : tipe, string tipe pada skema seperti "string" atau "integer".
-- Nilai balik: string sebutan tipe, misalnya "bilangan bulat" untuk integer.
local function nama_tipe(tipe)
if tipe == "integer" then
return "bilangan bulat"
end
return tipe
end
--- Memetakan sebuah nilai menjadi nama tipenya untuk pesan pelanggaran.
--
-- Tujuan : melaporkan tipe yang diterima secara tepat, termasuk
-- pembedaan bilangan bulat dan bilangan pecahan.
-- Parameter : nilai, nilai kandidat yang diperiksa.
-- Nilai balik: string nama tipe nilai tersebut.
local function nama_tipe_nilai(nilai)
local t = type(nilai)
if t == "number" then
return math.type(nilai) == "integer" and "bilangan bulat" or "bilangan pecahan"
end
return t
end
--- Memeriksa kesesuaian tipe nilai terhadap aturan skemanya.
--
-- Tujuan : memastikan nilai berada pada tipe yang diwajibkan skema,
-- termasuk kewajiban bilangan bulat untuk tipe integer.
-- Parameter : aturan, entri skema satu kunci, dan nilai yang diperiksa.
-- Nilai balik: true bila tipe sesuai, dan false bila tidak.
local function tipe_sesuai(aturan, nilai)
if aturan.tipe == "integer" then
return type(nilai) == "number" and math.type(nilai) == "integer"
end
return type(nilai) == aturan.tipe
end
--- Memvalidasi satu nilai terhadap aturan skemanya.
--
-- Tujuan : menghasilkan teks pelanggaran berkode TIPE_SALAH atau
-- NILAI_DI_LUAR_BATAS bila nilai tidak sah.
-- Parameter : jalur, nama kunci lengkap seperti "tema.nama", aturan,
-- entri skema kunci tersebut, dan nilai yang diperiksa.
-- Nilai balik: string pelanggaran bila nilai tidak sah, atau nil bila sah.
local function validasi_kunci(jalur, aturan, nilai)
if not tipe_sesuai(aturan, nilai) then
return string.format(
"TIPE_SALAH: kunci %s seharusnya bertipe %s, tetapi bertipe %s",
jalur, nama_tipe(aturan.tipe), nama_tipe_nilai(nilai)
)
end
if aturan.tipe == "integer" and (nilai < aturan.minimum or nilai > aturan.maksimum) then
return string.format(
"NILAI_DI_LUAR_BATAS: kunci %s seharusnya bernilai antara %d dan %d",
jalur, aturan.minimum, aturan.maksimum
)
end
return nil
end
--- Memuat dan memvalidasi kandidat konfigurasi terhadap skema.
--
-- Tujuan : menggabungkan nilai default skema dengan kandidat sehingga
-- konfigurasi final selalu lengkap dan seluruh nilainya sah.
-- Parameter : kandidat, table bersarang dengan grup tema, aturan, dan
-- konten. Table kosong diperlakukan sebagai kandidat tanpa
-- preferensi, sedangkan nilai selain table mengangkat error.
-- Nilai balik: final, table konfigurasi lengkap, dan pelanggaran, yaitu
-- array string pelanggaran yang sudah diurutkan.
-- Contoh : local final, pelanggaran =
-- mesin.muat_konfigurasi({ tema = { nama = "terang" } })
function mesin.muat_konfigurasi(kandidat)
if type(kandidat) ~= "table" then
error({ kode = "KANDIDAT_TIDAK_SAH",
pesan = string.format(
"kandidat konfigurasi seharusnya table, tetapi bertipe %s", type(kandidat)
) }, 0)
end
local final = {}
local pelanggaran = {}
for nama_grup, skema_grup in pairs(SKEMA) do
final[nama_grup] = {}
local grup = kandidat[nama_grup]
if grup ~= nil and type(grup) ~= "table" then
table.insert(pelanggaran, string.format(
"TIPE_SALAH: grup %s seharusnya table, tetapi bertipe %s",
nama_grup, nama_tipe_nilai(grup)))
grup = nil
end
for nama_kunci, aturan in pairs(skema_grup) do
local nilai = nil
if grup ~= nil then
nilai = grup[nama_kunci]
end
if nilai == nil then
final[nama_grup][nama_kunci] = aturan.default
else
local temuan = validasi_kunci(nama_grup .. "." .. nama_kunci, aturan, nilai)
if temuan == nil then
final[nama_grup][nama_kunci] = nilai
else
table.insert(pelanggaran, temuan)
final[nama_grup][nama_kunci] = aturan.default
end
end
end
end
for nama_grup, grup in pairs(kandidat) do
if SKEMA[nama_grup] == nil then
table.insert(pelanggaran, string.format(
"KUNCI_TAK_DIKENAL: grup %s tidak dikenal", nama_grup))
elseif type(grup) == "table" then
for nama_kunci in pairs(grup) do
if SKEMA[nama_grup][nama_kunci] == nil then
table.insert(pelanggaran, string.format(
"KUNCI_TAK_DIKENAL: kunci %s.%s tidak dikenal", nama_grup, nama_kunci))
end
end
end
end
table.sort(pelanggaran)
return final, pelanggaran
end
--- Membungkus muat_konfigurasi dengan pcall agar gagal tidak menyebar.
--
-- Tujuan : menjaga tepi lapisan skrip, yaitu fungsi yang dipanggil
-- host, agar tidak pernah mengangkat error yang menyebar
-- ke host.
-- Parameter : kandidat, sama seperti parameter muat_konfigurasi.
-- Nilai balik: sukses, true bila pelaksanaan normal dan false bila gagal,
-- serta hasil, table konfigurasi final bila sukses dan table
-- error terstruktur { kode = "...", pesan = "..." } bila gagal.
-- Contoh : local sukses, hasil = mesin.muat_aman(nil)
function mesin.muat_aman(kandidat)
local ok, hasil = pcall(mesin.muat_konfigurasi, kandidat)
if ok then
return true, hasil
end
if type(hasil) == "table" and hasil.kode ~= nil then
return false, hasil
end
return false, { kode = "ERROR_TAK_TERDUGA", pesan = tostring(hasil) }
end
--- Mencetak laporan ringkas konfigurasi final.
--
-- Tujuan : menampilkan enam kunci konfigurasi final dalam satu baris
-- per kunci dengan kolom yang sejajar untuk keperluan
-- demonstrasi dan pemeriksaan cepat.
-- Parameter : final, table hasil muat_konfigurasi atau muat_aman.
-- Nilai balik: nil, karena keluarannya berupa teks yang dicetak.
-- Contoh : mesin.tampilkan(final)
function mesin.tampilkan(final)
print(string.format("%-22s %s", "tema.nama", final.tema.nama))
print(string.format("%-22s %s", "tema.warna", final.tema.warna))
print(string.format("%-22s %s", "tema.ukuran_teks", final.tema.ukuran_teks))
print(string.format("%-22s %s", "aturan.batas_panjang", final.aturan.batas_panjang))
print(string.format("%-22s %s", "aturan.pola_berkas", final.aturan.pola_berkas))
print(string.format("%-22s %s", "konten.label", final.konten.label))
end
return mesinContohnya, komentar kepala fungsi muat_aman menyebutkan bahwa fungsi itu membungkus muat_konfigurasi dengan pcall dan mengembalikan pasangan status dan hasil, beserta bentuk objek errornya. Kepala modul juga memuat ringkasan API dalam tiga baris, sehingga pembuka berkas memperoleh gambaran utuh sebelum masuk ke rincian. Ringkasan semacam ini menjadi tempat pertama yang dibaca pengguna baru, dan tempat pertama yang diperbarui saat API berubah.
Tabel ringkas API#
Tabel berikut merangkum API publik mesin, dan tabel semacam ini biasanya menjadi bagian yang paling sering dirujuk pembuat skrip.
| Fungsi | Parameter | Nilai balik | Perilaku gagal |
|---|---|---|---|
muat_konfigurasi(kandidat) | table kandidat konfigurasi | final, pelanggaran | mengangkat error terstruktur { kode = "KANDIDAT_TIDAK_SAH", pesan = "..." } bila kandidat bukan table |
muat_aman(kandidat) | table kandidat konfigurasi | sukses, hasil | selalu mengembalikan status, kegagalan berupa { kode, pesan } dan tidak pernah mengangkat error |
tampilkan(final) | table konfigurasi final | tidak ada | mencetak laporan ringkas dan tidak mengangkat error |
Kolom perilaku gagal sengaja disertakan karena kontrak kegagalan sama pentingnya dengan kontrak keberhasilan. Pembuat skrip yang membaca tabel ini langsung tahu bahwa muat_aman aman dipanggil tanpa pelindung, sedangkan muat_konfigurasi perlu dibungkus dengan pcall bila bentuk kandidatnya tidak terjamin. Kebiasaan mendokumentasikan perilaku gagal juga dipraktikkan dokumentasi API Neovim dan Redis, yang menyebutkan kode dan bentuk error pada halaman fungsi masing-masing.
Dokumentasi skema konfigurasi#
Skema konfigurasi didokumentasikan sebagai tabel terpisah, karena pembuat berkas data tidak menyentuh kode mesin sama sekali. Skema pada mesin.lua menjadi sumber kebenaran, dan tabel berikut mengikuti skema tersebut.
| Kunci | Tipe | Default | Keterangan | Contoh |
|---|---|---|---|---|
tema.nama | string | gelap | nama tema tampilan yang dipilih | terang |
tema.warna | string | #1e1e2e | warna aksen dalam notasi heksadesimal | #f7f7f7 |
tema.ukuran_teks | integer | 14 | ukuran teks antarmuka, dari 8 sampai 32 | 15 |
aturan.batas_panjang | integer | 1000 | batas panjang catatan dalam karakter, dari 1 sampai 100000 | 500 |
aturan.pola_berkas | string | catatan%d+ | pola penamaan berkas catatan yang sah | catatan%d+ |
konten.label | string | Catatan | label antarmuka pada judul aplikasi | Catatan Harian |
Tabel ini juga menjadi tempat mencatat batas nilai, sehingga pembuat berkas data mengetahui batas ukuran_teks dan batas_panjang tanpa mencoba-coba. Saat skema berubah, pembaruan tabel ini wajib dilakukan bersama perubahan skemanya, dan pengujian pada komponen pengujian akan menangkap ketidaksesuaian perilaku yang paling terlihat.
Memverifikasi dokumentasi dengan pemakaian nyata#
Dokumentasi tidak berarti bila contoh pemakaiannya tidak berjalan. Potongan berikut menggunakan API persis sebagaimana tertulis pada tabel di atas, dan keluarannya dapat dibandingkan dengan janji dokumentasinya.
local mesin = dofile("mesin.lua")
local sukses, hasil = mesin.muat_aman({
tema = { nama = "terang" },
aturan = { batas_panjang = 500 },
})
print(string.format("sukses : %s", tostring(sukses)))
mesin.tampilkan(hasil)sukses : true
tema.nama terang
tema.warna #1e1e2e
tema.ukuran_teks 14
aturan.batas_panjang 500
aturan.pola_berkas catatan%d+
konten.label CatatanKeluaran memperlihatkan kontrak yang dijanjikan: muat_aman mengembalikan status true bersama table final, kunci yang tidak disebut terisi nilai bawaan, dan tampilkan mencetak seluruh kunci dengan urutan yang tetap. Contoh pemakaian yang dieksekusi seperti ini sebaiknya dijalankan ulang setiap kali dokumentasi diperbarui, sebagaimana contoh kode pada dokumentasi Redis dan Neovim yang selalu menyertakan pemanggilan nyata.
Kesalahan umum#
Kesalahan pertama adalah dokumentasi yang tidak diperbarui bersama kode. Komentar yang menyebut parameter yang sudah dihapus atau nilai balik yang sudah berubah lebih merugikan daripada tanpa komentar, karena pembaca mempercayainya dan menulis skrip yang salah. Kebiasaan yang menjaganya sederhana, yaitu memperlakukan pembaruan tanda tangan fungsi dan pembaruan komentarnya sebagai satu perubahan yang tidak terpisahkan.
Kesalahan kedua adalah contoh pemakaian yang tidak pernah dijalankan. Contoh yang tampak masuk akal tetapi salah nama fungsi atau salah urutan nilai balik menyesatkan pembuat skrip baru, dan kesalahan ini sering lolos karena tidak ada alat yang memeriksanya. Contoh pada topik ini dijalankan sungguhan dan keluarannya dicantumkan, sehingga setiap pembaruan dokumentasi dapat diverifikasi ulang dengan cepat.
Kesalahan ketiga adalah komentar yang mengulang nama fungsi tanpa menjelaskan perilakunya. Komentar berbentuk -- fungsi muat_aman tidak menambah pengetahuan apa pun di atas tanda tangannya. Komentar yang berguna menyatakan kontraknya, yaitu apa yang dijanjikan fungsi tersebut, bentuk kegagalannya, dan batasan yang tidak terlihat dari tanda tangan, sebagaimana komentar kepala pada versi mesin di atas.
Ringkasan#
- Dokumentasi API disimpan bersama kode sebagai komentar kepala per fungsi dengan bagian tetap, yaitu tujuan, parameter, dan nilai balik, ditambah ringkasan API di kepala modul.
- Tabel ringkas API memuat kolom perilaku gagal, karena kontrak kegagalan menentukan cara pemanggil melindungi dirinya.
- Skema konfigurasi didokumentasikan sebagai tabel kunci, tipe, default, keterangan, dan contoh yang mengikuti skema pada kode sebagai sumber kebenaran.
- Contoh pemakaian dieksekusi sungguhan dan keluarannya dibandingkan dengan janji dokumentasi, mengikuti kebiasaan dokumentasi API Neovim dan Redis.
Referensi#
- Lua guide — Neovim — Panduan resmi pemakaian Lua pada Neovim sebagai contoh dokumentasi API lapisan skrip host.
- Redis Lua API reference — Redis — Dokumentasi API skrip Redis yang menyebutkan batasan dan perilaku kegagalan pada setiap fungsi.
- Manual Referensi Lua 5.4 — Lua.org
— Bagian 6.4 tentang modul dan paket sebagai rujukan bentuk modul
mesinyang didokumentasikan.