30. prosince 2007

Nová Java: Chceme ji? - podruhé

Blíží se nám konec roku 2007 tak proč nepokračovat v mých představách změny jazyka, tj. takový malý výhled toho co by se v roce 2008 mohlo změnit.

V předchozím příspěvku jsem se rozjel na téma vylepšení programovaní paralelních programů. Dneska se pustím spíš do oblasti vylepšení psaní kódu, tj. změny, které cítím, že zpřehlední kód.

Vylepšení příkazu switch

Jistě všichni víme, že příkaz switch funguje pouze nad typy int a char. Takže žádný long, fload, double, natož např. String (o tomto konkrétním typu hovořili pánové v JavaPosse #151). Kdo se podíval pod pokličku překladu switch příkazu do bytecodu, zjistil, že k tomu slouží dvě instrukce, buď lookupswitch nebo tableswitch. V podstatě jde o to, že tato instrukce na základě hodnoty výrazu ve switch příkazu skočí na správný case. Pokud chceme vytvořit ekvivalent např. pro String, pak použijeme kaskádu if - else if - else if ... .
Proč, ale nepovolit switch i pro jiné výsledné typy výrazu (např. String) a až překladačem vše přeložit stejně jako onu kaskádu ifů?

Odchytávání více typů výjimek najednou při zachování typu výjimky

I v tomto případě jsem se nechal inspirovat povídáním pánů z JavaPosse. No představme si následující metodu:
protected Object invokeMethod(String s) throws IllegalArgumentException, 
IllegalAccessException, InvocationTargetException,
SecurityException, NoSuchMethodException
{
Method m = String.class.getMethod("toString", new Class[0] );
return m.invoke(s, new Object[0]);
}
Nyní chceme tuto metodu zavolat a vyhodit jinou výjimku v případě, že se nepodařilo najít metodu a jinou v případě, že se ji nepodařilo zavolat. Nemáme jinou volbu než:
try {
invokeMethod("A");
} catch (IllegalArgumentException e) {
throw new CantInvokeException(e);
} catch (IllegalAccessException e) {
throw new CantInvokeException(e);
} catch (InvocationTargetException e) {
throw new CantInvokeException(e);
} catch (SecurityException e) {
throw new CantFindMethodException(e);
} catch (NoSuchMethodException e) {
throw new CantFindMethodException(e);
}
A to je vskutnu zvěrstvo. Nebylo by přeci jenom lepší něco následujícího:
try {
invokeMethod("A");
} catch (IllegalArgumentException, IllegalAccessException,
InvocationTargetException: Exception e) {
throw new CantInvokeException(e);
} catch (SecurityException, NoSuchMethodException: Exception e) {
throw new CantFindMethodException(e);
}
Máme možnost specifikovat libovolný počet typů vyjímek, které chceme odchytit jediným catch příkazem, za seznam přidáme dvojtečku a specifikujeme typ proměnné (v našem případě je nejbližším společným předkem výjimek typ Exception, který je zároveň typem proměnné e). Kloním se k variantě, že programátor musí specifikovat typ proměnné, nenechával bych to na překladači.
Co znamená dodatek při zachování typu výjimky z názvu kapitoly? Pokud bychom kód z předchozího úryvku změnili na:
try {
invokeMethod("A");
} catch (IllegalArgumentException, IllegalAccessException,
InvocationTargetException: Exception e) {
//něco
throw e;
} catch (SecurityException, NoSuchMethodException: Exception e) {
//něco jiného
throw e;
}
a uvedli ho v metodě se stejnou klauzulí throws jako má metoda invokeMethod, pak bude vše v pořádku. Překladač se nebude zlobit, že se snažíme vyhodit výjimku typu Exception z metody, která ji nemá uvedenou ve throws, protože proměnná e je typu Exception, ale může nabývat pouze hodnot typů uvedených před dvojtečkou. Samozřejmě musí být final (není tak uvedená, ale je).

Závěr

Obě tyto vychytávky rozhodně nezesložití jazyk, a když tak jenom nepatrně. Ale přinesou bezesporu větší čitelnost řadě algoritmů. To je přínos, který jednoznačně stojí minimálně za zamyšlení.

21. prosince 2007

Nová Java: Chceme ji?

Zamýšlel jsem se nad myšlenkou, změny jazyka Java tak, aby lépe odrážel dnešní potřeby a obsahoval moderní prvky představené především ve skriptovacích jazycích. V poslední době se objevilo pár příspěvků na toto téma, na jedné straně příznivci změn reprezentovaní například JavaPosse #151 a na straně druhé spíše odpůrci, např. Dagi.

Já se spíš stavím za to, aby se jazyk vyvíjel a měnil. Jsem přesvědčen o tom, že požadavky kladené na dnešní "moderní" programovací jazyky jsou diametrálně odlišné od požadavků kladených před oněmi 12 lety, kdy byla Java představena.

Co považuji za nejdůležitejší změnu? Jednoduše použitelná podpora programování multithreadových aplikací. Jistě od roku 1995 máme synchronized, nyní již máme i java.util.concurrent. Do Javy 7 se blíží implementace fork-join algoritmu (pod vedením JSR 166), ale stále si myslím, že je to málo.

Proč chci víc? Programování multithreadových aplikací je stále složitá věc, rozhodně to není pro široké masy programátorů. A dneska je každý nový počítač více-procesorový a abychom využili jejich schopností, pak potřebujeme multithreadovou aplikaci.

Kde bych hledal inspiraci? Co třeba programovací jazyk X10. Ten je dokonce překládán do Javy. Obsahuje řadu konstruktů, které k programování fork-join konstruktů přímo vybízejí (async, atomic, finish, when, atd.).

Myslím, ře prostorů, ve kterých by se Java mohla zlepšovat je jistě nepřeberné množství, a Java bude muset tuto hozenou rukavici zvednout.

7. prosince 2007

Eclipse, Mylyn, IntelliJ IDEA a Teamcity v CZPodcast #19

Ještě jsem poslední (#19) CZ Podcast nedoposlouchal celý, ale když se věci začali točit okolo Eclipse Mylynu, tak bych si troufl provést malé doplnění. Mylyn už nějaký pátek (přesně už to bude přes čtvrt roku) používám a už se dlouho chystám, že se s vámi podělím o své, ve skrze pozitivní, zkušenosti, ale nějak nenacházím tolik potřebnou sílu ...

Takže Teamcity, není Mylyn, ale je to skutečně Continuous Integration server. Mylyn má ve skrze 3 oblasti, kam upírá svou pozornost:

  • práce s bug tracking systémem, plánování požadavků, jejich zobrazování, ukládání kontextu k nim atd.
  • zaměření prostředí na zobrazování pouze relevantních informací v IDE (týkajících se aktuálně zpracovávaného požadavku) = založeno na kontextu, jenž se dá k požadavku uložit - především se to týká filtrování toho, co není k právě realizovanému požadavku relevantní
  • vytváření change setů pro commit do subversion (CVS) na základě kontextu
Co je mi známo, pak IDEA má podporu pro podobnou věc, ale není navázána na bug tracking systém (tj. požadavky si vytváříte ručně) a UI projektu se nijak nemění (nezaměřuje). Tj. sleduje, který soubor se modifikuje a pak vytváří change set pro commit zdrojáků. Podporuje přepínání požadavků a vytvoření správných change setů podobně jako Mylyn.

Sice IDEAu nepoužívám, a nikdy neříkej nikdy .. Třeba mě tento příspěvek nakopne, abych sepsal ten MYLYN.

30. listopadu 2007

Unit testy - ty přece píše kodér, ne?

Tentokrát spíš chci prodiskutovat jedno téma, než vám něco sdělit. Právě jsem dočetl článek Akshay Sharmi o návrhu, který myslí na testovatelnost kódu (Design to Unit Test). Jediná věc, která mě na tomto článku přímo praštila do očí, je skutečnost, že je psán tak, jako že unit testy píše někdo jiný, než ten, kdo testovaný kód psal.

Já jsem vždy přemýšlel o unit testech jako o věci, kterou si kodér ověřuje, že jeho kód funguje tak, jak má. Navíc si kodér musí již dopředu rozmyslet, jak svůj kód otestuje, což bezesporu vede k jeho větší kvalitě. V neposlaení řadě unit testy vnímám také jako "dokumentaci" ke kódu, což v případě, že je píše někdo jiný opět selhává.

Je pravdou, že psaní testů je trochu jiná káva, než psaní kódu. Je nutné se to naučit. Ale není to zase tak složité. V případě integračních a akceptačních testů je již zřejmé, že je může (nebo má?) implementovat někdo jiný než kodér.

Obávám se, že tento přístup není pro mě. Navíc bych si troufl s ním nesouhlasit.

29. listopadu 2007

XML Signature - použít nebo ne?

V naší aplikaci přenášíme poměrně velké XML soubory a přenášíme jich více najednou. Proto jsou zazipované v ZIP archivu. Ovšem potřebujeme nějak zabezpečit, aby nebylo možné měnit obsah archivu (tj. nemožnost přidávat či mazat soubory) a dále aby nešlo měnit jednotlivé XML soubory. Navíc tato komunikace neprobíhá mezi naším softwarem, ale mezi naším a cizím. Jak věc vyřešit?

Použít XML Signature (specifikace) nebo ne? Než jsem se pustil do zkoušení hledal jsem na netu a našel jsem pár mailů v konferencích a blogů, že XML Signature implementace nefunguje dobře na "large documents". Už jsem přemýšlel o vlastním řešení, kde bych hashoval XML soubory jako stream bytů, nebo bych vytvořil nějaký vlastní algoritmus pracující přímo nad SAXem, který používáme jak pro čtení tak i pro zápis dokumentů. Ale nakonec jsem si řekl, že to zkusím přímo s XML Signature a šáhl jsem po implementací, která je součástí JDK 6. Pro XML Signature hovoří skutečnosti, že je to standard (nemusím specifikovat jak se podpis vypočítá) a existuje implementace pro skoro všechny rozšířené jazyky.

Napsal jsem tedy velmi jednoduchou třídu XMLSignatureTest (při jejím psaní jsem převážně vycházel z příkladu v XML Digital Signature API Examples):

public class XMLSignatureTest {

protected static final String KEY_ALIAS = "ALIAS";

public static void main(String[] args) {
try {
long milis = System.currentTimeMillis();
if ("sign".equals(args[0])) {
signing(new File(args[1]), new File(args[2]));
} else {
System.out.println(verify(new File(args[1])) ? "Verified - ok" : "Changed");
}
System.out.println("Done in " + (System.currentTimeMillis() - milis) + " ms.");
} catch (Throwable t) {
t.printStackTrace();
}
}

protected static void signing(File xmlFile, File signatureFile) throws Exception {
//načti vstupní XML dokument
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder builder = dbf.newDocumentBuilder();
Document doc = builder.parse(new FileInputStream(xmlFile));

//otevři keystore a načti z něj privátní klíč k podpisu hashe
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(new FileInputStream("keystore"), "XMLSig".toCharArray());
PrivateKey key = (PrivateKey) keyStore.getKey(KEY_ALIAS, "MEkeypwd".toCharArray());

//nadefinuj context podpisu
DOMSignContext dsc = new DOMSignContext(key, doc.getDocumentElement());
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");

//podepiš celý dokument, na canonicalizaci použij metodu INCLUSIV, jako hash použij SHA1 algoritmus
//podpis bude vložen do podepisovaného dokumentu a výsledný hash bude zakryptovát DSA klíčem
Reference ref = fac.newReference("", fac.newDigestMethod(DigestMethod.SHA1, null),
Collections.singletonList(fac.newTransform(Transform.ENVELOPED, (XMLStructure) null)),
null, null);
SignedInfo si = fac.newSignedInfo(
fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, (XMLStructure) null),
fac.newSignatureMethod(SignatureMethod.DSA_SHA1, null), Collections.singletonList(ref));

//do podpisu jako informaci o použitém klíči vložíme jeho alias
//tj. obě strany se musí domluvit na aliasech a předat si veřejné klíče
KeyInfoFactory kif = fac.getKeyInfoFactory();
KeyName kn = kif.newKeyName(KEY_ALIAS);
KeyInfo ki = kif.newKeyInfo(Collections.singletonList(kn));

//spočítej podpis
XMLSignature signature = fac.newXMLSignature(si, ki);
signature.sign(dsc);

//ulož výsledný dokument do souboru
OutputStream os = new FileOutputStream(signatureFile);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer trans = tf.newTransformer();
trans.transform(new DOMSource(doc), new StreamResult(os));
os.flush();
os.close();
}

protected static boolean verify(File xmlFile) throws Exception {
//načti XML document s vloženým podpisem
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder builder = dbf.newDocumentBuilder();
Document doc = builder.parse(new FileInputStream(xmlFile));

//najdi v dokumentu podpis
NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
if (nl.getLength() == 0) {
throw new Exception("Cannot find Signature element");
}

//otevři keystore pro načtení veřejného klíče
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(new FileInputStream("keystore"), "XMLSig".toCharArray());

//vytvoř validační kontext (node s podpisem a KeySelector pro získání
//klíče podle identifikace v podpisu - v našem případě je to ALIAS)
DOMValidateContext valContext =
new DOMValidateContext(new KeyNameKeySelector(keyStore), nl.item(0));
XMLSignatureFactory factory = XMLSignatureFactory.getInstance("DOM");
XMLSignature signature = factory.unmarshalXMLSignature(valContext);

//ověř podpis
return signature.validate(valContext);
}

//tato třída slouží k nalezení veřejného klíče použitelného k odkryptování podpisu
private static class KeyNameKeySelector extends KeySelector {
protected KeyStore keyStore;

public KeyNameKeySelector(KeyStore keyStore) {
super();
this.keyStore = keyStore;
}

//tato metoda je volána knihovnou
public KeySelectorResult select(KeyInfo keyInfo, KeySelector.Purpose purpose,
AlgorithmMethod method, XMLCryptoContext context) throws KeySelectorException {

if (keyInfo == null) {
throw new KeySelectorException("Null KeyInfo object!");
}
SignatureMethod sm = (SignatureMethod) method;
List list = keyInfo.getContent();

for (int i = 0; i < list.size(); i++) {
XMLStructure xmlStructure = (XMLStructure) list.get(i);
//my hledáme klíč podle jména (aliasu)
if (xmlStructure instanceof KeyName) {
try {
//natáhnem veřejný klíč z keystoru
Certificate certificate = keyStore.getCertificate(((KeyName) xmlStructure).getName());
PublicKey pk = certificate.getPublicKey();
//ověříme, že v podpisu je stejný algoritmus jako používá klíč
if (algEquals(sm.getAlgorithm(), pk.getAlgorithm())) {
return new SimpleKeySelectorResult(pk);
}
} catch (KeyStoreException kse) {
throw new KeySelectorException(kse);
}
}
}
throw new KeySelectorException("No KeyValue element found!");
}

//pouze porovnává algoritmy
static boolean algEquals(String algURI, String algName) {
if (algName.equalsIgnoreCase("DSA")
&& algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1)) {
return true;
} else if (algName.equalsIgnoreCase("RSA")
&& algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1)) {
return true;
} else {
return false;
}
}

//jenom holder pro klíč
private static class SimpleKeySelectorResult implements KeySelectorResult {
private Key pk;

SimpleKeySelectorResult(Key pk) {
this.pk = pk;
}

public Key getKey() {
return pk;
}
}

}

}
Úpravy spočívají pouze v použití key-storu pro získání klíču a nepřenášení veřejného klíče v podpisu, ale pouze jeho aliasu.

