načítání...
nákupní košík
Košík

je prázdný
a
b

Kniha: Údržba kódu převzatých programů - Michael C. Feathers

Údržba kódu převzatých programů
-14%
sleva

Kniha: Údržba kódu převzatých programů
Autor:

Vylepšete výkon, funkce, spolehlivost i ovladatelnost aplikace Programujete v týmu? Setkáváte se se zdrojovými kódy, které napsal někdo jiný? Potýkáte se s metodami a funkcemi, které ... (celý popis)
Titul je skladem 1ks - odesíláme ihned
Ihned také k odběru: Ostrava
Vaše cena s DPH:  497 Kč 427
+
-
rozbalKdy zboží dostanu
14,2
bo za nákup
rozbalVýhodné poštovné: 39Kč
rozbalOsobní odběr zdarma

hodnoceni - 0%hodnoceni - 0%hodnoceni - 0%hodnoceni - 0%hodnoceni - 0%   celkové hodnocení
0 hodnocení + 0 recenzí

Specifikace
Nakladatelství: » Computer press
Médium / forma: Tištěná kniha
Rok vydání: 2009-05-19
Počet stran: 368
Rozměr: 167 x 225 mm
Úprava: 367 stran : ilustrace
Vydání: Vyd. 1.
Název originálu: Working effectively with legacy code
Spolupracovali: překlad Jiří Berka
Vazba: brožovaná lepená
ISBN: 9788025121276
EAN: 9788025121276
Ukázka: » zobrazit ukázku
Popis

Vylepšete výkon, funkce, spolehlivost i ovladatelnost aplikace Programujete v týmu? Setkáváte se se zdrojovými kódy, které napsal někdo jiný? Potýkáte se s metodami a funkcemi, které nikdo neotestoval? Musíte denně luštit statisíce řádků kódu, abyste nalezli chybějící středník? Právě vám kniha nabízí postupy, jak efektivněji pracovat s rozsáhlými a netestovanými částmi převzatého kódu. Uvedené strategie a metody autor demonstruje na konkrétních modelových situacích. Ve svých postupech vychází z dlouholetého školení stovek vývojářů a testerů aplikací. Výklad je nezávislý na platformě nebo použitém programovacím jazyku. Autor se v knize věnuje mimo jiné těmto tématům: - Přidávání nových kousků kódu bez toho, aby se ztratila funkčnost - Přidání nové vlastnosti pomocí vývoje řízeného testy - Rušení závislostí pro všechny související třídy - Zlepšování původních návrhů - Optimalizace využití zdrojů - Vyhledání a smazaní nepoužívaného kódu - Rychlá refaktorizace - Využití typové kontroly a dalších možností překladače - Funkční a efektivní umístění testovacího kódu - Vytváření objektů z procedurálních modelů Příklady jsou uvedeny v jazycíchh C/C++/C#, Java, ale i v Delphi, Visual Basicu, COBOLu či Fortranu. Kniha obsahuje souborný výklad 24 nejužitečnějších metod pro rušení závislostí. Díky nim budete schopní měnit prvky kódu izolovaně, a tím i bezpečněji. O autorovi: Michael C. Feathers pracuje ve společnosti Object Mentor. Je expertem na agilní a extrémní programování, refaktorování a objektově orientované návrhy. Před svým nástupem do Object Mentor navrhoval vlastní programovací jazyky, překladače a multiplatformní knihovny tříd. Je aktivním členem komunity Agile/XP, ACM a IEEE.

Předmětná hesla
Kniha je zařazena v kategoriích
Recenze a komentáře k titulu
Zatím žádné recenze.


Ukázka / obsah
Přepis ukázky

KAPITOLA 20

Tato třída je příliš

velká a já ji nechci

dále zvětšovat

Mnoho vlastností, které lidé do systému přidávají, má charakter drobných vylepšení.

K tomu stačí malý objem kódu a možná několik metod navíc. Je lákavé provést tyto

změny v existující třídě. Je možné, že nový kód musí používat nějaká data z existující

třídy a nejjednodušší je prostě umístit kód do ní. Bohužel tento jednoduchý způsob

změn může vést k vážným problémům. Když přidáváme kód do exsitujících tříd,

skončíme u rozsáhlých metod a velkých tříd. Z našeho softwaru se stane bažina a bude

časově mnohem náročnější vymyslet, jak přidat nové vlastnosti nebo jen porozumět

těm stávajícím.

Jednou jsem navštívil tým, který měl něco, co vypadalo na papíře jako pěknáarchitek

tura. Řekli mi, která třída byla primární, a popsali běžné případy, kdy mezi sebou třídy

komunikovaly. Poté mi ukázali několik pěkných diagramů UML, které zobrazovaly

strukturu. Byl jsem překvapen, když jsem si začal kód prohlížet. Každou třídu bylo

možné rozdělit na deset dalších nebo podobně, a kdyby to udělali, pomohlo by jim to

překonat ty nejpalčivější problémy.

Jaké problémy přinášejí velké třídy? Prvním z nich je zmatek. Když máte v jedné třídě

50 nebo 60 metod, často je obtížné vycítit, které změny v kódu se mají provést a zda

neovlivní něco jiného. V tom nejhorším případě mají rozsáhlé třídy neuvěřitelné

množství datových složek a je obtížné se vyznat v tom, jaké následky bude mít změna

některé z nich. Jiný problém spočívá v plánování úkolů. Když má třída 20 odpovědností,

je možné, že budete mít řadu důvodů, proč provádět změny. Během jednoho pozmě

ňovacího kroku můžete mít několik programátorů, kteří potřebují dělat se třídou různé

úkony. Při souběžné práci to může vést k velkým těžkostem, především kvůli třetímu

problému: velké třídy se obtížně testují. Zapouzdření je dobrá věc, že? Dobrá, ale na to

se neptejte testerů, protože by vám utrhli hlavu. Rozsáhlé třídy toho obvykle mnoho

skrývají. Zapouzdření je výborné, pokud nám pomáhá provádět lépe dedukce a když


224

víme, že některé věci lze změnit jen za určitých okolností. Avšak když zapouzdříme příliš

