Category Archives: Uncategorized

Zpětná kompatibilita

Dnes budu psát o zpětné kompatibilitě v Javě. Předem upozorňuji, že nejsem expert na dané téma, spíš si chci jen ujasnit myšlenky. Takže když někdo narazíte na chybu v mých jinak bezchybných myšlenkách, tak to prosím pěkně rozmázněte v diskuzi pod článkem. Rád se něco přiučím.

Co to tedy je zpětná kompatibilita? Zpětná kompatibilita je, když moje staré programy běží i v novém prostředí. Kdo chce vznešenější definici, ať si ji laskavě najde sám. Jednoduchý příklad. Vezmu starou hru pro DOS a pustím jí ve Windows. Když poběží, vím že byla zachována zpětná kompatibilita, když ne, tak si přečtu skvělý článek od Joela, v kterém vysvětluje proč tomu tak není.

V Javě se její autoři snaží zpětnou kompatibilitu zachovávat. Ale aby to nebylo tak jednoduché, tak hlavně binární. To znamená, že program zkompilovaný pod Javou 1.4 spustím v JRE verze 6. Super. Samozřejmě naskytnou i výjimky, ale je velká pravděpodobnost, že ten program poběží. Což potěší naše zákazníky. Ti mohou program, který před lety draze zakoupili, používat i na nejnovějších JRE.

To je binární kompatibilita. Ale co zdrojáky? Ty by teoreticky měly být taky kompatibilní. Měl bych být schopen zkompilovat staré zdrojové kódy novým JDK. Tady už můžu narazit na víc problémů. Naštěstí se je SUN snaží vyjmenovat v dokumentaci (Java 5, Java 6). Takže to vypadá, že i na straně zdrojových kódů by mělo být vše v pořádku. Tedy až na pár obskurních případů.

To by bylo taky super, kdyby to ovšem byla pravda. V té dokumentaci se tak nějak zapomněli zmínit, že přidávají metody do existujících rozhraní. Nejlépe je to vidět na třídě java.sql.Connection. Pokud jenom používáte instance tříd implementujících toto rozhraní, tak je všechno v pohodě. Pokud si ho ale troufnete implementovat sami, narazíte na problém (viz. tento chudák). V nové verzi vaše implementace nebude úplná, bude jí chybět pár metod. Pokud namítáte, že není moc časté implementovat si JDBC driver, tak si představte, že chcete vytvořit prachobyčejný wrapper. Třeba kvůli logování. To je věc, s kterou už se v životě můžete setkat. Váš wrapper bude skvěle fungovat do té doby, než se ho pokusíte zkompilovat pod novou verzí. Kompatibilita na úrovni zdrojového kódu jde do háje. Nejen to. Představte si situaci, kdy ten wrapper bude zkompilovaný ve staré knihovně, kterou jen používáte. Když zavoláte třeba Connection.getNClob(), tak to samozřejmě spadne. To už není tak super.

(Měl bych brzdit, aby se na mě Roumen zas nenaštval.) Ono udržet zpětnou kompatibilitu a zároveň jazyk rozvíjet je hodně těžké. Možná i nemožné. Můžeme si vybrat to nebo ono, obojí mít nemůžeme. Proto se ozývají hlasy, že by Java měla zpětnou kompatibilitu zahodit. Já s nimi nesouhlasím. Občas se totiž pohybuji mezi konzervativními klienty. Nedávno se mě jeden člověk od AS400 zeptal něco v tomto smyslu. „My máme program v Cobolu, který je asi dvacet let starý. Nejen že jsme schopni ho dodnes provozovat, ale i upravovat. Je něco podobného možné i v Javě?“ Jako odpověď jsem blekotal něco v tom smyslu, že když bude mít všechny knihovny a kompilátor a JRE tak asi ano. Ale moc jistý si nejsem. A to jsme naštěstí nezabrousili do témat jako je podpora starších verzí atp. (pokud se chcete vyděsit, klikněte zde).

