Ano, i já, tvor neobyčejně dokonalý se mohu dopustit chyby. Chtěl bych o jedné z nich napsat, nebyla rozhodně první, ani poslední, byla ale dost zajímavá.
Všechno začalo dost nevině, při práci na svém úžasném projektu m2-proxy jsem jedné třídě potřeboval předat seznam dvojic hodnot. Abych si zjednodušil konfiguraci přes Spring, přetížil jsem setter tak, aby uměl pracovat s mapou, kterou následně přetransformuje na kolekci dvojic. Kód vypadá následovně:
public class TestBean { private static final Log LOG = LogFactory.getLog(TestBean.class);
public void setData1(Map map) {
LOG.debug("Setting map data1"); }
public void setData1(List list) { LOG.debug("Setting list data1"); } }
Už asi tušíte v čem je problém, když ve Springu napíši následující konfiguraci, nevím, která verze setteru bude použita.
<bean name="testBean1" class="net.krecan.test.TestBean"> <property name="data1"> <map> <entry key="a" value="b"> <entry key="c" value="d"> </map> </property> </bean>
Na první pohled by to měla být ta s mapou, nicméně je tu také možnost, že mi bude předán seznam jehož jediným prvkem bude mapa. Nyní se můžeme vrhnout do studia dokumentace všeho možného, abychom zjistili, jak Spring zafunguje nebo můžeme zkusit jednodušší možnost „Zeptej se stroje“. Napíši si test, který mi zjistí, jak se to zachová. Odpověď mě uspokojí, můj test mi řekne, že se zavolá metoda s parametrem Map, což je přesně to co potřebuji. Ne celý problém zapomenu, programuji dál a m2-proxy vypublikuji.
Jeden z prvních uživatelů (z těch asi 30, co si m2-proxy stáhli), mi asi druhý den po vypublikování nahlásí chybu, z které plyne, že se u něj při inicializaci volá metoda setData s parametrem List a ne Map. Čím to tak může být, mě to při všech testech fungovalo! Vrhnu se do zkoumání a zjistím zajímavý fakt. Na mém počítači se zavolá ta metoda, která je v zdrojovém kódu uvedena jako první! To znamená, že výsledné chování programu, závisí na takovém detailu, jako je pořadí metod ve zdrojovém kódu. Navíc na jiných JRE se to všechno může chovat úplně jinak. Evidentně jde o chybu ve Springu, pomocí reflekce procházejí seznam metod a když narazí na první s vhodnou signaturou, tak ji použijí. Nelenil jsem a přidal jsem do JIRY Springu hlášení o chybě. Po pár dnech mi odpověděl Juergen Hoeller:
Toto je ve skutečnosti definováno JavaBeans specifikací: Přetěžování není povoleno u setterů a getterů, protože každá vlastnost musí mít jasný typ.
Takže chyba je jako vždy na mé straně, řešení je jednoduché, stačí přejmenovat jeden ze setterů (což jsem mimochodem udělal, hned jak jsem přišel na to v čem je problém)
Jaké si z celé historie odnáším poučení?
- Testovat důkladně. Musím se přiznat, že jsem moc nezkoušel, která verze přetížené metody se zavolá. Prostě jsem to spustil a jelikož to fungovalo, na celý problém jsem zapomněl. Kdybych to zkoušel důkladně, asi bych otestoval, co se stane, když dám do Spring konfigurace List. Vyjimka, kterou bych dostal by mě upozornila, že něco není v pořádku
- Nedělat prasárny. Člověk může stokrát slyšet, že programovat se má tak, aby bylo na první pohled vidět co program dělá. Nicméně v realitě se to ne vždy podaří.