A nyní k samotným testům. Zajímal jsem se o dva faktory: rychlost a peměťová náročnost.

Takže nejprve jsem test prováděl na XML dokumentu o velikosti 6,5 kB. Výpočet podpisu i ověření proběhlo i s nejmenším možným heapem, tj. 2M (1M nestačilo ani na nastartování JDKčka) a výpočet podpisu a serializace trvala 0,6 s. Co se verifikace podpisu týče, došel jsem k úplně stejnému číslu.

Následně jsem přitvrdil a použil jsem soubor o velikost 4,1 MB. Minimální heap, nad kterým jsem se dobral výsledku a nedostav pouze OOM exception byl 53MB a výpočet podpisu včetně serializace výsledku trval 10s, ověření podpisu trvalo pouze 4,8s a dokonce potřebovalo minimálně pouze 47MB. Při použití heapu o velikosti 400MB jsem se u podpisu dostal na čas 4,8s a u ověření na 2,8s.

Závěr

V podstatě jsem byl velmi mile překvapen, jak vše hladce a na první pokus proběhlo a spíš jsem hledal skulinu, jak XML Signature použít. Z prvu mi vadilo, že je nutné použít DOM, protože používáme SAX (a použít Transaformer se vstupem SAXSource a s výstupem DOMResult se mi nechtělo). Pak jsem si uvědomil, že bych stejně nemohl výpočet podpisu zařadit přímo do zpracování dokumentu, protož zpracování může trvat i dost dlouho (třeba i desítky minut). Souborů se může zpracovávat více najednou. Pokud by každý, nedejbože, byl rovnou podepisován a vytváření podpisu by si slízlo 50MB heapu, pak bych měl na serveru pamět jenom pro podepisování.