mnoho kódu, vnitřní jádro degeneruje a hnije. Neexistuje žádný efektivní způsob, jakdetekovat následky změn v kódu, takže se lidé opět uchylují k programovací technice editujte amodlete se (str. 31). Za této situace trvají změny příliš dlouho nebo vzrůstá počet programových

chyb. Nějak za nečitelnost kódu zaplatit musíte.

Prvním úkolem, se kterým se setkáme u velkých tříd, je: jak máme pracovat, abychom

stav věci nezhoršili? Základní taktikou, kterou zde můžeme použít, je technika roubované

metody (str. 73) a roubované třídy (str. 76). Když musíme provádět změny, měli bychom

přemýšlet o tom, zda kód umístíme do nové třídy nebo do nové metody. Roubovaná třída

(str. 76) může opravdu zajistit, aby se stav kódu příliš nezhoršoval. Pokud umístíte nový

kód do nové třídy, možná jej budete muset z původní třídy volat, ale přinejmenším třídu

příliš nerozšíříte. Roubovaná metoda (str. 73) je rovněž užitečná, ale není to tak vidět. Když

přidáte do nové metody kód, budete mít další metodu, ale nakonec identifikujete apojmenováváte další věc, kterou třída dělá. Jména metod vám často napoví a pomohou rozdělit

třídu na menší úseky.

Základní léčbou velkých třídy je refaktorování. Pomáhá rozdělit třídy na několik menších

tříd. Ale největším úkolem je stanovení, jak by měly menší třídy vypadat. Naštěstí nějaké

vodítko máme.

Princip jedné odpovědnosti

(SRP: Single-Responsibility Principle)

Každá třída by měla mít jednu odpovědnost: měla by mít v systému jeden účel a k její změně

by měl existovat jen jeden důvod.

Princip jedné odpovědnosti je těžké popsat, protože tato myšlenka je poněkud mlhavá. Když

se na ni podíváme z velmi naivního úhlu pohledu, mohli bychom říci: „Hm, to znamená,

že by každá třída měla mít jen jednu metodu, že?“ Dobrá, na metody lze nazírat jako na

odpovědnosti. Třída Task je odpovědná za běh a použití své metody run, dále za to, že nám

v metodě taskCount říká, kolik běží podřízených úloh atd. Ale pravý význam odpovědnosti

vychází najevo tehdy, když mluvíme o hlavním záměru. Obrázek 20.1 ukazuje příklad.

Obrázek 20.1: Analyzátor pravidel

RuleParser

- current : string

- variables : HashMap

- currentPosition : int

+ evaluate(string) : int

- branchingExpression(Node left, Node right) : int

- causalExpression(Node left, Node right) int

- variableExpression(Node node) : int

- valueExpression(Node node) : int

- nextTerm() : string

- hasMoreTerms() : boolean

+ addVariable(string name, int value)

Kapitola 20 – Tato třída je příliš velká a já ji nechci dále zvětšovat


225

Máme malou třídu, která vyhodnocuje řetězce s početními úkony vyjádřené v nějakém

neznámém jazyce. Jaké má odpovědnosti? Můžeme se podívat na jméno třídy a zjistíme jednu

z nich: analyzuje. Ale je to její hlavní záměr? Nezdá se, že by to měla být jen analýza. Zdá se,

že je také vyhodnocuje.

Co dělá ještě jiného? Uchovává aktuální řetězec, který analyzuje. Také uchovává pole, vekterém během analýzy označuje aktuální pozici. Zdá se, že obě tyto miniodpovědnosti vyhovují

kategorii analyzování.

Podívejme se na druhou proměnnou, na pole variables. Uchovává několik proměnných,

které analyzátor používá k vyhodnocování aritmetických výrazů u početních úkonů, jako

je a + 3. Když někdo zavolá metodu addVariable s argumenty a a 1, bude mít výraz a + 3

hodnotu 4. Takže se zdá, že tato třída má ještě druhou odpovědnost, správu proměnných.

Jsou zde další odpovědnosti? Jiným způsobem, jak je najít, je podívat se na názvy metody.

Existuje nějaký přirozený způsob, jak seskupit názvy metod? Zdá se, že spadají do těchto

skupin:

evaluate branchingExpression nextTerm addVariable

causalExpression hasMoreTerms

variableExpression

valueExpression

Metoda evaluate (vyhodnoť) je vstupním bodem třídy. Je to jedna z celkem dvou veřejných

metod a nese hlavní odpovědnost třídy: vyhodnocování. Všechny metody, které končípříponou Expression (výraz), jsou přibližně stejné. Nejenže jsou podobně pojmenovány, alevšechny přijímají jako argument Nodes a jako hodnotu vracejí celé číslo, které vyjadřuje hodnotu

podvýrazu. Metody nextTerm (následující člen) a hasMoreTerms (má další členy) si jsou rovněž

podobné. Zdá se, že představují jakousi zvláštní formu tokenizace členů (jejich rozklady na

symboly). Jak jsme již řekli dříve, metoda addVariable se zabývá správou proměnných.

Abychom to shrnuli, zdá se, že metoda Parser má tyto odpovědnosti:

analýzu,

vyhodnocení výrazu,

tokenizaci podmínek,

správu proměnných.

Kdybychom dělali úplně od počátku návrh, ve kterém by všechny tyto odpovědnosti byly

oddělené, mohlo by to vypadat, jako na obrázku 20.2.:

Je toho příliš? Možná. Lidé, kteří vytvářejí malé jazykové interprety, často směšují analýzu

a vyhodnocení výrazů. Vyhodnocení provádějí souběžně s analýzou. I když to může být

praktické, přestane to fungovat, když se jazyk rozroste. Další skromná odpovědnost patří

metodě SymbolTable. Jedinou odpovědností této metody je obstarat vzájemné přiřazení jmen

proměnných a celočíselných hodnot. Tato třída pro nás neznamená žádnou zvláštní výhodu,

pokud používá jen nějakou hešovou tabulku nebo seznam. Pěkný návrh, ale víte co? Je to spíše

hypotetické. Dokud tuto část systému nepřepíšeme, náš malý návrh multitřídy bude pouze

vzdušným zámkem.

„

„

„

„

Tato třída je příliš velká a já ji nechci dále zvětšovat


226

