TDD, BDD og automatisert testing; viktige begreper for den moderne utvikler. Jeg deler mine erfaringer på dette i denne kategorien.

Litt F#-kode for å måle kjøretid

Tuesday, April 9th, 2013
5 kommentarer

I forrige blogpost (Telle ord-forekomster med F#) brukte jeg en funksjon som het benchmark til å måle hvor lang tid det tok å kjøre programet mitt. Nå vil jeg “avsløre” hva som skjuler seg bak denne funksjonen.

Da jeg eksperimenterte med ulike algoritmer i F# for å prosessere større datamengder hadde jeg behov for å enkelt kunne måle og sammenligne kjøretider på ulike funksjoner. Dette behovet har jeg hatt før, og i 2010 førte det til at jeg lagde biblioteket QuickBencher (tilgjengelig på CodePlex). Bibloteket var kraftig inspirert av tilsvarende funksjonalitet som er å finne i Ruby.

Jeg kunne selvfølgelig ha brukt QuickBencher fra F# også. Men det jeg i stedet gjorde var å røske ut den sentrale innmaten fra bibloteket, og kode det om til et par hendige F#-funksjoner. Her følger en gjennomgang av koden.

Noen typer som holder på data

Først importerer jeg noen navnerom jeg kommer til å bruke mye:

open System
open System.Diagnostics

Deretter lager jeg min første datatype, som er en record jeg kaller ProcessorTime. Formålet med denne er å holde på informasjon om forbrukt tid. Disse tidene kommer fra en prosess (System.Diagnostics.Process), og jeg inkluderer en statisk metode på typen min for å opprette en ProcessorTime-instans basert på en prosess. Dette ser slik ut:

type ProcessorTimes =
    { User   : TimeSpan
      System : TimeSpan
      Total  : TimeSpan }
    static member get (p : Process) =
        { User   = p.UserProcessorTime
          System = p.PrivilegedProcessorTime
          Total  = p.TotalProcessorTime }

Videre trenger jeg en liten hjelpefunksjon får å få tak i forskjellen mellom to Timespan (i sekunder):

let getDuration start stop =
    let duration : TimeSpan = stop - start
    duration.TotalMilliseconds / 1000.0

Og da kan jeg lage min andre datatype, som heter ProcessorDuration. Denne holder tidsinformasjon fra en prosess for to ulike tidspunkt – et starttidspunkt og et stopptidspunkt. Den har også et felt for å holde på forløpt klokketid. ProcessorDuration representerer rett og slett en benchmark-måling.

I tillegg har jeg overstyrt typens ToString-metode til å gi en printbar representasjon av målingen. Tekstformateringen som er brukt er den samme som du finner både i QuickBencher og i Ruby sin benchmark-funksjonalitet.

type ProcessorDuration =
    { Start     : ProcessorTimes
      Stop      : ProcessorTimes
      ClockTime : int64 }
    override x.ToString () =
        sprintf "%f user, %f system, %f total (%f)"
            (getDuration x.Start.User x.Stop.User)
            (getDuration x.Start.System x.Stop.System)
            (getDuration x.Start.Total x.Stop.Total)
            (float x.ClockTime / 1000.0)

Start og stopp

Funksjonen startBenchmark gjør det du antar at den gjør – den begynner å måle tiden. Men den gjør en ting til: Den oppretter og returnerer en lexical closure (altså en funksjon som har referanser til frie variabler fra det scopet hvor den ble opprettet). Denne closuren skal så brukes til å stoppe målingen, og den vil returnere resultatet i form av en ProcessorDuration-instans.

Slik er funksjonen implementert:

let startBenchmark ()=
    let proc = Process.GetCurrentProcess()
    let startTimes = ProcessorTimes.get proc
    let stopwatch = Stopwatch.StartNew()
    (fun () ->
        stopwatch.Stop()
        { Start = startTimes
          Stop = ProcessorTimes.get proc
          ClockTime = stopwatch.ElapsedMilliseconds })

Benchmark

Vi har kommet frem til selve rosinen – benchmark-funksjonen som måler forbrukt tid for en vilkårlig funksjon. Ta først en titt på koden, som ser slik ut:

let benchmark label thunk callback =
    let getResult = startBenchmark()
    let temp = thunk()
    callback label (getResult().ToString())
    temp

benchmark har altså tre parametre. Nummer én er en label som skal identifisere akkurat denne målingen. Typen er ikke gitt – det er typisk tenkt at det skal være en streng, men det kan være hva som helst.

Parameter nummer to heter thunk. I funksjonell programmering er thunk enkelt fortalt betegnelsen på en parameterløs closure. Dette er rett og slett funksjonen som skal måles.

Det tredje parameteret er en callback som vil bli evaluert etter at målingen er foretatt. Argumentene til callbacken vil være labelen og resultatet av målingen. Dermed er det altså signaturen til callbacken som avgjør hvilken type label’en må være.

Etter å ha startet en måling, eksekvert thunken, og gitt resultatet til callbacken, vil benchmark returnere resulatet fra thunken. Dette vil gjøre det enklere å snike inn benchmarking uten for mye endringer i eksisterende kode.

Benchmark til konsollet

I 99% av tilfellene ønsker du sansynligvis bare å printe ut målingen til konsollvinduet, så jeg lagde en enklere versjon av benchmark for akkurat det. Den er selvsagt enkel, men jeg tar den med for kompletthets skyld:

let benchmarkPrintn label thunk =
    benchmark label thunk (printfn "%s: %s")

En interessant demonstrasjon

For å vise hvordan benchmark virker vil jeg løse Project Euler-oppgaven nummer 1, en gjenganger på denne bloggen. Jeg skal finne summer av alle multipler av 3 og 5. Men denne gangen inkluderer jeg multipler helt opp til og med 1’000’000 – for det tar såpass lang tid at det blir noe å måle.

Jeg designer fire ulike løsninger på problemet, og er veldig spent på hva målingene vil vise.

let test1 ()=
    let mutable result = 0L
    for i in 1L..1000000L do
        if i % 3L = 0L || i % 5L = 0L
        then result <- result + i
    printfn "\nResult by for loop and mutation: %s"
            (string result)

let test2 ()=
    {1L..1000000L}
    |> Seq.filter (fun x -> x % 3L = 0L || x % 5L = 0L)
    |> Seq.fold (fun acc x -> acc + x) 0L
    |> string
    |> printfn "\nResult by range, filter and fold: %s"  

let test3 ()=
    let result = ref 0L
    {1L..1000000L}
    |> Seq.iter (fun x -> if x % 3L = 0L || x % 5L = 0L
                          then result := !result + x)
    printfn "\nResult by iter and reference cell: %s"
            (string !result)

let test4 ()=
    let rec inner acc n =
        match n with
        | 1000001L -> acc
        | n when n % 3L = 0L -> inner (acc + n) (n + 1L)
        | n when n % 5L = 0L -> inner (acc + n) (n + 1L)
        | _                  -> inner  acc      (n + 1L)
    printfn "\nResult by recursion: %s"
            (string (inner 0L 1L))

Når jeg så benchmarker de fire testene…

do
    benchmarkPrintn "For and mutation" test1
    benchmarkPrintn "Range, filter, fold" test2
    benchmarkPrintn "Iter and referece cell" test3
    benchmarkPrintn "Recursion" test4

…får jeg følgende output:

Result by for loop and mutation: 233334166668
For and mutation: 0.249602 user, 0.000000 system, 0.249602 total (0.250000)

Result by range, filter and fold: 233334166668
Range, filter, fold: 0.405603 user, 0.000000 system, 0.405603 total (0.417000)

Result by iter and reference cell: 233334166668
Iter and referece cell: 0.265202 user, 0.000000 system, 0.265202 total (0.260000)

Result by recursion: 233334166668
Recursion: 0.031200 user, 0.000000 system, 0.031200 total (0.033000)

At sekvensoperasjoner som filter og fold (test2) gir svakere kjøretid enn for-løkker og mutering av data (test1 og test3) er skuffende men ikke overraskende i et språk som F#. Men resultatet fra test4 veltet meg nesten av stolen! Rundt 85% reduksjon i kjøretid sammenlignet med forløkke-varianten ved bruk av rekursjon var totalt uventet, og viser at F# er blodtrimmet og optimalisert for algoritmer som bruker halerekursjon.

Til slutt

Hvis ytelse er viktig for deg så er det viktig å faktisk måle hvor lang tid ting tar. Å anta er skummelt, og å endre/optimalisere kode uten å holde track på kjøretid er ikke å anbefale. Da trenger du verktøy som det jeg har laget og vist frem i denne bloggposten, som er enkle å bruke, og som gjør én ting og gjør den godt.

FORTHolito

Thursday, January 31st, 2013
2 kommentarer

Jammen klarte jeg å levere et programmeringsspråk til PLT Games i januar også (se desember-bidraget Ropy her). README-filen sier det meste, så her er den gjengitt:


FORTHolito is a programming language – specifically an (incomplete) implementation of the FORTH programming language – made just for fun and for the learning experience. The interpreter is implemented in Ruby, and it has an interactive REPL.

But it’s more! It’s my entry into the PLT Games: Testing the Waters competition. I made it possible to write extensions to FORTHolito, and then wrote a test extension.

When you run FORTHolito with this extension all words you define have to include one or more tests – working examples of how the word operates.

Here’s an example of a word definition in FORTHolito:

: multiple-of? ( x n -- flag ) mod 0= ;

If I use the test extension however I would write it like this:

: multiple-of? ( x n -- flag ) mod 0=
  <EXAMPLE> given: 3 6  expect: true
  <EXAMPLE> given: 3 4  expect: false
;

FORTHolito (or FORTH basically) makes for some nice tests when all you do is operate on a data stack. The givens are just stack operations – “given 3 and 6 are pushed onto the stack”. What’s left on the stack when the word has been called is the expected part.

Try it out

FORTHolito is a small and limited language. But if you’re just curious about FORTH, or want to see how you can implement a simple language in Ruby, you should clone it and fire up the REPL.

FORTHolito have some basic options. Run it with –help to see how it’s used:

C:\dev\fortholito [master]> .\fortholito --help
Usage:

  FORTHolito [options] [files]

Options:
  --eval <code>         Evaluate code. Everything after --eval
                        is considered FORTHolito code. Code is
                        evaluated after files.
  --extend <extension>  Load an extension by name. Extensions
                        are loaded before files.
  --help                Display this help and exit.

When you enter the REPL this is what you’ll see:

C:\dev\fortholito [master]> .\fortholito
Welcome to FORTHolito version 0.2 by @tormaroe
Type 'help' and hit [ENTER] to get started..
ok

If you now type help you’ll get a list of useful words to try out:

ok  help
 SOME USEFULL WORDS YOU SHOULD TRY:
  words       \ display all words in the vocabulary
  describe    \ pop word (string) and show details
  .           \ pop and print the top item from the stack
  .s          \ display stack once
  showstack   \ toggle display of stack between commands
  stacktrace  \ display all modifications to stack for debugging
  help:stack  \ cheat sheet of stack overations
  bye         \ exit REPL
ok

To get a feel for how much (or how little) of FORTH I’ve implemented in FORTHolito, type words:

ok  words
 *                  +                  -                  -rot
 .                  .s                 /                  0<
 0<=                0<>                0=                 0>
 0>=                1+                 1-                 2*
 2/                 2drop              2dup               <
 <=                 <>                 =                  >
 >=                 abs                and                between
 bye                clear              cr                 depth
 describe           drop               dup                false
 help               help:stack         max                min
 mod                negate             nip                noop
 or                 over               push-range         rand
 rot                showstack          space              stacktrace
 swap               true               tuck               within
 words
ok

In addition to the words there are some constructs like if and iteration using begin..until and begin..while..repeat. You should have a look in the examples directory as well as the spec.rb file to learn more.

As a treat to Ruby developers I’ve also made it possible to drop into Ruby inside strings.

ok  "#{ 2 + 2 }"
ok  "2 + 2 equals "
ok  . . cr
2 + 2 equals 4
ok

Final thoughs

FORTHolito hasn’t become all I hoped it would be. I had big plans for the test extension when I started – it would become this great, interactive development environment for producing perfectly tested and safe code. I still believe my vision is possible, and possibly even useful, but it would take more time.

The drive and passion I had in the beginning has dissipated for now. But it was great fun while it lasted – and maybe some day I’ll pick up where I left off. Either way I did manage to enter into the PLT Games again.


FORTHolito finner du på Github.

Cruickshanks presenterer Arquillian [Luke 18, 2012]

Tuesday, December 18th, 2012
1 kommentar

Kevin Cruickshanks (@kevincruick) er en kompis fra studietiden ved Universitetet i Bergen. Det er ikke så ofte vi ser hverandre lengre, men fra tid til annen stiller han opp på NNUG-møter, og da er det alltid hyggelig.

Arquillian er en spennende plattform for automatiserte integrasjonstester for Java-utviklere. I denne tutorialen git Kevin deg en grundig innføring.

IMG_3472

Hvem er du?
Utflytta stording som no er partner og systemutvikler i Machina AS i Bergen

Hva er jobben din?
Systemutvikler på Java og .NET plattform

Hva kan du?
Har mykje erfaring med utvikling av Java server og klienter

Hva liker du best med yrket ditt?
Det er utfordringer kvar einaste dag!


Integrasjonstesting med Arquillian

Noe jeg alltid har ønsket meg er et rammeverk for integrasjonstesting av Enterprise Java system (J2EE) i en kontainer. Arquillian er et slikt verktøy som gir deg mulighet for å teste applikasjonene dine rett i en kontainer, som for eksempel Jboss, uten bruk av mockups eller andre omveier for å få koden din testet.

Enhetstester bør du også lage, men det trenger du ikke Arquillian til. Dersom du vil se hvordan applikasjonen din oppfører seg i forskjellige kontainere er Arquillian nettopp noe for deg. Denne artikkelen viser deg hvordan du kan bygge en liten webapplikasjon som kan testes med Arquillian.

Stikkord

Her et utvalg av teknologiene som er i bruk i artikkelen:

  • Arquillian – Dette er selve testrammeverket som utfører testene dine.
  • Shrinkwrap – Blir benyttet til å opprette ferdig applikasjonspakker (EAR, WAR, JAR) som til slutt kjøres i applikasjonsserveren
  • JBoss – I eksempelapplikasjonen som er laget til denne artikkelen har jeg fokusert på Jboss versjon 7 som applikasjonsserver. Arquillian støtter en mengde andre kontainere, som Tomcat, Glassfish, Jetty, Weblogic og WebSphere i tillegg til flere eldre versjoner av Jboss. For full oversikt over hvilke kontainere som støttes kan du se her.
  • Netbeans – Jeg benytter meg av Netbeans i mitt daglige virke og eksempelapplikasjonen er kodet ved hjelp av den. Prosjektet er satt opp ved hjelp av Maven og kan åpnes og kjøres i andre programmer som IntelliJ og Eclipse.
  • Java Server Faces (JSF) v2.0 er benyttet som webrammeverk.
  • Java – Applikasjonen er bygd på Java versjon 7 og Java EE versjon 6

Oppsett av Arquillian

For å kunne kjøre Arquillian må vi første importere det. Min webapplikasjon er bygd opp rundt Maven og dermed kan jeg legge til alle eksterne bibliotekreferenser rett i POM-filen min og la Maven laste ned alle nødvendige filer. Arquillian krever en god del tilleggsbiblioteker og kan ta litt tid å laste ned første gang.

Under kan du se et utdrag av POM-filen som omhandler oppsettet for Arquillian. I dependencyManagement angir du hvilken versjon av Arquillian du vil benytte. Alle avhengigheter (dependencies) som ikke har angitt versjon benytter versjonen i dependencyManagement.

JSFUnit biblioteket er et tilleggsbibliotek som kan integreres med Arquillian. JSFUnit er et testrammeverk for JSF webapplikasjoner. Jeg benytter dette rammeverket til testing av websidene i eksempelapplikasjonen. Eksempel på bruk av JSFUnit er omtalt lenger ned i artikkelen.

Profiler
De to profilene som er angitt nederst i utsnittet under, viser hvilke kontainere som kan benyttes til testene. Profilen jbossas-7-remote blir brukt til testing mot eksterne instanser, mens jbossas-7-managed brukes til lokale instanser, dvs, at Arquillian starter og stopper instansen for deg for hver testklasse. Det er desverre ikke mulig å stare en instans av jboss i managed-modus og la den fungere på tvers av flere testklasser. Men dette vil nok bli støttet i senere versjoner av Arquillian.

...
    <properties>
        ...
        <jboss-version>7.1.1.Final</jboss-version>
        ...
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.jboss.arquillian</groupId>
                <artifactId>arquillian-bom</artifactId>
                <version>1.0.1.Final</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.jboss.arquillian.junit</groupId>
            <artifactId>arquillian-junit-container</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.shrinkwrap.resolver</groupId>
            <artifactId>shrinkwrap-resolver-impl-maven</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.shrinkwrap.descriptors</groupId>
            <artifactId>shrinkwrap-descriptors-impl-javaee</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.arquillian.extension</groupId>
            <artifactId>arquillian-persistence-impl</artifactId>
            <version>1.0.0.Alpha5</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.jsfunit</groupId>
            <artifactId>jsfunit-arquillian</artifactId>
            <version>2.0.0.Beta3-SNAPSHOT</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.spec</groupId>
            <artifactId>jboss-javaee-6.0</artifactId>
            <version>3.0.1.Final</version>
            <scope>provided</scope>
            <type>pom</type>
        </dependency>
    </dependencies>
    <profiles>
        <profile>
            <id>jbossas-7-remote</id>
            <dependencies>
                <dependency>
                    <groupId>org.jboss.as</groupId>
                    <artifactId>jboss-as-arquillian-container-remote</artifactId>
                    <version>${jboss-version}</version>
                    <scope>provided</scope>
                </dependency>
            </dependencies>
        </profile>
        <profile>
            <id>jbossas-7-managed</id>
            <dependencies>
                <dependency>
                    <groupId>org.jboss.as</groupId>
                    <artifactId>jboss-as-arquillian-container-managed</artifactId>
                    <version>${jboss-version}</version>
                    <scope>provided</scope>
                </dependency>
            </dependencies>
        </profile>
    </profiles>
...

En webapplikasjon

Før vi går i gang med testing må vi få laget til en testbar webapplikasjon. Applikasjonen er bygd på en standard tre-lagsarkitektur med klient-, server- og databaselag. Til database kan du benytte en hvilken som helst SQLkompatibel database. Jeg har til testingen benyttet meg av den innebygde H2-databasen som følger med Jboss7 installasjonen.

JPA er brukt som rammeverk for kommunikasjon med databasen. For å kunne benytte oss av JPA trenger vi en Persistence Unit. Selve filen kan du legge i katalogen src/main/resources/META-INF/. Som tilbyder benytter jeg Hibernate som følger med Jboss. Du trenger ikke inkludere Hibernate i POM-filen din.

Du kan konfigurere og bruke forskjellige i testene dine, men jeg har kun laget til en som er konfigurert til å koble seg opp mot H2-databasen i Jboss.

<?xml version="1.0" encoding="UTF-8"?>
<persistence
    xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0">
    <persistence-unit name="budgettest">
        <jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <class>no.ksoft.budget.ds.BudgetPost</class>
        <class>no.ksoft.budget.ds.Budget</class>
        <class>no.ksoft.budget.ds.BudgetPostTag</class>
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
            <property name="hibernate.hbm2ddl.auto" value="create-drop" />
            <property name="hibernate.show_sql" value="true" />
        </properties>
    </persistence-unit>
</persistence>

Entitetsbønner (entity bean)

Jeg har laget til tre entiter (disse legges til i persistence.xml) til denne applikasjonen som er et forsøk på å lage et lite budsjettprogram:

  • Budget
  • BudgetPost
  • BudgetPostTag

Under ser du hvordan entiteten Budget ser ut. Alle entitetene arver klassen Any som er en MappedSuperClass. Alle entiteter som arver Any vil få tilordnet alle egenskapene til Any, uten at den er å betrakte som en egen entitet i systemet.

Klassen Any:

@MappedSuperclass
@EqualsAndHashCode
public class Any {
    @SuppressWarnings("unused")
    @Id
    @Column(name = "id", unique = true, nullable = false)
    @GeneratedValue
    @Getter
    @Setter
    protected int id;
}

Entiteten Budget:

@Entity
@Table(name = "budget", schema = "public")
@NamedQueries(value = {@NamedQuery(name = "Budget.findAll", query = "SELECT object(b) FROM Budget as b")})
public class Budget extends Any implements java.io.Serializable {

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "start_date", nullable = false, length = 29)
    private Date startDate;
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "end_date", nullable = false, length = 29)
    private Date endDate;
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "budget")
    private Set budgetPosts = new HashSet(0);

    public Budget() {
    }

    public Budget(int id, Date startDate, Date endDate) {
        this.id = id;
        this.startDate = startDate;
        this.endDate = endDate;
    }

    public Budget(int id, Date startDate, Date endDate, Set budgetPosts) {
        this.id = id;
        this.startDate = startDate;
        this.endDate = endDate;
        this.budgetPosts = budgetPosts;
    }
    ...
}

