Bayangkan sedang menelusuri bug: method yang Anda panggil dengan masukan yang sama memberi hasil berbeda antara percobaan pertama dan kedua. Penyebabnya hampir selalu sama — method tersebut menyimpan atau mengubah sesuatu di luar dirinya, sehingga perilakunya dipengaruhi sejarah pemanggilan sebelumnya. Dunia pemrograman fungsional memberi nama untuk penawarnya: pure function — method yang hasilnya sepenuhnya ditentukan masukannya dan tidak menimbulkan efek apa pun di luar dirinya. Java tidak memaksa kemurnian, tetapi menulis method inti secara murni membuat program jauh lebih mudah diuji, dibaca, dan diprediksi. Pada Chapter ini kita bandingkan langsung method murni dengan method yang bercampur state tersembunyi.
Dua Syarat Pure Function #
Sebuah method disebut pure bila memenuhi dua syarat sekaligus. Pertama, hasil hanya bergantung pada argument: masukan sama menghasilkan keluaran sama, kapan pun dan dalam urutan apa pun method dipanggil. Kedua, tanpa side effect: method tidak mengubah variabel di luar dirinya, tidak menulis berkas, tidak mencetak ke terminal, dan tidak menyentuh state program lainnya. Method seperti itu ibarat mesin hitung — ditekan tombol yang sama, keluar angka yang sama, dan mesinnya tidak mengingat apa pun tentang penekanan sebelumnya.
Mari kita mulai dari versi yang murni:
public class FungsiMurni {
// Pure: hasil hanya dari argument, tidak menyentuh apa pun di luar
static double hitungPajak(double harga, double tarifPersen) {
return harga * tarifPersen / 100.0;
}
static String label(String nama, int nilai) {
return nama + " (" + nilai + ")";
}
public static void main(String[] args) {
System.out.println(hitungPajak(100000, 11));
System.out.println(hitungPajak(100000, 11)); // hasil identik
System.out.println(label("Budi", 90));
System.out.println(label("Budi", 90)); // hasil identik
}
}Output yang Dihasilkan:
11000.0
11000.0
Budi (90)
Budi (90)Kedua method hanya membaca parameter dan mengembalikan hasil perhitungan atau rangkaian teks. Pemanggilan kedua hitungPajak(100000, 11) menghasilkan 11000.0 yang persis sama dengan pemanggilan pertama, dan pemanggilan kedua label("Budi", 90) tetap menghasilkan Budi (90) — tidak ada yang berubah karena memang tidak ada yang bisa berubah. Pustaka standar Java kaya akan method semacam ini: seluruh method Math (misalnya Math.abs, Math.max) bersifat murni, demikian pula operasi pada String seperti toUpperCase() yang mengembalikan String baru tanpa mengubah yang lama.
Wajah Method Bercampur #
Sekarang kita lihat kebalikannya. Program berikut memelihara dua variabel static sebagai state bersama, lalu menyentuhnya dari dalam method:
public class FungsiBercampur {
// State bersama di luar method: sumber ketidakmurnian
static int nomorUrut = 0;
static double totalPajak = 0;
// Impure: hasil bergantung pada state tersembunyi, bukan hanya argument
static int panggilanBerikutnya() {
nomorUrut = nomorUrut + 1;
return nomorUrut;
}
// Impure: nilai balik tetap, tetapi mengubah state di luar dirinya
static double catatPajak(double harga, double tarifPersen) {
double pajak = harga * tarifPersen / 100.0;
totalPajak = totalPajak + pajak; // side effect
return pajak;
}
public static void main(String[] args) {
System.out.println("panggilanBerikutnya() = " + panggilanBerikutnya());
System.out.println("panggilanBerikutnya() = " + panggilanBerikutnya());
System.out.println("catatPajak(100000, 11) = " + catatPajak(100000, 11));
System.out.println("catatPajak(100000, 11) = " + catatPajak(100000, 11));
System.out.println("totalPajak = " + totalPajak);
}
}Output yang Dihasilkan:
panggilanBerikutnya() = 1
panggilanBerikutnya() = 2
catatPajak(100000, 11) = 11000.0
catatPajak(100000, 11) = 11000.0
totalPajak = 22000.0Keluaran program tersebut memperlihatkan dua wajah ketidakmurnian. Method panggilanBerikutnya mematahkan syarat pertama: dua pemanggilan tanpa argument sama sekali menghasilkan 1 lalu 2, karena hasilnya ditentukan state tersembunyi nomorUrut, bukan masukan. Method catatPajak lebih halus — nilai baliknya konsisten 11000.0 di kedua pemanggilan, sehingga terlihat seperti murni, tetapi setiap pemanggilan menambah totalPajak hingga terakumulasi 22000.0: efek samping yang hanya terlihat bila kita membaca state tersebut. Perbandingannya dapat dirangkum begini:
| Aspek | Method pure | Method impure |
|---|---|---|
| Hasil dari masukan sama | Selalu sama | Bisa berbeda (panggilanBerikutnya) |
| Perubahan state luar | Tidak ada | Ada (catatPajak menambah totalPajak) |
| Aman dipanggil ulang | Ya | Tergantung efek yang ditimbulkan |
| Penelusuran bug | Lokal pada method | Harus membaca seluruh riwayat pemanggilan |
Mengapa Pure Function Disukai #
Kemurnian membayar dirinya sendiri dalam tiga mata uang. Pengujian: method murni diuji tanpa persiapan — panggil dengan masukan, bandingkan keluaran, selesai; tidak perlu mereset state atau mengurutkan skenario. Penalaran: untuk memahami method murni, cukup baca badannya — seluruh kebenarannya ada di satu tempat, tidak tersebar ke variabel global. Keselamatan: method murni aman dipanggil dari beberapa thread sekaligus karena tidak memperebutkan state bersama — beban berpikir concurrency jauh berkurang.
Program nyata tetap membutuhkan efek: mencetak laporan, menyimpan berkas, mengirim data. Strategi praktisnya bukan menghapus efek, melainkan mengekangnya: susun program agar logika inti (menghitung, memvalidasi, mentransformasikan data) berupa method murni, lalu kumpulkan pemanggilan efek pada tempat sempit di tepi program. main pada FungsiMurni.java adalah contoh pola ini — method hitungPajak dan label murni, sementara System.out.println sebagai efek diletakkan di pemanggil. Pemisahan semacam ini akan kita praktikkan penuh pada Chapter penutup Section: merombak satu program monolitik menjadi susunan method kecil yang murni dan teruji.
Referensi #
- Java API: java.lang.Math (kumpulan method pure)
- Java API: java.lang.String (operasi tanpa mutasi)
- dev.java: Belajar Java
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
FungsiBercampur.java
Unduh berkaspublic class FungsiBercampur {
// State bersama di luar method: sumber ketidakmurnian
static int nomorUrut = 0;
static double totalPajak = 0;
// Impure: hasil bergantung pada state tersembunyi, bukan hanya argument
static int panggilanBerikutnya() {
nomorUrut = nomorUrut + 1;
return nomorUrut;
}
// Impure: nilai balik tetap, tetapi mengubah state di luar dirinya
static double catatPajak(double harga, double tarifPersen) {
double pajak = harga * tarifPersen / 100.0;
totalPajak = totalPajak + pajak; // side effect
return pajak;
}
public static void main(String[] args) {
System.out.println("panggilanBerikutnya() = " + panggilanBerikutnya());
System.out.println("panggilanBerikutnya() = " + panggilanBerikutnya());
System.out.println("catatPajak(100000, 11) = " + catatPajak(100000, 11));
System.out.println("catatPajak(100000, 11) = " + catatPajak(100000, 11));
System.out.println("totalPajak = " + totalPajak);
}
}
FungsiMurni.java
Unduh berkaspublic class FungsiMurni {
// Pure: hasil hanya dari argument, tidak menyentuh apa pun di luar
static double hitungPajak(double harga, double tarifPersen) {
return harga * tarifPersen / 100.0;
}
static String label(String nama, int nilai) {
return nama + " (" + nilai + ")";
}
public static void main(String[] args) {
System.out.println(hitungPajak(100000, 11));
System.out.println(hitungPajak(100000, 11)); // hasil identik
System.out.println(label("Budi", 90));
System.out.println(label("Budi", 90)); // hasil identik
}
}