Desain dahulu

Merancang tipe, strategi memori, dan tata letak modul sebelum menulis kode.

Daftar isi

Menulis kode sebelum desainnya jelas adalah cara tercepat menghasilkan program yang berantakan di tengah jalan. Topik ini merancang proyek suhu sebelum implementasinya dimulai, mencakup tipe inti, strategi memori, tata letak modul, dan kontrak setiap proc. Hasil akhirnya adalah cetak biru berisi keputusan yang tertulis, sehingga topik Membangun dengan titik pemeriksaan dan Rilis cukup mengerjakan rencana tersebut tanpa menebak-nebak lagi. Setiap keputusan di sini diberi alasannya, bukan sekadar bentuknya.

Kontrak data dan tipe inti#

Desain dimulai dari data karena seluruh bagian program akan berdiri di atasnya. Kontrak data proyek suhu sudah ditetapkan pada pemilihan proyek, yaitu berkas CSV dua kolom dengan baris header tanggal,suhu dan satu catatan per baris. Berkas contoh berikut menjadi acuan resmi format tersebut, dan isi inilah yang dipakai pada seluruh contoh modul ini:

text suhu.csv
tanggal,suhu
2026-01-02,21.5
2026-01-03,22.1
2026-01-04,20.8
2026-01-05,23.4
2026-01-06,24.0
2026-01-07,22.6
2026-01-08,21.9
2026-01-09,23.1
2026-01-10,22.3
2026-01-11,20.5

Satu baris catatan dipetakan ke satu nilai objek, yaitu objek yang menyimpan nilainya sendiri tanpa rujukan ke pemilik lain. Tipe inti proyek ini bernama CatatanSuhu dengan dua bidang, yaitu tanggal bertipe string dan suhu bertipe float:

nim Desain: tipe CatatanSuhu
type
  CatatanSuhu = object
    tanggal: string
    suhu: float

echo sizeof(CatatanSuhu)
text Keluaran
24

sizeof melaporkan ukuran satu nilai CatatanSuhu adalah 24 byte pada arsitektur 64 bit, terdiri atas dua kata untuk string (penunjuk beserta panjangnya) dan satu kata untuk float. Angka ini bukan sekadar keingintahuan, melainkan dasar keputusan pada bagian Strategi memori. Kumpulan catatan direpresentasikan sebagai seq[CatatanSuhu], sehingga sepuluh catatan contoh menempati satu blok memori berukuran sekitar 240 byte ditambah isi string tanggalnya. Ukuran yang kecil ini juga membuat penyalinan satu catatan tetap murah, sebuah sifat yang dipakai bebas saat menambahkan nilai ke dalam seq tanpa pertimbangan khusus.

Strategi memori#

Strategi memori proyek ini dapat diringkas dalam satu kalimat, yaitu gunakan tipe nilai sebanyak mungkin dan hindari ref selama tidak ada alasan nyata. Nilai CatatanSuhu disimpan langsung di dalam seq, sehingga datanya berurutan di memori dan aksesnya ramah cache prosesor. Satu-satunya bagian yang menyentuh heap adalah string tanggal dan penyimpanan internal seq itu sendiri, dan keduanya dilepas otomatis oleh pengelola memori ORC ketika tidak lagi dirujuk.

Pilihan ini menghilangkan seluruh kerumitan kepemilikan memori. Tidak ada ref berarti tidak ada siklus referensi yang perlu dikumpulkan, tidak ada pertanyaan siapa pemilik data, dan tidak ada pembebasan manual. Apabila suatu saat profil menunjukkan salinan CatatanSuhu yang mahal, desain cukup diubah di satu tempat, misalnya menjadi seq[ref CatatanSuhu], tanpa menyentuh kontrak proc mana pun. Untuk lingkup proyek ini, salinan 24 byte justru lebih murah daripada biaya manajemen rujukan yang menyertainya.

Tata letak modul#

