Membangun dengan Checkpoint

Membangun proyek dengan checkpoint pengujian, benchmark, dan dokumentasi di setiap tahap.

Daftar isi

Checkpoint adalah titik pemeriksaan yang wajib lulus sebelum tahap pengerjaan berikutnya dimulai. Topik ini membahas siklus kerja empat tahap, yaitu menulis fungsi, menguji, mengukur, dan mendokumentasikan, lalu mempraktikkan ketiga checkpoint-nya pada satu fungsi rata-rata bergerak. Setiap checkpoint menghasilkan bukti yang dapat dibaca ulang, yaitu jumlah ujian yang lulus, angka alokasi memori, dan teks docstring.

Siklus kerja empat tahap#

Siklus kerja kecil memuat empat tahap yang berulang untuk setiap potongan fungsi baru. Tahap pertama menulis fungsi dengan anotasi tipe argumen dan docstring sejak definisi pertamanya. Tahap kedua mengujinya dengan pustaka Test, tahap ketiga mengukurnya dengan BenchmarkTools, dan tahap keempat melengkapinya dengan dokumentasi. Diagram berikut memperlihatkan urutan keempat tahap tersebut beserta posisi tiga checkpoint-nya.

Tulis fungsianotasi tipeUjipustaka TestUkurBenchmarkToolsDokumentasikandocstring123tahap berikutnya dimulai setelah seluruh checkpoint lulus
Siklus tulis, uji, ukur, dokumentasikan beserta tiga checkpoint

Tiga checkpoint menandai transisi antartahap. Checkpoint pertama menuntut test set yang lulus setelah tahap pengujian, checkpoint kedua menuntut angka pengukuran setelah tahap pengukuran, dan checkpoint ketiga menuntut teks dokumentasi yang melekat pada fungsi. Tahap berikutnya hanya dimulai setelah seluruh checkpoint sebelumnya lulus, sehingga tidak ada fungsi yang masuk ke implementasi berikutnya dalam keadaan belum teruji atau belum terdokumentasi. Siklus ini mengikuti kebiasaan pengembangan Julia yang dianjurkan dokumentasi resminya, yaitu melakukan iterasi cepat dengan ujian dan pengukuran sebagai dasar pengambilan keputusan.

Fungsi yang menjadi bahan latihan#

Fungsi rata_rata_bergerak menghitung rata-rata untuk setiap jendela berurutan dari sebuah vektor, dan fungsi inilah yang melewati ketiga checkpoint pada topik ini. Berkas berikut menyimpan docstring pada konstanta DOK_RATA_RATA lalu melekatkannya pada fungsi dengan @doc, sehingga teks dokumentasinya dapat dicetak ulang saat checkpoint dokumentasi. Implementasinya menggunakan view agar perhitungan per jendela tidak menyalin data.

julia statistik.jl
const DOK_RATA_RATA = """
    rata_rata_bergerak(x, jendela)

Menghitung rata-rata bergerak dari vektor `x` dengan lebar jendela `jendela`.
Elemen ke-i hasil adalah rata-rata dari `jendela` elemen terakhir hingga `x[i]`,
sehingga panjang hasil adalah `length(x) - jendela + 1`.
"""

@doc DOK_RATA_RATA function rata_rata_bergerak(x::Vector{Float64}, jendela::Int)
    n = length(x) - jendela + 1
    y = Vector{Float64}(undef, n)
    for i in 1:n
        y[i] = sum(view(x, i:(i + jendela - 1))) / jendela
    end
    return y
end

println(rata_rata_bergerak([2.0, 4.0, 6.0, 8.0], 2))
text Keluaran
[3.0, 5.0, 7.0]

Keluaran di atas berasal dari baris demonstrasi di akhir berkas, yang menghitung rata-rata bergerak dua elemen atas vektor [2.0, 4.0, 6.0, 8.0]. Hasilnya berupa vektor dengan panjang tiga, yaitu [3.0, 5.0, 7.0], sesuai rumus pada docstring. Signature fungsi menerima Vector{Float64} dan Int, sehingga jenis argumen yang dilayani dan yang ditolak sudah terdefinisi sejak rancangan.