Obrázek 20.2: Třídy početních úkonů se separovanými odpovědnostmi

V reálných případech velkých tříd je důležité identifikovat různé odpovědnosti a určit způsob,

jak se dostaneme krok za krokem k přesněji zaměřeným odpovědnostem.

Pochopení odpovědností

Na příkladě se třídou RuleParser v minulé sekci jsem ukázal jeden případ dekompozice

třídy na menší třídy. Byla to v podstatě rutinní práce. Vypsal jsem si všechny metody a začal

přemýšlet nad tím, co je jejich záměrem. Základní otázky, které jsem si položil, byly: „Proč je

zde tato metoda?“ nebo „Co dělá pro tuto třídu?“ Pak jsem je seskupil do seznamů a dal jsem

dohromady metody, které měly podobné zaměření.

Tento postup nazývám seskupování metod podle chápání odpovědností. Je to jen jedna

z mnoha možností, jak v existujícím kódu sledovat odpovědnosti. Naučit se porozumět

odpovědnosti je důležitá dovednost návrháře a vyžaduje praxi. Mohlo by se zdát divné mluvit

o schopnosti vytvářet návrh v souvislosti se zděděným kódem, ale mezi odhalovánímodpovědností v existujícím kódu a jejich formulací v kódu, který jste dosud nenapsali, je malý

rozdíl. Klíčovou záležitostí je schopnost pochopit odpovědnosti a naučit se, jak je správně

separovat. Pokud nic jiného, zděděný kód nabízí mnohem více možností uplatnění kvalifikace

návrháře, než umožňuje zavádění nových vlastností kódu. Je jednoduší mluvit okompromisech v návrhu, když vidíte kód, který bude ovlivněn, a je rovněž jednodušší vidět, zda jestruktura přiměřená danému kontextu, protože tento kontext je reálný a leží přímo před námi.

Tato sekce popisuje několik heuristik, které můžeme pro pochopení odpovědností vexistujícím kódu použít. Poznamenejme, že odpovědnosti nevynalézáme. Jen zjišťujeme, které tam

jsou. Bez ohledu na to, co struktura zděděného kódu obsahuje, provádějí jeho částiidentifikovatelné úkony. Někdy je těžké je odhalit, ale k tomu mohou být tyto techniky užitečné. Zkuste

je aplikovat i na kód, který nemusíte okamžitě měnit. Čím víc si budete všímat odpovědností

vlastních danému kódu, tím víc jej poznáte.

TermTokenizer

+ nextTerm () : String

+ hasMoreTerms() : boolean

RuleEvaluator

+ evaluate(string)

+ addVariable(string, int)

SymbolTable

+ addVariable(string, int)

RuleParser

+ parse(string) : Expression

{abstract}

Expression

+ evaluateWith(SymbolTable)

vytváří

parametr

Kapitola 20 – Tato třída je příliš velká a já ji nechci dále zvětšovat


227

Heuristika č. 1: Seskupte metody

Podívejte se na podobné názvy metod. Napište všechny metody dané třídy spolu s jejich

přístupovými omezeními (veřejná, soukromá atd.) a pokuste se nalézt ty, které by mohly

mít něco společného. Tato technika seskupování metod je docela dobrý začátek, zvláště u velkých tříd. Důležité je rozpoznat, že nemusíte kategorizovat všechna jména do nových tříd. Stačí se podívat, zda lze nalézt takové, které vypadají, jako by byly součástí nějaké obecné odpovědnosti. Můžete-li identifikovat některé z odpovědností, které jsou poněkud mimo hlavní odpovědnost třídy, máte směr, kterým se při práci s kódem časem můžete vydat. Počkejte si, dokud nebudete muset modifikovat některou z metod, které jste kategorizovali, a pak se rozhodněte, zda v tomto bodě budete třídu extrahovat. Seskupování metod je i dobré týmové cvičení. V místnosti, kde pracuje váš tým, si zřiďte nástěnku se seznamy názvů metod každé z hlavních tříd. Členové týmu mohou čas od času udělat značku na nástěnce a označit rozdílné seskupování metod. Celý tým můžeprodiskutovat, které seskupení je lepší, a rozhodnout, jakým způsobem se do kódu pustí.

Heuristika č. 2: Podívejte se na skryté metody

Věnujte pozornost soukromým a chráněným metodám. Pokud jich má třída příliš mnoho,

často to naznačuje, že je v této třídě jiná třída, která se mermomocí snaží dostat ven. Velké třídy toho mohou hodně skrývat. Tato otázka se objevuje znovu a znovu u lidí, kteří jsou v jednotkovém testování nováčci: „Jak mám testovat privátní metody?“ Mnoho lidí stráví řešením tohoto problému spoustu času. Ale jak jsem se již zmínil v předchozí kapitole,opravdová odpověď spočívá v tom, že když máte potřebu testovat soukromou metodu, neměla by být soukromá. Pokud je její převedení na veřejnou metodu obtížné, je naděje, že to je proto, že je součástí samostatné odpovědnosti. Měla by být v jiné třídě. Třída RuleParser z předchozí části této sekce je toho typickým příkladem. Má dvě veřejné metody: evaluate a addVariable. Všechno ostatní je soukromé. Jak by vypadala třída RuleParser, kdybychom převedli metody nextTerm a hasMoreTerms na veřejné? Velmineobvykle. Uživatelé tohoto syntaktického analyzátoru by se mohli domnívat, že musejí použít tyto dvě metody spolu s metodou evaluate k analýze a vyhodnocení výrazů. Bylo by nezvyklé, aby tyto metody byly ve třídě RuleParser veřejné, ale bylo by mnohem méně divné – a ve skutečnosti naprosto v pořádku – převést je na veřejné ve třídě TermTokenizer. Tím není třída RuleParser zapouzdřená o nic méně. I když jsou metody nextTerm a hasMoreTerms třídy TermTokenizer veřejné, přístup k nim v rámci metody parser je soukromý. To ukazuje obrázek 20.3.

Pochopení odpovědností


228

Obrázek 20.3: RuleParser a TermTokenizer

Heuristika č. 3: Hledejte rozhodnutí, která je možné

změnit

Hledejte rozhodnutí – nikoliv rozhodnutí, která provádíte v kódu, ale rozhodnutí, která jste