Tata letak modul mengikuti prinsip satu tanggung jawab per berkas. Proyek suhu terdiri atas tiga modul dan dua berkas tes, dengan arah ketergantungan yang mengalir satu arah saja dari titik masuk ke modul teknisnya:

suhu.nimtitik masuk: argumen, laporan, errorparser.nimurai CSVstatistik.nimstatistik deretsuhu.csvberkas data masukantes_parser.nimunittesttes_statistik.nimunittest
Tata letak modul proyek suhu

Susunan berkas di dalam direktori proyek mengikuti diagram tersebut secara langsung:

text
suhu/
├── suhu.nim
├── parser.nim
├── statistik.nim
├── tes_parser.nim
├── tes_statistik.nim
└── suhu.csv

Pemisahan ini punya dua alasan yang terukur. Pertama, parser.nim mengurus format dan statistik.nim mengurus perhitungan, sehingga perubahan format CSV tidak pernah menyentuh rumus statistik, begitu pula sebaliknya. Kedua, kedua modul itu murni tanpa IO percetakan, sehingga keduanya mudah diuji dengan unittest. Berkas suhu.nim sengaja dibuat tipis, yakni hanya mengurus argumen baris perintah, pemanggilan modul lain, dan laporan, karena logika semacam itu paling mahal untuk diuji secara otomatis.

Tata letak ini berlaku tanpa konfigurasi tambahan karena compiler menyelesaikan import parser dengan mencari parser.nim di direktori berkas yang mengimpornya. Simbol yang ditulis dengan tanda bintang, seperti uraiCsv*, menjadi bagian antarmuka modul, sedangkan simbol tanpa tanda bintang tetap privat bagi modulnya. Aturan sederhana itu menjadikan tanda bintang sebagai kontrak keterlihatan yang dapat dibaca langsung dari kode. Berkas tes diletakkan sejajar dengan modulnya agar perintah pengujian tetap pendek dan tidak memerlukan pohon direktori tambahan.

Kontrak proc#

Kontrak proc adalah pernyataan tentang nama, parameter, tipe hasil, dan perilaku yang dijanjikan, ditulis sebelum tubuhnya. Fragmen berikut memuat tanda tangan procnya saja, sehingga belum dapat dikompilasi dan baru menjadi kode utuh pada tahap pembangunan. Desain parser.nim menetapkan tiga proc dengan tanggung jawab yang meningkat bertahap:

nim Desain: kontrak proc modul parser
# tidak-dijalankan: kontrak ini belum memiliki tubuh proc
proc uraiBaris*(baris: string): CatatanSuhu
proc uraiCsv*(isi: string): seq[CatatanSuhu]
proc bacaCsv*(namaBerkas: string): seq[CatatanSuhu]

uraiBaris mengubah satu baris menjadi satu catatan dan memunculkan ValueError untuk baris yang cacat, uraiCsv mengerjakan isi berkas yang sudah dibaca dengan melewatkan header dan baris kosong, sedangkan bacaCsv menambahkan pembacaan dari disk. Pemisahan penguraian dari pembacaan berkas bukan gaya-gayaan, melainkan pintu masuk pengujian, karena pengurai dapat diuji langsung dari string tanpa berkas fisik. Desain statistik.nim lebih pendek karena seluruhnya bekerja atas seq[float]:

nim Desain: kontrak proc modul statistik
# tidak-dijalankan: kontrak ini belum memiliki tubuh proc
proc minimum*(data: seq[float]): float
proc maksimum*(data: seq[float]): float
proc rataRata*(data: seq[float]): float
proc median*(data: seq[float]): float

Keempat proc statistik menerima deret nilai suhu yang sudah diekstrak dari catatan, bukan seq[CatatanSuhu], sehingga modul ini tidak bergantung pada tipe milik parser. Kontraknya juga menetapkan prasyarat eksplisit, yaitu deret tidak boleh kosong untuk minimum dan maksimum, sedangkan rataRata dan median menyediakan definisi yang jelas untuk deret pendek. Titik kosong seperti ini, bila tidak diputuskan sekarang, akan memunculkan error IndexDefect pada saat paling tidak terduga.