Takže abych to uzavřel, zpětná kompatibilita je dobrá věc. Nikdy se ale nedá udržet na 100%. Tedy dá, ale jen za cenu stagnace. Jak tak sleduji situaci kolem vývoje Javy, tak stojíme na křižovatce. Rozhoduje se jestli chceme stagnovat nebo se chceme vyvíjet. Nerad to přiznávám, ale jsem spíš pro tu stagnaci. Respektive velmi opatrný vývoj. Pokud chcete nové cool věci, používejte třeba Groovy. To mi připadá jako rozumný kompromis. Konzervativci zůstanou u Javy a ti ostatní se mohou vrhnout na neprobádaná pole skriptovacích jazyků. Když je budou pouštět v JVM tak se za nimi možná časem vydají i ti konzervativci.

Nemám rád kontrolované výjimky

Už dlouho jsem nikoho nepoučoval a trochu mi to chybí, takže dneska budu psát o kontrolovaných alias synchronních alias čtverečkovaných alias checked výjimkách. Ano o těch výjimkách, které jste překladačem nuceni odchytit a zpracovat.

Teď pracuji s pěkně starým kódem. Jeho autoři si mysleli, že jsou kontrolované výjimky dobrý nápad, takže je hojně využívali. Já se jim nedivím, na první pohled se líbily i třeba Bruce Eckelovi. A nejen jemu, spousta důležitých knihoven v Javě je také hodně používá. Jenže já je nemám rád. Ukažme si to na příkladu ze života.

Představme si že píšeme finanční aplikaci která má být hyperrobustní. Vytovříme si třídu Money, do které budeme ukládat peníze. A to nejen množství, ale i měnu. Už při vytváření instance může nastat chyba. Filuta uživatel mé skvělé peněžní knihovny se mi může snažit podstrčit neexistující měnu. Jenže já nejsem žádný jakýpak copak, já mu na to přijdu a vyhodím mu CurrencyException. A abych mu ukázal, že se to může stát, tak udělám CurrencyException kontrolovanou (checked). Takže on musí počítat s tím, že mu ji vyhodím a správně ji zpracovat. Skvělé. Dokonce mohu i napsat metodu add, která si bude kontrolovat, jestli sčítám stejné měny a když ne, vyhodí mojí oblíbenou CurrencyException.

	public Money(float value, String currency) throws CurrencyException {
		//value should be BigDecimal, float is used for simplicity
		validateCurrency(currency);
		this.value = value;
		this.currency = currency;
	}
	
	public Money add(Money other) throws CurrencyException
	{
		if (!other.getCurrency().equals(getCurrency()))
		{
			throw new CurrencyException("Currencies do not match.");
		}
		return new Money(getValue() + other.getValue(),getCurrency());
	}

Takže mám super kód, který nutí ostatní programátory zabývat se tím, co se stane, když se pokusí míchat americké dolary a polské zloté. Ať žijí kontrolované výjimky! To si ovšem budeme říkat jen do doby než začneme tuto třídu používat. Nebo než uvidíme kód který ji používá. Setkáme se s podobnými perlami:

		Money money1 = null;
		try {
			money1 = new Money(123, "EUR");
		} catch (CurrencyException e) {
			//can not happen
		}
		Money money2 = null;
		try {
			money2 = new Money(121, "EUR");
		} catch (CurrencyException e) {
			log.log(Level.SEVERE, e.getMessage());
		}
		Money money3;
		try {
			 money3 = money1.add(money2);
		} catch (CurrencyException e) {
			throw new IllegalStateException(e);
		}