For hver entitet er det opprettet en egen sesjonsbønne (session bean):

  • BudgetServiceImpl
  • BudgetPostServiceImpl
  • BudgetPostTagServiceImpl

Hver bønne implementerer følgende standardmetoder for uthenting og lagring av data:

@Remote
public interface Service<T> {
    T findById(int id);
    T makePersistent(T entity);
    List<T> findAll();
}

En ferdigimplementert bønne ser slik ut:

@Stateless
public class BudgetServiceImpl implements BudgetService {
    @PersistenceContext
    private EntityManager em;

    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    @Override
    public Budget findById(int id) {
        return em.find(Budget.class, id);
    }

    @Override
    public List findAll() {
        List l = em.createNamedQuery("Budget.findAll").getResultList();
        if (l == null) {
            return new ArrayList<>();
        }
        return (List) l;
    }

    @Override
    public Budget makePersistent(Budget entity) {
        em.persist(entity);
        return entity;
    }
}

Klientlaget

To enkle websider er lagt til. Sidene er kodet i xhtml og begge benytter seg av hver sin Managed bean for uthenting av relevant informasjon fra databasen. Index.xhtml viser et tall for antall budsjetter i databasen og inneholder en knapp som lenker til et vilkårlig budsjett. Den andre websiden, view-budget.xhtml, viser informasjon om et Budsjett som blir hentet ut ved hjelp av en ID paramater.

