Category Archives: Uncategorized

O kvalitě open sourcu

Denně pracuji s open source (OS) produkty a docela často narážím na fakt, že i ty nejznámější z nich mají své mouchy, někdy i docela závažné. Proto jsem se rozhodl napsat něco o kvalitě těchto produktů. Když ale chceme mluvit o kvalitě, měli bychom si nadefinovat co to ta kvalita vlastně je. Nejvíc se mi asi líbí definice od pana Glasse

Kvalita je souhrn sedmi atributů: přenositelnost, spolehlivost, efektivita, použitelnost (přívětivost), testovatelnost, srozumitelnost a přizpůsobivost.

Když si tak projdu open source produkty, které používám, většinou narazím na nějaký prohřešek proti uvedeným atributům. Teď jsem zrovna asi čtvrt hodiny válčil s instalací kontroly pravopisu do nově nainstalovaných Open Office. Vzhledem k tomu, že nejsem úplný začátečník a řeším to pokaždé, když si nainstaluji novou verzi, myslím, že to je způsobeno špatnou použitelností a srozumitelností. Člověk by tak nějak čekal, že když si nainstaluje českou verzi, tak bude mít českou kontrolu pravopisu nainstalovanou automaticky.

Podobné je to například u Mavenu. Je to dobrý produkt, ale co mi je to platné, když strávím několik hodin hledáním funkčního pluginu. Opět tedy drobný nedostatek v použitelnosti. Nebo například pěkná kryptografická knihovna Bouncy Castle. Funguje perfektně, ale na problém narazíte, když se snažíte udělat něco jenom trochu nestandardního. Přijdete na to, že není moc rozšiřitelná (přizpůsobivá). Ve výčtu bych mohl pokračovat, ale nechám to na vás. Zkuste si projít produkty které používáte a podívat se na ně z hlediska atributů definujících kvalitu.

Důležité je, že problémy s kvalitou nemají jenom OS produkty. Naprosto stejné výhrady můžeme mít i proti komerčnímu software. Minulý týden jsem například ztratil půl dne bojem s Weblogicem, který mi vyhazoval naprosto nesmyslnou chybu. Do teď jsme přesvědčený, že to nebyla moje chyba. Ale i kdyby, kvalitní software by měl být schopný mi říci, co dělám špatně.

Takže, který software je kvalitnější? Komerční nebo open source? Můžeme se na to podívat z několika hledisek. Lidé ho píší dobrovolně, pro radost. Ne proto, že si tím vydělávají na chleba. Mají tedy mnohem lepší motivaci. To má pozitivní vliv na spolehlivost. Na druhou stranu se najde málo dobrovolníků, kteří by dělali ty „nudné“ práce jako je psaní dokumentace, testování a zvyšování uživatelské přívětivosti. Z toho plyne klasický problém OS software – horší srozumitelnost a použitelnost.

Pokud je OS projekt prací více lidí, má obvykle hodně striktní pravidla o tom kdo a za jakých podmínek smí přispívat. S takovými pravidly se setkáte u komerčních projektů méně často. U úspěšných OS projektů je také zažitá kultura unit testů. Z toho samozřejmě plyne testovatelnost – další atribut kvality. To že na projektech pracuje více vývojářů s různými zájmy má také velmi pozitivní vliv na rozšířitelnost. Úspěšné projekty obvykle obsahují prostředky pro snadné rozšíření a přizpůsobení. Často bývají hodně modulární. Další věc, s kterou se v komerčních softwarech setkáme méně často. U některých se naopak setkáme s prostředky zamezujícími rozšířitelnosti.

Velký vliv na kvalitu OS projektů má také evoluce. Nekvalitní projekty prostě nikdo nepoužívá a po pár verzích jsou odsouzeny k zániku. Problém nastane, když existuje jenom jeden projekt s daným zaměřením. Jako příklad můžeme vzít Acegi. Knihovna, která má z hlediska návrhu hodně slabin, ale je v podstatě jediná ve své oblasti. Tudíž nám nezbývá, než se s těmi slabinami smířit. V komerční oblasti efekt evoluce není tak silný. Když dodává nekvalitní produkt velká a známá firma, najde se hodně zákazníků, kteří ho koupí a používají jenom kvůli tomu jménu. Navíc je pro management těžké uvěřit tomu, že může být nekvalitní něco, do čeho investovali tolik prostředků. Jako příklad si můžeme uvést EJB (i když to není produkt ale specifikace). Ty byly šílené co se týče přívětivosti. Kdyby to byl OS projekt tak by ho nikdo nepoužíval. Protože za nimi ale stojí Sun, tak tu straší dodnes.

