19. května 2010

Jak na porovnávání Comparable objektů či pomocí Comparatoru

Nemít přetěžování operátorů skutečně považuji za velký problém Javy. Proč? Např. proto, že porovnávání objektů pomocí instance třídy Comparator či porovnání objektů implementující rozhraní Comparable je boj, který pernamentně prohrávám. Mějme např. dva datumy d1 a d2. Pokud chci zjistit, zda platí d1 <= d2, pak mám následující možnosti:

  • !d1.after(d2)
  • d1.compareTo(d2) <= 0
Přiznám se, že z těchto dvou variant mi přijde čitelnější ta první (druhou mi mozek nějak nebere a vždy mě stojí hrozně přemýšlení co to znamená). Ovšem metody before a after má pouze třída java.util.Date nikoliv obecná třída implementující rozhraní Comparable. Pokud budeme objekty porovnávat pomocí instance Comparator pak musíme vystačit s metodou compare.

Z tohoto důvodu jsem si udělal jednoduchý enum:

public enum Relation {
   eq, ne, lt, le, gt, ge;

   public static <C extends Comparable<? super C>> boolean rel(C c1, Relation oper, C c2) {
      switch (oper) {
         case eq:
            return c1.compareTo(c2) == 0;
         case ne:
            return c1.compareTo(c2) != 0;
         case lt:
            return c1.compareTo(c2) < 0;
         case le:
            return c1.compareTo(c2) <= 0;
         case gt:
            return c1.compareTo(c2) > 0;
         case ge:
            return c1.compareTo(c2) >= 0;
      }
      throw new IllegalArgumentException("Unsupported operation " + oper);
   }

   public static <C> boolean rel(Comparator<C> comp, C c1, Relation oper, C c2) {
      switch (oper) {
         case eq:
            return comp.compare(c1, c2) == 0;
         case ne:
            return comp.compare(c1, c2) != 0;
         case lt:
            return comp.compare(c1, c2) < 0;
         case le:
            return comp.compare(c1, c2) <= 0;
         case gt:
            return comp.compare(c1, c2) > 0;
         case ge:
            return comp.compare(c1, c2) >= 0;
      }
      throw new IllegalArgumentException("Unsupported operation " + oper);
   }
}
Díky použití tohoto enumu mohu přepsat výše uvedený příklad porovnávání dvou datumů do podoby:
rel(d1, le, d2)
(pokud použijeme statické importy na metodu rel a instanci enumu le). Protože se mi tento zápis ještě stále nezdál dostatečně výmluvný, použil jsem další vylepšení (stačí jej přidat do výše uvedeného enumu):

   protected static final Map<String, Relation> S2R;

   static {
      Map<String, Relation> tmp = new TreeMap<String, Relation>();
      tmp.put("==", eq);
      tmp.put("!=", ne);
      tmp.put("<=", le);
      tmp.put("<", lt);
      tmp.put(">=", ge);
      tmp.put(">", gt);
      S2R = unmodifiableMap(tmp);
   }

   public static <C extends Comparable<? super C>> boolean rel(C c1, String oper, C c2) {
      return rel(c1, S2R.get(oper), c2);
   }

   public static <C> boolean rel(Comparator<C> comp, C c1, String oper, C c2) {
      return rel(comp, c1, S2R.get(oper), c2);
   }
Nyní dostaneme již poměrně elegantní zápis:
rel(d1, "<=", d2)
Je škoda, že Java nemá přetěžování operátorů, které nás vede k takovýmto vylepšením. Např. Groovy si umí poradit a operace porovnání umí zavolat nad objekty implementující Comparable. Scala je na tom podobně díky možnosti přetížení operátorů.

5. ledna 2010

Lekce ze Scaly: Jak se vyvarovat NullPointerException

Jak se v jazyce Scala brání NullPointerException? V hojné míře se používá třída Option, která zajistí, že nemusí metoda vracet null, ale vrací existující instanci. Jak to celé funguje si ukážeme v Javě:

public abstract class Option<T> {
  public abstract T get();
  public abstract boolean isNone();
  public abstract boolean isSome();
}

Pokud potřebujeme aby nějaká metoda vracela něco a nebo null, použijeme třídu Option. Díky ní metoda vrátí existující instanci, ze které jednoduše zjistíme, zda návratová hodnota má být null (isNone() vrací true) a nebo je to nějaká smyslu plná hodnota (isSome() vrací true) a pak se k této hodnotě dostaneme pomocí metody get().

