TDD-kurs, våren 2013

No Gravatar

Squeed bjuder in till en ny omgång av kursen i testdriven utveckling (TDD). Denna omgång av kursen vänder sig till dig som är Java-utvecklare och nybörjare inom TDD-området. Kursen omfattar fem tillfällen om ca två timmar där du kommer få praktisk erfarenhet av att utveckla kod testdrivet.

Upplägget med datum (spikat) och ämnen (lite mer rörligt) ser ut så här:

tors 25 april, kl 16-18: TDD, basics
mån 13 maj, kl 16-18: TDD, test backlog och “enough upfront design”
tors 23 maj, kl 16-18: TDD + OO, classical approach
ons 5 juni, kl 16-18: TDD + OO with mock framework
tors 20 juni, kl 16-18: [något av: Clean Code, Mockist approach, BDD, three-clause style, Refactoring]

Kursen, som är gratis och har plats för 10 deltagare kommer hållas på svenska och leds av Fredrik Wendt, Niclas Åstrand och Peter Kristoffersson. Vi ger förtur för nya deltagare och du kan anmäla ditt intresse genom att fylla i ett enkelt formulär för intressanmälan.

Strategy X vs Y

No Gravatar

X vs Y

Skrev nyss om agila affärer och länkade till Thoughtworks sida där de beskrev hur de lämnat det så organisatoriskt skadande sales commission-tänket. Av en händelse läste jag idag två X vs Y-inlägg hos Fog Creek (folket bakom Trello, Stackoverflow, “Joel on Software” etc) och det första var på just ämnet “Why do we pay sales commision“. De drar slutsatsen att det kommer från två olika synsätt på sina anställda:

Theory X which assumes that people are lazy, want to avoid work and need to be controlled, coerced, punished, and lavishly rewarded in order to perform.

Theory Y which assumes that people are self-motivated, derive satisfaction from their work, are creative, and thrive when given autonomy.

Gillar åsikten om konsekvensen av X:

Theory X demands a lot of managerial control and tends to demotivate, generate hostility, and generally make people into sour pusses.

Git vs Mercurial

På X vs Y-temat har Fog Creekarna klämt ur sig en ny version av Kiln, nu med Harmony där man helt enkelt lyckats – mer eller mindre – transparent få ett repo att agera så att man kan interagera med det från både git och mercurial utan att bry sig om vad det “egentligen” är på andra sidan. (hg bookmarks för git branches, och git refs för hg branches – ja, det finns antagligen fler problemområden de varit lite olika kreativ att kringgå.)

Java vs Java?

En helt annan reflektion – jag såg två helt olika annonser som skulle locka “Java-folk”. Det finns väldigt mycket som snurrar på JVM:en och detta ämne blir dagens avslutande X vs Y, eller är det möjligen bara en vanlig Feature Envy?

Java X: JSP, JSF, CDI (inte DCI), JEE, EJB, JPA, JAX-*, WS-*, Spring-*, …

Java Y: Scala, Clojure, JQuery, Event Sourcing, Concurrency, Distributed Computing/P2P, Scrum, Cloud, NoSql, REST, CQRS, DevOps, AWS, EC2, Android, HTML5, MongoDB, Functional programming, Mule, DCI, BigData/Hadoop, JRuby, Akka, Neo4j, Qi4j …

Snabbkok: Jenkins och Sonar

No Gravatar