již udělali. Existuje tam cokoliv (komunikace s databází, komunikace s jinou množinouobjektů atd.), co by vypadalo jako pevně zakódované přímo ve zdrojovém kódu? Lze si představit,

že to změníte? Když chcete rozdělit velkou třídu, je lákavé věnovat značnou pozornost názvům metod. Koneckonců ve třídě je to ta nejnápadnější věc. Ale názvy metod neříkají vše. Velké třídy jsou často útočištěm pro metody, které vykonávají řadu úkonů na mnoha různých úrovních abstrakce. Například metoda updateScreen() by mohla generovat text, který se má zobrazit, formátovat jej a poslat jej několika objektům GUI. Když se podíváme na samotné jménometody, není z toho vidět, o jakou činnost jde a kolik odpovědností je v tomto kódu umístěno. Z tohoto důvodu se vyplatí trochu refaktorování metodou extrakce metody, a to ještě než si vyjasníme, které třídy budeme extrahovat. Které metody byste měli extrahovat? To vyřeším hledáním rozhodnutí. Co všechno předpokládám, že je v tomto kódu? Volá tento kód metody z určitého API? Předpokládáme, že budeme přistupovat vždy ke stejné databázi? Pokud dělá kód tyto věci, je dobré extrahovat metody, které odrážejí to, co máte v úmyslu provádět, na vysoké úrovni. Pokud máte určitou informaci z databáze, extrahujte metodu pojmenovanou podle té informace, kterou získáváte. Když tyto extrakce provedete, budete mít mnoho dalších metod, ale také možná zjistíte, že seskupování metod je jednodušší. A ještě lépe: můžete zjistit, že jste kompletně zapouzdřili nějaký zdrojový kód do určité skupiny metod. Když pro ně extrahujete nějakou třídu, zrušíte některé závislosti na nízké úrovni.

Heuristika č. 4: Hledejte vnitřní vztahy

Hledejte vztahy mezi instančními proměnnými a metodami. Existují nějaké instanční proměnné, které by používala jedna skupina metod a druhá ne? Je opravdu těžké nalézt třídy, ve kterých by všechny metody používaly všechny instančníproměnné. Obvykle se ve třídách uplatní jakési „shlukování“. Pouhé dvě nebo tři metody mohou používat množinu tří proměnných. Často vám k tomuto zjištění mohou pomoci jejich jména. Například ve třídě RulerParser je kolekce pojmenovaných proměnných a metoda jménem addVariable. To nám ukazuje, že mezi metodou a proměnou existuje zřejmý vztah. Neříká nám to, zda tam nejsou jiné metody, které přistupují k této proměnné, ale aspoň máme místo, od kterého můžeme hledání začít.

TermTokenizer

+ Tokenizer(String)

+ nextTerm () : String

+ hasMoreTerms() : boolean

RuleParser

+ evaluate(String) : int

Kapitola 20 – Tato třída je příliš velká a já ji nechci dále zvětšovat


229

Jinou technikou, kterou můžeme použít pro hledání těchto „shluků“, je zhotovení malého

náčrtku vztahů uvnitř třídy. Takové náčrtky se nazývají náčrtky vlastností. Ukazují, které

metody a které datové složky používají jednotlivé metody dané třídy a jejich pořízení jecel

kem jednoduché. Zde máme příklad:

class Reservation

{

private int duration;

private int dailyRate;

private Date date;

private Customer customer;

private List fees = new ArrayList();

public Reservation(Customer customer, int duration,

int dailyRate, Date date) {

this.customer = customer;

this.duration = duration;

this.dailyRate = dailyRate;

this.date = date;

}

public void extend(int additionalDays) {

duration += additionalDays;

}

public void extendForWeek() {

int weekRemainder = RentalCalendar.weekRemainderFor(date);

final int DAYS_PER_WEEK = 7;

extend(weekRemainder);

dailyRate = RateCalculator.computeWeekly(

customer.getRateCode())

/ DAYS_PER_WEEK;

}

public void addFee(FeeRider rider) {

fees.add(rider);

}

int getAdditionalFees() {

int total = 0;

for(Iterator it = fees.iterator(); it.hasNext(); ) {

total += ((FeeRider)(it.next())).getAmount();

}

return total;

}

int getPrincipalFee() {

return dailyRate

* RateCalculator.rateBase(customer)

* duration;

}

public int getTotalFee() {

return getPrincipalFee() + getAdditionalFees();

}

}

Prvním krokem je namalovat pro každou proměnnou kroužek podle obrázku 20.4.

V dalším kroku se podíváme na každou metodu a pro každou z nich také namalujeme

kroužek. Pak namalujeme od každého kroužku metody čáru ke každému kroužku datové

složky nebo metody, kterou používají nebo modifikují. Obvykle můžeme vynechatkonstruk

tory. Ty obecně modifikují všechny datové složky.

Pochopení odpovědností


230

Obrázek 20.4: Proměnné třídy Reservation

Obrázek 20.5 ukazuje týž diagram poté, co jsme přidali kroužek pro metodu extend:

Obrázek 20.5: extend používá duration

duration

dailyRate

date

customer

fees

duration

extend

dailyRate

date

customer

fees

Kapitola 20 – Tato třída je příliš velká a já ji nechci dále zvětšovat


231

Pokud jste již četli kapitoly, které popisují schémata účinků, možná jste si všimli, že se tyto

náčrtky vlastností dost podobají schématům účinků. V podstatě si jsou oba typy diagramů

velmi blízké. Hlavní rozdíl mezi nimi je, že jejich šipky míří obráceně. V případě náčrtku

vlastností míří šipka ve směru metody nebo proměnné, které používá jiná metoda nebopro

měnná. Ve schématech účinků míří šipka směrem k metodám nebo proměnným, které jsou

ovlivněny jinými metodami a proměnnými.

Jde o dva různé, zcela legitimní pohledy na systémové interakce. Schémata účinků jsou dobrá

pro dopřednou dedukci směrem od místa změny.

Je matoucí, že vypadají vesměs stejně? Ne tak docela. Tato schémata jsou nástroje na jedno

použití. Je to ten druh náčrtku, kdy si sednete s partnerem a namalujete ho deset minut před