Nakonec jsem se rozhodl, že prvním krokem zpracování bude načtení dokumentu pomocí DOM a ověření podpisu. Následně teprve, pokud bude vše v pořádku, budu dokument načítat pomocí SAX (do budoucna stejně přejdeme na StAX) a dokument budu zpracovávat a zároveň budu generovat výstup (také pomocí SAX) a ten budu serializovat do souboru. Na závěr výstupní soubor načtu pomocí DOM a vygeneruju podpis.

V neposlední řadě, tak trochu pod čarou, zmíním canonicalizaci, která funguje opravdu pěkně, přidávání mezer, nových prázdných řádků, přehazování pořadí atributů neovlivní platnost podpisu.

19. listopadu 2007

JavaScript a Dependency Injection

Všichni jste už asi slyšeli o Spring IoC, Picocontaineru, HaveMindu či Tapestry IoC. Ale což takhle IoC pro JavaScript?

Ano je to možné. Jmenuje se Squirel Ioc. Jedná se o velmi jednoduchou knihovna, která má 4kB a používá jQuery.

Použití je velmi jednoduché (jak už se v mých příspěvcích stalo pravidlem). Máme následující třídy, které budeme propojovat:


function MyModel(){
//obsah třídy MyModel
}

function MyDAO(model){
var _model=model;

this.get = function() {
return _model;
}

this.save = function() {
//uloz
}

this.delete = function() {
//smaz
}
}

