Dnes budu psát o knize Practical API Design od Jaroslava Tulacha. Pomiňme její kvalitní zpracování, které se jen tak nevidí, zajímavý je obsah.
Na začátku je tam na můj vkus docela dost filozofování, ale možná to ke knize podobného zaměření patří. Zabývá se totiž pohledem na návrh API v Javě z trochu vyššího hlediska. To znamená, že se nebabrá v detailech, jako například správné pojmenování metod a rozdělení balíků. Kouká se na to víc shora. Tzn, jak dělat API tak, aby přežilo alespoň pár verzí. Dočtete se co dělat a co nedělat, na co si dát pozor. Autor se prostě pokusil o nemožné, zaznamenat na papír a předat své dlouholeté zkušenosti z návrhu NetBeans API.
Kromě toho je tam také hodně zajímavých myšlenek, které vás donutí se zamyslet. Třeba tvrzení, že krása kódu je věc sice pěkná, ale v podstatě neužitečná. Zní to jako samozřejmost, ale poprvé jsem to takhle natvrdo napsané uviděl až v této knize.
Nejvíc mě zaujala strana 173, která se zabývá tím, co o metodě řeknou její modifikátory. Tzn. věci jako public
, final
, abstract
a podobně. To je věc, která mi do té doby nedošla. Pokud totiž chceme aby měla metoda jeden jediný účel, připadají v úvahu jedině následující kombinace:
public final
– Metoda je tu k tomu aby ji někdo volalprotected abstract
– metoda je to k tomu, aby ji někdo zastínil (naimplementoval)protected final
– metoda má být volaná podtřídami
Všechny ostatní kombinace, které obsahují public
nebo protected
mají víc významů, takže by se jim člověk měl vyhýbat. Například public
se dá jak volat tak zastínit, takže není moc jasné, co je její správné použití. Jen pro úplnost upozorňuji, že private
a (package)
nás u API nezajímá.
A tady s dostáváme k věci, na kterou je třeba u knihy upozornit. Psal ji člověk, který se dlouhá léta zabývá návrhem API pro jiné programátory a to navíc pro Javu na desktopu a to ještě navíc v dynamickém prostředí, kde se moduly za běhu magicky načítají do paměti a zase se z ní vyhazují. Jeho styl myšlení a případy užití jsou tedy o hodně jiné než u mě. Já jsem zvyklý na serverovou Javu, která je zatím dost statická a navíc moji spotřebitelé jsou hlavně koncoví zákazníci. (více viz. v sesterském článku). Nejenže řeším úplně jiné problémy, některé rady jsou pro mě dokonce chybné!
Například stránka 221, kde autor řeší vzájemnou závislost listeneru a sledovaného objektu. Objekt v tomto případě drží odkazy na listenery a když se s ním něco stane, dá jim vědět. Autor tvrdí, že by objekt měl na své listenery odkazovat jen slabou (weak) referencí. Tak mohou být listenery uvolněny z paměti, když už nejsou potřeba, ale objekt samotný žije dál.
V případě NetBeans je to asi užitečná rada. K čemu držet v paměti listener, který sleduje změny v editačním okně, když leží v modulu, na který už se nikdo neodkazuje a tedy ho ani nepotřebuje. Tím, že editační okno drží na listener odkaz, znemožňuje odstranění celého modulu z paměti.
Když se ale na danou radu člověk podívá z mé serverové perspektivy, tak je to naprosto zvrhlé. Mám často listener, který na startu aplikace zaregistruji a pak už mě nezajímá. On dělá něco užitečného, může být třeba součástí složitého auditovacího modulu. Často je ten listener jediným pojítkem mezi daným modulem a zbytkem aplikace. Rozhodně nechci aby mi auditovací modul přestal fungovat, jen proto, že garbage collector uvolní listener z paměti. Tady vidíme, že rada která je naprosto legitmní pro NetBeans nemusí být platná pro server. Na to je potřeba dát pozor.
To je jen jeden aspekt. Navíc jsem přesvědčen, že „obyčejní“ programátoři jako já moc API nepíší. Původně jsem to chtěl na tomto místě víc rozebrat, ale věnuji tomu speciální článek.
Takže podle mého názoru je kniha hodně užitečná buď pro lidi, kteří opravdu píší knihovny, což není můj případ, nebo pro lidi kteří se rádi zamýšlí nad tím jak dělat software, což můj případ je. Je na vás, abyste se rozhodli, jestli do těchto skupin patříte. Pokud ano, rozhodně si ji nenechte ujít. Pokud ne, radši si znovu přečtěte Java Efektivně.
Konkrétně ty listenery jsou zajímavé téma. Taky jsem nad tím přemýšlel, ale weaky mi přijdou z obecného hlediska jako blbost. Došel jsem spíš k názoru, že je lepší zařídit automatickou správu (add*Listener a remove*Listener) podle například focusu. (Jasně, konkrétně focus by nemusel být vždy to pravé, toto jsem promýšlel spíš pro J2ME, tady by šlo spíš o zavření okna.) To by byl celkem univerzální způsob (z obecného pohledu). A nemuselo by se čekat na garbage collector. Popravdě řečeno, přijde mi ne zrovna čisté řešení to nechávat na garbage collectoru, protože ten má uvolňovat paměť, ne snižovat nároky na procesor. Neuklizený listener totiž není jen zátěž pro paměť (v případě jejího nedostatku se spustí garbage collector), ale i zátěží pro procesor, což teoreticky může způsobit dokonce odložení spuštění garbage collectoru. Neznám sice pořádně způsoby rozhodování, kdy spustit garbage collector, ale to se může postupně měnit a je logické, že vyšší zátěž na procesor může způsobit spíše odložení spuštění garbage collectoru, protože zatěžuje procesor. Pochopitelně, bylo by to v tomto případě kontraproduktivní.
BTW Jak byste dělali weak kolekci pro listenery? Jak vyřešit problém stále přibývajících starých WeakReferencí odkazujících nikam? Napadá mě použít trik s finalize, ale nezdá se mi to zrovna jako čisté řešení. A hotovou implementaci neznám.
No teda delat na listenery weak-refy mi prijde jako s prominutim pekna prasarna. Kdyz modul umre, mel by se odregistrovat. Vyrabet weak-refy je IMHO akorat work-around proti mizerne kvalite kodu, ktery si listenery registruje, ale neumi od-registrovat.
Weak reference na listener je ochrana mé komponenty před cizím špatným kódem. Jistě, onen kód by se správně měl odregistrovat — ale já jako programátor kód, kde se listenere registruje, to nemůžu ovlivnit (pokud nejsem náhodou zrovna i autorem kódu posluchače).
A že by se mi mohla reference na listener sama od sebe ztratit? Pokud budu potřebovat někdy v budoucnosti listener odregistrovat, stejně na něj musím mít referenci. Takže se mi nemůže stát, že mi jej GC z ničeho nic zruší.
Takže weak reference na listener dobře napsanému listeneru neuškodí a tomu špatně napsanému občas může pomoci.
A jak weak reference uchovávat v kolekci a mazat ty neplatné? Snadno — při procházení kolekce za účelem vyvolání listenerů použiju ListIterator, a když zjistím prázdnou referenci, rovnou ji z kolekce vymažu.
> Navíc jsem přesvědčen, že „obyčejní“ programátoři jako já moc API nepíší.
A já bych klidně řekl pravý opak. Zjednodušeně řečeno, kdokoliv programuje něco, co budou používat jiní programátoři, navrhuje API. Pokud se takové API nedostane mimo firmu, určitě tolik nezáleží na jeho stabilitě, ale další vlastnosti dobrého API jsou pořád stejně důležité: snadné a intuitivně správné použití, dobrá dokumentace pokrývající i výjimečné stavy, konzistence s ostatními API a tak.
Něco na tom bude, ale:
1. Co když z nějakého důvodu chci mít listener se stejnou životností jako daný objekt?
2. Pak by bylo dobré udělat debug výstup při takovémto odstranění.
Filip Jirsák to píše velmi správně.
Je tu ale jeden VELKÝ problém: Návrhový vzor Listener (Observer) je zvykem v Javě implementovat BEZ weak-refů. A ve chvíli, kdy je určitý zvyk, je nutné ho dodržet při návrhu API, i kdyby to byl zvyk hloupý. Bohužel to tak je a tím pádem mi nezbývá nic jiného než říct: Listenery by měly být (za normálních okolností) drženy klasickými referencemi (pokud ne, musíte to VÝRAZNĚ zdokumentovat).
Když totiž zaregistrujete listener na JButton pomocí anonymní vnitřní třídy (což se běžně dělá) a ten listener by byl držen Weak-refem, při nejbližším GC by se to uvolnilo a program by se vám choval dost divně.
Co naděláme, je takový zvyk, takže správné API má být podle zvyklostí. Žádné Weak-reference bych tedy nedoporučovala…
Ještě dodán jednu věc, na kterou jsem minule zapomněl: samozřejmě, je je možné úklid zavěsit na určitou událost, třeba volání listenerů. To ale znamená, že pokud by listenery často přibývaly a ubývaly, zůstalo by tu mnoho takového plevelu. Asi by to nebylo kritické, ale je to další problém slabých listenerů.
Samotný koncept slabých listenerů mě napadl nezávisle, ale jednoduše dělá víc problémů než řeší.
Jé, první recenze, kterou vidím v češtině. Díky. Jsem rád, že tu můžu psát česky, a tak tu nechám pár komentářů.
Každého, kdo četl tu knížku, zaujme něco jiného. Dělám si svoji malou hitparádu a jsem rád, že jste si vybral tu jednoznačnost přístupových modifikátorů. To je má oblíbená kategorie. Vypovídá dost o rozdílu různých pohledů na svět. Co vypadá smysluplně pro normální OOP, má ve světě API zvláštní konsekvence.
Co se týká těch velmi diskutovaných referencí. Tak nejdůležitější, co jsem chtěl říci je, že jsou součástí API. Je rozdíl mezi tím jestli jsou normální či “weak”. A samozřejmě souhlasím, že konzistence je důležitá a když už se v JavaBeans specifikaci rozhodli pro normální referenci (oni tedy museli, v té době jiná nebyla), tak by se to mělo obvykle dodržovat.
Nakonec mohu jen dodat, že se zdá, že skutečně existují různé programovací světy. Ne každý nutně píše API pro obecné použití. Téměř každý pracující v týmu však alespoň dává API svým kolegům. Jednou jsem to rozebíral s bratrancem, který se také tvářil, že píše “napiš, vyhoď a přepiš” aplikace pro koncové uživatele. Nakonec však stejně sám rád musel přiznat, že má část kódu, kterou využívá znovu a znovu a u níž dodržuje kompatibilitu.
Hodně štěstí při psaní API a dík za recenzi.