index.xhtml

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core">
    <head><title>Budgets</title></head>
    <body>
        <f:view>
            <h:form id="viewBudgetForm">
                <h1><h:outputText value="Budgets"/></h1>
                <h:outputText id="totaltAmountOfBudgets" value="#{indexBean.allBudgets.size()}"/>
                <h:commandLink id="viewAbudget" value="View budget" action="view-budget.xhtml">
                    <f:param name="budgetId" value="#{indexBean.allBudgets.get(0).id}" />
                </h:commandLink>
            </h:form>
        </f:view>
    </body>
</html>
                                                        

view-budget.xhtml

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core">
    <head>
        <title>Budgets</title>
    </head>
    <body>
        <f:view>
            <h:form id="viewSingleBudgetForm">
                <h:outputText value="Current budget ID: "/>
                <h:outputText id="currentBudgetId" value="#{viewBudgetBean.currentBudget.id}"/><br />
                <h:outputText value="Current budget start date"/>
                <h:outputText id="currentBudgetStartDate" value="#{viewBudgetBean.currentBudget.startDate}"/><br />
                <h:outputText value="Current budget end date"/>
                <h:outputText id="currentBudgetEndDate" value="#{viewBudgetBean.currentBudget.startDate}"/><br />
            </h:form>
        </f:view>
    </body>
</html>
                                                        

Det bakomforliggende bønnene (managed beans) benytter CDI for å legge inn referanser til sesjonsbønnene. Bønnen IndexBean benyttes i index.html, mens bønnen ViewBudgetBean benyttes i view-budget.xhtml. Ingen fantasifull navngivning av bønner dette, men navnet mer enn antyder hvor den aktuelle bønnen er i bruk. Du kan selvsagt bruke bønner på tvers av flere sider, men du kan fort ende opp i situasjoner som er vanskelig å håndtere etterhvert som applikasjonen vokser.

Bønnen IndexBean

@Named("indexBean")
@RequestScoped
public class IndexBean {

    @Inject
    BudgetService budgetService;

    public List getAllBudgets() {
        return budgetService.findAll();
    }
}

Bønnen ViewBudgetBean

@Named("viewBudgetBean")
@RequestScoped
public class ViewBudgetBean {

    @Inject
    BudgetService budgetService;
    @Getter
    @Setter
    @Inject
    @RequestParam("budgetId")
    Instance<Integer> budgetId;

    public Budget getCurrentBudget() {
        return budgetService.findById(getBudgetId().get().intValue());
    }
}

Testing av serverlaget med Arquillian

Til testing av serverlaget trenger vi først litt mer konfigurering av Arquillian. Konfigurasjonsfilen Arquillian.xml legges i <sti til prosjektet ditt>src/test/resources. Min fil ser slik ut:

<arquillian xmlns="http://jboss.org/schema/arquillian" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jboss.org/schema/arquillian http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
    <engine>
        <property name="deploymentExportPath">target/</property>
    </engine>
    <defaultProtocol type="Servlet 3.0" />
    <container qualifier="jbossas-7-managed" default="false">
        <configuration>
            <property name="jbossHome">X:\sti\til\jboss\jboss-as-7.1.1.Final</property>
        </configuration>
        <protocol type="jmx-as7">
            <property name="javaVmArguments">-Xmx512m -XX:MaxPermSize=128m -Xverify:none -XX:+UseFastAccessorMethods</property>
            <property name="executionType">REMOTE</property>
        </protocol>
    </container>
    <container qualifier="jbossas-7-remote" default="true">
        <configuration>
            <property name="managementAddress">192.168.1.195</property>
            <property name="managementPort">8080</property>
        </configuration>
        <protocol type="jmx-as7">
        </protocol>
    </container>
</arquillian>

Filen inneholder konfigurasjonselementer for begge profilene jeg oppgav i POM-filen (jbossas-7-managed og jbossas-7-remote). For remote-profilen oppgir jeg ip-adressen og portnummeret til den kjørende jbossinstansen, mens for managed, oppgir jeg den fulle stien til jbossinstallasjonen. Feilinformasjonen fra Arquillian er ikke alltid like intuitiv, men den gir nøyakig informasjon dersom deler av konfigurasjonen din er feil.

Du kan lage flere spektakulære tester med Arquillian, men jeg viser kun det grunnleggende her. Testen under viser det som trengs for å kjøre en enkelt testklasse med Arquillian.

Du trenger følgende tre ting for å lage en testklasse:

      1: En @RunWith(Arquillian.class) annotasjon for å angi at du vil la Arquillian behandle testklassen.
      2: En metode som er annotert med @Deployment som returnerer et ShrinkWrap arkiv.
      3: Minst en metode annotert med @Test som angir at det er en testmetode.

I testen under har jeg også lagt til klasseannotasjonen @Transactional med TransactionMode.ROLLBACK som parameter. Denne angir at databasen min blir rullet tilbake etter hver testmetode slik at jeg kan være sikker på at databasen er tom for hver test som kjøres. Denne annoteringen kan og ligge på metodenivå. Andre paramtetere som er tilgengelig for denne annotasjonen er COMMIT og DISABLE.

@UsingDataSet(“navn på dataset.xml fil”) annotasjonen gir deg mulighet for å populere databasen din med verdier før du kjører testene. Bruken av denne annotasjonen er smør på flesk i midt eksempel da den i praksis gir samme resultat som TransactionMode.ROLLBACK siden jeg benytter meg av et tomt dataset før hver test kjøres. Du kan lese mer om bruk av dataset i testene her.

@Deployment-metoden lager et ShrinkWrap arkiv som blir innstallert i kontainerinstansen under kjøring. I eksempelet er det en WAR-fil som blir opprettet. Alle klasser som er nødvendige for å kunne kjøre testen må legges inn i arkivet. Du trenger ikke ta med alle klassene i prosjektet ditt dersom de ikke er knyttet opp mot noen av klassene du trenger for å gjennomføre testene testene.

Det er også mulig å knytte opp hele prosjekt (F.eks.: et Java bibliotekprosjekt) inn i arkivet. Det vil med andre ord kunne være lurt å lage et eget Arquillian testprosjekt som kobler inn det nødvendige web-, ejb- og javabibliotekene du trenger og deretter utfører testene. Eneste forskjellen vil være et mer håndterbart arkiv som du kan benytte i testene dine.

@RunWith(Arquillian.class)
@UsingDataSet("empty.xml")
@Transactional(TransactionMode.ROLLBACK)
public class BudgetServiceTest {

    private static final Logger LOGGER = Logger.getLogger(BudgetServiceTest.class.getName());
    @Inject
    private BudgetService budgetService;

    public BudgetServiceTest() {
    }

    @Deployment
    public static Archive<?< createTestableDeployment() {
        final WebArchive war = ShrinkWrap.create(WebArchive.class, "service.war");
        war.addClass(Any.class);
        war.addClass(Budget.class);
        war.addClass(BudgetPost.class);
        war.addClass(BudgetPostTag.class);
        war.addClass(Service.class);
        war.addClass(BudgetService.class);
        war.addClass(BudgetServiceImpl.class);
        war.addAsResource("META-INF/persistence.xml", "META-INF/persistence.xml");
        war.addAsWebInfResource(EmptyAsset.INSTANCE, ArchivePaths.create("beans.xml"));
        LOGGER.info(war.toString(Formatters.VERBOSE));
        return war;
    }

    @Test
    public void testFindAllBudgets() {
        List<Budget< all = budgetService.findAll();
        assertEquals(true, all.isEmpty());
        insertABudget();
        all = budgetService.findAll();
        assertEquals(1, all.size());
    }

    @Test
    public void testFindBudgetById() {
        final Budget expected = insertABudget();
        assertTrue(expected.getId() < 0);

        List<Budget< all = budgetService.findAll();
        assertEquals(1, all.size());

        Budget result = budgetService.findById(expected.getId());
        assertEquals(expected, result);
    }

    @Test
    public void testMakePersistent() {
        Budget expected = insertABudget();
        Budget test = budgetService.findById(expected.getId());
        assertEquals(expected, test);
    }

    private Budget insertABudget() {
        final Budget b = new Budget();
        b.setEndDate(new Date(System.currentTimeMillis()));
        b.setStartDate(new Date(System.currentTimeMillis()));
        return budgetService.makePersistent(b);
    }
}

JSFUnit

Til slutt vil jeg vise hvordan du kan få testet ut alle dei tre lagene i applikasjonen. Under vises et eksempel på en testklasse for å teste ut manipulering og navigering mellom to websider. Denne testing er mulig ved hjelp av JSFUnit som gir deg tilgang til en del nyttige metoder for å manipulere webapplikasjonen under kjøring i en kontainer.