function MyService(){
var _dao = null;

this.get = function() {
return _dao.get();
}

this.getDao = function() {
return _dao;
}

this.setDao = function(dao) {
_dao = dao;
}
}

function MyApp(){
var _service=null;
//rikame IoC containeru, ze az vse nastavi, ma zavolat metodu onContextSupport
ContainerSupport.call(this);

this.onContextSupport = function(containerContext){
var model=_service.get();
//udelej cokoliv dalsiho s hodnotou ...
}

this.setService = function(service){
_service = service;
}
}
Z pohledu IoC v Javě máme POJO objekty a teď je potřebuje spolu nějak svázat (ve Springu se k tomu např. používá xml soubor nebo anotace). Při použití Squirrel IoC nám poslouží velmi jednoduchý kód:
var definition={
'model':{type:MyModel},
'dao':{type:MyDAO,args:[{ref:'model'}]},
'service':{type:MyService,props:[{name:'dao',ref:'dao'}]},
'app':{type:MyApp,props:[{name:'containerContext',ref:'containerContext'},{ref:'service'}]}
};


var container = new IContainer();
container.load(definition);
V příkladu je vidět constructor injection (na příkladu definice dao objektu) či setter injection (na příkladu objektu service).

Když to funguje v Javě, proč ne v JavaScriptu. Opět se ukazuje jednoduchost, kterou nám JavaScript jako dynamický jazyk nabízí. A příště až budete potřebovat propojit pár objektů mezi sebou, zamyslete se, zda vám Squirrel Ioc neusnadní práci.