Teď už to chce jenom nějaký rozumný příklad. Vezněme si např. Map a její metodu get:

V get(T key);
Ta v případě, že klíč v mapě neexistuje vrací null. Takže typicky píšeme něco jako:

String value = map.get(key);
if (value != null) {
  System.out.println(value);
} else {
  System.out.println("Nic");
}

A jak by tedy měla metoda get objektu Map vypadat kdybychom byli ve Scale:

Option<V> get(T key);
A nyní jak by vypadal náš kód pro čtení z mapy:

Option<String> value = map.get(key);
if (value.isSome()) {
  System.out.println(value.get());
} else {
  System.out.println("Nic");
}

A zbavili jsme se ošklivého porovnání na null, což jsme chtěli. Navíc pokud metoda vrací Option a ne přímo hodnotu je podstatně větší pravděpodobnost, že nezapomeneme otestovat zda Option něco obsahuje. Pokud metoda vrací přímo hodnotu nebo null, pak hrozí, že hodnotu rovnu použijeme a neotestujeme ji na null.

A nyní přistupme k implementaci:

public class None<T> extends Option<T> {

  public T get() {
    throw new UnsupportedOperationException("The None had no value");
  }

  public boolean isNone() {
    return true;
  }

  public boolean isSome() {
    return false;
  }

  public boolean equals(Object obj) {
    return ((obj == this) || (obj instanceof None));
  }

 public int hashCode() {
   return 1;
  }

}

public class Some<T> extends Option<T> {
  protected T value;
 
  public Some(T value) {
    super();
    this.value = value;
  }

  public T get() {
    return value;
  }

  public boolean isNone() {
    return false;
  }

  public boolean isSome() {
    return true;
  }

  public boolean equals(Object obj) {
    if (obj == this) return true;
    if (obj instanceof Some) {
      Some other = (Some) obj;
      return value.equals(other.value);
    }
    return false;
  }

  public int hashCode() {
    return value.hashCode();
  }
 
}

A jak to celé funguje ve Scale? Díky pattern matchingu a type inference ještě elegantněji:

val value = map.get(key) 
value match {
  case None => print "Nic"
  case Some(x) => print x
}

1. října 2009

Gradle - druhý krůček

V prvním popisku použití gradlu jsem si ukázali jak na jednoduchý projekt, dneska se podíváme, jak jsme zbuildovali projekt do waru.

Vyjdeme z předcházejícího příkladu. Co musíme změnit, abychom měli jako výsledek projektu war, ve správném layoutu a ne jar? Je toho pekelně málo.


usePlugin "java"
usePlugin "maven"
usePlugin 'eclipse'
usePlugin 'war'

sourceCompatibility = 1.6
group = "cz.svt"
version = "${version}"

manifest.mainAttributes "Implementation-Title": name, ...

configurations {
deployerJars
}

dependencies {
compile fileTree(dir: "lib", includes: [ "util.jar", "JMSMailer.jar"])

compile "org.apache.activemq:activemq-core:5.2.0"
...

providedCompile "javax.servlet:servlet-api:2.4"

runtime "datedFileAppender:datedFileAppender:1.0.2"

deployerJars "org.apache.maven.wagon:wagon-http:1.0-beta-2"
}

repositories {
...
}

uploadArchives {
...
}


Tam kde jsou uvedeny 3 tečky jsem build zkrátil, buď protože obsah je totožný s buildem z minula a nebo výpis nepřináší nic zajímavého. Takže co se fakticky změnilo? Jeden řádek. Přidali jsme na úvod:


usePlugin 'war'


To je celé. Navíc jsme do struktury adresářů přidali adresáře src/main/webapp/META-INF a src/main/webapp/WEB-INF. Mě to přijde úžasně jednoduché a musím říct, že se mi gradle líbí víc a víc.

CZJUG - Hans Dockter - Gradle

Po velmi dlouhém čase jsem se dostal na CZJUG. Nelituju, spíš lituju, že mi to termínově nevychází se tam dostávat častěji. Přednáška o gradle byla hodně zajímavá. Druhou přednášku o MPS jsem nepochopil, jestli to bylo tím pivem nevím. Samozřejmě jsem rád, že jsem mohl potkat staré známé z java komunity.