Při vytváření instance money1 si programátor zjevně myslel, že výjimka nemůže nastat. Vždyť tam má to euro natvrdo. Tak ji ignoruje. Tento případ je ze všech nejhorší. O trochu lepší případ nastane, když výjimku zapíše do logu jak to udělal v případě money2. Ale i tak, pokud při vytváření money2 dojde k výjimce, program bude pokračovat jen proto, aby o kousek dál skončil s NullPointerException. To je ta lepší varianta. V horším případě k NPE dojde o velký kus dál a my si budeme marně lámat hlavu, jak se nám tam dostal null. Asi nejlepší ze špatných řešení je použito u money3. Ali i to je špatně čitelné a ošklivé. Už chápete proč nemám rád kontrolované výjimky? Pokud by CurrencyException byla potomkem třeba IllegalArgumentException, kód by vypadal takto:

		Money2 money1 = new Money2(123, "EUR");
		Money2 money2 = new Money2(121, "EUR");
		Money2 money3 = money1.add(money2);

Nejen že to je lépe čitelné, ono je to i mnohem bezpečnější. Pokud dojde k té nepravděpodobné události a něco se pokazí, dozvím se o tom ihned. A pokud výjimku neodchytím někde o kus dál, tak se o tom dozví bohužel i uživatel. Ale i to je mnohem lepší, než aby se aplikace tvářila jako že funguje.