provedením změn. Poté je vyhodíte. Nemá cenu je uchovávat, takže je malá pravděpodobnost,

že si je navzájem spletete.

Obrázek 20.6 ukazuje schéma poté, co jsme přidali kroužky pro všechny vlastnosti a čáry pro

všechny vlastnosti, které používají:

Obrázek 20.6: Náčrtek vlastností třídy Reservation

Co se můžeme z tohoto schématu dovědět? Jedna samozřejmá věc je, že v této třídě dochází

k určitému seskupování. Proměnné duration, dailyRate, date a customer používají primárně

metody getPrincipalFee, extend a extendForWeek. Jsou kterékoliv z těchto metod veřejné?

Ano, metody extend a extendForWeek veřejné jsou, ale metoda getPrincipalFee veřejná

duration

extend

dailyRate

date extendForWeek

customer

getPrincipalFee

getAdditionalFees

getTotalFee

fees

addFee

Pochopení odpovědností


232

není. Jak by vypadal náš systém, kdybychom vložili tuto skupinu do své vlastní třídy (viz

obrázek 20.7)?

Obrázek 20.7: Seskupování ve třídě Reservation

Velká bublina v diagramu by mohla být nová třída. Mohla by potřebovat metody extend,

extendForWeek a getPrincipalFee jako public, ale všechny ostatní metody mohou být

privátní . Mohli bychom zachovat proměnnou fees a metody addFee, getAdditionalFees

a getTotalFee ve třídě Reservation a metody přesměrovat do nové třídy (viz obrázek 20.8).

Obrázek 20.8: Třída Reservation používá novou třídu

«»

«»

date

duration

customer

fees

dailyRate

extend

extendForWeek

addFee

getAdditionalFees

getPrincipalFee

getTotalFee

Reservation

+ extend(days)volá

+ extendForWeek() volá

+ addFee(FeeRider)

+ getTotalFee()

- getAdditionalFee()

?

+ extend(days)

+ extendForWeek()

+ getPrincipalFee()

Kapitola 20 – Tato třída je příliš velká a já ji nechci dále zvětšovat


233

Hlavní věcí, kterou je třeba zjistit ještě před tímto pokusem, je, zda má tato třída dobrou

a jasně odlišenou odpovědnost. Můžeme navrhnout její jméno? Dělá zřejmě dvě věci:rozši

řuje rezervaci a počítá základní poplatek. Zdá se, že jméno Reservation je dobré, ale už ho

používáme pro původní třídu.

Máme jinou možnost: mohli bychom věci prohodit. Namísto extrakce veškerého kódu zvel

kého kruhu můžeme extrahovat ostatní kód, jak ukazuje obrázek 20.9.

Extrahovanou třídu můžeme nazývat FeeCalculator. To by mohlo fungovat, ale metodaget

TotalFee potřebuje volat getPrincipalFee třídy Reservation – nebo ne?

Obrázek 20.9: Jiný pohled na Reservation

Co když zavoláme metodu getPrincipalFee třídy Reservation a pak tuto hodnotu předáme

třídě FeeCalculator? Zde je výpis kódu:

public class Reservation

{

...

private FeeCalculator calculator = new FeeCalculator();

private int getPrincipalFee() {

...

}

date

duration

customer

fees

dailyRate

extend

extendForWeek

addFee

getAdditionalFees

getPrincipalFee

getTotalFee

Pochopení odpovědností


234

public Reservation(Customer customer, int duration,

int dailyRate, Date date) {

this.customer = customer;

this.duration = duration;

this.dailyRate = dailyRate;

this.date = date;

}

...

public void addFee(FeeRider fee) {

calculator.addFee(fee);

}

public getTotalFee() {

int baseFee = getPrincipalFee();

return calculator.getTotalFee(baseFee);

}

}

Pak vypadá naše struktura jako na obrázku 20.10.

Můžeme dokonce uvažovat o přesunutí metody getPrincipalFee do FeeCalculator, abychom dosáhli lepší shody mezi odpovědnostmi s názvy tříd, ale po zjištění, že metoda

getPrincipalFee závisí na množství proměnných třídy Reservation, bude lepší ji nechat tam,

kde je.

Obrázek 20.10: Reservation používá FeeCalculator

Náčrtky vlastností jsou výborným nástrojem pro hledání samostatných odpovědností vetřídách. Můžeme se pokusit vlastnosti seskupovat a určit, které třídy můžeme na základě jejich

jmen extrahovat. Ale kromě toho, že nám schémata vlastností pomáhají v určováníodpovědností, umožňují nám také pochopit strukturu závislostí uvnitř tříd, což často může být stejně

důležité, jako je odpovědnost při rozhodování, co extrahovat. V tomto případě existují dvě

velké skupiny proměnných a metod. Jediným spojením mezi nimi je volání metodygetPrincipalFee uvnitř metody getTotalFee. U náčrtků vlastností můžeme tento druh propojení

vidět jako malou množinu čar spojujících větší skupiny. Tohle nazývám kritickými místy

a mluvím o nich v kapitole 12, Potřebujeme provést mnoho změn v jedné oblasti. Máme zrušit

závislosti pro všechny související třídy?

U některých náčrtků, které namalujete, žádný kritický bod nenajdete. Nejsou tam vždy.

Každopádně může být užitečné znát jména a závislosti mezi vlastnostmi.

Máte-li náčrtek, můžete si pohrávat s různými způsoby dělení tříd. K tomuto účeluzakroužkujte skupiny vlastností. Když zakroužkujete vlastnosti, čáry, které přetnete, mohou definovat

rozhraní nové třídy. Během kroužkování se pokuste vytvořit pro každou skupinu jméno třídy.

Upřímně řečeno, bez ohledu na to, zda si během extrahování tříd zvolíte to nebo ono, tohle

je výborný způsob, jak zvýšit obratnost při tvorbě jmen. Je to rovněž dobrý způsob prozkoumání různých alternativ návrhu.

Reservation

+ extend (days)

+ extendForWeek()

+ addFee(FeeRider) volá

+ getTotalFee()

- getPrincipalFee()

FeeCalculator

+ addFee(FeeRider)

+ getTotalFee()

- getAdditionalFee()

