Archive for September, 2008

Bagian 13. Make It Right
September 19, 2008

$5 + Rp 10=Rp 9.010 jika $1=Rp 9000

Rp 10 + Rp 10=Rp 20

Pada bagian 12 kita telah membuat test untuk penjumlahan dua mata uang. Kita memperkenalkan dua kiasan (metaphor), bank dan ekspresi (ungkapan). Kita juga telah membuat test kita berjalan (green). Sayangnya impelementasi kita baru sebatas implementasi boongan (tipuan/fake). Kini saatnya kita memberikan implementasi betulan(right).

Langkah kita apa sekarang? Ok. Kita telah melakukan langkah 1 sampai 4. Langkah selanjutnya adalah membuang duplikasi. Adakah duplikasi yang telah kita perbuat pada bagian sebelumnya? Perhatikan kedua code berikut ini:

public class Bank{
	public Money Tukar(IEkspression ekpspresi, string tukarKe){
		return Money.Rp(20);
	}
}

dan pada bagian test berikut ini,

[Test]
public void SimpleAddition(){
	Money sepuluhRupiah=Money.Rp(10);
	IEkspression sum=sepuluhRupiah.Plus(sepuluhRupiah);
	Bank bank=new Bank();
	Money hasilTukar=bank.Tukar(sum,"Rp");
	Assert.AreEqual(Money.Rp(20),hasilTukar);
}

Kita tidak membuat duplikasi code tetapi duplikasi data. Kita telah mereturn konstanta Rp20, sebuah implementasi tipuan sekedar agar bisa jalan, yang sebetulnya sama persis dengan Rp10+Rp10.

Dus, apa yang sebaiknya kita lakukan agar duplikasi ini menghilang? Dalam contoh sebelumnya kita mengganti konstanta dengan variabel kemudian melakukan langkah mundur sampai kebagian test. Sayangnya disini kita tidak begitu jelas bisa menerapkan itu. Kita harus mencari jalan keluar lain, kita bergerak maju saja, sedikit sepekulasi sih.

$5 + Rp 10=Rp 9.010 jika $1=Rp 9000

Rp 10 + Rp 10=Rp 20

Return Sum bukan Money untuk ekspresi

Pertama, method Money.Plus() daripada mereturn Money lebih baik mereturn IEkspression. Kita harus membuat class baru katakanlah Sum. Untuk itu kita harus membuat test baru,

[Test] 
public void PlusReturnSum(){ 
	Money sepuluhRupiah=Money.Rp(10); 
	IEkspression result=sepuluhRupiah.Plus(sepuluhRupiah); 
	Sum sum=(Sum)result; 
	Assert.AreEqual(sepuluhRupiah,sum.Penambah); 
	Assert.AreEqual(sepuluhRupiah,sum.Tertambah); 
}

Agar test ini berjalan kita harus membuat class Sum dengan dua buah property Penambah dan Tertambah.

public class Sum{ 
	public Money Penambah{ 
		get { 
			return null; 
		} 
	} 
	public Money Tertambah{ 
		get { 
			return null; 
		} 
	} 
}

Program bisa dikompile tapi test berakhir gagal (Red), dengan pesan tidak bisa melakukan casting . Ini karena method Money.Plus() masih mereturn Money bukan Sum.

public  class Money:IEkspression{ 
	...
	public IEkspression Plus(Money addend){ 
		return new Sum(this,addend); 
	} 
}

dan class Sum memerlukan konstruktor,

public class Sum{ 
	public Sum(Money tertambah, Money penambah){ 
	 
	} 
	...
}

Selian itu Sum juga harus mengimplementasikan IEkspression,

public class Sum:IEkspression{ 
	public Sum(Money tertambah, Money penambah){ 
		...
	}
}

Test bisa kita kompile tetapi tetap gagal, kali ini disebabkan karena kita belum menset value dikonstruktor dan masih mereturn property sebagai null,

public class Sum:IEkspression{ 
	private Money m_tertambah; 
	private Money m_penambah; 
	public Sum(Money tertambah, Money penambah){ 
		this.m_penambah=penambah; 
		this.m_tertambah=tertambah; 
	} 
	public Money Penambah{ 
		get { 
			return this.m_penambah; 
		} 
	} 
	public Money Tertambah{ 
		get { 
			return this.m_tertambah; 
		} 
	} 
}

Setelah kita ubah seperti ini test kita berubah menjadi Green.

Berikutnya, method Bank.Tukar() menerima Sum sebagai parameter. Jika currency didalam Sum keduanya sama dan target currency dari method Bank.Tukar() juga sama, maka hasilnya adalah Money yang nilainya merupakan penjumlahan kedua amount dalam Sum. Berikut ini adalah test untuk method Tukar(),

