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:
- Postupně jsme přesunuli společný kód z jedné třídy (Dolar) do nadřazené třídy (Penize).
- Vytvořili jsem také druhou podtřídu (Frank).
- Tím jsme propojili obě implementace - metody equals() - předtím než jsme odstranili tu nadbytečnou.
Vývoj řízený testy, Kapitola 5 | Dr3dweRkZ | Vývoj řízený testy, Kapitola 7