Fråga: Hur lång tid tar det att sätta upp Continuous Integration, om man har lite bråttom? (Jag var iväg på en utbildning och den virtuella maskinen som skulle användas för koddelning och CI var trasig på flera sätt.)

  1. Starta virtuell maskin (Amazon i detta fall, valde en m1.large), Ubuntu Server 12.10. Lägg till portar 8080 och 9000 i den Security Group som används.
  2. ssh -i pem ubutu@machine-at-amazon
  3. sudo -s
  4. aptitude install openjdk-7-jdk git
  5. wget http://updates.jenkins-ci.org/latest/jenkins.war; java -jar jenkins.war &
  6. wget http://dist.sonar.codehaus.org/sonar-3.4.1.zip; unzip *.zip; ./sonar-3.4.1/bin/linux-x86-64/sonar.sh start
  7. http://machine-at-amazon:8080/pluginManager – installera “Jenkins GIT plugin”, “Jenkins Sonar plugin”
  8. http://machine-at-amazon:8080/configure
    1. JVM: /usr/lib/jvm/java-7-openjdk-amd64
    2. Sonar Runner: Install from Codehaus
    3. Maven: Install from Apache
    4. Sonar: Add Sonar
  9. Skapa repo på Github, peta in en pom.xml, kopiera URL (HTTP, read only) till repot.
  10. http://machine-at-amazon:8080/view/All/newJob – ange ett namn, Maven 2/3-projekt. Peta in URLen till Github-repot, välj “Sonar” som Post-build step (längst längst ner)***
  11. Tryck på Play-(bygg-)knappen – kontrollera att allt fungerar. Högerklicka på Play-knappen och kopiera länken
  12. Under Settings på Github-repot, gå till Service Hooks, Webhook URLs och klistra in URL:en från Play-knappen på Jenkins
  13. Peta på en fil i projektet lokalt (t ex stega nummer i pom.xml), git commit, git push. Kontrollera att Jenkins startar automatiskt (webhook från Github), att informationen trycks över till Sonar (http://machine-at-amazon:9000/).

Resultat

Komplett miljö igång på under 15 minuter. Användes i fyra dagar – kostnad: ~20 USD.

Ett par brasklappar

*** Gällande aktivering av Sonar som ett post-build step – detta gör ju att feedbackloopen blir långsammare för happy path: dvs om bygget går bra kommer analysen Sonar utför göra att det tar längre tid innan man får “grön flagg”. Om bygget däremot är trasigt är feedbackloopens längd oförändrad.

CloudBees är ju också ett givet alternativ och ännu roligare ihop med Sauce Labs om man t ex behöver Selenium WebDriver.

Både Jenkins och Sonar kan givetvis installeras som vanliga Ubuntu/Debian-paket istället, vilket är mer lämpligt för en permanent lösning (troligen vill man då fundera över databas och konfiguration till Sonar, kanske autentisering osv).

London Style TDD aka Mockist Approach

No Gravatar

GothPy User Group Logotype

Igår huserade Göteborgs Python-användargrupp, GothPy, i Squeeds lokaler och temat för kvällen var “London Style TDD“. En annan term som också använts för samma(?) stil eller variant av TDD är Mockist approach. För dem av som sitter i en statiskt/starkt typad, kompilerad miljö, tycker jag att detta är en teknik man skall förstå och gärna behärska – för varje software craftsman (eller kanske software crafts-hen) så bör detta vara en teknik eller ett verktyg bland flera, det är ett ganska tydligt tillvägagångssätt som används för att mejsla fram mjukvara.

Vi var dryga dussinet, åt många ovanligt små pizzor från Pizza Hut och hade oväntat mycket diskussioner kring mycket: jag hade väntat mig mindre diskussion och mer kodande. Jag tyckte dock det var mycket hög nivå på diskussionerna och många bra sidor och argument lyftes fram, vilket jag tror hela gruppen upplevde eftersom det var få som uttryckte någon önskan att “de ville gå vidare”. Lite oväntat för mig var den klart intressanta och underhållande dissektionen av klassificering av Test Doubles (alltså vad är egentligen dummies, stubs, fakes, spies, mocks?). Utan att gå in på detaljer så tog jag med mig en något rubbad uppfattning om klassisk TDD jämfört med mockbaserad TDD (classic vs stubborn).

Före

Mina två huvudsakliga åsikter som jag hade med mig in i rummet var de följande.

Klassisk = tillståndsbaserad testning, Mockbaserad = beteendebaserad

Jag är av uppfattningen att mängden testkod i ett icke-trivialt system kommer vara lika stor oavsett om man kör klassisk eller mockbaserad stil.

Den “stora” skillnaden är att fake-implementationerna behöver vara tillräckligt tillståndsfulla för att tillåta inspection så att man kan stämma av tillstånd i testens assertions. Dessutom behöver fake-implementationerna uppfylla beteendedelen av de kontrakt som finns. I starkt typade språk (Java) innebär det inte sällan komplicerade implementationer för att kunna nå gränsfall, eller multipla fake-implementationer.

Med mockbaserade test-doubles är tillstånd aldrig intressant och därför har man “bara” en aspekt att bry sig om när man skriver sina test doubles – beteende-/kontraktsaspekten.

Detta blir intressant i fallet när kodbasen rör på sig, gränssnitt och beteenden förändras. Med fake-implementationer behöver man ändra beteenden + sin tillstånds-implementation (som behövs för att kunna göra asserts på sluttillstånd) så att den fortfarande matchar originalets beteende. Med mock-implementationer behöver man “bara” ändra beteenden.

(Borde det inte kunna bli mindre kod rent av med mockning då, om man nu inte behöver lagra state någonstans? Jo, det kan man ju tycka från argumentationen ovan, men det inte var jag har kunnat se. Radmässigt blir möjligen testen i klassisk stil något kortare (man behöver inte välja mockarnas beteenden – fake-implementationernas default-beteenden är nog för de flesta test), men å andra sidan hamnar mer kod i test double-implementationerna.

Självklart underhåller man sin testkod med samma kärlek och omsorg som produktionskod, vilket gör att stubbning av mockobjekt – i större system – hamnar i “MockFactories” där man placerar återanvänd kod. Jag förordar inte stilen där varje mocks beteende skall uttryckas explicit i varje test – “self containing test”. Jag ser inte varför man skall lita mer på kod som ligger i test doubles av typen fake, jämfört med av typen mock. (Det argumentet låter som skrock och rädsla för det okända: “jag litar mer på min kod i min fake, än på mockito-ramverket” t ex.

I Python är behovet av mockramverk mycket litet

Min bild har varit att eftersom alla objekt är “kontraktslösa”, i meningen att allt är löst typat (Duck typing), därför gör det billigt* att skapa test doubles av olika slag, när de behövs, där de behövs och på det sätt de behövs.
I Java-språket med stark typning måste man skicka objekt som uppfyller kontrakt, medan enhetstest sällan har behov av fullvärdiga implementationer och än mindre utnyttjar enheten under test (SUT) hela, eller ens stora delar av, kontrakten från sina collaborators i enskilda test.

Efter

Ett bra argument jag själv helt förbisett (vilket är en klar hint om hur ofta jag jobbar med Python) är att ett bra mockramverk kan ge bra feedback när man utvecklar (“Expected %s invocations of %s, got %s”) vilket ju rättfärdigar nyttan med ett mockningsramverk.

Stubbar man mockobjekt, eller definierar man kontraktuella beteenden hos sina collaborators under viss interaktion (ett tests livscykel) med följande kod:

when(collaboratorA.reserveSeats(10)).thenThrow(new RuntimeException("database is down"));
// or
when(collaboratorA.cancelReservation("1234")).thenReturn(new ReservationCancellation());

En terminologigrej tror jag – jag har inte använt ordet stub utan använt andra formuleringar (förbereder, “berättar för mockobjektet hur det ska bete sig” t ex).

Jämför med BDDMockito-smaken:

given(collaboratorA.reserveSeats(10)).willThrow(...);
// or
given(collaboratorA.cancelReservation("1234")).willReturn(...);

En annan sak jag vill fundera mer på var rekommendationen att inte mocka 3pp, “don’t mock what you don’t own” tror jag var originalfraseringen. Den diskussionen gick lite för fort för mig och ambitionen att “bara lägga på ett till lager” mellan plattformens eller 3pp-ramverkets API:er och det man själv vill använda minimerar möjligen kontaktytan (och framtvingar ett mer explicit ställningstagande i hur beroende man vill vara av 3pp-saken), men flyttar i viss mån bara runt problemet till det nya lagret.

Bevis

Nej, det finns givetvis inte. Kod är i stora delar ett hantverk och då är världen sällan skalan svart eller vit.

Jag började för ett litet tag försöka skriva ett exempel på hur kod kan se ut med mockbaserad teststil, jämte klassisk stil. Dock inte i Python. Jag är en bit från “klar”, exemplet är inte heller perfekt men jag tror intentionerna synas i det som finns där nu. https://github.com/FredrikWendt/tdd-classic-vs-mockist/tree/master/src/test/java/se/wendt/tdd

Slutsats

GothPy är en härlig samling med trevliga och mycket kompetenta människor från ganska varierande och olika bakgrund. Mixen är klockren och ger alltid härligt nyanserade diskussioner med högt i tak och genuin vilja att utforska olika riktningar och synsätt.

Pga denna gruppdynamik kände jag att det fanns mycket mer att fundera kring, testa (koda) och gräva i på detta ämne och jag åkte hem på vagnen utan att känna mig riktigt “färdig”! :-)

* Billig kod är lätt att läsa, förstå (lagom kort och koncis) och underhålla. Den kräver också mycket lite kognitiv belastning hos läsaren.

Devoxx 2012 Antwerpen Belgien

No Gravatar

 

Det var ett tag sen man satt bakom skolbänken, vilket man tydligt känner av efter tre dagars totalt korvstoppande i vad som sägs vara europas andra största bioanläggning, kinepolis.
Årets upplaga bjöd inte på några riktigt stora namn, men det är inte enbart rock-star-status som styr om ett föredrag blir bra.

Onsdag

Keynote

Denna keynote var något av det segaste jag sett i världshistorien, då den till 80% handlade om hur duktiga devoxx-folket var, och hur bra deras konferrens gått senaste åren. Vidare handlade den om devoxx-klan-ledarens Stephan Janssens egna presentations-site parleys.com, vilket iofs är en fantastisk sida. Hans egotrip drog ut på tiden, och Oracles Nandini Ramani fick mindre tid, vilket sedan gav ripple-effects i schemat resten av dagen.
Stephen Chin kom sedan inrullande på motorcykel fram till podiet, och även här syntes inte ett spår av självinsikt.
Oracles presentation var bra, och handlade mestadels om JDK8 och vad som komma skall, inga direkta nyheter. JavaFX verkar ges fokus igen, trots dess minst sagt misslyckade framgång hittils. Nu kommer en 3D-funktion, för att enkelt kunna göra 3D-grafik likt Direct3D på native-klienter. Personligen tror jag inte detta kommer kännas intressant, då WebGL har kommit så långt, men vem vet.

Neall Ford från ThoughtWorks var väl kanske ett av de största namnen, och pratade lite om hur bra det är att vara generös med sin specialistkunskap, och låta den rinna ut i andra delar av organisationen och i sitt övriga liv. Hans stora idol Richard Feynman var på duken i stora delar av hans presentation, då Feynman löste många olika problem, trots att han egentligen var specialist inom ett givet område. Poäng efter poäng handlade om hur man kan flytta en lösning inom en domän in till en annan.
Även om det är omöjligt att jämföra sig med geniet Feynman, kan det vara en bra tankeställare.
Neal har släppt en bok som täcker detta ämne fullt ut som heter: Presentation Patterns (http://presentationpatterns.com/).

Lunch

Efter lunch halkade jag för sent in i en snabbföreläsning, quickie, om Arquillian. Mha Arquillian och ShrinkWrap, byggde han ihop en snyggt och prydligt unit-test där data-sourcen bestod av lite vad man ville. I hans fall var det bl.a. en simpel textfil som beskrev tabeller med data.
Klart smidigt då man vill mocka data, vilket man ju gör typ hela tiden i sina integrationstester.

Nästa quickie var om JMS 2.0 av Nick Wright från c2b2 consulting. Han beskrev i korthet alla smaskiga nyheter i JMS 2.0. Han tyckte det var dags att fräscha upp saker, JMS 1.1  har levt i ca 10 år nu. Bland annat bygger jms nu ut de delar som massor av andra ramverk byggt ovanpå jms, eller parallellt med. Exempelvis finns nu stöd för Java 7 Autoclosable och ett betydligt kompaktare och snyggare API, som absolut ger JMS en konkurrensfördel mot andra aktörer. Vidare finns batch-delivery, delivery-delay och async-delivery. P2P och Pub-Sub måste implementeras av API:et iom 2.0. På det stora hela ser det riktigt modernt och bra ut, blir kul att testa.

Java 8

Oracles Joseph Darcy hade sedan sin presentation om Java 8.
Här fick alla reda på att det var dags att skynda på, då slutdatum för Java 1.6 är bara 4 månader bort. Lustigt nog tycker jag att Java 7 fortfarande är rätt buggig, och G1 har ju precis blivit klar. Jag tror 1.6 kommer få leva lite till, men det är bara min teori.
Joseph försvarade och förklarade hur Oracle tänker när de gör olika typer av teknikval och vilka olika nivåer av kompatiblitet som måste efterlevas.
De har i praktiken tre dimensioner; source-, behavioral- och binary-compatibility.
Intressant nog har de nästan aldrig brutit alla tre dimensionerna tidigare, men det blir det ändring på iom Java 8.
Java 8 kommer bygga in default-methods på interfaces för att möjliggöra interface evolution, dvs vi kommer få traits in i java till sist, inga nyheter här.
Det mest spännande han tog upp var hur de valt att implementera lambda uttryck.
Istället för att utnyttja statisk kompilering, dvs använda antingen syntaktiskt socker och köra med anonyma klasser undertill, eller utnyttja MethodHandles, valde Oracle att göra detta helt i run-time.
Detta görs via den i Java 7 introducerade invoke dynamic (indy), vilket visar sig passa aldeles utmärkt in i deras val att göra tillräckligt stora ingrepp för att lösa problemet, utan att skapa en situation de inte kan komma ur i framtiden.
Det fiffiga med att använda indy är att hur lambdas faktiskt plockas fram, det blir en implementationsdetajl som Oracle kan ändra i framtiden om de har lust, mycket smart.
I Java 8 kommer ditt anrop till en lambda sättas upp första anropet, för att i följande anrop anropas i praktiken med samma prestanda som vilken vanlig metod som helst (han visade lite lovande grafer på detta).

Vertx.io

Nästa presentation var av Tim Fox och handlade om vertx.io.
Detta hade jag redan lite kunskap om tidigare, och det var kul att höra lite mer om detta lovande ramverk. Passade även på att ställa lite väl valda frågor till Tim efteråt.
Vertx.io löser ett ganska vanligt problem som dagens Web 2.0 appar måste lösa, nämligen en smidig kommunikation mellan server tillbaka till klienten, dvs comet, ajax-push osv vad alla namn kan tänkas vara.
Serverkod körs i en enkeltrådad event-loop, likt node.js, all kommunikation görs via en eventbus (hazelcast). Det fiffiga är att även klienter kan anropas på samma eventbus, gömt för app-utvecklaren.
Klienten har tillgång till att inkludera vertx.js-javascriptet, och kan därefter lyssna och ta emot json-data via denna event-bus. Klienten kommer utnyttja websockets om browsern stödjer det, eller använda i nedstigande ordning; SockJS, long-polling, timed-polling.
Jag tror vi kommer se mer av detta ramverk, han är trots allt backad av VMWare.

Mäss-mingel

Efter detta var det mingeldags. JetBrains och Spring fick mestadels av min tid. Jag är numera kändis på JetBrains hemsida, då deras kameraman sprang runt och ville fånga alla olika nationaliteters beröm. Det kommer komma en film där de klipper ihop olika språk som berättar hur fantastisk deras IDEA är.

Spring Cloud Foundry blir en ny konkurrent inom PaaS. Deras setup är mycket lik Amazon EC2, där de i princip har ett appmoln med tomcats, riak, mongodb, mysql, postgre etc.
De har en fin plugin till eclipse och IDEA 12 har en plugin klar i pipan.
En fiffig feature är att om man har en spring-applikation, kan CloudFoundry autokonfigurera och injecta bönor med dataSources rakt in i din app, autoconfig kallade de detta, verkar ganska smidigt.

Torsdag

Keynote

Keynote-dags igen, Googles tur att skryta lite och Tim Brady stod för vocals.
Google har gjort en rad förbättringar och förenklingar i GAE/J som verkade lovande.
Maven och jenkins-integration och möjligheta att auto-deploya ett bygge till GAE/J smidigt direkt efter success i jenkins. Bra för continous deployment.
GAE/J har nu traffic-splitting så att man smidigt kan lyfta över lite trafik åt gången till sin nya version, lite live-testning a’la FaceBook, smidigt.

Tim var näst intill aggressiv i sitt framförande om hur viktigt säkerhet är på nätet, och hur viktigt det är att vi utvecklare tar ansvar för våra kunders data.
Han praktiskt taget skrek ut: ‘ANVÄND HTTPS’, varefter han promotade 2-step-verifcation, och att alla borde använda det. Han promotade sedan OAuth2, och tyckte att hela världen helst skulle logga in via deras SSO-lösning, och inte någon annan. Man skulle absolut inte skriva en egen login, då det var svårt och skulle bli hackat for sure.
Hur läskigt ett internet där alla loggar in via OAuth2 än må vara, verkar det vara en bra och säker lösning, speciellt tillsammans med deras nya Account Chooser, som enkelt gör val av olika OAuth2-leverantörer enkelt.
Efter detta var det en kort intro till Dart. Dart löser samma problem som JetBrains Kotlin och RedHat Ceylon enligt mig, speciellt eftersom det ännu inte ens kör i Chrome, utan endast i en special chrome-variant kallad Dartium. Alla tre språken är snygga, bra, genomtänkta och har lambdas etc, ser ingen klar vinnare här ännu.

Blandad kvalitet

Halkade sedan snett in i vad jag trodde skulle vara en intressant presentation om G1.
Det visade sig vara en Jaromir Hamala, som borde gå en ordentlig engelska-lektion (något år eller två). Tyvärr kunde man inte se denna föreläsning, och det fick istället bli lite BigData och Hadoop.
Costin Leau presenterade en rad olika snygga wrappers för att få in Hadoop-algoritmer i ditt Spring-projekt.
Antingen kan man använda Apache Pig, PigRunner och specifikt PigTemplate. Pig utnyttjar ett speciellt query-språk kallat Pig Latin för att beskriva dina map-reduce steg. PigTemplate trycker in och sätter upp en Hadoop och en Pig i ditt spring-projekt, smidigt.
Alternativ två som demonstrerades var HiveTemplate, som i praktiken gör samma sak, men där utnyttjar man Hive och specifikt HiveSQL som query-språk.
HiveSQL är nästan som SQL, men med lite extra socker för att göra allt distribuerat och map-reducigt. Verkar vara enklare att använda och hans exempel med HiveTemplate såg bra ut.

Costin visade sedan hur man med Spring Batch, Spring Integration och någon av tidigare valda tekniker kunde bygga en snygg graf av händelser för att distribuerat parsa stora mängder loggar.

Flera olika sources i Spring Integration samlade loggar, som skrevs till en fil eller katalog, varpå dessa processades av olika steg m.h.a HiveTemplate, mycket snyggt.

Project butter

Chet Haase och Romain Guy, Google, förklarade sedan varför deras operativsystem Android alltid har haft lagg och vad de kalla ‘jerk’, vid t.ex. scroll av table-views, drag av ikoner över skrivbord etc, och hur de löst problemen nu till sist, iom project ‘Butter’ i android 4.1 Jelly Bean.
Inte en dag försent enligt mig.
En mycket teknisk genomgång av tripple-buffering och annat ganska intressant, men det mest intressanta var deras tooling de presenterade.
De har byggt en fin uppsättning verktyg för att hitta och identifiera exakt vad som sker i Android då visuella problem uppstår.
De hade live-kodning, som så många andra under Devoxx, och visade DumpSys, SysTrace, TraceView och Allocation Tracker.
Allocation Tracker är ett program jag tycker Oracle skall skriva ihop och skicka med i deras JDK, nu måste man ju köpa dessa produkter separat.

Ceylon

I ett extrainsatt nummer presenterade sedan Gavin King RedHat’s senaste språk, Ceylon.
Han var inte alls förberedd, då han blivit inkastad i sista stund.
Det blev en enminuters intro, sedan rakt in i eclipse och deras egengjorda Ceylon-plugin.
Ceylon är starkt typat och kompilerar till javascript och bytecode.
Gavin valde att lägga hela tiden till att visa hur man bygger tupler för hand, och hur man kan med det kraftfulla typsystemet skapa listor som består av olika typer, t.ex. List<String, Integer, Float>, och hur man sedan kan skapa en operator som kan addera en sådan lista med en annan sådan lista.
Ganska trevliga saker, enkelt att förstå, och framförallt extremt bra plugin som meddelar typfel direkt. Ceylon gör otroligt intelligenta statiska kompileringar, han har gjort ett bra jobb, skall bli intressant att testa.

Java posse

Efter detta blev det en stunds avslappning med en öl i hand, då JavaPosse 2012 spelades in live i en av salongerna. Kanske en aning utdraget, men roligt trots allt.

Tillbaka in i allvaret igen fortsatte Daniel Spiewak att förklara hur han gått till väga när han skapade sin kompilator till sitt språk han utvecklar på Precog.
Energisk i sitt framförande vandrade han fram och tillbaka barfota i sina slitna jeans framme vid podiet, denna mannen brann för att koda avancerade saker, mycket inspirerande.
Han hade tagit fram en konstruktion som han kallade Atom. Namnet kom av total idébrist enligt Daniel.
En Atom var en klass, med en metod, som utifrån sett var helt pure, men internt var mutable.
Ganska intressant lösning på problemet att populera noddata i ett spanning tree, när kompilatorn vandrar nedåt. Ärvda attribut är sällan ett problem men när man har ett språk som hanterar lambdas som skall evalueras i ett senare skede är det problematiskt. Den enklaste lösningen är att vandra genom hela trädet (grafen) igen då ett lambda påträffas. En annan är att skapa denna typen av lazy-init-data-holders.
Klart intressant, men man var helt slutkörd efter att ha dels lärt sig scala, och dels tagit upp grafteori igen, allt på mindre än 40 minuter.

Spring 3.2

Josh Long presenterade sedan nyheter med Spring 3.2.
Han ägnade stor tid åt att repetera vad 3.1 innehöll men kom till sist till 3.2.
Bland nyheterna kan man hitta async processing i Servlet 3.0 som vi väntat på i ca tre år. Josh Long är för övrigt ganska kort.
De har byggt ut stödet för mockning vid test, och lagt in en rad nya mock-objekt och byggt på de gamla en del.
Det finns nu generic factory methods, som möjliggör att mockning blir korrekt i samtliga tester, om man utnyttjar Mockito eller EasyMock.
En annan trevlig nyhet är det utbyggda stödet för att testa hela web-appen smidigt med deras junit-runner.
Allt är stiligt konfigurerat, med convention over configuration, och flertalet olika mock-mekanismer går automatiskt igång för att testa dina controllers på ett smidigt sätt.
Även lite förbättrade konfigureringsmöjligheter vid test-setup har tillkommit via ContextInitializers.
Tester kan nu även ha hierarkier av spring contexts, vilket inte fanns tidigare. Detta möjliggör att ha samma konfig som i produktion om man så önskar.
Sammantaget kan man säga att VMWare har lagt mesta energin på att förbättra för testbarheten i spring, vilket vi givetvis välkomnar.

Bästa presentationen

Eftersom inspelningsutrustningen inte fungerat, körde sedan Chet Haase sin presentation igen, som handlade om framtidens Processer.
Detta var bland det roligaste jag sett i form av presentation, av en smart och karismatisk programmerare.

En hand-jagad version av hans presentation finns här: https://www.youtube.com/watch?v=b8H90xr61FY

Med lite belgisk choklad under armen lämnar vi Antwerpen med dess fantastiska katedral bakom oss för den här gången.

Sammanfattning

Lite allmäna reflektioner om konferrensen gäller hygien och könsfördelning, dock inte sammanhängande.
Det var 4% kvinnligt deltagande i år, 4%. Det är lite, och det påpekades av flertalet arrangörer och talare. Det lite ironiska är att Stephan i sin keynote frågade publiken om hur många som ‘doesn’t have an erection now?’ när han visade sin nya funktion i parleys.com. Det kan ju vara plumpa skämt som detta som skrämmer bort våra kvinnliga programmerarvänner kanske.
Vidare till hygien då. Belgare och fransmän och deras cigaretter. Det röks, och inte lite, och inte långt utanför där alla sitter.
Faktum är att det röks precis utanför byggnaden, och all denna rök letar sig med enkelhet in via ventilation och öppna dörrar.
Vidrigt.

De hade styrt upp WiFi riktigt ordentligt, och trots 3400 deltagare fungerade WiFi…ibland. Inte första dagen, men andra fungerade det utmärkt…ish.

Alla fick ett åkband som innehöll ett NFC/RFID-chip. Detta kunde man utnyttja till att rösta Upp eller Ned efter man sett ett bidrag. Vidare kunde samma chip utnyttjas till diverse mat-utdelningar, öl-utskänkning, omröstningar och annat i olika montrar, genom att de scannade med en tablet eller telefon. Detta fungerade imponerande väl och gjorde allt betydligt mer strömlinjeformat än tidigare konferrenser.

Talarlistan var inte speciellt stjärnspeckad men konferrensen bjöd ändå på mycket matnyttigt och intressant, och sammanfattningsvis var den bra arrangerad i sin helhet.

 

Programmeringsglädje del 2: Scala

No Gravatar

För några dagar sedan bloggade jag om Programmeringsglädje. Idag var det dags att fortsätta min högst spontana programmeringsresa genom att skriva om patienslösarprogrammet i Scala.

Scala har de senaste åren blivit allt mer uppmärksammat även utanför entusiastkretsarna. Redan 2010 var IDG igång och propagerade för Scala som ett alternativ till Java. Själv har jag sneglat några gånger på språket – tilltalats av interoperabiliteten med Javas API:er, möjligheten att i viss mån blanda imperativ programmering med funktionell och inte minst möjligheten att på allvar lära mig ett funktionellt programmeringsspråk. Visst, jag testade på lite Haskell och Prolog på universitetet för tiotalet år sedan, men på en alldeles för ytlig nivå för att något skulle fastna.

Mitt problem med Scala har hittills varit relativt syntaxrelaterat. Till vardags programmerar jag vanlig gammal hederlig Java och relativt mycket Javascript. Jag har f.ö. ett något lätt perverst förhållande till Javascript. Jag känner en blandning av kärlek och lätt äckel när jag snickrar ihop webblösningar baserade på jQuery, DWR/CometD, XHTML, REST-tjänster, JSON etc. Underbar turn-around tid, funktionella inslag och framförallt en helt annan känsla av kontroll över applikationen jämfört med när jag använder GWT/JSF etcetera. Men också otaliga småbuggar pga. uselt IDE-stöd (har inte IntelliJ), genomkrångliga scope-konstruktioner och riskerna med dynamisk typning. Jag har också kodat en hel del C/C++, PHP, ASP och C# i mina dar. Och i stort sett ingenting ser likadant ut i Scala. Mycket möjligt att Ruby, Groovy, Python, Clojure etc. är mer likt Scala än de språk jag behärskar sedan tidigare.

Jag har tittat på Scalakod och ofta fastnat redan vid den första metoddeklarationen:

// SCALA
def myMethod(paramName1 : String, paramName2 : MyClass) : MyOtherClass = {
    // method body
}
// JAVA
MyOtherClass myMethod(String paramName1, MyClass paramName2) {
    // Method body
}

Nu, efter att jag tagit mig förbi den initiala inlärningströskeln tycker jag inte Scalas metoddeklaration ser särskilt konstig ut. Jag har börjat förstå skillnaden mellan scalas “class” och “object”, där class är vad man tror det är, medan object närmast är att betrakta som en singleton utan att behöva trixa till en GoF-singleton.

För att komma igång ordentligt laddade jag hem Scalatron och följde till punkt och pricka deras 10-stegs tutorial som på ett utmärkt vis introducerade språkets olika konstruktioner. Vissa saker är dock svåra att begripa enligt mitt tycke, men det ger sig ju mer man experimenterar och försöker. Nackdelen med Scalatron-tutorialen var kanske att man efteråt hade en robot som i stort sett inte kunde göra någonting. Och steget efter tutorialen var att kolla på deras referensimplementation av en robot vilken var långt mer komplex än det man gått genom fram tills dess. Tanken är förstås att man skall fortsätta själv efter tutorial, men jag kände att det var ett ganska stort steg att ta då man inte skriver någon kod själv under tutorialen.

Istället beslutade jag mig för att skriva om den tidigare nämnda patienslösaren i Scala. För det ändamålet installerade jag Scala IDE for Eclipse.

Problem nummer 1: Hur gör jag en klass med motsvarande main()-metod? Nätsökning på “Scala Hello World” gav mest träffar som blandade in SBT, medan jag ville hålla mig till Eclipse. Lösningen var som tur var mycket enkel då det fanns en färdig template i Scala IDE.

object MainProgram extends App {
}

Här råkade jag omgående på en av Scalas “egenheter”. Jag ville skriva mig en main()-metod, men det ville inte Scala IDE vara med om. Det visade sig att i ovanstående konstruktion, så skriver man sin “main-metod” direkt i sitt objects body. Och att man skriver sina metoder direkt in i samma body:

object MainProgram extends App {
    println("Starting the main() method")
    myMethod("Erik")

    def myMethod(name : String) = {
        println(name)
    }
}

Något förvirrande för en Javautvecklare. Jag bestämde mig för att snickra ihop samma Card och Stack-klasser som jag använde i Javaversionen av min patienslösare. I Scala kan man skriva klasserna i vilken .scala fil som helst, men av gammal vana skapade jag ändå filerna Card.scala och Stack.scala.

Låt oss ta en titt på Stack.scala, klassen som representerar ett kort eller (n) kort som ligger staplade på varandra.

package com.squeed.scalapatiens
import scala.collection.mutable.ArrayBuffer

case class Stack(cards: ArrayBuffer[Card]) {
  def getTopCard(): Card = {
    this.cards.apply(0)
  }

  def clear() = {
    this.cards.clear();
  }

  def addCard(card: Card) = {
    this.cards.insert(0, card)
  }

  def addStack(stack: Stack) = {
    this.cards.insertAll(0, stack.cards)
  }

}

case class? Nyckelordet “case” ger oss bl.a. autogenererade equals och toString metoder samt någon form av “getter” för “cards”. Tar man bort “case” ger ovanstående kod kompileringsfel på
this.cards.insertAll(0, stack.cards)

då stack.cards inte verkar vara synlig när den refereras utanför this-kontexten. Med följande ändringar fungerar det:
class Stack(cards: ArrayBuffer[Card]) {
  def getTopCard(): Card = {
    this.cards.apply(0)
  }

  def clear() = {
    this.cards.clear();
  }

  def addCard(card: Card) = {
    this.cards.insert(0, card)
  }

  def addStack(stack: Stack) = {
    this.cards.insertAll(0, stack.getCards)
  }
  
  def getCards() : ArrayBuffer[Card] = {
    cards
  }
}

Dvs. jag har skrivit en enkel getCards()-metod. Notera Scalas något luriga syntax för att returnera ett värde. Scala är väldigt bra på att skala bort onödigt syntaktiskt socker. getCards-metoden kan faktiskt skrivas så här:
def getCards = cards

Nu ska vi se: Först så kan vi ta bort () efter metodnamnet. Då vår metod inte tar något argument så tvingar inte Scala oss heller att skriva ut parenteserna. Och genom “type inference” (vet ej hur man översätter det till svenska) kan Scalas kompilator titta på vad “cards” är för typ och då behöver man inte explicit skriva ut returtypen. Och slutligen kan vi också skippa {} då en ensam kodrad i metoden inte kräver {} kring sig.

Själv är jag något kluven. Å ena sidan är def getCards = cards oerhört lättläst. Men å andra sidan är jag van som javaprogrammerare att alltid ha samma syntax för alla metoder. I Scalas fall är det långt ifrån alltid man slipper undan typdeklarationer och jag tycker på ett sätt att det är enkelt och tydligt när typerna skrivs ut och när man alltid har {} kring sin metodkropp. Kanske en smaksak?

Om vi återgår till huvudprogrammet, kreativt nog kallat MainProgram.scala, så finns det ett par andra egenheter att lägga märke till.

#1: Rekursion
Då jag översatte javaprogrammet ganska rakt av så blev det förstås en del rekursion även här. Men Scala tillåter inte metoder utan returtyp att kallas rekursivt! Se här för en mer detaljerad förklaring. Men kort och gott så är lösningen mycket enkel då Scala har ett “specialobjekt” som heter Unit som helt enkelt är en typrepresentation av void. Så här blir en sådan metod deklarerad:

def tryToStack(stacks: ArrayBuffer[Stack]): Unit = {
    // Method body
    if(someCondition)
        tryToStack(stacks)
}

#2: For-loopen
Syntaxen för for-loopen är också lite annorlunda än vad jag är van vid. Och då jag vill avbryta och börja om försöken att flytta korthögar varje gång en hög flyttats vill jag hemskt gärna kunna använda break för att någorlunda elegant ta mig ur min for-loop. Det visar sig att Scala inte riktigt gillar break och continue. Det finns några workarounds såsom att explicit köra return inne i loopen (return anses tydligen fult i scala-sammanhang). Jag hittade en konstruktion där man får deklarera for loopen inuti ett breakable {} block:

breakable {
      for (i <- (stacks.size - 1) to 1 by -1) {

        val card1 = stacks.apply(i).getTopCard()
        if (i - 1 >= 0) {
          val adjacentCard = stacks.apply(i - 1).getTopCard()
          if (card1.canStackCard(adjacentCard)) {
            performStacking(i, i - 1, stacks)
            tryToStack(stacks)
            break
          }
        }
        if (i - 3 >= 0) {
          val threeSlotsAwayCard = stacks.apply(i - 3).getTopCard()
          if (card1.canStackCard(threeSlotsAwayCard)) {
            performStacking(i, i - 3, stacks)
            tryToStack(stacks)
            break
          }
        }
      }
    }

Om ni tittade på Javakoden i förra blogginlägget ser koden helt klart lik ut. Det är ungefär samma if-satser, samma typ av sjunkande for-loop, rekursivt anrop när man utfört en “stackning” osv. For-loopen är som sagt lite annorlunda:
for (i <- (stacks.size - 1) to 1 by -1)

Ingångsvärdet i sätts till stacks.size-1. Värdet skall ändras till det blir 1. Och ändringen skall vara -1 för varje iteration. Ganska klart, även om jag inte riktigt läst in mig på varför man skriver “i <- initalValue".

Avslutningsvis kan vi kika lite på koden som gör iordning kortleken och blandar den:

def setupDeck(deck: ArrayBuffer[Card]): Array[Card] = {
    for (i <- 0 until 13) {
      deck + (new Card(i, "SPADES"))
      deck + (new Card(i, "HEARTS"))
      deck + (new Card(i, "CLUBS"))
      deck + (new Card(i, "DIAMONDS"))
    }

    return Shuffler.shuffle(deck.toList.toArray).toArray
  }

object Shuffler {
  def shuffle[T](array: Array[T]): Array[T] = {
    val rnd = new java.util.Random
    for (n <- Iterator.range(array.length - 1, 0, -1)) {
      val k = rnd.nextInt(n + 1)
      val t = array(k); array(k) = array(n); array(n) = t
    }
    return array
  }
}

Kortleken representeras alltså som en ArrayBuffer[Card], vilket på ett ungefär verkar motsvara en ArrayList i Java. En enkel for-sats skapar de 52 korten och sedan blandar jag den mha en singleton "object" jag kallar Shuffler. Först försökte jag använda Collections.shuffle(..) men misslyckades. Själva shuffle-metoden plockade jag från StackOverflow, men den borde vara ganska lättläst. Det intressanta är dels hur enkelt det är att använda en klass (java.util.Random) från Javas API:er, dels hur anropet till shuffle blir. Det blev flera konverteringssteg (toList.toArray) för att få datatyperna att samarbeta.

Prestanda
Jag var förstås tvungen att jämföra prestandan på Scalaimplementationen och Javaimplementationen. Och det var rejäl skillnad. För att få patiensen att gå ut krävs fortfarande runt 1350 försök när man kör tills patiensen gått ut 1000 gånger.

Java: ca 35 sekunder
Scala: ca 85 sekunder

Som sagt, en mycket stor differens till Scalas nackdel. Detta var som sagt mitt första Scalaprogram någonsin och jag har knappast lyckats särskilt bra med det hela heller. Finns nog mängder med förbättringar för att göra det hela effektivare, mer funktionellt och definitivt mer lättläst. Källkoden finns på min publika GitHub-sida: https://github.com/eriklupander/ScalaPatiens/

Hoppas någon orkat läsa ända hit och att jag fått fram lite om mina första erfarenheter av detta spännande programmeringsspråk!

Programmeringsglädje!

No Gravatar

Så här i semestertider kanske man borde koppla bort allt vad datorer, mjukvara och projekt heter och ägna sig åt annat. Men regniga dagar kommer ändå skaparlusten fram i mig. Då jag inte har särskilt mycket Ernst i mig vänder jag mig istället till den digitala domänen.

Härom kvällen lade svägerskan några olika patienser och frågade om jag kunde någon. Den enda jag kom att tänka på lärde jag mig redan som barn av min mor – livslång patiensentusiast – och det speciella med den patiensen är att den skall vara i stort sett omöjlig att få att gå ut. Jag vet inte vad den heter, men ska förklara de enkla reglerna nedan. Jag och svägerskan lade den några gånger var – och mycket riktigt var vi inte ens nära att få den att gå ut en endaste gång.

Patiensens regler är som sagt enkla. 52 kort och målet är att få samtliga kort i en enda hög. Man tar ett kort och lägger ut. Sedan drar man nästa kort och lägger till höger om det första., Om det är av samma färg eller siffra som kortet direkt till vänster, eller tre kort till vänster, får man lägga kortet på den högen. Om det inte matchar drar man ett nytt och lägger till höger osv. När man flyttar en hög så får man givetvis försöka matcha vidare eftersom den flyttade  högens översta kort kanske matchar något i sin nya position.

I brist på soligt väder gav jag det hela en bit tankeverksamhet och bestämde mig för att ta reda på hur många försök man egentligen behöver för att få patiensen att gå ut med hjälp av lite Javakodning på semesterdatorn med den konstiga tangentbordslayouten. Lyckligtvis fanns Eclipse i gammal version redan installerad, så förstärkt av en stor kopp kaffe och ett par timmar barnfritt skred jag till verket.

Uppgiften i sig är ganska banal för en utvecklare över nybörjarnivå och jag tror faktiskt uppgiften hade passat alldeles utmärkt som en introduktionsuppgift i en programmeringskurs. Men låt mig ändå redogöra för lösningen och utfallet i stora drag. WordPress är inte riktigt med mig så mina generics verkar försvinna och någon vettig kod-vy får jag heller inte till, så jag beklagar den bristande läsbarheten.

1. Representera kortleken och spelbrädet

Kortleken är en ArrayList<Card> där Card har en färgenum och en siffra 1-13. 52 kort skapas i en for-sats och blandas med Collections.shuffle(..)

for(int a = 0; a &lt; 13; a++) {
    deck.add(new Card(CardType.CLUBS, a));
    deck.add(new Card(CardType.HEARTS, a));
    deck.add(new Card(CardType.SPADES, a));
    deck.add(new Card(CardType.DIAMONDS, a));
}

// Shuffle
Collections.shuffle(deck);

Spelbrädet är en Listdär Stack enbart innehåller en List. Man hade således kunnat skriva den som List>, vad som är tydligast kan säkert någon clean-code guru tala om för mig.

List<Card> deck = new ArrayList<Card>();
List<Stack> stacks = new ArrayList<Stack>();

2. Dra kort

Plocka översta kortet i leken genom deck.remove(0); och placera kortet i en ny CardStack.

Card card = deck.remove(0);
// Put card in new stack at the bottom
stacks.add(new Stack(card));

3. Försök lägga korten i högar enligt spelreglerna
Jag tittar efter varje förändring av spelbrädet (nytt kort eller flyttad hög) efter möjlighet att stapla högarna.

private void tryToStackCards() {
  for(int a = stacks.size() - 1; a &gt; 0; a--){

    Card card1 = stacks.get(a).getCards().get(0);
    if(a-1 &gt;= 0) {
      Card adjacent = stacks.get(a-1).getCards().get(0);
      if(canStack(card1, adjacent)) {
         // stack and recurse
         stack(a, a-1);
         tryToStackCards();
         break;
      }
   }
  if(a-3 &gt;= 0) {
     Card twoAway = stacks.get(a-3).getCards().get(0);
     if(canStack(card1, twoAway)) {
        // stack and recurse
        stack(a, a-3);
        tryToStackCards();
        break;
     }
   }
}

4. Själva stackningen – att flytta över en hög till en annan är enkla operationer mha Collection API:t.
Stack from = stacks.get(fromIndex);
Stack to = stacks.get(toIndex);

to.getCards().addAll(0, from.getCards());
from.getCards().clear();
stacks.remove(fromIndex);

5. Kör programmet tills dess att patiensen “går ut”
private void startRound() {
// Setup deck
setupDeck();

// Start drawing cards
drawCardFromDeck();

if(stacks.size() &gt; 1) {
  tries++;
  resetGame();
  startRound();
} else {
  System.out.println("Succeeded after " + ++tries + " tries!");
  System.out.println("Average num of stacks: " + ((float) totalStacks/tries));
}

}

Utfall
Syftet med denna lilla semesterprogrammering var att ta reda på hur många gånger man egentligen behöver lägga denna oidentifierade patiens innan den går ut. Innan jag redovisar utfallet skall dock nämnas att mitt program inte har någon form av artificiell intelligens som försöker avgöra vilket stackningsalternativ som är det bästa i de fall man har valet att flytta antingen från flera ställen eller till flera ställen. (1 eller 3 steg åt vänster och/eller att man genom att flytta A->B får möjlighet att i nästa steg kanske flytta B->C). Kan mycket väl bli en utmaning att ta sig an en annan regnig semesterdag.

Utfallet då? Jag körde programmet 1000 ggr i rad och i genomsnitt behövde datorn lägga patiensen 1353 gånger med minimiantalet 8 gånger och maximiantalet 8865 gånger. Jag antar att utfallet är tämligen normalfördelat. Att lägga ca 1.353 miljoner omgångar tar ungefär 45 sekunder på den här datorn (MacBook Pro Core2Duo 2.4ghz).

Framtida utmaningar
* Förbättra prestandan genom flertrådning, något actor-ramverk etc.
* Förbättra prestandan genom effektivare kod, renare datastrukturer, bättre algoritmer.
* Förbättra utfallet genom mer avancerad “AI” som likt en schackdator testar olika utfall innan den väljer vilken kortflytt som skall utföras när flera alternativ finns. Man kan också låta programmet känna till vilka kort som redan ligger på bordet och fatta beslut utifrån sannolikhet att en viss färg/siffra är på nästa kort.
* Implementera i annat programmeringsspråk. Kanske en ursäkt för att lära mig Scala, Groovy eller varför inte JRuby?

Vettig Maven JSLint-plugin

Äntligen en ordentlig maven-plugin som kör jslint på javascript-koden. Version 2.0.2 har bättre dokumentation än 2.0.1. Här är en snippet från en pom:

<project ...>
	<build>
		<plugins>
			<plugin>
				<groupid>com.googlecode.jslint4java</groupid>
				<artifactid>jslint4java-maven-plugin</artifactid>
				<version>2.0.2</version>
				<executions>
					<execution>
						<id>lint</id>
						<phase>compile</phase>
						<goals>
							<goal>lint</goal>
						</goals>
						<configuration>
							<sourcefolders>
								<sourcefolder>${basedir}/src/main/webapp/js</sourcefolder>
							</sourcefolders>
							<failonerror>true</failonerror>
							<options>
								<browser>true</browser>
								<forin>true</forin>
								<fragment>true</fragment>
								<undef>true</undef>
								<nomen>true</nomen>
								<plusplus>true</plusplus>
								<predef>Ext,jQuery</predef>
								<sloppy>true</sloppy>
								<vars>true</vars>
								<white>true</white>
							</options>
						</configuration>
					</execution>
				</executions>
			</plugin>
</plugins></build></project>

Spaning från konferensen SDC.

No Gravatar

Agila team är mer inne än någonsin och mer norm än undantag. Men det pratas inte så mycket SCRUM utan det är en mix av SCRUM, Lean och XP där det gäller att hitta sin egen implementation för framgång. När det gäller att få delarna i företagen utanför utvecklingsteamen med på det agila spåret så börjar det hända en hel del, men många har fortfarande samma problem som tidigare. Maktstrukturer rubbas och ansvarsområden förändras när team och projekt skall ta egna beslut. Men det är en livsnödvändig förändring för de företag som vill hänga med i den allt snabbare utvecklings- och förändringstakt som är på marknaden.
En talare sa att dagens stora komplexa system kräver att man jobbar i grupp för att kunna lösa uppgifter tillsammans, en hjärna räcker helt enkelt inte till för att tänka ut ett helt system, och det är de som kan utnyttja dynamiken och kreativiteten i teamen bäst som är vinnarna.

Nyckelbegrepp att fördjupa sig i:

  • Små agila team som är sjävvalda, självgående och väljer sina egna uppgifter ur backloggen.
  • Satsa på hög utvecklingstakt, så kommer teamen själva att vilja effektivisera arbetet.
  • Tänk alltid; Användaren i fokus! Jobba med UX.
    Ingen har råd att bygga något användarna inte vill ha!
  • Test ofta
  • Satsa på kontinuerliga releaser hela vägen till slutanvändarna, då följer automatisk en satsning på “Continuous Integration” och kvalité.

 

3:e Maj – Åter en kväll att minnas!

No Gravatar

Nu är det åter dags för en inspirationsafton av stora mått. Javaforum och nForum samkör sina teknikseminarier 3:e maj i Folkets hus vid Järntorget i Göteborg.
Denna gång behandlas allt från modularitet på JVM’en och case-studies ur veckligheten till scenario-testning och säkerhetsaspekter på .net-plattformen.
Agenda och anmälan hittar ni på respektive site. Som vanligt räcker det att anmäla sig på ett ställe för att sedan kunna växla mellan spåren efter behag.

Kvällen arrangeras av Squeed och sponsras av Experis.

Dela gärna med dig av denna flyer.

Vi ses där!