Kapitola 20 – Tato třída je příliš velká a já ji nechci dále zvětšovat


235

Heuristika č. 5: Hledejte primární odpovědnost

Pokuste se jednou větou popsat odpovědnost třídy.

Princip jedné odpovědnosti nám říká, že třídy by měly mít jedinou odpovědnost. Pokud je

tohle náš případ, mělo by být jednoduché ji popsat jednou větou. Zkuste to s některou zrozsáhlých tříd vašeho systému. Podle toho, co budete předpokládat o tom, co potřebují aočekávají klienti, přidávejte další vedlejší věty. Třída dělá tohle a tohle a tohle a tamto. Je mezi

nimi cokoliv, co se zdá být důležitější než vše ostatní? Pokud ano, možná jste našli klíčovou

odpovědnost třídy. Ostatní odpovědnosti by se pravděpodobně měly vložit do jiných tříd.

Existují dva způsoby, jak porušit princip jedné odpovědnosti. Lze jej porušit na úrovni rozhraní

a na úrovni implementace. Princip jedné odpovědnosti je porušen na úrovni rozhraní, pokud

třída skýtá rozhraní, které vypadá, jako by bylo odpovědné za velký počet činností. Například

rozhraní této třídy (viz obrázek 20.11) vypadá, jako by jej bylo možné rozdělit do tří nebo

čtyř tříd.

Obrázek 20.11: Třída ScheduledJob

Porušení principu jedné odpovědnosti, které nás nejvíce zajímá, je porušení na úrovniimplementace. Jednoduše řečeno, chceme vědět, zda třída skutečně dělá všechny tyto záležitosti

nebo pouze deleguje na několik jiných tříd. Pokud je deleguje, nemusíme mít velkoumonolitickou třídu. Můžeme mít třídu, která je fasádou, přední stranou skupiny malých tříd, které

lze jednodušeji spravovat.

Obrázek 20.12 ukazuje třídu ScheduledJob s odpovědnostmi delegovanými na několik

dalších tříd.

Princip jedné odpovědnosti je stále porušován na úrovni rozhraní, ale na úrovni implementace

je to poněkud lepší.

ScheduledJob

+ addPredecessor(ScheduledJob)

+ addSuccessor(ScheduledJob)

+ getDuration() : int

+ show();

+ refesh()

+ run()

+ postMessage() : void

+ isVisible() : boolean

+ isModified() : boolean

+ persist()

+ acquireResources()

+ releaseResources()

+ isRunning()

+ getElapsedTime()

+ pause()

+ resume()

+ getActivities()

...

Pochopení odpovědností


236

Obrázek 20.12: Třída ScheduledJob s extrahovanými třídami

Jak bychom řešili problém na úrovni rozhraní? To je trochu horší. Obecně je snaha zjistit,

zda některé třídy, do kterých delegujeme odpovědnosti, mohou být skutečně použity přímo

klienty., Pokud například na spouštění ScheduledJobs mají zájem jen někteří klienti, mohli

bychom refaktorovat a dospět k něčemu podobnému:

Obrázek 20.13: Rozhraní třídy ScheduledJob specifické pro daného klienta

ScheduledJob

+ addPredecessor(ScheduledJob)

+ addSuccessor(ScheduledJob)

+ getDuration() : int

+ show();

+ refesh()

+ run()

+ postMessage() : void

+ isVisible() : boolean

+ isModified() : boolean

+ persist()

+ acquireResources()

+ releaseResources()

+ isRunning()

+ getElapsedTime()

+ pause()

+ resume()

+ getActivities()

...

JobPool

+ addJob(Job)

ScheduledJobView

+ show()

+ isVisible()

+ refresh()

JobController

+ pause()

+ resume()

+ isRunning()

ResourceAcquistion

+ ResourceAcquistion(Job, Resource)

+ acquire()

+ release()

ScheduledJob

+ addPredecessor(ScheduledJob)

+ addSuccessor(ScheduledJob)

+ getDuration() : int

+ show();

+ refesh()

+ run()

+ pause()

+ resume()

+ isRunning()

+ postMessage() : void

+ isVisible() : boolean

+ isModified() : boolean

+ persist()

+ acquireResources()

+ releaseResources()

+ getElapsedTime()

+ getActivities()

...

rozhraní

JobController

+ run()

+ pause()

+ resume()

+ isRunning()

Kapitola 20 – Tato třída je příliš velká a já ji nechci dále zvětšovat


237

Klienti, kteří mají na starosti jen řízení úkolů, mohou nyní přijímat instanci třídyScheduled

Jobs jako instanci rozhraní JobControllers. Tato technika vytváření rozhraní pro určitou

množinu klientů udržuje návrh v souladu s principem segregace rozhraní.

Princip segregace rozhraní (ISP – Interface

Segregation Principle)

Když je třída rozsáhlá, zřídkakdy využívají všichni klienti všechny její metody. Často můžeme

vidět různá seskupení metod, která používají určití klienti. Vytvoříme-li pro každé z těch

to seskupení rozhraní a bude-li tato rozhraní rozsáhlá třída implementovat, může každý

z klientů tuto třídu vidět skrze určité rozhraní. To nám pomáhá skrývat informace a rov

něž to snižuje závislost v systému. Klienti již nemusejí provádět překlad, je-li přeložena

velká třída .

Máme-li rozhraní pro určité skupiny klientů, můžeme v mnoha případech začít přesouvat kód

z velké třídy do nové, která původní třídu používá. To lze vidět na obrázku 20.14.

Obrázek 20.14: Segregace rozhraní třídy ScheduledJob

Místo aby třída ScheduledJob delegovala odpovědnosti na třídu JobController, deleguje

je JobController na třídu ScheduledJob. Kdykoliv nyní chce nějaký klient spustit třídu

ScheduledJob, vytvoří instanci třídy JobController, předá jí parametr typu ScheduledJob

a použije JobController, aby ovládal její běh.

Tento druh refaktorování je téměř vždy těžší, než se zdá. Stává se často, že musíte ve

veřejném rozhraní odkrýt více metod původní třídy (ScheduledJob) tak, aby nová fasáda

(StandardJobController) měla přístup ke všemu, co pro svoji práci potřebuje. Taková změna