I metoden testIndexBean(JSFServerSession server, JSFClientSession client) blir server og client injisert inn i metoden. JSFServerSession gir deg tilgang til deler av JSF API`et, mens JSFClientSession er en wrapper-klasse for HtmlUnit som imiterer nettleser interaksjon med en JSF-applikasjon.

@RunWith(Arquillian.class)
@InitialPage("/index.xhtml")
public class WebTest {

    private static final Logger LOGGER = Logger.getLogger(WebTest.class.getName());
    @Inject
    private BudgetService budgetService;

    @Deployment(testable = true)
    public static Archive>?< createTestableDeployment() {
        MavenDependencyResolver resolver = DependencyResolvers.use(
                MavenDependencyResolver.class).loadMetadataFromPom("pom.xml");

        final WebArchive war = ShrinkWrap.create(WebArchive.class, "budget.war");
        war.addClass(Any.class);
        war.addClass(Budget.class);
        war.addClass(BudgetPostTag.class);
        war.addClass(BudgetPost.class);
        war.addClass(Service.class);
        war.addClass(BudgetService.class);
        war.addClass(BudgetPostService.class);
        war.addClass(BudgetPostTagService.class);
        war.addClass(BudgetServiceImpl.class);
        war.addClass(BudgetPostServiceImpl.class);
        war.addClass(BudgetPostTagServiceImpl.class);
        war.addClass(IndexBean.class);
        war.addClass(ViewBudgetBean.class);
        war.addClass(FieldParameterNames.class);
        war.addAsWebResource(new File("src/main/webapp", "index.xhtml"));
        war.addAsWebResource(new File("src/main/webapp", "view-budget.xhtml"));
        war.setWebXML(new File("src/main/webapp/WEB-INF/web.xml"));
        war.addAsWebInfResource(new File("src/main/webapp/WEB-INF/faces-config.xml"), "faces-config.xml");
        war.addAsWebInfResource(new File("src/main/webapp/WEB-INF/jboss-web.xml"), "jboss-web.xml");
        war.addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml");
        war.addAsResource("META-INF/persistence.xml", "META-INF/persistence.xml");
        war.addAsLibraries(resolver.artifact("org.jboss.solder:solder-impl").resolveAsFiles());

        LOGGER.info(war.toString(Formatters.VERBOSE));
        return war;
    }

    @Before
    public void addSomeValues() {
        Budget b = new Budget();
        b.setEndDate(new Date(System.currentTimeMillis()));
        b.setStartDate(new Date(System.currentTimeMillis()));
        budgetService.makePersistent(b);
    }

    @Test
    @InitialPage("/index.xhtml")
    public void testIndexBean(JSFServerSession server, JSFClientSession client) throws IOException {
        //Sjekk at vi er på indexsiden
        assertEquals("/index.xhtml", server.getCurrentViewID()); 

        //Sjekk at IndexBean får hentet ut det ene budsjettet vi har opprettet
        assertEquals(1, server.getManagedBeanValue("#{indexBean.allBudgets.size()}")); 

        UIComponent totalAmountOfBudgetsOutput = server.findComponent("totaltAmountOfBudgets");

        //Sjekk at antall budsjett vises korrekt på indexsiden
        assertEquals("1", totalAmountOfBudgetsOutput.getAttributes().get("value").toString()); 

        //Klikk på vis budsjettknappen
        client.click("viewBudgetForm:viewAbudget"); 

        //Sjekk at vi har skiftet side til view-budget.xhtml
        assertTrue(server.getCurrentViewID().contains("view-budget"));
    }
}

Kildekode

Kildekoden til denne artikkelen er tilgjengelig som en github repository.

Mer informasjon

Henning har arvet kode [Luke 15, 2012]

Saturday, December 15th, 2012
Ingen kommentarer

Jeg har hengt endel rundt på de programmeringsrelaterte forumene på diskusjon.no, og en av dem jeg har lagt merke til der er brukeren GeirGrusom. Han har vært medlem siden 2003, og har i skrivende stund over 12 tusen innlegg. Det verste er at det meste jeg har sett ham skrive faktisk er nyttig og relevant for diskusjonen.

Så jeg sporet opp denne GeirGrusom – eller Henning Moe, som han faktisk viste seg å hete. Og han ville gjerne hjelpe til med årets julekalender.

henning

Hvem er du?
Hobbyutvikler blitt proff for å slippe å jobbe som truckfører.

Hva er jobben din?
Jobber som IT konsulent hos OKB AS.

Hva kan du?
Jeg jobber primært med C# og .NET i backend og er sjeleglad så lenge jeg slipper å jobbe med UI.

Hva liker du best ved yrket ditt?
Det beste med yrket er egentlig å jobbe med noe som faktisk påvirker tusenvis av mennesker hver eneste dag. Jeg får bare håpe påvirkningen er positiv.


Legacy

De fleste prosjekter har det: En del av kodebasen som ingen vil røre!

Legacy kode og unit-testing av dette er det skrevet bøker om, fordi det er en skitten jobb, og svært få vil faktisk ha noe med det å gjøre. Som regel er dette teknisk gjeld som aldri blir tilbakebetalt fordi man ikke har tid, eller prosjektledelsen ikke har ressurser til å takle det. Koden benytter ofte gamle konsepter som enten er sett på som utdatert, eller som aldri burde vært der i utgangspunktet. Det kan være fra en svunnen æra hvor man benyttet et merkelig programmeringsspråk, eller man trodde at string alltid var den beste måten å behandle data med (såkalt “string typing”). Det kan være svære klasser som benytter seg utelukkende av singletons (som er sett på som et relativt dårlig konsept, men likefullt nyttig i sammenheng som abstract factory), eller fullstendig logikk med kun statiske funksjoner (som er det verste en kan røre i forbindelse med unit-testing) fordi forfatteren kanskje ikke forstod objektorientering når det ble skrevet.

Jeg skal bare fortelle litt om mine erfaringer med legacy kode.

Legacykode kan være skrevet i et gammelt språk, eller i en dialekt som ikke lenger brukes (eksempelvis C# 1.0 selv om prosjektet ellers er skrevet i C# 4.0). For C# sin del betyr dette veldig mye kode med svakt typede komponenter slik som ArrayList eller HashSet mot List<T> og Dictionary<TKey, TValue> og som for C# sin del både betyr nedsatt ytelse, dårlig lesbarhet og vanskelig å vedlikeholde. Ofte blir det bare liggende fordi koden ikke er dekket av tester, og fordi utvikleren da løper en risiko for å ødelegge produksjonskode dersom dette ikke blir dekket av tester før noe som helst annet blir gjort, noe det ikke alltid vil bli satt av tid til.

Det er noen ting å være obs på når man skriver unit-tester: det hender at det er feil i legacy-kode som ikke blir fanget opp. Faktisk er det slik at en må ta en avgjørelse om man skal rette disse, for det hender sluttbrukere har gjort seg vandt til disse problemene, eller at andre deler av systemet har jobbet seg rundt disse.

Det jeg har merket er ganske vanlig er at behandling av lokalisering ofte er gjort feil. Bytt tallsystem eller valuta på maskinen og koden feiler. Dette fordi det er blitt hardkodet inn datoformater eller antar at desimalskilletegn alltid er punktum eller komma. Flere steder har jeg sett at standardinnstilling er satt til USA (av en eller annen grunn), og da er det snikende feil med datoer da USA bruker kanskje verdens mest dustete måte å skrive datoer på.

Sikker trådbehandling er også en tilbakevendende feil med eldre komponenter som ikke blir lagt merke til. Mange utviklere vurderer ikke dette når programmet blir utviklet, og kanskje det som regel ikke er et problem i produksjon, eller oppleves som et fantomfeil som er umulig å gjenskape i test. Det var også noe som er blitt satt mer fokus på i de siste årene da det er langt mer vanlig å ha mange prosessorer på systemene, og det er da logisk å ha et bedre rammeverk for behandling av flere tråder. Bytt ut suspekte komponenter med trygge versjoner. Selv om komponenten fungerer uten å være trygg, så er det liten skade i å være sikker på at den er trygg. Se etter dette i singletons og statiske funksjoner. Singletons som inneholder en state er bortimot garantert å kunne føre til slike feil.

Som en anekdote…

… kan jeg fortelle at jeg nylig fikk en feil i produksjon som jeg forårsaket. Problemet var at jeg rettet en feil i koden som var validering av fakturalinjer. Dette ble gjort med et regulærtuttrykk som sjekket et merkelig karaktersett, men dessverre sjekket om dette gjentok seg 0 eller flere ganger uten noen anchors, eller at den engang sjekket om en match gikk over hele uttrykket. Jeg rettet dette slik at valideringen faktisk gjorde noe, og det kom ganske fort klager fra kunder om at de ikke lenger klarte å fylle inn i fakturaer dersom visse tegn befant seg i dem.

Dette med validering av slikt er noe jeg er litt imot. Aldri utfør valideringer som ikke har noe funksjonelt grunnlag. Jeg kan garantere at det dukker opp sluttbrukere som bruker ´ eller ` istedet for ‘ eller folk med merkelige tegn i e-posten sin. Ikke utfør valdering for noe som ikke er en teknisk begrensning. Ved siden av å kaste bort tiden din, så vil det føre til flere support-tilfeller. Her har jeg opplevd at de som skriver spesifikasjonene legger inn begrensninger i validering utelukkende fordi de har sett de begrensningene andre steder. I mitt tilfelle var dette at spesifikasjonen hevdet at passord ikke kunne ha mellomrom og kunne ikke være lenger enn 10 tegn. Ved å teste, så viste det seg at dette ikke var tekniske begrensninger.

Det er også ofte en god del overutviklede komponenter i legacy kode. Dette er ofte klasser som en utvikler har skrevet, og beholdt som et lite privatprosjekt der en klasse blir utvidet og utvidet til å takle alt mulig som strengt tatt ikke burde vært nødvendig, kanskje over flere år. Problemet med klassene er ikke at de ikke fungerer, men at dersom kravene til komponenten endres, så er den kanskje så omfattende at det enkleste og billigste er å bare forkaste hele komponenten til fordel for en ny, og enklere, noe som gjør at en komponent som det er brukt mange timer på, bare må kastes fordi ingen lenger har kontroll på hva den gjør, eller hvordan den fungerer. Det jeg har opplevd av dette er en utrolig omfattende querystring klasse, ORM system, og programflytmodeller.

For meg er kode fin når den er simpel, løst bundet, sterkt typet og enkel å bruke.Legacy kode kan ofte ikke tilhøre noen av disse kategoriene, noe som er frustrerende dersom en skal skrive unit-tester av eksisterende kode. En må bare starte fra bunnen og skrive en integrasjonstest, og dermed starte med refaktorering i andre enden.

