Halo, teman-teman mahasiswa ilmu komputer dan pegiat coding kampus! Kalau kalian sering bikin proyek aplikasi atau game, pasti pernah ngalamin momen di mana kode yang udah dites berkali-kali dan hijau semua di unit test, eh, malah error pas dijalankan di environment lain. Menyebalkan, kan? Nah, seorang developer belum lama ini membagikan kisah nyata yang sangat relatable tentang bagaimana sebuah bug tersembunyi berhasil ditemukan bukan karena kodingannya dibaca ulang, melainkan karena menggunakan trik cross-runtime yang cerdas.
Cerita ini bermula dari kritikan seorang pembaca bernama Ryan di postingan sebelumnya. Ryan bilang kalau artikel-artikel sebelumnya terlalu banyak istilah teknis yang berat dan mirip proposal jualan ketimbang case study nyata. Kritikan itu jitu, dan solusinya sederhana: mari kita bedah satu tugas nyata dari awal sampai akhir, tanpa ada rahasia yang disembunyikan!
Setup Proyek: Game Match-3 di JVM dan Browser
Mari kita mulai dari latar belakang masalahnya. Developer ini membangun sebuah game match-3 yang ditenagai oleh rules engine berbasis Java murni. Game ini dijalankan dengan dua cara:
- Pertama, berjalan di atas JVM (tempat unit test dan CI berada).
- Kedua, berjalan di dalam browser setelah dikompilasi ke JavaScript menggunakan TeaVM, sehingga kode Java yang sama bisa menggerakkan papan game yang bisa dimainkan secara nyata.
Runtime kedua ini bukan sekadar demo biasa. Dengan menjalankan logika yang sama di dua mesin yang berbeda, kita mendapatkan fasilitas cek gratis: jika keduanya tidak sepakat, pasti salah satunya ada yang keliru! Kita bahkan tidak perlu tahu jawaban benar di awal; kita hanya perlu peka saat ada perbedaan.
Apa yang Diubah dan Masalah yang Muncul
Saat engine dipindahkan ke browser dengan modul demo-js baru dan konfigurasi TeaVM, ada satu baris kode penentu ID untuk setiap gem yang tidak pernah diperhatikan secara serius sebelumnya:
String id = row + "-" + col + "-" + System.nanoTime() + "-" + RNG.nextInt(1000);
Baris kode tersebut menggunakan timestamp ditambah angka acak. Keliatannya aman dan sudah lulus semua test selama berbulan-bulan, bukan?
Apa yang Ditemukan oleh Pengecekan Dua Arah
ID tersebut berfungsi agar renderer bisa membedakan satu gem dengan gem lainnya. Jika ada dua gem dengan ID yang sama, animasinya bakal kacau dan terbaca sebagai satu gem tunggal. Developer ini kemudian menguji generasi papan yang sama di kedua runtime dan menghitung jumlah tabrakan (collisions) dari 128.000 gem:
- JVM: 0 duplikat.
- Browser (TeaVM): 301 duplikat!
Sumber kodenya sama. Inputnya sama. Hasilnya berbeda. Angka "301" ini menjadi sinyal kuat bahwa ada sesuatu yang tidak beres.
Langkah Intervensi Manusia
Angka spesifik seperti ini tetap membutuhkan sentuhan manusia untuk menganalisis mana sisi yang salah. Di sinilah tugas developer berperan:
System.nanoTime() memang terlihat unik, tetapi kinerjanya sangat bergantung pada resolusi clock sistem. Timer di JVM bekerja dengan baik, sehingga masalahnya tidak terlihat di sana. Sebaliknya, browser secara sengaja membatasi resolusi clock (dikenal sebagai mitigasi keamanan Spectre) sekitar 100 mikrosekon. Akibatnya, nanoTime hampir tidak bertambah di antara pembuatan gem, dan RNG.nextInt(1000) sering mengalami tabrakan.
Padahal, tidak ada runtime yang rusak. Yang salah adalah kode itu sendiri karena terlalu mengandalkan resolusi clock yang tidak pernah dijanjikan oleh browser. Perbaikannya cukup membosankan, tapi justru di situ kuncinya:
private static long idSeq = 0L;
private static synchronized long nextId() {
return idSeq++;
}
Dengan mengganti formula lama menjadi fungsi counter yang aman:
-String id = row + "-" + col + "-" + System.nanoTime() + "-" + RNG.nextInt(1000);
+String id = row + "-" + col + "-" + nextId();
Counter tersebut dijamin unik di setiap detak waktu, tanpa bergantung pada platform apa pun.
Memastikan Bug Tidak Datang Lagi dengan Unit Test
Aturan emas dalam ngoding: sebuah perbaikan belum selesai jika hanya diklaim selesai oleh pembuatnya. Perbaikan baru benar-benar tuntas ketika ada pengujian otomatis yang siap gagal jika bug tersebut mencoba bangkit lagi. Untuk kasus ini, ditambahkan tiga unit test baru untuk memastikan ID tidak pernah lagi bergantung pada resolusi clock:
@Test
public void idsAreUniqueWithoutRelyingOnClockResolution() {
Set<String> seen = new HashSet<>();
for (int i = 0; i < 50; i++) {
GameBoard.Gem[][] b = BoardEngine.createBoard(plain());
for (GameBoard.Gem[] row : b)
for (GameBoard.Gem g : row) seen.add(g.id);
}
assertEquals(50 * 64, seen.size());
}
Suite pengujian yang tadinya berjumlah 51 meningkat menjadi 59 tes. Ketiga tes tambahan ini menjadi tanda terima sah bahwa kegagalan serupa tidak akan pernah kembali secara diam-diam.
Kejadian Serupa: Ketika Kompilator yang Keliru
Menariknya, kebiasaan menjalankan logika di dua arah ini juga pernah menemukan kasus lain di mana compiler-lah yang bermasalah. Ketika mengaudit perbedaan antara TeaVM dan JVM terkait kasus tanggal:
YEAR = 2002, WEEK_OF_MONTH = 2 (America/New_York, en_US)
JVM: Sun Jan 06 2002
TeaVM: Sat Jan 12 2002
Ternyata kodenya bersih. Bug-nya justru berada di implementasi ulang TeaVM untuk GregorianCalendar milik Java, di mana ada satu baris kode yang menggunakan days - 2 sementara cabang lainnya menggunakan days - 3. Berkat pengujian diferensial, bug tersembunyi berumur sebelas tahun itu berhasil ditambal hanya dengan mengubah angka 2 menjadi 3!
Kesimpulan untuk Mahasiswa
Pelajaran berharga buat kita semua: Jangan pernah hanya mengandalkan satu sumber kebenaran (single source of truth) untuk logika yang sulit divalidasi secara manual. Jalankan kodenu dengan dua cara, dan jadikan setiap perbedaan pendapat antar-runtime sebagai sebuah bug sampai kamu berhasil membuktikan di mana letak kesalahannya—bisa jadi kodinganmu yang keliru, atau bahkan compiler-nya!
Tips untuk Mahasiswa: Selalu biasakan menulis unit test yang komprehensif untuk setiap tugas kuliah atau proyek portofolio kalian. Jangan malas membuat skenario pengujian ekstrem karena di situlah kualitas problem solving kalian diuji. Yuk, siapkan CV dan portofolio koding terbaikmu sekarang juga, asah skill debugging-mu, dan jadilah software engineer handal masa depan!