Hans pojmul přednášku tak jak jsem to nečekal a bylo to hodně zajímavé. V podstatě na úvod nám ukázal jak je gradle inteligentní. Na příkladu přidání source directory do projektu nám ukázal jak na 4 řádcích je možné vyjádřit vše. Závislosti (interní, externí), vytvoření tasků na přeložení, závislostí mezi tasky. Pak přislo na pořad dne základní motivace a tím pádem trochu srovnání s ANTem a mavenem.

Dále se mluvilo o výhodách v podobě vyhodnocování tásků, které se spustí, o multiprojektech atd. Dále jsme se dozvěděli i něco o budoucnosti (především se na nás chystá spolehlivý build bez clean, tj. že gradle bude shopen zaručeně identifikovat co se změnilo a co je díky tomu nutno zbuildovat).

Nosná myšlenka, pro mě, je, že gradle se snaží být takovým vylepšeným mavenem, tj. používá intenzivně standardní layout projektu, standardní lifecycle buildu, ale navíc nabízí neuvěřitelnou flexibilitu, vše je možno změnit.

Pro mě je to cesta správným směrem, byť to vyžaduje kontrolu nad tím, jak je gradle používán, aby se nám build nevymkl z ruky. Navíc je gradle stále mladý a hodně se mění, takže použití v dnešní době není úplně bezproblémové. Ale je to systém hodně perspektivní.

Pokud někoho obsah přednášky zaujal, pak se vše nahrávalo, takže se můžete kouknout na video (odkaz na něj jistě bude na stránkách CZJUGu).

16. srpna 2009

iBATIS 3 - nový bojovník přichází

Jsem dlouholetý uživatel Hibernate, které používáme na velkém projektu a musím se přiznat, že dnes nám spíš přináší komplikace (především u 50GB databáze už hodně záleží jaká SQL se volají a to nám Hibernate neumožňuje ovlivnit). iBATIS to umí i ve verzi 2. Co se mi líbilo na návrhu připravované verze 3 jsem již psal. Ale my tu již máme první betu trojky. (Než se pustím do svých postřehů, rád bych zmínil seriál otce Fura, který je vyčerpávajícím popisem novinek v porovnání s dřívější verzí iBATISu.)

Krom věcí zmíněných již v předcházejícím článku existují další, které mě hodně potěšili:

  • vytváření TypeHandlerů je o poznání jednodušší než podobných UserTypů v Hibernate
  • objekty natažené z DB nejsou vytvářené pomocí bezparametrického konstruktoru, ale pomocí ObjectFactory, takže máte způsob vytvoření objektu plně v ruce (objekty půjde vytvářet pomocí DI frameworku - Spring, Guice) - to je obrovská bolest Hibernate, která se řešila pomocí AOP
  • vytváření dynamických SQL

Jsem moc rád že komunita za iBATISem stojící sebrala odvahu a vrhla se do designu nového frameworku, i když je to za cenu menší zpětné kompatibility. Moc se jim nový iBATIS povedl a řada věcí řeší bolesti, na které musí každý kdo používá Hibernate narazit.

Spolu s iBATISem byl uvolněn i další produkt a to iBATIS Schema Migration System, který umožnuje automatizovat migraci DB schéma z jedné verze tam i zpět (pokud si SQL skript na zpětnou migraci napíšete). Inspirace jistě pochází z Ruby on Rails Migrations. Bohužel Migrations jak je představil team iBATISu mají jeden zásadní problém: migrační scripty se píší přímo v SQL. Takže jen velmi těžko budeme vytvářet skript, který bude fungovat s různými databázemi nebo s různými konfiguracemi téže databáze.

11. srpna 2009

Gradle - první krůčky

O novém build nástroji gradle jsem již psal. Je to už rok a co se za tu dobu stalo? Gradle nám vyrostl z verze 0.2 na verzi 0.7, která je už velmi rozumně použitelná. A proto jsme se rozhodli gradle použít pro náš první projekt.

Jedná se o velmi jednoduchý projekt (matcher pro easymock). Tento projekt obsahuje 3 třídy, které je potřeba zkompilovat. Neobsahuje testy. Výsledný jar je nutno deploynout do firemní artifactory.

A jak takovéto jednuché věci dosáhnout:

usePlugin "java"
usePlugin "maven"

sourceCompatibility = 1.6
group = "cz.svt"
version = "${version}"

manifest.mainAttributes "Implementation-Title": name, "Implementation-Version": version, "Implementation-Vendor": "ČSAD SVT Praha s.r.o."