Tredjepartskomponenter er det første som må løsrives. Disse er verdiløse i en unit-test, og i beste fall suspekte i en integrasjonstest, og kan skape store problemer. Finn et mock-objekt rammeverk. Det er ikke spesielt gunstig om kode inneholder en hel masse override klasser som ikke gjør noe som helst, og enda verre om de inneholder mock-klasser som gjør noe spesielt. Ikke skriv simulatorer, eller lignende. Det er en bjørnetjeneste som noen andre kanskje må rydde opp i senere. Dersom det er enkelt å lage mock-objekter, så lag en som utelukkende returnerer forventet verdi. Dersom denne verdien blir generert av noe annet enn konstanter, så er det i utgangspunktet utestet kode i en unit-test, og det har ikke noe der å gjøre.

Som en konklusjon…

… så er det viktig å bare ha i bakhodet når man utvikler at kode må leses og kanskje videreutvikles av andre. Det er ikke farlig å ha mange klasser, bare navn, forståelse og kontekst er enkelt å skjønne. En kan ikke ha for mange interfaces (innenfor rimelighetens grenser), og for enkelt å unit-teste gjør det ingenting om kjernefunksjonalitet i en klasse faktisk er bestemt av interfaces gjennom inversion of control. Eksempelvis kan private funksjoner erstattes av et interface og en privatindre klasse som implementerer dette.

En vil alltid oppleve at kode man har skrevet vil bli legacy, men det er fint å sørge for å minimere problemene knyttet til dette. Få også andre til å gå over koden din, og det burde være noen som ikke er redd for å påpeke slike problemer. Hvis koden din alltid blir godkjent av den andre utvikleren, burde du kanskje finne en annen som er litt mer kritisk til det du gjør.

Karianne lytter til testene [Luke 12, 2012]

Wednesday, December 12th, 2012
1 kommentar

Karianne Berg (@karianneberg) er et relativt kjent navn i det norske utviklermiljøet. Hun er en engasjert utvikler og en dyktig, morsom og uformell foreleser. I dagens adventsluke gir Karianne oss et innblikk i en av hennes åpenbaringer…

karianne

Hvem er du?
Ikke-bergenser i Bergen som liker å bygge ting.

Hva er jobben din?
Seniorutvikler i Vimond Media Solutions og arrangør av Booster-konferansen

Hva kan du?
Kodekvalitet 

Hva liker du best?
Det faglige engasjementet! Det er ingen tannleger som møtes på kveldstid i grupper over en øl for å diskutere den siste rotfyllingstypen, for eksempel.


Jeg har holdt på med testdrevet utvikling i en del år nå. Det er mulig at jeg er over gjennomsnittlig tjukk i huet, men en av de tingene jeg har hatt aller vanskeligst med å forstå er uttrykket «å lytte til testene». Det var et uttrykk som kom opp i mange bøker, artikler og presentasjoner om TDD og enhetstesting, men jeg var aldri i stand til å fatte hva det faktisk betydde. Ikke før i fjor.

For drøyt et år siden bestemte den fantastiske Kjersti B. Berg og jeg oss for at vi skulle holde en (i to deler) workshop om refaktorering på Smidigkonferansen. Vi ønsket å lage en hands-on workshop med en eksisterende kodebase som hadde full testdekning, men et dårlig design. Slik kunne vi ha en trygg “sandkasse” med kode som folk kunne bryne seg på å gradvis forbedre, med testene som et sikkerhetsnett. Mens vi laget denne kodebasen støtte vi imidlertid på et problem: Dersom vi innførte en feil, feilet testene slik vi ville de skulle gjøre. Imidlertid var det ikke bare en test som knakk, det var opptil halvparten av dem. Dette strider jo imot hva vi ønsket: En test skal teste bare en ting, og den skal knekke kun dersom du ødelegger funksjonaliteten for denne ene tingen.

Vi forsøkte alt vi kunne komme på for å bøte på dette: Vi delte opp tester. Vi slo sammen tester. Vi innførte flere mocks. Vi fjernet all mocking. Vi innførte hjemmesnekrede test doubles. Vi trakk ut initialisering av testdata i egne objekter. Vi reduserte antall assertions. Ingenting hjalp. Vi følte oss som totale enhetstestings-newbies. Men konferanser bytter dessverre ikke dato selv om de som skal holde workshops ikke er fornøyde med innholdet sitt, så vi måtte til slutt gi opp å forbedre testene. Det sluttet imidlertid ikke å surre rundt i huet mitt: Hva i alle dager var det vi hadde gjort feil? Hvorfor klarte vi ikke å fikse dette?

Det var ikke før jeg satt på flyet til Smidig at det endelig gikk opp for meg, og etterpå følte jeg meg så dum som ikke hadde skjønt det før at jeg tror jeg fysisk klaska håndflata i panna (og fikk den dresskledte sidemannen til å se på meg med en viss skepsis). Selvfølgelig! Det var jo ikke testene våre i seg selv det var noe gæærnt med, det var designet av produksjonskoden! Vi hadde jo med vilje laget et design som hadde en god del problemer for at folk skulle få øve se på å forbedre den. Det skulle da bare mangle at ikke testene klaget! «Problemet» vi hadde med testene var egentlig testenes måte å si: «HALLO!!?! Hva for en elendig programmerer er du egentlig? Koden din er pokker meg mer sammenfiltret enn vennekretsen til Trond Giske!»

Det er dette som etter min (nåværende!) forståelse er å lytte til testene. Det er å registrere at noe er vanskelig eller tungvint å teste, og så spørre seg selv «Hva er det med designet mitt som gjør at det er sånn?»

Jeg tror egentlig at mange som lærer seg TDD går gjennom et sett med stadier. Det første er “Testing suger!”-stadiet. Når man starter med TDD føles alt bare vanskelig, og det er lett å konkludere med at det er TDD som er en idiotisk teknikk. Når (hvis?) man kommer videre derfra, havner man på “Testene mine suger!”-stadiet. Her tror man gjerne at TDD er en bra måte å jobbe på, men at man bare ikke kan den godt nok og dermed ikke får den til. Det siste stadiet er “Designet mitt suger!”-stadiet. Her plasserer vi ansvaret der det oftest hører hjemme – hos designet til produksjonskoden. Det kan godt være at det er flere stadier her, men de har ikke jeg kommet til enda.

Dette siste stadiet tar tid å venne seg til. Det handler om å omstille hjernen fra “dette er umulig å teste!” til “hva er det i designet mitt som gjør det umulig å teste?”. Fra “pokker, nå brakk alle testene!” til “hvorfor brakk alle testene når jeg gjorde dette?”. Det er vanskelig, men for meg har det i alle fall gitt enormt utbytte.

Dette kan kanskje bli litt abstrakt, så la oss se på et eksempel (Red: Bare scroll ned om du synes det blir litt mye kode):

YahtzeeGame.java

public class YahtzeeGame {
    private final ThrowResultStrategy throwResultStrategy;
    private final Setgt; scoredCombinations;

    private Throw currentThrow;
    private int currentScore;
    private int currentRoundNumber;
    private List<Integer> currentlyHeldDice;
    private int currentNumberOfThrowsInThisRound = 0;

    public static final int NUMBER_OF_ROUNDS = Combination.values().length;
    private static final int MAX_NUMBER_OF_THROWS_PER_ROUND = 3;

    public YahtzeeGame(ThrowResultStrategy throwResultStrategy) {
        this.throwResultStrategy = throwResultStrategy;
        this.currentlyHeldDice = new ArrayList<Integer>();
        this.currentRoundNumber = 1;
        this.scoredCombinations = new HashSet<Combination>();
    }

    public void throwDice() {
        if (currentNumberOfThrowsInThisRound >= MAX_NUMBER_OF_THROWS_PER_ROUND) {
            throw new YahtzeeException("You cannot throw the dice more than "
                    + MAX_NUMBER_OF_THROWS_PER_ROUND + " times per round!");
        }

        Throw newThrow = throwResultStrategy.throwDice();
        currentThrow = currentlyHeldDice.isEmpty()
                           ? newThrow
                           : currentThrow.mergeWith(newThrow, currentlyHeldDice);
        currentNumberOfThrowsInThisRound++;
    }

    public int scoreFor(Combination combo) {
        if (noThrowsYetInThisRound()) {
            throw new YahtzeeException("You have to throw at " +
                    "least once before you score");
        }

        if (scoredCombinations.contains(combo)) {
            throw new YahtzeeException("This combination has " +
                    "already been taken!");
        }

        scoredCombinations.add(combo);

        int score = 0;

        switch (combo) {
            case ONES:
                score = scoreForNumberOfDiceWithValue(1);
                break;
            case TWOS:
                score = scoreForNumberOfDiceWithValue(2);
                break;
            case THREES:
                score = scoreForNumberOfDiceWithValue(3);
                break;

             // More cases here for other combinations
        }

        currentScore += score;
        currentRoundNumber++;
        currentNumberOfThrowsInThisRound = 0;
        currentlyHeldDice = new ArrayList<Integer>();
        currentThrow = null;

        return score;
    }

    private int scoreForNumberOfDiceWithValue(int value) {
        int num = currentThrow.findNumberOfDiceWithValue(value);
        return num * value;
    }

    public int finalScore() {
        return currentScore;
    }

    // More functionality here...
}

YahtzeeGameTest.java

public class YahtzeeGameTest {
    private ThrowResultStrategy resultStrategy;
    private YahtzeeGame game;

    @Before
    public void setup() {
        resultStrategy = mock(ThrowResultStrategy.class);
        game = new YahtzeeGame(resultStrategy);
    }

    @Test
    public void allOnesGivesFivePointsForOnes() throws Exception {
        when(resultStrategy.throwDice()).thenReturn(new Throw(1, 1, 1, 1, 1));

        game.throwDice();
        int score = game.scoreFor(Combination.ONES);

        assertThat(score).isEqualTo(5);
    }

    @Test
    public void allTwosGivesTenPointsForTwos() throws Exception {
        when(resultStrategy.throwDice()).thenReturn(new Throw(2, 2, 2, 2, 2));

        game.throwDice();
        int score = game.scoreFor(Combination.TWOS);

        assertThat(score).isEqualTo(10);
    }

    @Test
    public void allThreesGivesFifteenPointsForThrees() throws Exception {
        when(resultStrategy.throwDice()).thenReturn(new Throw(3, 3, 3, 3, 3));

        game.throwDice();
        int score = game.scoreFor(Combination.THREES);

        assertThat(score).isEqualTo(15);
    }

    // More similar tests here...
}

Klassen under test representerer et yatzyspill. Koden er forkortet litt av hensyn til lesbarheten, men om du leser testene, så bør det være noe som skurrer. Det de tester er at et gitt kast faktisk gir et gitt antall poeng for en gitt kombinasjon (for eksempel enere). For å kunne teste dette, må jeg imidlertid gjøre tre ting:

  1. Opprette et objekt av klassen YahtzeeGame
  2. Opprette en mock av interfacet ThrowResultStrategy, som gir oss et kast
  3. Kalle YahtzeeGame.throwDice()