Pokud vám uvedený příklad připadá umělý, podívejte se na konstruktor java.net.URL nebo na jakýkoliv JDBC kód. Nebo klasický příklad s kódováním.

		Writer writer = null;
		try {
			writer = new OutputStreamWriter(out, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			//can not happen 
		}

Co se stane když vaše JVM nebude znát UTF-8? Správně, dostanete NullPointerException. S trochou štěstí po pár minutách debugování zjistíte co se děje. Když štěstí mít nebudete, tak vám to může zabrat i den.

Takže abych to shrnul. Kontrolované výjimky se mohou používat v jediném případě. Pokud jsem si jist, že programátor který ji odchytí bude vždy vědět co s ní má dělat. Což je asi důvod proč v C# kontrolované výjimky vůbec nejsou. Na celé záležitosti je navíc matoucí to, že ve skutečnosti máme opravdu dva druhy výjimek.

Jeden druh je obvykle způsoben špatnými vstupními daty. V takovém případě má smysl vyhazovat kontrolovanou výjimku a donutit programátora ji nějak zpracovat.

Jenže pak tu máme chyby způsobené chybou v kódu, konfigurací, selháním sítě, disku a podobně. V takovém případě nemá cenu výjimku obvykle chytat, protože beztak nevíme co s ní.

To by napovídalo, že pro chyby prvního typu bychom měli používat kontrolované výjimky, pro chyby druhého typu běhové (runtime) výjimky. EJB specifikace tak dokonce funguje. Podle typu výjimky dělá nebo nedělá rollback transakce (pokud ji neřekneme jinak).

Finta je v tom, že dopředu nikdy nevíme, ke kterému druhu chyby může dojít. Vraťme se k příkladu s penězi. Může se stát, že nám špatná měna přijde z jiného systému v XML. Pak má smysl vrátit specifickou chybovou hlášku a používat kontrolovanou vyjímku. Stejně tak může být měna načtena z konfiguračního souboru. Pak obvykle nemá smysl se výjimku nějak speciálně zabývat, aplikace je prostě nefunkční. Hláška v logu je naprosto dostačující.

Takže opakuji, nikdy dopředu nevíme jestli bude moci programátor používající náš kód umět výjimku zpracovat. Pravděpodobnější je, že to nedovede a provede s ní něco ošklivého. Proto se přikláním k názoru, že by kontrolované výjimky měly být používány zřídka, nejlépe vůbec. Běhové výjimky jsou bezpečnější a kód je s nimi mnohem čitelnější. Nicméně pokud má někdo nějaký příklad, kde si je jist, že by měly být za všech okolností použity kontrolované výjimky, sem s ním, rád se poučím.

Zdroje (aneb důkaz, že nosím dříví do lesa):

Does Java need Checked Exceptions?

Java theory and practice: The exceptions debate

Není revize kódu jako revize kódu

Dagi se nám nějak rozepsal. Takže abych s ním udržel krok, napíšu velmi opožděnou reakci na to co napsal skoro před rokem a dneska mi připomněl svým nejnovějším zápiskem.

Revizi kódu můžeme pojmout několika způsoby. Zažil jsem jednu čistě formální revizi, která se dělala jen proto, aby se odškrtl chlívek v nějakém předávacím protokolu. Vypadá to tak, že vezmete svůj kód, předáte ho někomu koho jste nikdy neviděli a on vám pošle protokol s výsledky. Protože obvykle recenzent nemá moc času, tak to jen tak prolétne, napíše vám že tady měly být tři mezery místo čtyř a tato proměnná měla mít tři slabiky. Obvykle není přínos moc velký. Tedy kromě toho, že se splní smlouva, což je samozřejmě důležité. Někdy můžou být výsledky i docela zajímavé. Pamatuji se, že mi při jednom code review vytkli, že jsem použil návrhový vzor singleton. No nakonec jsem si ho obhájil. Toho, že jsem o kus dál dělal docela divočárny s Java reflection si nikdo kupodivu nevšiml.

Takováto revize kódu je z pohledu programátora k ničemu. Nicméně to není jediná možnost jak je dělat. Můžeme dělat i neformální revize kódu. Mám s nimi docela dobrou zkušenost. A nejsem sám. Jak taková revize vypadá?

Je to jednoduché. Vybere se kus kódu, který je nějak rozumně ohraničený a přiměřeně velký (všimněte si mého exaktního jazyka). Jeho autor ho připraví na revizi, může ho prohnat automatickou kontrolou jako třeba FindBugs a podobně. Pak ho dostanou do ruky recenzenti (obvykle dva), kteří mají za úkol udělat samotnou revizi. Ale pozor, recenzenti musí být s autorem kódu členy stejného týmu. To je nesmírně důležité. Tím že dělají revizi kódu se ho mimochodem i naučí. Takže místo jednoho člověka, který zná danou část aplikace už máme tři lidi, kteří se s ní kamarádí.

A to není všechno. Celá revize by měla být završena sezením. To se sejde celý tým v zasedačce, pouští si kód na projektoru a povídá si o něm. Recenzenti ukazují co se jim líbilo, co by vylepšili a všichni dohromady pojídají koláč. Je nesmírně důležité udržet neformální a přátelskou atmosféru. Pokud máte manažera, který nechápe, že se při programování dělají chyby, na sezení ho nepouštějte. Je také dobré mít moderátora a zapisovatele. Moderátor řídí diskuzi, dohlíží na to aby se sezení nezvrhlo v hádku o tom, jestli je lepší HashMap nebo TreeMap. Zapisovatel zapisuje rozhodnutí o tom co je potřeba na kódu ještě vylepšit. Podobná revize kódu vám přinese několik výhod.

  1. Autor kódu se víc snaží. Znám to ze své zkušenosti. Když vím, že po mě někdo bude kód číst, tak se prostě víc snažím. Odpustím si některé prasárny, víc se zamýšlím nad tím co dělám. Je to hloupé, ale je to tak. (Alespoň se nestydim to přiznat)
  2. Šíří se znalosti. To je podle mě největší přínos revizí. Jak recenzenti, tak autor kódu, ale i ostatní členové týmu se od sebe navzájem hrozně moc naučí. Při sezení si povídají nad konkrétním kódem, můžou si ukázat jak autor super použil návrhový vzor, jak by mohl lépe použít knihovnu XYZ apod.
  3. Občas se při revizi najdou i nějaké chyby.

Revize kódu mají i své nevýhody. Pokud je v týmu nezdravá atmosféra, můžou se zvrhnout v ošklivou hádku. Od toho máme moderátora aby tomu zabránil. A koláč, ten dokáže atmosféru docela uvolnit. Samozřejmě největší nevýhodou revizí je to, že žerou čas. Stejně jako dobrý návrh a unit testy, tak i revize kódu zabírají čas. A toho není nikdy nazbyt. Tady mi nezbývá než použít svůj oblíbený citát.

Nikdy není dost času udělat to pořádně, vždycky je dost času udělat to znovu.