Checkpoint pengujian#

Checkpoint pertama menuntut test set yang lulus untuk perilaku yang dinyatakan pada docstring. Ujian mencakup kasus biasa, kasus jendela sepanjang vektor, banyaknya elemen hasil untuk data yang lebih panjang, dan penolakan terhadap lebar jendela Float64 yang tidak dilayani signature-nya. Empat ujian itu dituliskan pada blok berikut.

julia Contoh: checkpoint pengujian
using Test

# berkas dimuat sekali; isdefined mencegah definisi ulang konstanta
isdefined(Main, :rata_rata_bergerak) || include("statistik.jl")

hasil = redirect_stdout(devnull) do
    @testset "Checkpoint rata_rata_bergerak" begin
        @test rata_rata_bergerak([2.0, 4.0, 6.0, 8.0], 2) == [3.0, 5.0, 7.0]
        @test rata_rata_bergerak([1.0], 1) == [1.0]
        @test length(rata_rata_bergerak(fill(1.0, 10), 3)) == 8
        @test_throws MethodError rata_rata_bergerak([2.0, 4.0], 1.5)
    end
end
println("ujian lulus: ", hasil.n_passed)
text Keluaran
ujian lulus: 4

Pola redirect_stdout(devnull) meredam keluaran demonstrasi statistik.jl beserta ringkasan test set yang memuat kolom waktu, sehingga yang tampil hanyalah jumlah ujian yang lulus. Angka 4 itu adalah bukti checkpoint pertama, dan ia dapat dibaca ulang kapan pun dengan menjalankan blok yang sama. Ujian keempat menjaga kontrak signature, yaitu penolakan MethodError untuk argumen 1.5 bertipe Float64, karena kontrak penolakan sama pentingnya dengan kontrak perhitungan.

Checkpoint pengukuran#

Checkpoint kedua mencatat alokasi memori per pemanggilan sebagai angka yang deterministik. Fungsi dipanggil satu kali untuk pemanasan agar kode mesinnya sudah tersusun, lalu @benchmark mengukurnya dan hasilnya disimpan pada objek bm. Yang dicetak hanyalah allocs dan memory dari objek hasil, yaitu banyaknya alokasi dan banyaknya byte yang sama pada setiap eksekusi, sebagaimana ditunjukkan pada blok berikut.

julia Contoh: checkpoint pengukuran
using BenchmarkTools

# berkas dimuat sekali; isdefined mencegah definisi ulang konstanta
isdefined(Main, :rata_rata_bergerak) || include("statistik.jl")

data = collect(1.0:1000.0)
rata_rata_bergerak(data, 5)
bm = @benchmark rata_rata_bergerak($data, 5)
println("alokasi per pemanggilan: ", allocs(bm))
println("memori per pemanggilan: ", memory(bm), " byte")
text Keluaran
alokasi per pemanggilan: 3
memori per pemanggilan: 8072 byte

Angka 3 dan 8072 byte menjadi bukti checkpoint kedua sekaligus pembanding bagi perubahan implementasi. Array hasil untuk seribu data dengan jendela lima menempati 7968 byte, sedangkan sisanya berupa alokasi kecil dari jalur perhitungan sum atas view. Apabila implementasi disempurnakan, misalnya perhitungan jendela diganti dengan penjumlahan berjalan, angka-angka ini diukur ulang, dan perbaikan hanya diterima apabila angkanya benar-benar turun tanpa melanggar test set.

Checkpoint dokumentasi#

Checkpoint ketiga memastikan docstring ada dan isinya sesuai dengan perilaku yang terukur. Karena docstring disimpan pada konstanta DOK_RATA_RATA, isinya dapat dicetak ulang dari skrip tanpa REPL, sebagaimana ditunjukkan pada blok berikut.