Stále jsem ještě ale neodpověděl na otázku jestli je kvalitnější open source nebo komerční software. A odpověď je přitom tak snadná: Ani ten, ani onen. Jsou špatné a dobré open source projekty, stejně jako jsou špatné a dobré komerční produkty. Ono se není čemu divit, píší je ti samí lidé.

Jak na List

Chtěl jsem napsat rozsáhlejší článeček o kolekcích, ale venku je tak krásně, že se mi do toho moc nechce. Tak napíšu něco kratšího. Začnu kvízovou otázkou: Jaká je nejjednodušší metoda pro vytvoření Listu obsahujícího tři prvky v Javě 5?

Tzn. jak jednoduše udělat něco takového:



    List<String> list = new ArrayList<String>();
    list.add("one");
    list.add("two");
    list.add("three");

V Javě 7 to bude snadné, budeme mít krásný nový literál, který nám přečte myšlenky a udělá to všechno za nás. Navíc to bude mnohem snažší než v Ruby nebo jiném v té době módním jazyce.

Ale jak to udělat v Javě 5? Tak co napadá vás něco?

Abyste měli ještě trochu času, ukaži vám špatné, ale zajímavé řešení.



    List<String> list = new ArrayList<String>();
    Collections.addAll(list, "one","two","three");

Používám zde novou, docela zajímavou funkci z třídy java.util.Collections. Doporučuji vám si tuto třídu nastudovat, v Javě 5 v ní přibyly některé nové zajímavé funkce.

Už jste přišli na to jediné správné řešení? Je to docela snadné:

List<String> list = Arrays.asList("one","two","three");

V Javě 5 se funce Arrays.asList změnila tak, že místo pole Objectů přijímá parametr typu T …, takže nám umožňuje takovýto zápis. Nevýhodou uvedeného řešení je to, že získaný seznam je neměnitelný. Při pokusu například o přidání, dostaneme UnsupportedOperationException.

Abychom se v Javě 5 opravdu vyžili, můžeme zkusit vytvořit seznam čísel.

List<Number> list = Arrays.asList(1,2L,3d);

Toto nám překladač nepovolí. Zablábolí něco takového „Type mismatch: cannot convert from List> to List“. Pokud to chceme vyřešit, musíme mu trochu pomoci a říci, jakého typu by výsledný seznam měl být.

List<Number> list = Arrays.<Number>asList(1,2L,3d);

Otázka je, nakolik bude výsledný kód čitelný. Ale s trochou cviku to jde. Ono se nám to bude hodit na dobu, kdy nám z Javy udělají Ruby.

Pozor na Equals

Tento článek jsem chtěl napsat už dlouho. Dnes jsem si na to o čem chci psát sám naběhl, takže jsem se konečně rozhoupal. O co jde? Všichni Java programátoři vědí, že každá třída má metodu equals. Často se ovšem setkáme s tím, že není správně naimplementována.

Uveďme si jednoduchý příklad. Máme třídu PhoneNumber, která obsahuje telefonní číslo ve tvaru předčíslí a samotné číslo.



public class PhoneNumber {
  private String areaCode;
  private String extension;
  
  public PhoneNumber(String areaCode, String extension) {
    this.areaCode = areaCode;
    this.extension = extension;
  }
  
  
  public String getAreaCode() {
    return areaCode;
  }
  public String getExtension() {
    return extension;
  }
}

Třída pěkně funguje, ovšem jenom do té doby, než se ji pokusím vyhledat v nějaké kolekci.



    PhoneNumber phoneNumber = new PhoneNumber("555","123-456");
    Set<PhoneNumber> aSet = new HashSet<PhoneNumber>();
    aSet.add(phoneNumber);
    
    assertTrue(aSet.contains(phoneNumber));
    assertTrue(aSet.contains(new PhoneNumber("555","123-456")));