Kriteria desain yang cukup#

Desain selesai bukan ketika dokumennya tebal, melainkan ketika pertanyaan-pertanyaan kuncinya sudah punya jawaban tertulis. Untuk proyek sekecil ini, kriteria yang cukup terpenuhi apabila tipe data inti terdefinisi beserta ukurannya, setiap modul punya tanggung jawab satu kalimat, arah ketergantungan tidak bercabang mundur, setiap proc punya kontrak parameter dan hasil beserta error yang mungkin dimunculkan, dan setiap kontrak punya rencana pengujian yang nyata. Seluruh butir di atas sudah dipenuhi pada bagian-bagian sebelumnya.

Kriteria semacam ini mencegah dua kelewatan yang berlawanan, yaitu desain yang kurang sehingga implementasi menebak-nebak, dan desain yang berlebihan sehingga memakan waktu pengerjaan. Proyek pamungkas ini sengaja tidak memuat abstraksi seperti kelas pembaca CSV umum, konfigurasi berkas, maupun antarmuka plugin, karena tidak ada kebutuhan nyata di baliknya. Desain yang baik pada proyek kecil adalah desain terkecil yang membuat pertanyaan teknisnya habis.

Apabila keputusan perlu diubah di tengah pengerjaan, perubahan itu disunting pada catatan desain yang sama, bukan hanya pada kodenya, sehingga cetak biru tetap mewakili keadaan proyek yang sebenarnya. Cara kerja ini meniru praktik pada proyek yang lebih besar, dengan dokumen desain sebagai tempat pertama untuk menimbang apakah sebuah perubahan aman atau justru merusak asumsi bagian lain. Biaya penulisannya kecil, sedangkan manfaatnya bertahan sampai proyek dirilis.

Kesalahan umum#

  • Merancang tipe tanpa data nyata. Tipe yang dirancang dari tebakan sering kekurangan bidang atau salah tipe, misalnya suhu yang disimpan sebagai string. Perbaikannya adalah menetapkan berkas contoh sebagai kontrak format sejak awal, seperti suhu.csv di atas.
  • Mencampur IO dengan logika dalam satu proc. Proc yang membaca berkas sekaligus menghitung tidak dapat diuji tanpa berkas fisik, sehingga cakupan pengujian atasnya rendah. Perbaikannya adalah memisahkan penguraian dari pembacaan, persis seperti kontrak uraiCsv dan bacaCsv.
  • Mendesain abstraksi yang tidak dipakai. Lapisan konfigurasi dan antarmuka plugin menambah berkas tanpa menambah kemampuan pada lingkup ini. Perbaikannya adalah menerapkan kriteria desain yang cukup, lalu berhenti ketika seluruh butirnya terpenuhi.
  • Menetapkan kontrak tanpa menyebut errornya. Kontrak yang tidak menyatakan error yang mungkin muncul memaksa pemanggil menebak perilakunya saat data buruk. Perbaikannya adalah menyebutkan pengecualian pada setiap proc, sebagaimana ValueError pada uraiBaris, dan menjadikannya bagian tetap dari catatan desain.

Ringkasan#

  • Kontrak data proyek suhu adalah CSV dua kolom dengan header tanggal,suhu, dan setiap barisnya dipetakan ke objek nilai CatatanSuhu berukuran 24 byte.
  • Strategi memorinya memilih tipe nilai tanpa ref, sehingga data berurutan di memori dan pembersihannya otomatis oleh ORC tanpa siklus referensi.
  • Tata letaknya terdiri atas parser.nim, statistik.nim, dan titik masuk suhu.nim yang tipis, dengan ketergantungan satu arah dan pengujian yang dipisah per modul.
  • Desain dinyatakan cukup ketika tipe, tanggung jawab modul, kontrak proc, error, dan rencana pengujian semuanya sudah tertulis.

Referensi#