author-pic

Ferry S

An ISTJ, Type 5, Engineer, Gamer, and Thriller-Movies-Lover
Jebakan Boolean (The Boolean Trap & Mistake)
Sun. Oct 11th, 2020 11:25 AM7 mins read
Jebakan Boolean (The Boolean Trap & Mistake)
Source: Bing Image Creator - a woman Caught In Mouse Trap

Boolean adalah tipe data yang hanya punya dua nilai. True dan False, hanya itu. Pada beberapa bahasa pemrograman atau database nilai True dan False bisa diganti dengan bilangan 1 dan 0. Boolean ini sangat sederhana karena hanya ada 2 kemungkinan tersebut. Saking simple-nya, pemakaian Boolean ini seringkali menjebak hingga sulit dibaca dan di-maintain. Berikut adalah jebakan-jebakan pada Boolean yang umum ditemui. Sample code-nya gw pakai Java karena ini bahasa yang paling gw kuasai. Tapi kasus seperti ini ga hanya terjadi pada bahasa Java saja.

Jebakan pertama terletak pada penamaan. Sesuai Naming Convention yang berlaku, penamaan variable yang baik adalah menggunakan kata benda (noun), seperti person, name, object, dan lainnya atau bisa juga berupa kata sifat (adjective) seperti active, height, width, dan lainnya. Sedangkan penamaan method yang baik adalah berupa kata kerja (verb), seperti getPerson(), printName(), constructObject(), dan sebagainya. Tak terkecuali dengan tipe data Boolean. Convention seperti itu dapat memudahkan code untuk dibaca karena code yang baik adalah code yang bisa dibaca secara English oleh manusia. Pada praktiknya, seringkali penamaan variable Boolean menggunakan kata kerja (verb). Contohnya pada code berikut:

boolean isPresent = true;
boolean present = true;
if(isPresent){
	System.out.println("isPresent = " + isPresent);
}
if(present){
	System.out.println("present = " + present);
}

Kira-kira lebih baku mana kalimat "if present" atau "if is present"? Secara English tentu lebih baku "if present". Kalau grammarnya salah, guru bahasa Inggrisnya ntar bisa marah😠.

Sebaliknya, untuk penamaan method maka conventionnya adalah menggunakan kata kerja (verb). Contoh best practice penamaan method dengan return boolean diantaranya isAvailable(), hasWarranty(), containsSomething(), doesExist(), checkStatus(), dll.

public class Product{
	public boolean isAvailable(){
		return true;
	}

	public boolean available(){
		return true;
	}
}

Product product = new Product();
if(product.available()){
	//logic if product is available
}
if(product.isAvailable()){
	//logic if product is available
}

Pada code di atas, secara grammar tentu lebih enak dibaca "if product is available" daripada "if product available"🙂.

Accessor pada Java Bean pun begitu. Misalkan pada sebuah Entity ada property Boolean seperti code berikut:

public class Employee{
	private static final long serialVersionUID = 1L;

	private int id;
	private String name;
	private String email;
	private boolean isDead;
	private boolean active;

	public boolean isDead(){
		return isDead;
	}

	public void setDead(boolean dead){
		isDead = dead;
	}

	public boolean isActive(){
		return active;
	}

	public void setActive(boolean active){
		this.active = active;
	}

}

Ini dapat menimbulkan beberapa masalah pada property isDead. Pertama, saat melakukan generate getter and setter, biasanya IDE akan men-generate method isDead() untuk accessor boolean by default, bukan getIsDead(). Mau tidak mau ini harus di-fix manual. Ok lah, anggap aja ini bisa kita fix secara manual. Masalah kedua adalah saat menggunakan Library pihak ketiga. Biasanya Library semacam Hibernate, Spring Data JPA, Jackson, Map Struct, dan sejenisnya membutuhkan method is[property] untuk mengakses property Boolean, karena mereka berpedoman pada Java Bean Convention. Bahkan pada beberapa Library, property Boolean dengan awalan is[property] tetap tidak bisa dibaca meskipun accessor-nya sudah diganti manual. Tentu ini akan sangat merepotkan dan membingungkan. Udah repot, bingung lagi, makanya solusi terbaik ikuti Convention aja😁. Nama field menggunakan noun, dan nama method accessor menggunakan verb. FYI, pada Java Bean Convention sendiri accessor untuk property bertipe Boolean dapat berupa is[property] atau bisa juga get[property], tapi yang paling sering digunakan adalah is[property].