Dette synes jeg føles fryktelig tungvint å teste. Jeg vil egentlig bare teste fjorten linjer kode (markert med gult) inni en metode i YahtzeeGame-klassen, men ender opp med å måtte mocke ut noe jeg føler jeg ikke trenger (ThrowResultStrategy) og kalle metoder som egentlig ikke er relatert til det jeg ønsker å teste. Med andre ord, testene har vist meg et problem. Dessverre er tester ofte litt som hylende babyer – det er ikke alltid så lett å skjønne hvorfor de klager, selv om du hører veldig godt at de gjør det.

Når en test krever veldig mye oppsett, slik som denne gjør, er det ofte et tegn på at klassen under test gjør mer enn den burde. Dersom vi ser nærmere på scoreFor()-metoden i YahtzeeGame, ser vi at den egentlig gjør alt. Den regner ut poengsummer (som er det vi forsøker å teste), men den holder også styr på hvilken runde vi er i, legger til poengsummen for akkurat denne kombinasjonen til den endelige poengsummen, resetter antall kast så langt i runden og gjør validering. Det er fryktelig mange ansvarsområder for en metode å ha, og YahtzeeGame-klassen bryter dermed med Single Responsibility Principle. Det er med andre ord ikke et testproblem, men et designproblem. Testene fortalte meg bare at noe var galt.

Alt jeg trengte å gjøre, var å høre etter.

Ackenhausen om BDD og arkitektur [Luke 7, 2012]

Friday, December 7th, 2012
2 kommentarer

Jeg ble kjent med Svein Arne Ackenhausen da jeg ble en del av programkommiteen for Norwegian Developers Conference. Under ser du ham i jam session med Carl Franklin fra .NET Rocks! 

Svein Arne har bidratt til årets kalender med en tankevekkende artikkel – den er lang, men bør leses. Den har potensiale til å gi mang en utvikler dårlig samvittighet.

acken

Hvem er du?
Trommis som byttet ut noter med kode og bandet med et konsulentfirma.

Hva er jobben din?
Driver et lite konsulentfirma ved navn Contango Consulting AS hvor jeg skriver kode for de som måtte ønske det.

Hva kan du?
Er blitt ganske flink til å se på programmering forbi programmeringsspråkene.

Hva liker du best med yrket ditt?
Følelsen av å ha skapt noe som faktisk er til nytte for andre.


Hva BDD skulle gjort med arkitekturen vår

Når jeg hører Dan North snakke om BDD er det som om alle brikkene faller på plass. Dette er hva software utvikling handler om. Strukturering av språk og kommunikasjonsform hjelper oss til å definere problemene vi prøver å løse. Når vi forstår problemet er det også en mulighet for at vi klarer å løse det. Spol frem 6-12 måneder og vi har akkurat det produktet kunden alltid har drømt om. Vel kanskje ikke men det er sånn vi ønsker å se på det.

Det BDD har lyktes med er å bidra med et sterkere fokus på å mer presist definere hva systemet gjør. Vi kommuniserer, vi skriver Given When Then (GWT) tekster (jeg sier bevisst ikke tester) sammen med beslutningstakerne. Vi lærer oss å forstå og utvikle konseptene i produktet. Dette er veldig bra! Vårt yrke har lenge hatt et for teknisk fokus. Vi «teknifiserer» problemet i et forsøk på å enklere kunne løse det teknisk. Arkitekturen, et pyramidisk sett med bokser (lag delt arkitektur) / et veldefinert sett med løkringer (onion architecture) osv. Vi får alle et bilde i hodet når vi tenker på arkitektur. For eksempel et ark bestående av piler og bokser. Faktum er at vi skal løse IKKE-TEKNISKE problemer ved hjelp av teknologi. Som tekniske kvinner og menn er vi blitt nødt til å innse at teknologien kommer i andre rekke i forhold til problemet vi prøver å løse.

La oss gjøre et lite sammendrag:

  • Vi bruker kommunikasjon og metoder som GWT som verktøy for å definere hva vi ønsker å produsere.
  • Den tekniske løsningen er et resultat av og kommer derfor i andre rekke i forhold til funksjonaliteten.
  • Arkitekturen definerer den totale tekniske løsningen.

Som jeg begynte med er det en følelse av noe nesten magisk når Dan North snakker om BDD. Når jeg ser hvordan BDD adopteres i praktisk bruk klarer jeg sjelden å beholde den følelsen. Det virker på meg som om vi mister noe på veien fra konsept til praksis. Hvis du kjenner deg igjen i og er enig i de tre punktene over så gjør følgende: Tenk tilbake på prosjektene du har vært med på å utvikle. Se for deg kun arkitekturen.

Ok, så softwareproduktet kan defineres som summen av behaviours. Implementasjonen av en spesifikk behaviour er en konsekvens av behaviouren. Det betyr at den utfører de nødvendige stegene for å tilfredsstille behaviouren. Arkitekturen er da summen av alle implementerte behaviours. Altså Behaviours -> Implementasjoner -> Arkitektur. HVORFOR KAN JEG DA IKKE FORSTÅ HVA PRODUKTET GJØR UT I FRA ARKITEKTUREN??

Avstanden mellom behaviours og det tekniske

Som oftest når jeg ser et arkitekturdokument ser jeg noe som beskriver en lagdeling som vist på bildet under. I tillegg til dette lagdelingsbildet kommer et massivt databaseskjema som er beskrevet ned til hver minste detalj. Vi sprinkler på litt tekst for å forklare noen deler av arkitekturen bedre og voila! her har vi arkitekturen vår.

Ok, dette var en sterk forenkling av det hele, men jeg håper du forstår tanken. Vanligvis ser jeg GWT-tekster implementert som kjørbare tester. Det er forsåvidt bra. Tester er en god ting!

Problemet er bare at arkitekturen som beskrevet over representeres ikke som et resultat av Behaviours -> Implementasjoner -> Arkitektur. Den er mer et koderammeverk hvor vi kan dele hver behaviour inn i en UI del, en tjeneste del, en domene del og en data del. Deretter tar vi hver av delene og implementerer de der de hører hjemme. Vi har altså sett på behaviouren, teknifisert den inn i UI, tjeneste, domene og data så den passer inn i en forhåndsdefinert lagdelingsstruktur.

Resultatet blir at vi distanserer implementasjonen fra hva produktet faktisk skal gjøre. Dette leder igjen til at vi tar en god del beslutninger på helt feil grunnlag.

Men la oss først gå tilbake til BDD i praksis. I og med at vi har distansert implementasjonen fra behaviours er vi nødt til å abstrahere implementasjonen fra GWT-tekstene når vi gjør de om til eksekverbare spesifikasjoner. Resultatet blir dette:

Grunnet at det ikke er noen klar relasjon mellom implementasjon og spesifikasjon ender vi opp med et stort sett med systemtester rundt en blackbox. Den eneste måten vi kan teste en blackbox på er igjennom systemtester. Systemtester er som vi vet veldig skjøre, veldig avhenginge av miljø, veldig tunge å vedlikeholde og generelt sett veldig utsatte for feil. En test man endrer oftere enn koden den tester har også høyere risiko for feil enn koden den prøver å verifisere. Altså faller den på sin egen urimelighet.

Vi var lenge enige om at systemtester var et nødvendig onde. Vi trengte noen av dem men vi holdt antallet så lavt som mulig fordi vi visste hva bakdelene med dem var. Jeg vil gå så langt som å si at et stort antall systemtester ofte er en code smell. Det tyder på at det underliggende systemet er en blackbox.

Hvordan ble systemet en blackbox? Hva gjorde at implementasjonen ikke ble til som en konsekvens av behavioren? Jeg tror mye av det ligger i hvordan vi tradisjonelt sett tenker på arkitektur. Tradisjonelle arkitekturer legger ofte føringer på hvordan kode skal implementeres i hvert lag. Dette betyr at vi tilpasser behaviouren til teknologien og ikke motsatt.

Et godt eksempel på akkurat dette er måten vi implementerer det vi kaller data-laget på. For det første ser jeg nesten aldri noe som kan kalles et datalag. Et lag er en absolutt separasjon. Et lag eksponerer ikke sin interne state. Det vil si at i et datalag kan ikke unit of work/transaksjonener eksponeres. Med det er vi inne på på noe veldig essensielt. Behovet vi har for transaksjoner og unit of works er grunnen til at ikke implementasjonen blir til som et resultat av behaviouren.

Forstå problemet og implementer det eksplisit

Så hvorfor trenger vi egentlig konseptet transaksjon utenfor persisteringslogikken? En generisk unit of work / transaksjon er noe vi trenger når vi ikke vet hva som er resultatet av et funksjonskall. Det vil si, vi starter en unit of work, kaller en overordnet metode som utfører en behaviour med alt det den måtte ville endre og til slutt committer unit of work. Realiteten er følgende: Vi vet ikke hva denne behaviouren gjorde. Hva den nå enn gjorde så ble alle endringer lagret et eller annet sted når vi committet.

Vi har med det distansert oss fra hva resultatet av behviouren er. Selve essensen i problemet vi prøver å løse forsvinner i “hva som nå enn ble committet”.

Hadde vi satt oss ned og funnet ut akkurat hva resultatet av behaviouren er kunne vi i stedet sagt at funksjonen vi kaller returnerer resultatet av behaviouren. Det gir oss en klar input og en definert output. Det kan trygt testes! Vi vil da separere mellom det å utføre behaviouren og det å persistere eventuell resulterende state. Dette er en veldig viktig distinksjon.

Toppnivå-funksjonen vi vanligvis kaller innenfor en unit of work kan da isteden returnere resultatet av behaviouren. Det returnerte resultatet kan sendes videre til noe som håndterer persistering. Innenfor funksjonalitet fokusert rundt ren persistering er det helt naturlig å håndtere eventuelle transaksjoner. Dette er relatert til det Greg Young og Udi Dahan snakker om i CQRS når de nevner «transaction per aggregate». Ved å gjøre implementasjonen eksplisitt har vi kunnet fjerne behovet for unit of works. I stedet for å bygge systemet rundt en generisk persisteringsplatform fokuserer vi heller på persistering som direkte relatert til behaviouren. Det vil si at vi kan oppfylle punkt to over: «Den tekniske løsningen er et resultat av og kommer derfor i andre rekke i forhold til funksjonaliteten».

Systemets koblingspunkter