Poslední aserce selže. Není divu, Java neví jak poznat, že jsou si obě instance třídy rovny. To ji musí říci programátor. Když ji to neřekne, jsou instance porovnány pomocí operátoru ==, což v tomto případě není to co bychom chtěli. Náprava je snadná, můžeme použít například následující implementaci



  @Override
  public boolean equals(Object obj) {
    if (obj==thisreturn true;
    if (!(obj instanceof PhoneNumber)) return false;
    PhoneNumber second = (PhoneNumber)obj;
      return new EqualsBuilder()
                     .append(this.areaCode, second.areaCode)
                     .append(this.extension, second.extension)
                     .isEquals();
  }

Z lenosti jsem použil EqualsBuilder, který je součástí commons-lang knihovny. Nicméně test mi stále neprojde. Zkušenější už vědí, porušil jsem základní pravidlo

Když překrýváte equals, překryjte také hashCode

To napravím jednoduše, například takto:



  @Override
  public int hashCode() {
    return new HashCodeBuilder(1737).
         append(areaCode).
         append(extension).
         toHashCode();
  }

(Alternativou k předchozím implementacím, je nechat si obě metody vygenerovat vývojovým prostředím. Výsledek se bude lišit jenom v detailech. )

Důležitá ovšem není implementace, ale logika celého porovnání. Musíme určit, jak poznám rovnost instancí. To často závisí na logice aplikace a často se může u stejné třídy lišit pro různé případy užití. Mějme například třídu Client, která obsahuje jméno, příjmení a kolekci telefonních čísel. Musíme určit, kdy jsou si dvě instance této třídy rovny. Ideální je případ, kdy máme tzv. business klíč. V případě klienta by to mohlo být například rodné číslo. Poté stačí porovnat jenom rodná čísla, a víme, jestli obě instance představují stejného klienta.

Když ovšem business klíč nemáme, musíme si poradit jinak. Můžeme tvrdit, že instance jsou si rovny, pokud mají stejné všechny položky. To je ovšem často velmi pracné, chybové a člověk hledá cestu jak si práci ulehčit. Navíc pokud toto equals chci používat v kolekcích, musím zajistit, aby se mi equals v čase neměnilo. Tzn. nesmí se mi například změnit, když klientovi přidám telefonní číslo. V tomto případě selže poslední řádek následujícího testu.



    Client client = new Client("John","Doe");
    
    Set<Client> aSet = new HashSet<Client>();
    aSet.add(client);
    
    assertTrue(aSet.contains(client));
    client.addPhoneNumber(new PhoneNumber("555","123-456"));
    assertTrue(aSet.contains(client));//fails

Důvod je jasný. Změnila se hodnota hashCode a prvek je hledán na špatném místě Setu (ve špatném kýblu). To může vést k veselým „záhadným“ chybám. Řešením by bylo udělat třídu Client neměnitelnou. To si ovšem často nemůžeme dovolit.

Pokud používáme objektově relační mapování, nabízí se použít databázové ID jako položku přes kterou porovnávám. Na první pohled to může vypadat jako dobrý nápad. ID je jednoznačné a nemění se. Je tu ovšem jeden zásadní háček. Po vytvoření instance je toto ID obvykle null. Takže následující test selže.



    Set<Client> aSet = new HashSet<Client>();
    aSet.add(new Client("John","Doe"));
    aSet.add(new Client("Bill","Smith"));
    assertEquals(2, aSet.size());

Důvod je jednoduchý, v Setu může být každý prvek jenom jednou. Při vkládání Billa Smithe, Set zjistí, že už vkládaný prvek obsahuje (equals vrací true, protože mají stejné ID). Takže prvního klienta nahradí druhým! To asi není to co bychom chtěli (hledáním této chyby jsem strávil jenom jednu hodinu, to jde ne?). Jednoho klienta to prostě v tichosti sežere. Pokud nebudeme vkládat neperzistované objekty do Setu, neměli bychom mít žádný problém. Pro jistotu můžeme upravit equals vyhozením výjimky, takže kód neselže potichu, ale pěkně nahlas (kód vygenerovaný pomocí Eclipse, výjimka přidána ručně).



  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    final Client other = (Clientobj;
    
    if (id==null && other.id==null)
    {
      throw new UnsupportedOperationException("Can not compare two transient objects.");
    }
    if (id == null) {
      if (other.id != null)
        return false;
    else if (!id.equals(other.id))
      return false;
    return true;
  }

Pro shrnutí uvedu tabulku možností implementace, abyste si mohli vybrat to nejmenší z možných zel:

Implementace equals Nevýhody
Porovnání business klíčů (rodné číslo)
Porovnání neměnitelných polí
Porovnání měnitelných polí Po změně hodnoty, může selhat vyhledání v kolekci
Implicitní implementace (a==b) Nelze porovnávat různé instance. Při použití Hibernate, nelze porovnat instance, které nejsou ze stejné Session
Porovnání id (a.getId().equals(b.getId()) Neperzistované objekty nelze vkládat do Setu (a podobných kolekcí)

Kde se dočíst více:

Povinná kniha pro každého kdo se chce věnovat Javě:
Bloch Joshua, Java Efektivně (57 zásad softwarového experta) Grada 2002, http://www.grada.cz/katalog/kniha/java-efektivne/

Kapitola z originálu o Equals:
http://developer.java.sun.com/developer/Books/effectivejava/Chapter3.pdf

Povídání o equals a Hibernate:
http://www.hibernate.org/109.html