julia Contoh: checkpoint dokumentasi
# berkas dimuat sekali; isdefined mencegah definisi ulang konstanta
isdefined(Main, :rata_rata_bergerak) || include("statistik.jl")

println(DOK_RATA_RATA)
text Keluaran
    rata_rata_bergerak(x, jendela)

Menghitung rata-rata bergerak dari vektor `x` dengan lebar jendela `jendela`.
Elemen ke-i hasil adalah rata-rata dari `jendela` elemen terakhir hingga `x[i]`,
sehingga panjang hasil adalah `length(x) - jendela + 1`.

Pada REPL, mode help yang diakses dengan ? menampilkan teks yang sama untuk fungsi yang sudah terdokumentasi. Penyimpanan docstring pada konstanta hanyalah cara membuat teks itu tampil dari skrip, dan konstanta itu sendiri menjadi bagian dari bukti checkpoint. Dengan lulusnya checkpoint ini, satu potongan fungsi memiliki tiga bukti lengkap, yaitu ujian yang lulus, angka pengukuran, dan dokumentasi yang melekat.

Daftar periksa checkpoint#

Daftar berikut meringkas syarat penutupan setiap checkpoint dan dipakai ulang untuk setiap potongan fungsi baru.

  • Fungsi baru ditulis dengan anotasi tipe argumen dan docstring sejak definisi pertama.
  • Test set untuk fungsi tersebut lulus sebelum tahap berikutnya dimulai.
  • Angka allocs dan memory dicatat sebagai pembanding bagi perubahan implementasi.
  • Perubahan implementasi hanya dilakukan setelah pengukuran menunjukkan masalah, lalu diukur ulang setelahnya.
  • Seluruh runtests.jl dijalankan ulang sebelum checkpoint ditutup, sehingga fungsi lama tidak rusak oleh fungsi baru.

Pada proyek yang sudah berbentuk package, ketiga bukti checkpoint menumpuk pada tempat yang sudah dibahas pada modul Lingkungan Proyek. Project.toml mencatat dependensi seperti BenchmarkTools, Manifest.toml mengunci versinya, dan runtests.jl menampung seluruh test set sehingga perintah Pkg.test() menjalankan semuanya sekali jalan. Struktur itu membuat bukti setiap checkpoint dapat dipanggil ulang dengan satu perintah, baik oleh pengerjanya maupun oleh alat otomatis.

Kesalahan umum#

Kesalahan pertama adalah mengukur tanpa pemanasan. Panggilan pertama memuat penyusunan kode mesin, sehingga hasilnya mencerminkan kompilasi alih-alih pekerjaan fungsi. Satu pemanggilan pemanasan sebelum @benchmark menghilangkan penyimpangan itu. Kesalahan kedua adalah mengoptimalkan sebelum test set lulus, karena pengukuran atas fungsi yang salah hanya mempercepat kode yang keliru, dan urutan yang benar adalah uji dahulu, ukur kemudian.

Kesalahan ketiga adalah menunda docstring hingga akhir proyek. Pada saat itu, alasan di balik pilihan parameter sudah pudar, sehingga teksnya menyalin kode alih-alih menjelaskan kontraknya. Docstring ditulis sejak fungsi pertama dibuat dan disempurnakan bersama implementasinya. Kesalahan keempat adalah menganggap checkpoint selesai ketika satu ujian lulus, padahal seluruh runtests.jl harus lulus agar fungsi yang sudah ada tidak ikut rusak.

Ringkasan#

  • Siklus kerja empat tahap, yaitu tulis, uji, ukur, dan dokumentasikan, menghasilkan bukti yang dapat dibaca ulang pada setiap checkpoint.
  • Bukti checkpoint berupa jumlah ujian yang lulus, angka allocs dan memory yang deterministik, serta teks docstring yang melekat pada fungsi.
  • Pada proyek berbentuk package, bukti-bukti itu menumpuk pada runtests.jl beserta Project.toml dan Manifest.toml yang dikelola Pkg.

Referensi#