Perencanaan proyek menetapkan batas dan bentuk program sebelum baris kode pertama ditulis, sehingga pengerjaan berjalan mengikuti rancangan dan bukan keputusan dadakan di tengah jalan. Topik ini merancang proyek utuh pengelola catatan harian, yaitu aplikasi baris perintah dengan penyimpanan berkas CSV (comma-separated values), mencakup tabel lingkup, struktur direktori, rancangan modul, desain yang mengutamakan coroutines, dan daftar milestone. Setiap keputusan pada rancangan ini dipakai kembali ketika proyek dibangun pada topik Sprint Implementasi, sehingga kedua topik membentuk satu rangkaian pengerjaan yang utuh.
Menetapkan lingkup proyek#
Lingkup proyek adalah kesepakatan tentang kemampuan yang dibangun dan kemampuan yang sengaja ditinggalkan. Batas ini ditulis sejak awal karena proyek kecil yang lingkupnya melebar di tengah pengerjaan biasanya tidak selesai tepat waktu, padahal setiap fitur tambahan menuntut rancangan, kode, dan pengujian sendiri. Tabel berikut memisahkan keduanya untuk proyek pengelola catatan harian.
| Masuk lingkup | Keluar lingkup |
|---|---|
| Operasi tambah, daftar, dan cari catatan lewat baris perintah | Antarmuka grafis dan aplikasi web |
| Penyimpanan satu berkas CSV di folder kerja | Database server dan penyimpanan daring |
| Tanggal tersimpan sebagai teks berformat ISO 8601 | Zona waktu berganda dan kalender lokal |
| Laporan ringkas jumlah catatan per bulan | Statistik lanjutan dan ekspor berkas lain |
Kolom kiri menjadi acuan penerimaan, yaitu daftar kemampuan yang harus berjalan sebelum proyek dinyatakan selesai. Kolom kanan bukan penilaian bahwa fitur itu tidak berguna, melainkan penundaan yang disengaja agar pengerjaan tetap terarah. Apabila sebuah ide baru muncul di tengah sprint, idenya dicatat sebagai kandidat versi berikutnya dan tabel ini tidak ikut berubah.
Struktur direktori proyek#
Struktur direktori mengikuti tata letak baku proyek JVM yang dikelola Gradle, yaitu kode sumber Kotlin berada di bawah src/main/kotlin dan konfigurasi build berada di build.gradle.kts pada akar proyek. Satu modul rancangan ditempatkan pada satu berkas, sehingga batas antarmodul terlihat langsung dari daftar berkasnya.
pengelola-catatan/
├── build.gradle.kts
└── src/
└── main/
└── kotlin/
├── Main.kt
├── Catatan.kt
├── PenyimpananCatatan.kt
├── PencarianCatatan.kt
└── LaporanBulanan.ktBerkas Main.kt menjadi pintu masuk program yang menyatukan panggilan antarmodul. Empat berkas lainnya memuat satu modul masing-masing, sehingga pengujian dan perubahan dapat dilakukan per modul tanpa menyentuh modul yang lain. Penempatan satu modul pada satu berkas juga membuat perubahan lebih mudah ditinjau, karena beda kode hanya menyentuh satu berkas pada satu waktu. Struktur inilah yang dibaca Gradle ketika proyek dibangun, sehingga tidak ada konfigurasi tambahan yang diperlukan untuk memuat kelima berkas tersebut.
Rancangan modul#
Empat modul rancangan membagi tanggung jawab dari data sampai keluaran. Pembagian ini menempatkan logika data yang murni, yaitu kode yang hasilnya hanya bergantung pada masukan, terpisah dari kode yang menyentuh berkas. Pemisahan semacam ini membuat bagian yang murni mudah diuji dan bagian yang menunggu I/O mudah dibungkus coroutine.
| Modul | Berkas | Tanggung jawab |
|---|---|---|
| Data model | Catatan.kt | data class Catatan beserta konversi dua arah dengan baris CSV |
| Penyimpanan | PenyimpananCatatan.kt | membaca dan menulis berkas CSV di folder kerja |
| Pencarian | PencarianCatatan.kt | menyaring collection catatan berdasarkan kata kunci |
| Laporan | LaporanBulanan.kt | menghitung jumlah catatan per bulan dan mengurutkannya |
Format berkas CSV disepakati sejak perancangan karena seluruh modul bergantung padanya. Satu baris mewakili satu catatan dengan empat kolom, yaitu id, tanggal, judul, dan isi.
1,2026-01-05,Mulai proyek,Merancang struktur pengelola catatan
2,2026-01-20,Selesai perancangan,Model data dan format CSV disepakati
3,2026-02-11,Laporan pertama,Laporan bulanan berhasil dibuatData di atas menjadi data tetap untuk seluruh contoh pada jalur ini, termasuk pada topik Sprint Implementasi, sehingga setiap keluaran dapat direproduksi. Tiga kolom pertama berisi nilai tanpa tanda koma, sedangkan kolom isi dibaca dengan pembatasan jumlah bagian sehingga tetap utuh walaupun mengandung tanda koma.
Desain yang mengutamakan coroutines#
Prinsip desain coroutines-first menempatkan keputusan konkurensi pada batas yang jelas, yaitu hanya operasi yang menunggu, seperti pembacaan dan penulisan berkas, yang dijalankan pada dispatcher Dispatchers.IO. Fungsi yang menyentuh berkas dinyatakan sebagai fungsi suspend dan membungkus pekerjaannya dengan withContext(Dispatchers.IO), sedangkan seluruh logika data, seperti konversi CSV, pencarian, dan penghitungan laporan, tetap berupa fungsi biasa yang tidak menyentuh coroutine sama sekali.
Alasan pembagian ini bersifat teknis, bukan sekadar gaya. Operasi berkas membuat thread yang menjalankannya diam menunggu, sehingga pekerjaan semacam itu sebaiknya dipindahkan ke pool IO yang besar dan tidak memenuhi thread utama program. Apabila logika data dipisahkan dari lapisan berkas, fungsi data dapat diuji tanpa dispatcher dan tanpa berkas sama sekali, sementara pemilihan dispatcher cukup dilakukan satu kali pada lapisan penyimpanan.
Pada proyek Gradle, pilihan ini dinyatakan sebagai satu dependensi library coroutines pada build.gradle.kts.
// tidak-dijalankan
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0")
}Versi JVM murni tanpa Gradle dapat membuktikan pola yang sama sekarang juga. Program berikut menjalankan dua pekerjaan berkas secara bersamaan pada Dispatchers.IO, yaitu penulisan laporan bulanan dan penulisan arsip tahunan, lalu menunggu keduanya dengan await.
import kotlinx.coroutines.*
import java.nio.file.Files
import java.nio.file.Path
// folder kerja demo, meniru lokasi penyimpanan berkas proyek
val folderKerja = Path.of("/tmp/kerja-perencanaan-proyek")
// operasi berkas dibungkus fungsi suspend dan Dispatchers.IO
suspend fun tulisBerkas(nama: String, isi: String): String = withContext(Dispatchers.IO) {
Files.createDirectories(folderKerja)
val berkas = folderKerja.resolve(nama)
Files.writeString(berkas, isi)
"berkas ${berkas.fileName} tersimpan, ${isi.length} karakter"
}
fun main() = runBlocking {
println("Menulis dua berkas laporan secara bersamaan pada Dispatchers.IO")
val laporan = async { tulisBerkas("laporan-januari.txt", "Jumlah catatan Januari: 2") }
val arsip = async { tulisBerkas("arsip-tahun-2026.txt", "Total catatan 2026: 3") }
println(laporan.await())
println(arsip.await())
println("Kedua pekerjaan berkas selesai")
}# compiler Kotlin dan library coroutines yang dipakai jalur ini
export PATH=/home/kal/my-project/tools/kotlinc/bin:/home/kal/my-project/tools/jdk-merged/bin:$PATH
export LIBS=/home/kal/my-project/tools/libs
export STDLIB=/home/kal/my-project/tools/kotlinc/lib/kotlin-stdlib.jar
# kompilasi main.kt dengan library coroutines pada classpath, lalu jalankan programnya
kotlinc main.kt -cp "$LIBS/kotlinx-coroutines-core-jvm-1.11.0.jar" -d out.jar && java -cp "out.jar:$LIBS/kotlinx-coroutines-core-jvm-1.11.0.jar:$STDLIB" MainKtMenulis dua berkas laporan secara bersamaan pada Dispatchers.IO
berkas laporan-januari.txt tersimpan, 25 karakter
berkas arsip-tahun-2026.txt tersimpan, 21 karakter
Kedua pekerjaan berkas selesaiKeluaran menunjukkan kedua berkas tersimpan, dan urutan cetaknya tetap karena penentuan urutan ada di tangan pemanggil, bukan pada waktu selesainya tiap coroutine. Dua berkas yang ditulis program berada di /tmp/kerja-perencanaan-proyek, yaitu folder kerja sandbox jalur ini, sehingga folder topik tidak tersentuh oleh eksekusi demo.
Milestone pengerjaan#
Pengerjaan diurutkan menjadi empat milestone, dan setiap milestone menghasilkan program yang dapat dijalankan sampai tuntas. Urutannya mengikuti ketergantungan data, yaitu model lebih dahulu, kemudian penyimpanan, lalu analisis, dan penyatuan konkurensi di akhir.
- Model data dan konversi CSV. Deklarasi data class
Catatan, fungsi konversi dua arah, serta penanganan baris rusak yang mengembalikan nilai null. - Penyimpanan berkas CSV. Kelas
PenyimpananCatatanyang menyimpan dan membaca collection catatan di folder kerja. - Pencarian dan laporan bulanan. Penyaringan kata kunci pada judul dan isi serta rekap jumlah catatan per bulan yang terurut.
- Penyatuan dengan coroutine. Seluruh operasi berkas dibungkus fungsi suspend dan dijalankan pada
Dispatchers.IOtanpa mengubah logika data.
Setiap milestone dinyatakan selesai hanya bila programnya mencetak keluaran yang benar dari data tetap catatan.csv, sehingga kemajuan proyek diukur dari perilaku yang teramati dan bukan dari perkiraan. Urutan ini juga menjaga setiap langkah tetap kecil, karena satu milestone hanya menyentuh satu modul rancangan. Apabila sebuah milestone gagal mencapai keluarannya, penyebabnya mudah dicari karena lingkup perubahannya satu berkas saja.
Kesalahan umum#
- Lingkup melebar di tengah pengerjaan. Penyebabnya, ide fitur baru muncul setiap kali program berhasil berjalan. Perbaikannya, simpan ide tersebut sebagai kandidat versi berikutnya dan tuntaskan kolom masuk lingkup sampai selesai.
- Logika data digabung dengan pembacaan berkas. Penyebabnya, fungsi yang seharusnya murni memanggil
File.readText()di dalam perhitungannya, sehingga pengujian membutuhkan berkas nyata. Perbaikannya, pisahkan batas suspend pada lapisan penyimpanan dan teruskanList<Catatan>sebagai masukan fungsi data. - Operasi berkas ditempatkan langsung pada thread utama tanpa dispatcher. Penyebabnya, panggilan berkas ditulis seadanya di dalam fungsi utama. Perbaikannya, bungkus dengan
withContext(Dispatchers.IO)sejak desain, sebagaimana ditunjukkan demo pada topik ini. - Tanggal contoh diubah di tengah pengerjaan. Penyebabnya, isi atau tanggal catatan diganti semaunya saat mencoba, sehingga keluaran tidak lagi cocok dengan yang dinyatakan rancangan. Perbaikannya, perlakukan
catatan.csvsebagai data uji tetap, dan simpan berkas percobaan di folder lain agar keluaran setiap milestone tetap dapat dibandingkan.
Ringkasan#
- Lingkup proyek dipisahkan menjadi tabel masuk dan keluar, sehingga keputusan penerimaan dan penundaan punya acuan tertulis.
- Struktur proyek mengikuti tata letak JVM ber-Gradle dengan satu berkas untuk setiap modul rancangan.
- Desain coroutines-first menempatkan seluruh I/O berkas sebagai fungsi suspend pada
Dispatchers.IO, sedangkan logika data tetap berupa fungsi murni yang mudah diuji. - Empat milestone mengurutkan pengerjaan dari model data sampai versi coroutine, dan setiap milestone diakhiri program yang dapat dijalankan.
Referensi#
- Coroutines guide — JetBrains — Panduan resmi coroutines Kotlin yang menjadi dasar desain konkurensi pada rancangan ini.
- Configure a Gradle project — JetBrains — Dokumentasi penataan proyek Gradle dan deklarasi dependensi pada build script.