Jebakan selanjutnya adalah saat menggunakan method dengan Boolean sebagai parameter. Penggunaan Boolean pada parameter seringkali akan menimbulkan kebingungan seperti pada code berikut:

public void printGender(boolean maleGender){
	if(maleGender){
		System.out.println("this is a man");
	} else {
		System.out.println("this is a woman");
	}
}

Ok, untuk sementara waktu code ini terlihat tidak ada masalah sama sekali. Tapi misalkan di masa depan aplikasinya membolehkan pilihan gender lain selain male & female (oops🤫), ini akan menimbulkan refactor besar-besaran terhadap code terkait yang menggunakan method ini. Bagaimana jika ada perubahan lagi? Misalkan ada tambahan requirement untuk orang yang ingin merahasiakan jenis kelaminnya. Makin pusing maintanance-nya, makin banyak refactor code, makin susah dibaca method-nya. Solusinya adalah dengan mengganti parameter Boolean dengan pilihan constant seperti enum, string constant, atau objek khusus. Pada Java bisa menggunakan Enum seperti berikut:

public void printGender(Gender gender){
	System.out.println("this is a " + gender.getNickName());
}

public enum Gender{
	MALE("man"),
	FEMALE("woman"),
	TRANSGENDER("transgender"),
	OTHER("unknown"),
	;

	private final String nickName;

	Gender(String nickName){
		this.nickName = nickName;
	}

	public String getNickName(){
		return nickName;
	}
}

Dengan begini, perubahan hanya terdapat pada satu tempat saja dan tidak akan mengganggu code lainnya yang sudah ada. Selain itu maintainance-nya juga lebih gampang, ga perlu ribet. Mau nambah opsi lagi berapapun juga cukup nambah constant value saja.

Selain penamaan variable, yang sering membuat bingung adalah negasi pada Boolean. Penggunaan negasi (!) pada method atau variable yang maknanya sudah negatif akan sangat membingungkan. Contohnya sebagai berikut:

boolean notAlive = isNotAlive();
if(!notAlive){
	System.out.println("it's alive!");
}

"not alive" maknanya sudah negatif, ditambah negasi jadi double negatif. Bisa bikin miskomunikasi dan ribet saat dibaca kan😵? Solusinya adalah by default gunakan method boolean bermakna positif isAlive(). Jika method boolean bermakna negatif dibutuhkan, gunakan !isAlive(). Jika method boolean bermakna negatif sering dipakai maka sebaiknya disediakan saja keduanya, yaitu isAlive() dan isNotAlive() untuk negasinya.

public boolean isAlive(){
	return true;
}

public boolean isNotAlive(){
	return !isAlive();
}

Dengan begitu, user tinggal pilih sesuai kebutuhan method mana yang mau dipakai.

Ga hanya itu, misalkan pada saat ingin melakukan aktivasi atau inaktivasi email, seperti pada kasus berikut:

public void activateEmail(boolean activate){
	if(activate){
		//logic to activate email
	} else {
		//logic to inactivate email
	}
}

Secara bahasa, "activate email" sudah merupakan perintah untuk melakukan aktivasi email. Tidak perlu lagi ditambahkan true karena perintah "activate email" sudah bermakna positif. Begitu juga bila menggunakan value false karena akan mengakibatkan makna yang berlawanan antara method dengan value parameter. Selain "activate", keyword lainnya yang juga berlawanan makna adalah "enable/disable". Penggunaan "disableEmail = true" atau "enableEmail = false" juga mengakibatkan makna berlawanan antara method dengan value parameter dan membingungkan ketika dibaca sekilas. Kalaupun ada perintah untuk inaktivasi email, maka itu sudah diluar scope dari perintah "activate email". Selain karena akan membingungkan, code di atas juga melanggar Single Responsibility Principle. Solusinya adalah memecah code tersebut menjadi dua method berbeda tanpa parameter boolean.

public void activateEmail(){
	//logic to activate email
}

public void inactivateEmail(){
	//logic to inactivate email
}

Jadi sekarang masing-masing method fokus bertanggung-jawab pada tugasnya masing-masing. Nama methodnya lebih jelas dan to-the-point.

Satu lagi yang membingungkan adalah ketika ada logic conditional yang agak panjang. Misalnya logicnya adalah kita akan memprint objek employee jika objeknya memiliki akun gmail. Kriterianya yaitu ketika statusnya aktif, emailnya ga null, dan emailnya diakhiri dengan "@gmail.com". Biasanya karena engineernya lagi males, logic tersebut ditulis aja di dalam kurung conditionalnya. Contohnya seperti ini:

if(employee != null && employee.getEmail() != null && employee.getEmail().endsWith("@gmail.com") && employee.isActive()){
	System.out.println("employee " + employee + " si eligible to execute");
}

Masalahnya adalah ketika engineer lain membaca code tersebut yang ga tau apa-apa jadi bertanya-tanya, "itu maksudnya apa ya?"🤔. Kalau conditionalnya cuma satu atau dua statement masih mending, tapi kalau banyak, maksud dari code tersebut jadi ga keliatan. Dia terpaksa harus membaca detail keseluruhan isi conditionalnya untuk bisa mengerti. Solusinya adalah conditional tersebut dibungkus jadi variabel boolean dan dikasih nama misalnya "employeeHasGmail".

boolean employeeHasGmail = employee != null && employee.getEmail() != null && employee.getEmail().endsWith("@gmail.com") && employee.isActive();
if(employeeHasGmail){
	System.out.println("employee " + employee + " is eligible to execute");
}

Sekarang lebih enak dibaca. Engineer lain jadi paham bahwa maksudnya adalah untuk memprint employee yang memiliki akun gmail. Ga perlu membaca keseluruhan isi conditionalnya. Cukup tau dari nama variabelnya saja.

Jebakan yang satu ini mungkin hanya terjadi di bahasa Java atau bahasa lain sejenis. Misalkan ketika kita menyimpan boolean menggunakan wrapper class, ada kemungkinan nilainya null. Melakukan pengecekan menggunakan Nullable Boolean ga bisa dilakukan seperti Primitive boolean. Semua wrapper class di Java ketika diperlakukan seperti Primitive Type maka akan di-unboxing otomatis menjadi Primitive Type sehingga terjadi NullPointerException seperti yang pernah gw bahas di post Easter Eggs Java. Contohnya seperti ini:

Boolean b = null;
if(b){
	System.out.println("halo");
}

Untuk mengatasinya, kita harus membandingkannya dengan sesama objek Boolean. Di Java sendiri udah ada constant Boolean.TRUE & Boolean.FALSE untuk digunakan sebagai pembanding objek Boolean seperti ini:

Boolean b = null;
if(Boolean.TRUE.equals(b)){
	System.out.println("halo");
}

Dengan begini kita bisa terhindar dari NullPointer.

Kesimpulan dari gw adalah pikir-pikir dulu ketika menggunakan Boolean. Jangan sampai terjebak di kesalahan-kesalahan di atas. Ini bukan sebuah "larangan" atau "hukum" yang wajib ditaati. Ini hanya semacam panduan agar code lebih dapat dibaca dan dikembangkan. Tetap ikuti arahan tentang kata benda (noun) untuk variable, dan kata kerja (verb) untuk method. Penamaan Boolean yang tidak tepat dapat menimbulkan kebingungan saat memahami code. Penggunaan Boolean pada parameter juga seringkali susah di-maintain seperti kasus di atas. Bukan hanya untuk kasus simple saja, secara umum memang pemakaian Boolean sebagai parameter seringkali tidak tahan perubahan alias susah di-maintain. Misalkan pada bisnis yang sedang berjalan, sekarang hanya ada dua pilihan status pembayaran, dibayar dan belum dibayar. Jadi hanya menggunakan Boolean isPaid. Atau dua pilihan status pengiriman, diterima dan tidak diterima. Jadi hanya menggunakan Boolean isReceived. Nah, ketika terjadi improvement di masa depan, misalkan pilihan pembayaran bisa juga dibayar setengah, atau status pengiriman juga bisa diterima sebagian. Ini dapat mengakibatkan "kebakaran" dan rombak code. Makanya, lebih baik gunakan constant sedari awal. Tapi bukan berarti setiap pemakaian Boolean pada parameter benar-benar "diharamkan". Tetap ada pengecualian untuk beberapa kasus yang memang membutuhkan Boolean sebagai parameter. Salah satunya setter untuk property Boolean pada Entity model yang tentu saja membutuhkan Boolean sebagai parameter. Kalau nggak, gimana caranya nge-set nilainya 😂. Dan ini lebih sering berlaku pada bisnis Class. Sedangkan untuk Class yang isinya lebih teknis mungkin ada pengecualian pada kasus tertentu. Oh ya, untuk bahasa yang tidak ada type Enum bisa menggunakan object seperti pada Javascript. Untuk Jebakan lainnya, bisa dibaca juga post tentang Jebakan Optional