Author Archives: admin

Jak jsem udělal chybu aneb chyba ve Springu

 

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í?

  1. 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
  2. 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ří.

Mýtický člověko-měsíc

Nedávno se mi dostala pod ruku známá kniha The Mythical Man-Month, Frederick P. Brooks, Jr. Není bez zajímavosti, že letos slavíme 20 let od jejího prvního vydání. Musím se přiznat, že mě dost nadchla. Člověk se v ní dočte, jaké problémy museli vývojáři řešit v prehistorických dobách softwarového inženýrství.

Pro příklad uvedu volný výtah z kapitoly která dala jméno celé knize – Mýtický člověko-měsíc. Začněme nejdříve citátem, který se dnes označuje jako Brooksův zákon:

Přidáním lidí na zpožděný softwarový projekt ho ještě více zpozdíte.

Autor uvádí krásný příklad (text kurzivou jsou moje poznámky):

Mějme projekt který je odhadován na 12 člověko-měsíců. Na začátku nasadíme 3 programátory a budeme předpokládat, že projekt bude hotov za 4 měsíce. Stanovíme si milník na konec každého z nich. Dejme tomu, že jsme udělali chybu v odhadech a k prvnímu milníku dorazíme až po dvou měsících. Máme několik možností jak situaci řešit:

  1. Předpokládejme, že musíme dodržet termín a že byly špatně odhady jenom první části. Z původně odhadovaných 12 člověko-měsíců nám jich zbývá devět. S dvěma měsíci na dokončení nám to dává, že jsme 3 člověko-měsíce ve skluzu, a že pro dohnání skluzu potřebujeme 4,5 programátorů. Ke stávajícím třem tedy přidáme další dva.
  2. Předpokládejme, že musíme dodržet termín a že byly stejně špatné všechny odhady. Zbývá nám tedy 18 měsíců, což dává 9 programátorů. Najmeme dalších 6 programátorů
  3. Odložení termínu. Rada pana P.Fagga říká: „Nedělejte malé odklady“. Tzn. nechte si dost času, abyste termín dokončení nemuseli posouvat znovu.
  4. Zmenšení rozsahu. Pokud se nestíhá termín dojde k tomu tak jako tak. Jediná manažerova šance je snížit rozsah formálně, dřív než se ve spěchu začne odbývat design a testování.

Zamysleme se co se stane, když zvolíme první variantu. Předpokládejme, že dva nově najatí muži budou potřeba měsíc na zaučení se. Celou tu dobu se jim bude věnovat jeden ze stávajících programátorů (to se může zdát jako poněkud pesimistický předpoklad, nicméně můžeme do něj započítat vyšší nároky na vzájemnou komunikaci a koordinaci týmu, složitější rozdělení si práce atp.). Ve třetím měsíci vývoje tedy budou naplno pracovat jenom 2 programátoři. Před posledním měsícem projektu budeme mít 5 programátorů a 7 člověko-měsíců práce. Jsme tedy 2 čm ve skluzu. Jak to vyřešit? Že by dalším rozšířením týmu? (Tady přicházejí ke slovu manažerské dovednosti k tomu jak přesvědčit programátory aby to „nějak“ stihli, tzn. za cenu přesčasů a nízké kvality aplikace, často také zkrácením „nepotřebného“ testování)

Toliko ukázka z knihy (pro nezkreslenou informaci doporučuji originál). Vidíme jaké máme štěstí, že již žijeme v moderní době a nemusíme řešit stejné problémy jako v prehistorii softwarového inženýrství. Nicméně kdybychom si chtěli přeci jen odnést nějaké poučení, bylo by asi následující:

  1. Lidé a měsíce se nedají volně směňovat za člověko-měsíce
  2. Člověk by se měl dvakrát rozmyslet, než se rozhodne zachraňovat rozjetý projekt přidáváním lidí.

Closures v Jave – děkuji nechci

O nápadu prosadit closures (uzávěrách?) do Javy jsem se dočetl poměrně nedávno. Odkaz na původní článek najdete zde. Mojí první reakcí bylo: „Proboha proč?!“. Čím více o tomto návrhu přemýšlím, tím více motivaci jeho tvůrce chápu. Stále si s nimi ale dovoluji nesouhlasit. Pokusím se uvést několik argumentů proti jejich akceptaci.

  1. Většina programátorů je nepotřebuje. Přiznejte si, jak často si při práci říkáte „tady by to chtělo closure“. Já pracuji s Javou denně a na mysl my to přišlo jedině u listenerů ve Swingu (bohužel jen u některých). Porovnáme-li closures s rozšířeními v Javě 5 jako jsou generics nebo autoboxing, vidíme právě v tomto aspektu veliký rozdíl. Po tom, abych mohl mapě říci co má obsahovat, vzdychám skoro denně a neustále psaní new Integer(3) mi občas leze na nervy a znepřehledňuje kód.
  2. Java ztrácí svojí objektovost. Připadá mi, že closures jsou stejným problémem jako primitivní typ. Je to takový degenerovaný objekt. Zatímco na primitivní typ se můžeme zjednodušeně dívat jako na objekt bez metod, closure je metoda bez dat. Z čistě objektového hlediska je to paskvil. K primitivním typům přistoupili tvůrci Javy z důvodu efektivity výsledné aplikace, nejsem si jist, že máme stejně silnou motivaci v případě closures.
  3. Přicházíme o znovupoužitelnost kódu. Kód
    int(int) plus2b = (int x) {return x+2; };
    nemám šanci použít kdekoliv jinde. Ano, to samé platí i pro anonymní třídy. Ale tím, že do deklarace své metody dám jako jeden z parametrů closure, nutím klienta mého API používat kód, který není znovupoužitelný!
  4. Kód se může i znepřehlednit. Možná je to tím, že nejsem zvyklý tento zápis číst, ale například toto:
    void(int) throws InterruptedException closure = (int t){ Thread.sleep(t); }
    ve mně vyvolává smíšené pocity.
  5. Do jazyka se zavlékají kontroverzní konstrukce. Zde nemám na mysli closures samotné. V návrhu se například objevuje i nová syntaxe klíčového slova return pro návrat z metody, která closure používá! (kapitola Non-local transfer). Mělo by dokonce jít i napsat něco takového:
    int(int) brrr = (int x) {if (x==13) break; };
    Zde doufáme, že je naše funkce volána z cyklu, který můžeme přerušit. (Konkrétně u této kapitoly doufám, že jsem ji nepochopil a že to je všechno jinak. Budu rád, když mě někdo z mého omylu vyvede)

Na druhou stranu je dobře, že autoři tuto diskuzi vyvolali. Je na komunitě, aby si rozhodla, jestli chce mít z Javy další Ruby nebo Perl, nebo jestli chce mít jazyk, který se programátora snaží nutit psát čistě, přehledně a objektově.