10. listopadu 2007

Nejde říci co je cennější, zda znalosti nebo zkušenosti

Jak je naznačeno v příspěvku Znalosti a zkušenosti, zkušenost je víc než znalost.

Podle mě jde hlavně o rozdíl mezi znalostí a zkušeností. Znalosti jsou laciná věc. Stačí zajít do školy, projít si pár článku na internetu a nebo nedej bože přečíst knihu. Pokud máte funkční paměť nejen v počítači ale i v hlavě, tak znalosti nabudete velice snadno.
A hned jsem si vzpomněl na strýčka z Jirotkova Saturnina, který měl malou chemickou továrnu a nebyl vzdělán v oboru, na všechno si přišel sám. Vše co bylo napsáno v moudrých knihách on složitě vymýšlel. Neměl žádné znalosti, ale nespočetně mnoho zkušeností. Bohužel, vždy když vyráběl nové mýdlo, netušil co se mu v kádích uvaří ...
Jsou to zkušenosti co se počítá. Můžu si přečíst kolik knih chci o tom jak se mají správně dělat softwarové projekty. Dokud to ale nezažiji na vlastní kůži, mají tyto znalosti malou váhu (bojím se že nulovou). Zkušenosti mají také jednu obrovskou výhodu. Málo stárnou. Když mám zažito jak psát software, jaká pravidla dodržovat a čeho se vyvarovat, je mi více méně jedno jakou technologii používám. Detaily si dokáži nastudovat.
Není přeci nutné mít zkušenost s popáleninou od rozpálených kamen, když mám znalost, že to je dost hrozné. Zkušenost dává znalostem rozměrem. Dává jim ten osobní kontakt s nimi. Ale zároveň nám umožňuje tento rozměr v budoucnu dát znalostem přímo, bez zkušenosti. Znalost nás směřuje správným směrem a zaměřuje naše zkoumání a vytváření zkušeností k užitečným cílům.

A teď musím navázat na Vlastůvovu reakci Co se počítá, ve které zase uhodil hřebíček na hlavičku (jak on to dělá?). Nejde o to, jak své dovednosti získáme, či zkušeností nebo znalostí, každý jsme jiný (někomu vyhovuje si přečíst referenční příručku, někdo jiný si věc raději osahá), ale jde o to, jak svých schopností využijeme k plnění cílů, které jsou nám předkládány.

Takže čtěme, vzdělávejme se, nebojme se poučit od druhých, zkušenějších, berme jejich rady vážně, protože jenom tak nezopakujeme chyby, kterých se dopustili, a získáme více drahoceného času. A třeba to dotáhneme ještě dál než oni.