obvykle znamená dost práce. Klientský kód může nyní být změněn tak, aby používal novou

třídu místo staré. Abychom to provedli bezpečně, potřebujete mít pro tyto klienty testy. Na

refaktorování je alespoň jedna pěkná věc: umožní vám odřezávat kód od velké třídy naúrov

ScheduledJob

+ addPredecessor(ScheduledJob)

+ addSuccessor(ScheduledJob)

+ getDuration() : int

+ show();

+ refesh()

+ postMessage() : void

+ isVisible() : boolean

+ isModified() : boolean

+ persist()

+ acquireResources()

+ releaseResources()

+ getElapsedTime()

+ getActivities()

...

rozhraní

JobController

+ run()

+ pause()

+ resume()

+ isRunning()

StandardJobController

+ StandardJobController(ScheduledJob)

+ run()

+ pause()

+ resume()

+ isRunning()

Pochopení odpovědností


238

ni jejího rozhraní. Všimněte si, že třída ScheduledJob již nemá metody, které jsou obsaženy

v rozhraní JobController.

Heuristika č. 6: Když vše selže, proveďte nějaké

rychlé refaktorování

Pokud máte s pochopením odpovědností ve třídě velké problémy, proveďte nějaké rychlé

refaktorování. Rychlé refaktorování (str. 198) je velmi výkonný nástroj. Uvědomte si, že tohle je umělé cvičení. To, co uvidíte po rychlém reafktorování, nemusí být nutně tím, k čemu se dopracujete normálním refaktorováním.

Heuristika č. 7: Zaměřte se na současnou práci

Věnujte pozornost tomu, co musíte udělat právě teď. Jestliže se chystáte něco předělat,

možná jste identifikovali odpovědnost, kterou byste měli extrahovat a poté umožnit její

substituci. Počet různých odpovědností, které můžete ve třídě identifikovat, vás může lehce udolat. Nezapomeňte na to, že změny, které právě provádíte, svědčí o určitém způsobu, jakým lze software měnit. Často jen rozpoznání tohoto způsobu změn stačí, abyste viděli nový kód, který píšete, jako novou odpovědnost. Jiné techniky Heuristika pro identifikaci odpovědností vám může opravdu pomoci odkrýt a nalézt ve starých třídách nové abstrakce, ale to jsou pouze triky. Způsob, jak se v identifikováníopravdu zlepšit, je více číst. Čtěte knihy o vzorech návrhů. A důležitější je čtení kódu jiných lidí. Podívejte se na projekty s otevřeným zdrojovým kódem a věnujte trochu času prohlížení a pochopení toho, jak pracují jiní lidé. Věnujte pozornost tomu, jak jsou pojmenovány třídy a jak spolu korespondují názvy tříd a metod. Časem se v identifikaci skrytých odpovědností zlepšíte a začnete je rozeznávat, když si budete prohlížet neznámý kód. Pokrok Když identifikujete ve velké třídě skupinu různých odpovědností, máte před sebou dvě další otázky, které budete muset řešit: strategii a taktiku. Promluvme si nejdříve o strategii. Strategie Co bychom měli dělat poté, co jsme identifikovali všechny tyto samostatné odpovědnosti? Měli bychom si vyhradit jeden týden a začít se prát s velkými třídami systému? Měli bychom je rozdělit na malé kousky? Pokud na to máte čas, je to ohromné, ale to nestává příliš často. Může to být také riskantní. Téměř v každém případě, který jsem viděl, pokud se týmy pustily

Kapitola 20 – Tato třída je příliš velká a já ji nechci dále zvětšovat


239

do rozsáhlého refaktorování, stabilita systému se na chvíli zhroutila, i když postupovali sopatrností a v průběhu své práce psali testy. Pokud jste na počátku vašeho vývojového cyklu,

jste ochotni toto riziko podstoupit a máte dost času, refaktorovací orgie může být dobrá. Jen

nedopusťte, aby vás chyby v programu odradily od dalšího refaktorování.

Nejlepší postup, jak rozdělit velké třídy, je identifikovat odpovědnosti, ověřit si, že všichni

v týmu jim rozumějí, a pak podle potřeby třídu rozdělit. Když to provedete, snížíte riziko

změn a dokončíte i ostatní práci.

Taktika

Ve většině zděděných kódů můžete maximálně začít aplikovat princip jedné odpovědnosti na

úrovni implementace: v podstatě extrahujte třídy z vaší velké třídy a delegujte na ně odpovědnost. Zavedení principu jedné odpovědnosti bude vyžadovat víc práce. Klienti vaší třídy

budou muset projít změnami a vy pro ně budete potřebovat testy. Dobře, zavedením principu

jedné odpovědnosti na úrovni implementace se zjednoduší jeho pozdější zavedení na úrovni

rozhraní. Nejdříve se podívejme na případ implementace.

Techniky, které používáte pro extrakci tříd, závisejí na mnoha okolnostech. Jednou z nich je,

jak zavést jednoduchým způsobem testy pro metody, které mohou být změnami ovlivněny.

Vyplatí se prohlédnout si třídu a pořídit si seznam všech datových složek a metod, kterébudete muset přemístit. Z toho byste měli získat dobrou představu o tom, pro které metody byste

měli psát testy. V případě třídy RuleParser, kterou jsme viděli v předchozím textu, platí, že

pokud bychom zvažovali vyčlenění třídy TermTokenizer, chtěli bychom přemístit řetězcové

složky current, currentPosition a metody hasMoreTerms a nextTerm. Skutečnost, žemetody hasMoreTerms a nextTerm jsou privátní, znamená, že nemůžeme psát testy přímo pro ně.

Mohli bychom je převést na veřejné (koneckonců stejně je přemístíme jinam), ale stejně tak

jednoduché by mohlo být vytvoření třídy RuleParser v testovací šabloně a dát jí vyhodnotit

několik řetězců. Pokud to uděláme, budeme mít testy, které pokryjí metody hasMoreTerms

a nextTerm, a budeme je moci bezpečně přemístit do nové třídy.

Bohužel je obtížné vytvořit v testovací šabloně instance mnoha velkých tříd. Viz kapitolu

9, Nemohu dostat tuto třídu do testovací šablony, kde naleznete několik použitelných rad.

