TDD_I_06: Rovnost pro všechny se vrací

$5 + 10 CHF = $10 je-li kurz 2:1

$5 * 2 = $10

Převést položku mnozstvi na soukromou

Vedlejší účinky třídy Dolar?

Zaokrouhlování peněz?

Metoda equals()

Metoda hashCode()

Rovnost s null

Rovnost objektů

5 CHF * 2 = 10 CHF

Dolar / Frank duplicita

Společná metoda equals()

Společné násobení

Část příběhu v knize "Crossing to Safety" od wallace Stegnera, v níž autor popisuje dílnu hlavní postavy. Každá věc je na svém místě, podlaha je bez poskvrnky, všechno je uklizené a vycíděné. Avšak to hlavní postava nikdy neudělala. "Jeho životním cílem se stala příprava. Nejdříve se připravil a pak uklidil" (tato kniha také způsobila, že jsem v první třídě cestou přes Atlantik nahlas brumlal. Dejte si na tu knihu pozor).

Této pasti jsme se vyhnuli v páté kapitole. V podstatě jsme zprovoznili nový test. Dopustili jsme se však vážných hříchů tím, že jsme kopírovali a vkládali tuny kódu, jen abychom to udělali rychle. Přišel čas to po sobě uklidit.

Jedna možnost je přimět naši třídu, aby byla potomkem té druhé. Zkusil jsem to, ale téměř žádný kód se tím neušetří. Místo toho najdeme společnou nadřazenou třídu pro obě třídy, jak je vidět níže (už jsem to také zkoušel a funguje to skvěle, i když to chvilku zabere).

	Dolar			Penize
	  |			  /\
	  |			 /  \
	  |			/    \
	Frank		      Dolar Frank

A co kdybych do třídy Penize umístil společný kód pro porovnání? Začneme s málem:

Penize

class Penize {
}

Všechny testy stále fungují - ne že bychom snad něco rozbili, ale stejně je dobré pustit testy ještě jednou. Pokud třída Dolar rozšiřuje třídu Penize, nemůže se snad nic pokazit.

Dolar
class Dolar extends Penize {
	private int mnozstvi;
}

Nebo ano? Ne, všechny testy stále fungují. Nyní můžeme přesunout instanční proměnnou mnozstvi nahoru do třídy Penize:

Penize

class Penize {
	protected int mnozstvi;
}

Dolar

class Dolar extends Penize {
}

Viditelnost se musela změnit ze soukromé na chráněnou, takže podtřída proměnnou stále vidí (kybychom chtěli postupovat ještě pomaleji, mohli jsme nejdříve deklarovat položku ve třídě Penize, pak ji odstranit ze třídy Dolar. Připadám si odvážný).

TeĎ se můeme zaměřit na kód metody equals() a na to, jak ho přesunout nahoru. Nejprve změníme deklaraci pomocné proměnné:

Dolar

public booelan equals(Object object) {
	Penize dolar = (Dolar)object;
	return mnozstvi == dolar.mnozstvi;
}

Testy pořád ještě fungují. TeĎ změníme přetypování:

Dolar

public boolean equals(Object object) {
	Penize dolar = (Penize)object;
	return mnozstvi == dolar.mnozstvi;
}

Chceme-li být důslední, měli bychom změnit jméno pomocné proměnné:

Dolar

public boolean equals(Object object) {
	Penize peniz = (Penize)object;
	return mnozstvi == penize.mnozstvi;
}

Bybí můžeme tuto proměnnou přesunout ze třídy Dolar do třídy Penize.

Penize

public boolean equals(Object object) {
	Penize dolar = (Penize)object;
	return mnozstvi == dolar.mnozstvi;
}

A nyní se musíme zbavit metody Frank.equals(). nejdřív si povšimneme, že testy na rovnost neobsahují porovnání mezi franky. Tady se nám vymstilo naše kopírování kódu. Než změníme kód, napíšeme testy, což jsme měli udělat nejdříve.

Často budeme využívat TDD přip saní kódu, který nemá odpovídající testy (alespoň v přísštích deseti letech nebo tak). Když nemáte dostatek testů, jste nuceni refaktorovat bez podpory testů. V takové refaktorizaci byste mohli udělat chybu a testy by přesto fungovaly, co s tím?

Napište všechny testy, které chcete. Pokud to neuděláte, nakonec něco pokazíte během refaktorizce. V tom případě se vám refaktorizace nebude líbit a přestanete ji používat. pak se váš návrh zhorší. Dostanete výpověď. Uteče vám pes. Přestanete si hlídat linii. Zkazí se vám zuby. Abyste si zachránili chrup, před refaktorizací zpětně testujte. Naštěstí existují testy, které se snadno píší. Pouze zkopírujeme testy pro třídu Dolar.

	public void testRovnosti() {
		assertTrue(new Dolar(5).equals(new Dolar(5)));
		assertFalse(new Dolar(5).equals(new Dolar(6)));
		assertTrue(new Frank(5).equals(new Frank(5)));
		assertFalse(new Frank(5).equals(new Frank(6)));
	}

Více duplicit, dva řádky navíc! Napravíme také tyto hříchy. Když jsou testy připraveny, můžeme přimět třídu Frank, aby rozšířila třídu Penize:

Frank

class Frank extends Penize {
	private int mnozstvi;
}

Můžeme smazat položku mnozstvi ze třídy Frank, protože už jednu máme ve třídě Penize:

Frank

class Frank extends Penize {
}

Metoda Frank.equals() je téměř stejná jako Penize.equals(). Budou-li naprosto stejné, můžeme smazat implementaci ze třídy Frank, aniž by se změnil smysl programu. nejprve změníme deklaraci pomocné proměnné:

Frank

	public boolean equals(Object object) {
		Penize frank = (Frank)object;
		return mnozstvi == frank.mnozstvi;
	}

Pak změníme přetypování:

Frank

	public boolean equals(Object object) {
		Penize frank = (Penize)object;
		return mnozstvi == frank.mnozstvi;
	}

Skutečně musíme měnit název pomocné proměnné, aby odpovídala nadřazené třídě? NEchám to na vašem uvážení... tak jo, uděláme to:

Frank

	public boolean equals(Object object) {
		Penize penize = (Penize)object;
		return mnozstvi == penize.mnozstvi;
	}

$5 + 10 CHF = $10 je-li kurz 2:1

$5 * 2 = $10

Převést položku mnozstvi na soukromou

Vedlejší účinky třídy Dolar?

Zaokrouhlování peněz?

Metoda equals()

Metoda hashCode()

Rovnost s null

Rovnost objektů

5 CHF * 2 = 10 CHF

Dolar / Frank duplicita

Společná metoda equals()

Společné násobení

Porovnání franků a dolarů

Teď už neexistuje rozdíl mezi metodami Frank.equals() a Penize.equals(), takže smažeme nadbytečnou implementaci ve třídě Frank a spustíme testy. Fungují.

Co se stane, když porovnáme franky a dolary? Dostaneme se k tomu v sedné kapitole. Abychom si to zopakovali:

Vývoj řízený testy, Kapitola 5 | Dr3dweRkZ | Vývoj řízený testy, Kapitola 7