Det vi definerer inn i det vi kaller lagdeling er en liten del av arkitekturen men det er ikke arkitekturen. Hva er det da?

Dette er min definisjon. Jeg velger å heller notere punkter istedenfor lag som vist over. Punktene definerer hovedabstraksjonene i systemet. Transport-punkter en behaviour kan bevege seg imellom. For å få noen nytte av transportkartet må det knyttes opp mot selve behaviourene. La oss si at en av behaviourene i dette systemet er live rapportering av varme i UI’et. Vi kan da definere opp stegene i behaviouren som i bildet under.

 

Ved å representere hovedabstraksjoner som punkter har vi ikke lagt føringer på hvordan en behaviour skal implementeres, bare hvilke stopp reiseruten involverer. Vi er derfor frie til å definere implementasjonen av behaviouren på den måten som er best egnet.

En behaviour kan ofte sammenlignes med en funksjonell kjede. Man starter kjeden med et sett med data (parametere) man sender med det første funksjonskallet. Deretter igjennom flere steg transporterer, transformerer og beriker vi den initielle dataen. Når vi kommer til det siste steget i behaviouren sitter man med et sett med data som representerer konklusjonen av hva behaviouren er ment å oppnå.

Normen pr i dag er i stedet å lage en generisk arkitektur som teknisk definerer hvordan alle behaviours skal se ut. Vi definerer for eksempel at alle kall skal komme igjennom service laget. Deretter skal domeneklasser implementeres ut i fra n tekniske guide lines. Datalaget har selvfølgelig også sterke føringer på hvordan det skal implementeres.

Ok, stop!

La oss i stedet si at en behaviour selv dikterer hvordan den skal implementeres. Vi vil da gå bort fra den vanlige “one size fits all”-tankegangen.

Tanken mange har er at et system er mer forståelig når alle behaviours er implementert på samme måte. Jeg forstår hvor vi vil med en slik tanke men vi klarer ikke å se hvor stort kompromisset er ved en slik løsning. Vi har lært oss til å tro at en slik løsning blir en mindre kompleks løsning fordi koden da vil se enhetlig ut. Problemet er: Det at koden ser enhetlig ut er irrelevant! Det er en missvisende trygghet!

Kode-evolusjon og stabilitet

Problemet enhetlig kode / kode som ser lik ut kan ha er best forklart ved å se på DRY prinsippet. DRY (don’t repeat yourself) er kanskje den største fallgruven vi som utviklere kan havne i. Missforstå meg rett – DRY er en god ting når det er brukt riktig, men når det er brukt feil er det et masseødeleggelsesvåpen!

Når man snakker om DRY glemmer man ofte å nevnte kontekst. Uten kontekst ødelegger DRY mer enn det hjelper. På plassen etter å faktisk løse problemet produktet er ment å løse kommer stabilitet. Vi må derfor være sikre på at når vi endrer et sted i koden så ødelegger vi ikke noe annet.

Kode skal IKKE være DRY fra et teknisk perspektiv men fra et funksjonelt perspektiv. Hele tanken bak DRY er at man ikke skal ha duplisert kode. Når man endrer kode skal man vite at man kun trenger å endre ett sted. Nøkkelen til dette ligger i å vite hva som endres sammen. Endringer kommer fra stakeholders. Endringer er nesten alltid rettet mot en behaviour. Vi må kunne garantere for at vi kan gjøre endringer i en behaviour uten å ødelegge andre behaviours. Produktets evolusjon kan ikke holdes tilbake som et resultat av at vi er redde for å ende opp med et ustabilt produkt. Vi må
iverata stabilitet samtidig som vi lar produktet evolvere.

Vi må derfor avgrense DRY til å gjelde innenfor behaviours, ikke alt som ser teknisk likt ut. Behaviours kan ofte inneholde de samme konseptene. Det vil ikke si at når et delt konsept endres i den ene behaviouren så skal det automatisk endres i den andre. Vi vet at hvis vi tar på noe, ødelegger vi det. Det er bare sånn softwareutvikling er. Det som endres sammen deler samme kode og det er da mer forståelig at det ødelegges sammen ;) (bruk av plumbing og rammeverk er unntak).

Vet vi hvor store kompromiss vi aksepterer?

La oss gå tilbake til problemet med “one size fits all”-arkitekturen. Denne typen implementasjon forutsetter også at alle behaviours er like. Hvis det er tilfellet er det verdiløs kode å skrive.

Se på Ruby on Rails. De har forstått at hvis applikasjonen din er ren CRUD så trenger du ikke skrive koden. Du kan i stedet generere den. Alle behaviours er bygget opp på samme måte: Hente data, endre data, lagre endringen. Med en gang det begynner å komme behaviours som ikke er CRUD ender vi isteden opp med et sammensurium hvor alt ser enhetlig ut. Det vil være det samme som om jeg forteller deg at den personen snakker italiensk men jeg kan ikke fortelle deg hva hun/han sier. Jeg forstår strukturen i språket men ikke innholdet. Dette er kompromisset vi tar ved å fokusere på at kode ser teknisk lik ut. Vi forstår at vi jobber med repositories, services, entiteter og units of work. Men vi har ingen anelse angående hva den gjør. Dette er et ENORMT kompromiss. Vi har allerede sagt oss enige i at behaviours er viktigere enn implementasjonen som er et resultat av behaviouren. Likevel har vi endt opp med å vurdere lesbarheten av de tekniske konseptene høyere enn forståelsen av behaviours.

Konklusjon

Software er ment å endre seg. Arkitekturen er ment å endre seg. Med mindre du har klart å lage et perfekt under av et produkt på første forsøk kommer systemet til å endre seg. Så lenge det endrer seg betyr det at vi forstår mer om problemet vi prøver å løse. Vi perfeksjonerer produktet mot å løse stakeholders problem på best mulig måte.

For kontinuerlig å kunne forbedre produktet må det være optimalisert for endringer. Det er derfor kritisk at behaviours er implementert i isolasjon. Som nevnt tidligere er endringer nesten alltid relatert til en behaviour. For å kunne ivareta det må vi sørge for at koden er nærmere knyttet til selve problemet.

BDD har satt oss på rett spor. Vi må bare ta det videre inn i implementasjonen og arkitekturen. Et for sterkt fokus på den teniske strukturen av softwarearkitektur vil selv med gode hensikter ofte lede til en stor andel “accidental complexity”. Kompleksitet som er der fordi distansen mellom den funksjonelle definisjonen av systemet og den tekniske definisjonen er for stor. Å krympe/eliminere denne distansen er hva BDD skulle gjort med arkitekturen vår!

Høy IQ-faktor i Oslo akkurat nå!

Wednesday, June 6th, 2012
Ingen kommentarer

Nå er NDC2012 pre-conference workshop-dagene vel gjennomført, og i morgen braker det løs med selve konferansen i Oslo Spektrum. Geeks fra hele verden har flydd inn, og utviklerne som deltar vil kunne velge blant over 160 foredrag av høyeste kvalitet.young_mccoy_battle

I går og i dag deltok jeg på Greg Youngs workshop. Han er skummelt intelligent, men samtidig en dyktig pedagog. Det han fikk oss til å kode disse dagene var egenlig ganske enkle greier, men de inneholdt noen fundamentale og spennende budskap. Han hjalp oss å se hvordan man kan redusere noen problemer som virker vanskelige til noe som blir helt trivielt.

Hva jeg har lært av Greg

Første dag lagde vi et testrammeverk som genererer detaljert dokumentasjon om koden som testes. Vi lekte med Lambda Calculus og funksjonelle pipelines, og jeg fikk parprogrammert i både C# og Clojure.

For noen år siden deltok jeg på en workshop med Scott Bellware. Han lærte oss å skrive context specifications – tester som dokumenterer koden bedre, får deg til å tenke på hvilken adferd du ønsker at systemet skal ha, og som også kan generere dokumentasjon. Siden har jeg blant annet brukt ting som Gherkin og SpecFlow til å skrive akseptansetester som tar dette enda lengre.

Men Greg har fått meg til å innse hva som er problemet med det jeg har gjort til nå; SpecFlow-testene og koden som kjører under dem henger ikke sammen. Det blir det samme problemet som man har med kommentarer i kode – etterhvert begynner de å divergere, og slutter å fortelle sannheten.

Testrammeverket vi lagde med Greg produserer dokumentasjon som er tettere koblet sammen med koden som faktisk kjører i testene.

Læresetning å huske: Alle tester kan modelleres som en komposisjon av fem funksjoner!

For eksempel i Clojure:

(-> given
    transition-to-when
    when
    transition-to-then
    then)

Det er det at det er en funksjons-komposisjon, en pipeline, som er det viktige. Og at output fra hvert av disse stegene kan bli til dokumentasjon som forretningsfolk kan forstå.

Andre dag av workshopen dreide seg om Pi Calculus, Actor Model og message based finite state machines. Greg illustrerte noen av fordelene med å modellere problemer med disse teknikkene, og hvordan problemer som i utgangspunktet kan virke vanskelige plutselig blir ganske enkle. Som å skrive enhetstestet kode som er avhengig av tid.

Hjerne er nå full av ideer, og disse to dagene har gitt meg ting jeg helt sikkert kommer til å jobbe og eksperimentere med i mange måneder fremover. Noen flere blogposter blir det nok også…

GeekBeer etc.

Det sosiale aspektet ved en konferanse som dette må ikke undervurderes. Det er som sagt mange spennende utviklere som har tatt turen til Oslo denne uken. Diskusjonene og læringsprosessen har begge dagene fortsatt til langt ut på kvelden.

Og nå i kveld var det GeekBeer utenfor Østbanehallen. Det virket som om de fleste foreleserne som allerede var ankommet tok turen ut for å møte sitt publikum, og stemningen var upåklagelig..

På bildet lengre oppe ser du (fra høyre) Greg Young, IL Ninja Philip Laureano og .NET-hacker John McCoy. Det utartet seg nesten til en “battle” mellom Greg og John da de diskuterte avanserte finurligheter rundt hvordan man kan analysere og endre kjørende .NET-kode. Jeg er fortsatt svimmel av å forsøke å henge med på hva de to snakket om!

I mine øyne har NDC 2012 allerede vært en stor suksess. Og konferansen har ikke begynt engang! Hvor skal dette ende?

Cobra

Monday, December 5th, 2011
3 kommentarer

Programmeringsspråket du skal få møte i dag har mye til felles med Boo, språket jeg fortalte om i går. Cobra er nemlig også laget for .NET og Mono, og syntaksen er Python-inspirert (men Ruby-utviklere vil også kunne føle seg som hjemme). Det har både statisk og dynamisk binding, og er generelt sett en smeltedigel som forsøker å kombinere det beste fra mange andre språk.