configurations {
deployerJars
}

dependencies {
compile "commons-beanutils:commons-beanutils:1.7.0"
compile "org.easymock:easymock:2.2"
compile "org.easymock:easymockclassextension:2.2.1"

deployerJars "org.apache.maven.wagon:wagon-http:1.0-beta-2"
}

repositories {
mavenRepo urls: "${artifactoryURL}/repo1"
}

uploadArchives {
repositories.mavenDeployer {
name = 'httpDeployer'
configuration = configurations.deployerJars
repository(url: "${artifactoryURL}/libs-releases-local") {
authentication userName: "${artifactoryUid}", password: "${artifactoryPwd}"
}
snapshotRepository(url: "${artifactoryURL}/libs-snapshots-local") {
authentication userName: "${artifactoryUid}", password: "${artifactoryPwd}"
}
}
}


Maven uživatel může jenom závidět, jak je tento script kompaktní a krátký. Nyní si postupně projdeme co musíme ve scriptu udělat, aby vše fungovalo.

Nejprve definujeme, které pluginy budeme používat. java plugin potřebujeme pro překlad Javy a maven pro deployment do artifactory (jedná se o maven repository).

Další 3 řádky definují, že používáme Javu 1.6, skupina deployovaného artefaktu je "cz.svt" a na verzi se odvoláváme jako na property (o těch si povíme dále). Následuje řádek s definicí manifestu (hojně používáme property dříve nastavené).

Další 3 řádky definují novou konfiguraci deployerJars, kterou použijeme v tasku na deployment jaru do naší sdílené firemní artifactory.

A začínáme tím zajímavým, závislostmi. Stačí nám na ně 7 řádků. Závislosti se definují podobně jako v mavenu, ale skupiona, název, verze se oddělují dvojtečkou. Jinak gradle umí díky ivy pracovat nejen s ivy, ale i maven repository. Poslední závislost říká, že deployování bude potřebovat wagon-http.

Následně defunujeme repository pro resolvování závislostí (opět pomocí property). Poslední je task uploadArchives, který je nejsložitější z celého scriptu. Složitost ovšem spočívá v definování rozdílné repository pro snapshoty a pro finální verze (v podstatě dvakrát to samé). Protože deployment do artifactory nemůže provést každý, je nutné specifikovat už. jméno a heslo (není přímo ve scriptu, ale je to properta).

Jak gradle resolvuje property? Gradle hledá v aktuálním adresáři soubor gradle.properties a dále se kouká do adresáře $HOME/.gradle/ po stejnojmenném souboru. V těchto souborech můžeme definovat property, jenž můžeme využít v buildu. Takže v projektu uvádíme verzi a v domovském adresáři definujeme adresu repository a už. jméno a heslo.

A jaké budou další krůčky? Nejprve přidáme testy, pak generování standardních reportů (javadocs, findbugs, cobertura) a nakonec groovy, aspectj atd...

16. července 2009

Super myšlenka učení programovat na škole - programovat open source projekt

Když jsme byli na jOpenSpace 2009 tak jsme v hloučku zainteresovaných vedli diskusi na téma: "Co by měla vysoká škola dělat, aby naučila své studenty programovat?". V této diskusi byl hodně aktivní Petr Adámek, který jako učitel na Masarykově univerzitě, měl k tématu hodně co říci.

Já jsem šel do diskuse s nosnou myšlenkou, že škola musí více do svého programu zatáhnout komerční sféru (u nás ve firmě pracuje řada studujících), aby se studenti dostali do kontaktu s realitou. Pracovali na skutečných projektech, se skutečným zadáním, spolupracovali s již protřelými programátory atd.

Petr se mnou de facto souhlasil, ale tvrdil, že to je nemožné zrealizovat, že zájem firem není.

Dneska jsem poslouchal Java Posse #263 - Interview with Cay Horstmann (profesor na universitě), kde zazněla ona geniální myšlenka. Jak pracovat na reálném projektu, když firmy nemají zájem. No přeci pracovat na open source projektu. Cay Horstmann řekl, že mají na universitě předmět, jehož náplní je pracovat na již existujícím open source projektu (existujícím - dle výběru studenta). Student se naučí psát kvalitní kód, spolupracovat s kolegy, číst cizí kód, inteligentně se ptát atd.

Řekl bych, že tato myšlenka rozsekla onen pomyslný gordický uzel, který vznikl v diskusi mezi mnou a Petrem.