[Test] 
public void HasilTukarDariSum(){ 
	IEkspression sum=new Sum(Money.Rp(10),Money.Rp(20)); 
	Bank bank=new Bank(); 
	Money hasilTukar=bank.Tukar(sum,"Rp"); 
	Assert.AreEqual(Money.Rp(30),hasilTukar); 
}

Pesan yang keluar jika test dijalankan tidak cukup jelas, “Money” tidak sama dengan “Money”. Agar pesan ini menjadi jelas, saya terpaksa mengoverride method ToString() dari Money,

public  class Money:IEkspression{ 
	...
	public override string ToString(){ 
		return String.Format("{0} {1}",this.m_amount,this.m_currency); 
	}
}

Setelah saya override, kini pesan kesalahan menjadi jelas : “diharapkan Rp30, tetapi yang keluar Rp20.” Darimana datangnya Rp20? Masih ingatkan, kita sedang menghilangkan duplikasi di method Bank.Tukar(), yang implementasinya kita kasih tipuan? Masih ingat cara ketiga: Triangulation.

Agar test kita kembali menjadi green, kita ubah method Tukar() dari Bank sebagai penjumlahan dua amount dari Money,

public class Bank{ 
	public Money Tukar(IEkspression ekspresi, string tukarKe){ 
		Sum sum=(Sum)ekspresi; 
		int amount=sum.Tertambah.Amount+sum.Penambah.Amount; 
		return new Money(amount,tukarKe); 
	} 
}

Sayangnya kita tidak memeliki property Amount di class Money. Jadi kita harus tambahkan,

public  class Money:IEkspression{ 
	...
	public int Amount { 
		get { 
			return this.m_amount; 
		} 
	} 
}

Sampai disini test kita telah menjadi green kembali. Sayangnya IEkspression bisa diimplementasikan oleh siapapun, masih ingat Money juga mengimplementasikan. Jika object Money kita passing masuk, tentu tidak akan bisa dicasting. Ok, kita buatkan test,

[Test] 
public void HasilTukarDariMonye(){ 
	Bank bank=new Bank(); 
	Money hasilTukar=bank.Tukar(Money.Rp(1),"Rp"); 
	Assert.AreEqual(Money.Rp(1),hasilTukar); 
			 
}

Benar, tidak bisa dicasting ke Sum. Karena itu kita ubah method Tukar() menjadi,

public class Bank{ 
	public Money Tukar(IEkspression ekspresi, string tukarKe){ 
		if(ekspresi is Money) 
			return (Money)ekspresi; 
		Sum sum=(Sum)ekspresi; 
		int amount=sum.Tertambah.Amount+sum.Penambah.Amount; 
		return new Money(amount,tukarKe); 
	} 
}

yaitu kita melakukan cek, yang masuk Money atau bukan. Jika Money kita langsung kembalikan.

Karena penjumlahan amount didalam method Tukar() hanya berguna untuk Sum, maka kita pindahkan penjumlahan ini ke Sum dan kita tambahkan method Tukar() kedalam Sum dengan keluaran Money,

public class Sum:IEkspression{ 
	....
	public Money Tukar(string ke){ 
		int amount=this.Tertambah.Amount+this.Penambah.Amount; 
		return new Money(amount,ke); 
	} 
}

Sehingga method Tukar() di class Bank bisa kita ubah menjadi,

public class Bank{ 
	public Money Tukar(IEkspression ekspresi, string tukarKe){ 
		if(ekspresi is Money) 
			return (Money)ekspresi; 
		Sum sum=(Sum)ekspresi; 
		return sum.Tukar(tukarKe); 
	} 
}

Test kita jalankan dan tetap green. Langkah selanjutnya kita juga perkenalkan method Tukar() ke class Money,

public  class Money:IEkspression{ 
	...
	public Money Tukar(string ke){ 
		return this; 
	} 
}

dan class Bank selanjutnya kita ubah menjadi,

public class Bank{ 
	public Money Tukar(IEkspression ekspresi, string tukarKe){ 
		if(ekspresi is Money) 
			return ((Money)ekspresi).Tukar(tukarKe); 
		Sum sum=(Sum)ekspresi; 
		return sum.Tukar(tukarKe); 
	} 
}

Test saya jalankan dan tetap green. Kini terlihat kedua class yang mengimplementasikan IEkspression masing-masing telah memiliki method Tukar(). Jika method ini juga kita perkenalkan ke interface IEkspression, kita bisa hapus reference Sum dan Money dari class Bank selamanya.

public interface IEkspression{ 
	Money Tukar(string ke); 
}

dan class Bank menjadi,

public class Bank{ 
	public Money Tukar(IEkspression ekspresi, string tukarKe){ 
		return ekspresi.Tukar(tukarKe); 
	} 
}

Sampai disini test tetap green. Apa yang akan kita lakukan selanjutnya? Kita akan melakukan konversi betulan. Tetapi tentu saja bukan disini melainkan di bagian berikutnya. Selamat mengikuti.