Můžete-li vytvořit instanci třídy, mohli byste využít rad z kapitoly 10, Tuto třídu nemohu

v testovací šabloně spustit, a zavést tam testy.

Pokud jste schopni testy zavést, můžete začít extrahovat třídu zcela jednoduchým způsobem za použití refaktorovací metody extrakce třídy, popsané v knize Martina Fowlera

– Refactoring: Improving the Design of Existing Code (Addison-Wesley, 1999). Avšak pokud

testy zavést nemůžete, stále můžete udělat další kroky, ačkoliv o trochu riskantnějšímzpůsobem. Tohle je velmi opatrný postup a funguje bez ohledu na to, zda máte k dispozici nástroj

pro refaktorování. Zde je jeho postup:

1. Identifikujte odpovědnost, kterou chcete separovat do jiné třídy.

2. Určete, zda nebude nutné přemístit do nové třídy některé instanční proměnné této

třídy. Pokud ano, přemístěte je do samostatné části deklarace třídy a dál od ostatních

instančních proměnných.

3. Pokud máte celé metody, které chcete přemístit do nové třídy, extrahujte tělo každé

z nich do nové metody. Název každé nové metody by měl být stejný jako název staré

Pokrok


240

metody, ale se specifickou předponou před každým jménem, třeba MOVING, vše

velkými písmeny. Pokud nepoužíváte nástroj pro refaktorování, mějte při extrakci

metod na paměti zachování signatury (str. 278). Každou metodu při extrakci uložte do

separátní části deklarace třídy, vedle proměnných, které přemísťujete.

4. Pokud by měly být části metod přemístěny do jiných tříd, extrahujte je z originálních

metod. Použijte pro jejich jména opět předponu MOVING a vložte je do separátního

oddílu.

5. V tomto okamžiku byste měli mít ve své třídě oddíl, který obsahuje datové složky, které

máte přemístit, spolu s množstvím metod, které rovněž chcete přemístit. Prohledejte

textově aktuální třídu a všechny její podtřídy a ujistěte se, že žádná z přenášených

proměnných není použita vně metod, které přemísťujete. V tomto kroku je důležité

nepoužít techniku spolehněte se na překladač (str. 280). V mnoha objektově orientovaných jazycích může odvozená třída deklarovat proměnné se stejným jménem, jako

jsou proměnné v základní třídě. Tomu se často říká skrývání nebo zastiňování (shadowing). Pokud vaše třída skrývá jakoukoliv proměnnou a jiné používá, mohli byste

při přesunu proměnných změnit funkčnost kódu. Podobně platí, že pokud použijete

techniku spolehněte se na překladač (str. 280), abyste vyhledali proměnné, kteréskrývají jiné, nenajdete všechna místa, kde se používají. Zakomentování deklarace skrývající

proměnné jen odkryje tu původní.

6. V tomto bodě můžete přesunout všechny datové složky a separované metody přímo

do nové metody. Vytvořte novou instanci třídy ze staré a použijte techniku spolehněte

se na překladač (str. 280), abyste nalezli místa ne ve staré třídě, ale v nové instanci,

odkud se mají tyto přemístěné metody volat.

7. Když přemístíte kód a přeložte jej, můžete začít odstraňovat předponu MOVING ze všech

přemístěných metod. Spolehněte se na překladač (str. 280) a projděte si všechna místa,

kde tato jména potřebujete změnit. Kroky tohoto refaktorování jsou dost spletité, ale v komplikované části kódu jsou nutné,chcete-li extrahovat třídy bezpečně a bez testů. Je několik věcí, které můžete během extrahování tříd bez testů udělat špatně. Nejméněnápadné chyby, které můžeme způsobit, jsou ty, které se týkají dědičnosti. Přenesení metody z jedné třídy do jiné je celkem bezpečný krok. Můžete si pomoci technikou spolehněte se na překladač (str. 280), ale ve všech jazycích jde vše stranou, když se pokoušíte přenášet metodu, která překrývá jinou metodu. Pokud to uděláte, volající objekty této metody v původní třídě budou nyní volat metodu stejného jména ze základní třídy. Podobná situace může nastat sproměnnými. Proměnná v podtřídě může překrývat proměnnou stejného jména v nadřízené třídě. Její přemístění pouze odkryje tu, která byla dosud skrytá. Abychom tyto problémy překonali, původní metodu nebudeme přemísťovat vůbec. Vytvoříme nové metody tím, že extrahujeme těla starých metod. Předpona je jen mechanickoupomůckou, jak vytvořit nové jméno a jak zajistit, aby před přemístěním nebyla v rozporu s jinými jmény. Datové složky jsou o něco ošidnější: před jejich použitím vyhledáme v kódu manuálně místa, kde se vyskytují. Je možné se dopustit v tomto kroku chyb. Buďte velmi opatrní avykonávejte tuto práci s partnerem.

Kapitola 20 – Tato třída je příliš velká a já ji nechci dále zvětšovat


241

Poté extrahuj třídu

Extrahování tříd z velké třídy bývá prvním správným krokem. Pro týmy je v praxi největším

nebezpečím, když se v tomto směru stanou příliš ctižádostivými. Možná, že jste provedli

rychlou refaktorizaci (str. 198) nebo přijali nějaký jiný náhled na to, jak by měl systém vypadat.

Ale nezapomínejte, že struktura vaší aplikace něčemu napomáhá. Podporuje funkcionalitu

a nemusí být prostě připravena na kroky ke zlepšování kódu. Někdy tím nejlepším, co můžete

udělat, je zformulovat stanovisko, jak má vypadat velká třída po refaktorování, a pak na to

prostě zapomenout. Udělali jste to, abyste zjistili, co je možné. Abyste o kousek pokročili,

budete muset být citliví k tomu, co tam je, a postupovat ne nutně k ideálnímu návrhu, ale

přinejmenším směrem k lepšímu.

Poté extrahuj třídu




       
Knihkupectví Knihy.ABZ.cz - online prodej | ABZ Knihy, a.s.
ABZ knihy, a.s.
 
 
 

Knihy.ABZ.cz - knihkupectví online -  © 2004-2018 - ABZ ABZ knihy, a.s. TOPlist