cobra

Hvorfor bruke tid på Cobra?

Cobra er utviklet med fokus på å øke kvaliteten i koden din. For det første er enhetstester førsterangs borgere i Cobra. De lever som en naturlig del av metode-definisjonene, og kjøres som en del av kompileringen.

Men i tillegg til normale tester støtter språket også kodekontrakter, inspirert av Eiffel. Dette innebærer at du kan deklarere invarianter, pre- og post-conditions som objekter og metoder må tilfredstille. Kontraktene blir en del av grensesnittet til klassene, og brudd på en kontrakt under kjøring resulterer i exceptions.

Ønsker man optimal ytelse kan både tester og kontrakter enkelt elimineres når man kompilerer for produksjon.

Andre interessante egenskaper er compile-time nil-tracking. Skal noe kunne være en nil (eller null om du vil) i Cobra så må det deklareres eksplisitt. Er du lei av å bli overrasket av NullReferenceException når du kjører programmet ditt? Da kan dette være noe som hjelper deg.

Cobra tilbyr også utvidet informasjon når et problem oppstår. Man kan f.eks. velge å få generert en HTML-formatert post-mortem rapport når programmet kræsjer.

Et eksempel

Jeg tipper du ikke lar deg overraske av at jeg skal bruke Cobra til å finne summen av alle positive heltall under 1000 som er multipler av 3 eller 5. Klassen nedenfor implementerer et komplett program som skriver ut løsningen.

Euler1-klassen inneholder to metoder: main, som kjøres når programmet eksekveres, og validNumber, som inneholder både tester og en liten precondition.

10 class Euler1
11     shared
12         def main
13             """
14             Finds the sum of all numbers from 1 
15             below 1000 that are multiples of 3 or 5.
16             """
17             sum = 0
18             for n in 1:1000
19                 if .validNumber(n)
20                     sum += n
21             print sum
22 
23         def validNumber(n) as bool
24             require
25                 n > 0
26             test
27                 assert .validNumber(6)
28                 assert .validNumber(10)
29                 assert not .validNumber(1)
30                 assert not .validNumber(8)
31             body
32                 return n % 3 == 0 or n % 5 == 0

Noen bekymringer

Cobra har vært under utvikling i mange år nå, men virker fortsatt litt uferdig. Det skal blant annet støtte mixins, men dette virker ikke som det skal. Støtten for anonyme metoder og lamda er heller ikke så bra enda. På den andre siden har det blitt laget en Cobra add-in for Visual Studio 2010, men denne har jeg ikke forsøkt.

Jeg er litt usikker på hvor aktiv utviklingen egentlig er. Jeg ser det stadig gjøres små bugfikser, men gjeldende release er over ett år gammel. Det er også endel som ikke er oppdatert på websidene, men diskusjonsforumet er i alle fall aktivt.

Tenker man at Cobra kan være noe å satse på bør man derfor vurdere disse tingene nærmere.

Hvordan komme i gang

Cobra finner du på cobra-language.com. Der kan du laste ned språket, og så er det rimelig enkelt å komme igang. Dokumentasjonen er brukbar, men som sagt inneholder den både hull og enkelte ting som har gått ut på dato.

Det er likevel interessant å teste ut et språk hvor Design by Contract-teknikken har fått en så sentral plass. Microsoft er jo i ferd med å levere støtte for dette i sine egne språk på .NET-plattformen, og det kan være lurt å lære seg dette som et suplement til testdrevet utvikling basert på enhetstester.

"Rename method" kan være farlig

Friday, November 18th, 2011
3 kommentarer

Denne historien fra virkeligheten forteller om en tilsynelatende uskyldig og ufarlig refakturering som førte til at en katastrofal feil ble deployet til produksjon. Jeg har anonymisert og forenklet koden for å beskytte den skyldige (som var undertegnede selv) og for å gjøre det enkelt å forstå hva problemet er.

Det hele begynte med en nokså sentral klasse som vi skal kalle SomeBaseClass. Den hadde blant annet en metode vi for anledningen kaller DoSomething.

1     class SomeBaseClass
2     {
3         public void DoSomething()
4         {
5         }
6     }

Etterhvert som systemet vokste ble det innført mange klasser som arvet fra SomeBaseClass. En av disse var SomeDescendant.

1     class SomeDescendant : SomeBaseClass
2     {
3         public void DoIt()
4         {
5             DoSomething();
6         }
7     }

Alt fungerte som det skulle i lange tider, og alle var glade. Men så en dag fant en utvikler ut at han ikke likte navnet på metoden DoSomething. Heldigvis hadde han CodeRush-plugin i Visual Studio, og var derfor ganske sikker på at han raskt og trykt kunne endre navnet på metoden med den automatisert refaktureringen rename.

Utvikleren bestemte seg for at DoIt var et mye bedre og mere beskrivende navn!

Uheldigvis fungerte refaktureringen som den skulle. Koden kompilerte, testene var grønne, og endringen ble med i neste release. Utvikleren husket ikke at han allerede hadde kalt en av metodene i SomeDescendant for det samme.

10     class SomeBaseClass
11     {
12         public void DoIt()
13         {
14         }
15     }
16 
17     class SomeDescendant : SomeBaseClass
18     {
19         public void DoIt()
20         {
21             DoIt();
22         }
23     }

Ser du problemet? DoIt i SomeDescendant er nå en evig rekursjon.

Blir den kalt vil den føre til et stack overflow exception som det ikke er mulig å fange. Eller hvis JIT-kompilatoren har klart å optimalisere rekursjonen (TCO), noe jeg faktisk tror var tilfellet her, så vil programmet bare stå og spinne inn i uendeligheten eller til serveren brenner opp.

Bug retrospective

Burde denne feilen ha blitt oppdaget før release? Var det tankeløst å gjennomføre en sånn refakturering? Burde C# tillate denne koden i det hele tatt?

Jeg har alltid sett på rename som en veldig trygg refakturering i et IDE som Visual Studio og et statisk typet språk som C#, og de fleste av oss gjør det sikkert hele tiden. Navngivning er veldig viktig, og føler man at et navn er feil så bør man endre det.

Endringen førte derimot til at kompilatoren genererte en warning. Jeg har forsøkt å bruke “treat warnings as errors” opsjonen, men i praksis er det så mange “false positives” i større prosjekter at det å vedlikeholde alle unntakene blir en urimelig oppgave. Jeg tar manuelle gjennomganger av warnings fra tid til annen, men det er ikke rart at ting faller under radaren.

Vi kunne ha innført en kodestandard fra starten hvor vi alltid kalte metoder i baseklassen eksplisitt – altså base.DoIt() – men det ville også vært vanskelig å overholde.

Den eneste feilen jeg er villig til å innrømme var å ha for dårlig test coverage på koden! Hadde SomeDescendant.DoIt() blitt kalt i en test, ville problemet ha blitt oppdaget. Grunnen til at dette ikke ble gjort i tilfellet her var at DoIt i utgangspunktet var logikkløs, og at funksjonaliteten var blitt testet andre steder. Det føltes veldig trygt å ikke teste. Og det er alltid i slike tilfeller man angrer bittert i etterkant!

Det jeg sitter igjen med er likevel spørsmålet om C# burde tillate dette i det hele tatt. Som warningen sier: ‘SomeDescendant.DoIt()’ hides inherited member ‘SomeBaseClass.DoIt()’. Use the new keyword if hiding was intended. Hvorfor kunne ikke dette vært en error i stedet? Finnes det reelle tilfeller hvor “hiding” uten “new” faktisk kan være nyttig? En error ville gjort at jeg hadde oppdaget problemet og fikset det uten problemer.

Bowling Kata

Monday, June 20th, 2011
Ingen kommentarer

Jeg har laget en ny video til dere; denne gangen har jeg tatt opp mitt forsøk på å gjennomføre Bowling Kata i Clojure (bakgrunn om Bowling Game Kata på codingdojo.org og butUncleBob.com).

Jeg sier forsøk fordi jeg knoter litt underveis. Jeg valgte derimot å beholde videoen slik den var i stedet for å ta den opp igjen – å se hvordan jeg feiler, og hvordan jeg får hjelp av testene til å hente meg inn igjen, har også en viss verdi.

Så hvis du trenger litt TDD og/eller Clojure-inspirasjon, her har du en ærlig og munter code cast:

Siste kommentarer

best seo services company
I'm not sure where you are getting your information, but good topic. I needs to spend some time learning more or understanding more. Thanks for wonder...
Louis Vuitton Outlet
30 years old Kalamazoo-born Vitalia totally likes it barbecuing bicycling. Last but not least she is intrigued by charters and flights as an example, ...
Børge Hansen
Denne likte jeg veldig godt. Du skriver godt og har gode betraktninger  Keep it up – flere trenger å tørre å lære mer om ledelse – du l...
Tormod
Er egentlig ikke overrasket. F# sin fortè er programmererens produktivitet/kvalitet og anledning til parallell kjøring. Men kjøremotoren har ...
Stian
Ville også prøvd med et større problem (x100 eller x1000 f.eks). Når man snakker så små brøkdeler av et sekund som her så kan tiden for en ell...
Torbjørn
Har ikke sjekket - tar en titt i morgen hvis tid :)...
Einar W. Høst
Mhp tco: hva sier ILSpy?...
Torbjørn
Har ikke sett noe på PSeq før, men kjenner til den typen funksjoner fra blant annet Clojure. Og problemet med slike funksjoner i sammenhenger som de...
Håvard
Veldig bra sammenligning! Har du sett på ytelsen av PSeq.* fra powerpakken? Tipper den vil gi performancehit på små mengder, men kan kanskje resul...
Torbjørn
Jeg kom på en demonstrasjon-variant til jeg burde inkludere, nemlig bruk av list comprehension (en type computation expression (også kalt monads)). ...
Creative Commons-lisens
Innholdet på denne bloggen er tilgjengelig under Creative Commons Navngivelse-Ikkekommersiell-DelPåSammeVilkår 3.0 Norge lisens.

Programmeringsbloggen
Kjempekjekt.com

© 2006-2013 Torbjørn Marø

Jeg har vært en profesjonell programmerer siden 1999, og dette er min blogg. Målet med bloggen er å stimulere meg selv og alle andre til kontinuerlig eksperimentering og læring.

Jeg forsøker å være allsidig, og programmerer blant annet i C#, Ruby, Erlang og Clojure.

Jeg praktiserer TDD og andre smidige utviklingspraksiser. Jeg er opptatt av kvalitet og ren kode.

Dette og ganske mye mer kan du lese om på denne bloggen!